1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.tool;
  28 
  29 import java.awt.Component;
  30 import java.awt.Container;
  31 import java.awt.Dimension;
  32 import java.awt.EventQueue;
  33 import java.awt.Rectangle;
  34 import java.awt.Toolkit;
  35 import java.awt.event.ActionEvent;
  36 import java.awt.event.ActionListener;
  37 import java.io.BufferedInputStream;
  38 import java.io.BufferedOutputStream;
  39 import java.io.File;
  40 import java.io.FileInputStream;
  41 import java.io.FileOutputStream;
  42 import java.io.InputStream;
  43 import java.io.IOException;
  44 import java.io.OutputStream;
  45 import java.io.StringWriter;
  46 import java.lang.reflect.InvocationTargetException;
  47 import java.lang.reflect.Method;
  48 import java.net.URL;
  49 import java.util.*;
  50 import javax.swing.BorderFactory;
  51 import javax.swing.Icon;
  52 import javax.swing.JButton;
  53 import javax.swing.JComponent;
  54 import javax.swing.JDialog;
  55 import javax.swing.JEditorPane;
  56 import javax.swing.JFrame;
  57 import javax.swing.JMenuBar;
  58 import javax.swing.JOptionPane;
  59 import javax.swing.JTextArea;
  60 import javax.swing.KeyStroke;
  61 import javax.swing.Timer;
  62 import javax.swing.ToolTipManager;
  63 import javax.swing.UIManager;
  64 
  65 import com.sun.javatest.InterviewParameters;
  66 import com.sun.javatest.JavaTestError;
  67 import com.sun.javatest.TestSuite;
  68 import com.sun.javatest.tool.jthelp.ContextHelpManager;
  69 import com.sun.javatest.tool.jthelp.HelpBroker;
  70 import com.sun.javatest.tool.jthelp.HelpSet;
  71 import com.sun.javatest.tool.jthelp.JTHelpBroker;
  72 import com.sun.javatest.util.*;
  73 import com.sun.javatest.report.HTMLWriterEx;
  74 import com.sun.javatest.util.Properties;
  75 
  76 import java.awt.print.Printable;
  77 import java.awt.print.PrinterJob;
  78 import javax.print.Doc;
  79 import javax.print.DocFlavor;
  80 import javax.print.DocPrintJob;
  81 import javax.print.PrintException;
  82 import javax.print.PrintService;
  83 import javax.print.PrintServiceLookup;
  84 import javax.print.ServiceUI;
  85 import javax.print.SimpleDoc;
  86 import javax.print.attribute.HashPrintRequestAttributeSet;
  87 import javax.print.attribute.PrintRequestAttributeSet;
  88 import javax.swing.LookAndFeel;
  89 
  90 /**
  91  * Desktop is the host for a series of Tools,
  92  * which may be displayed as in a number of styles,
  93  * provided by a DeskView.
  94  * <p>Much of the functionality of a desktop is provided by the current view,
  95  * and because of that, many of the methods here simply call through to the
  96  * underlying current view object.
  97  * @see DeskView
  98  */
  99 public class Desktop
 100 {
 101     /**
 102      * Create a desktop using a style determined according to the
 103      * user's preferences.
 104      */
 105     public Desktop() {
 106         this(getPreferredStyle());
 107     }
 108 
 109     /**
 110      * New desktop, using preferred style and given context.
 111      */
 112     public Desktop(CommandContext ctx) {
 113         this(getPreferredStyle(), ctx);
 114     }
 115 
 116 
 117     public Desktop(int style, CommandContext ctx) {
 118         commandContext = ctx;
 119 
 120         String val = preferences.getPreference(TTIP_PREF);
 121         boolean t = (val == null || val.equalsIgnoreCase("true"));
 122         setTooltipsEnabled(t);
 123 
 124         int delay = getTooltipDelay(preferences);
 125         setTooltipDelay(delay);
 126 
 127         int duration = getTooltipDuration(preferences);
 128         setTooltipDuration(duration);
 129 
 130         String soe = preferences.getPreference(SAVE_ON_EXIT_PREF);
 131         setSaveOnExit(soe == null || "true".equalsIgnoreCase(soe));
 132         String rtos = preferences.getPreference(RESTORE_ON_START_PREF);
 133         setRestoreOnStart(rtos == null || "true".equalsIgnoreCase(rtos)); // false by default (null)
 134 
 135         File f = getDesktopFile();
 136         firstTime = !(f != null && f.exists());
 137 
 138         initHelpBroker();
 139         initLAF(ctx);
 140 
 141 
 142         uif = new UIFactory(getClass(), helpBroker);
 143 
 144         initToolManagers();
 145 
 146         /* defer view initialization in case we do a restore
 147         initView(style);
 148         */
 149         this.style = style;
 150     }
 151 
 152     private void initLAF(CommandContext ctx) {
 153         int preferredLAF;
 154         if (ctx != null) {
 155             preferredLAF = ctx.getPreferredLookAndFeel();
 156         } else {
 157             // can occur from the test
 158             preferredLAF = CommandContext.DEFAULT_LAF;
 159         }
 160 
 161         switch (preferredLAF) {
 162             case CommandContext.SYSTEM_LAF:
 163                 try {
 164                     UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
 165                 } catch (Exception e) {
 166                 }
 167                 break;
 168             case CommandContext.METAL_LAF:
 169                 try {
 170                     Class nimbusClass = Class.forName("javax.swing.plaf.metal.MetalLookAndFeel");
 171                     UIManager.setLookAndFeel((LookAndFeel) nimbusClass.newInstance());
 172                 } catch (Throwable e) {
 173                 }
 174                 break;
 175             case CommandContext.NIMBUS_LAF:
 176                 try {
 177                     Class nimbusClass = Class.forName("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
 178                     UIManager.setLookAndFeel((LookAndFeel) nimbusClass.newInstance());
 179                 } catch (Throwable e) {
 180                 }
 181                 break;
 182             // no need to process CommandContext.DEFAULT_LAF - it should equal one of the others constatns
 183         }
 184 
 185     }
 186 
 187     /**
 188      * Create a desktop using a specified style.
 189      * @param style a value indicating the desired desktop style.
 190      * @see #MDI_STYLE
 191      * @see #SDI_STYLE
 192      * @see #TAB_STYLE
 193      */
 194     public Desktop(int style) {
 195         this(style, null);
 196     }
 197 
 198     /**
 199      * Get a value indicating the current style of the desktop.
 200      * @return a value indicating the current style of the desktop
 201      * @see #setStyle
 202      * @see #MDI_STYLE
 203      * @see #SDI_STYLE
 204      * @see #TAB_STYLE
 205      */
 206     public int getStyle() {
 207         return (currView == null ? style : currView.getStyle());
 208     }
 209 
 210     /**
 211      * Get a value indicating the user's preferred desktop style,
 212      * as recorded in the user's preferences.
 213      * @return a value indicating the user's preferred desktop style
 214      * @see #MDI_STYLE
 215      * @see #SDI_STYLE
 216      * @see #TAB_STYLE
 217      */
 218     public static int getPreferredStyle() {
 219         // would be better(?) to use classname, perhaps
 220         // NO! It must be better to use the tabbed view only.
 221 
 222         return TAB_STYLE;
 223         /* SDI/MDI are not supported any more...
 224         String prefStyleName = preferences.getPreference(STYLE_PREF);
 225         int i = indexOf(prefStyleName, styleNames);
 226         return (i != -1 ? i : TAB_STYLE);
 227          */
 228     }
 229 
 230     /**
 231      * Set the current style of the desktop.
 232      * @param style a value indicating the current style of the desktop
 233      * @see #getStyle
 234      * @see #MDI_STYLE
 235      * @see #SDI_STYLE
 236      * @see #TAB_STYLE
 237      */
 238     public void setStyle(int style) {
 239         //System.err.println("Desktop.setStyle: " + style);
 240         if (style == getStyle())
 241             return;
 242 
 243         if (currView == null) {
 244             this.style = style;
 245             return;
 246         }
 247 
 248         DeskView oldView = currView;
 249         //System.err.println("DT: creating new desktop (" + style + ")");
 250         switch (style) {
 251         case MDI_STYLE:
 252             currView = new MDIDeskView(oldView);
 253             break;
 254 
 255         case SDI_STYLE:
 256             currView = new SDIDeskView(oldView);
 257             break;
 258 
 259         case TAB_STYLE:
 260             currView = new TabDeskView(oldView);
 261             break;
 262 
 263         default:
 264             throw new IllegalArgumentException();
 265         }
 266 
 267         //System.err.println("DT: disposing old deskview");
 268         oldView.dispose();
 269 
 270         //System.err.println("DT: setStyle done ");
 271     }
 272 
 273     /**
 274      * Get the Help Broker used by the Help menu amd context sensitive help.
 275      * @return the help broker
 276      */
 277     public HelpBroker getHelpBroker() {
 278         return helpBroker;
 279     }
 280 
 281     /**
 282      * Determine if this is the first time that JT Harness has been run.
 283      * This is determined by checking if a saved desktop exists from
 284      * a prior run of JT Harness.
 285      * @return true if this appears to be the first time the user has
 286      * run JT Harness, and false otherwise
 287      */
 288     public boolean isFirstTime() {
 289         return firstTime;
 290     }
 291 
 292     /**
 293      * Set the flag indicating whether or not this is the first time
 294      * that JT Harness has been run.
 295      * @param b true if JT Harness should behave as though this is th
 296      * first time JT Harness has been run
 297      * @see #isFirstTime
 298      */
 299     public void setFirstTime(boolean b) {
 300         firstTime = b;
 301     }
 302 
 303     /**
 304      * Check whether the desktop is empty of any tools.
 305      * @return true if there are no tools on the desktop, and false otherwise
 306      */
 307     public boolean isEmpty() {
 308         return (currView == null ? true : currView.isEmpty());
 309     }
 310 
 311     /**
 312      * Get the set of tools currently on the desktop.
 313      * @return the set of tools currently on the desktop
 314      */
 315     public Tool[] getTools() {
 316         return (currView == null ? new Tool[0] : currView.getTools());
 317     }
 318 
 319     /**
 320      * Add a new tool to the desktop.
 321      * @param t the tool to be added
 322      * @see #removeTool
 323      */
 324     public void addTool(Tool t) {
 325         ensureViewInitialized();
 326         currView.addTool(t);
 327     }
 328 
 329     /**
 330      * Remove a tool from the desktop.
 331      * @param t the tool to be removed
 332      * @see #addTool
 333      */
 334     public void removeTool(Tool t) {
 335         if (currView != null)
 336             currView.removeTool(t);
 337     }
 338 
 339     /**
 340      * Get the currently selected tool on the desktop.
 341      * @return the currently selected tool on the desktop
 342      * @see #setSelectedTool
 343      */
 344     public Tool getSelectedTool() {
 345         return (currView == null ? null : currView.getSelectedTool());
 346     }
 347 
 348     /**
 349      * Set the currently selected tool on the desktop.
 350      * @param t the the tool to be selected on the desktop
 351      * @see #getSelectedTool
 352      */
 353     public void setSelectedTool(Tool t) {
 354         ensureViewInitialized();
 355         currView.setSelectedTool(t);
 356     }
 357 
 358     /**
 359      * Add a new default tool to the desktop. The default can be set via the
 360      * system property "javatest.desktop.defaultTool", which should identify
 361      * the class name of an appropriate tool manager; if not set, the default
 362      * is com.sun.javatest.exec.ExecManager.
 363      * @see #removeTool
 364      */
 365     public void addDefaultTool() {
 366         if (!EventQueue.isDispatchThread()) {
 367             invokeOnEventThread(new Runnable() {
 368                     public void run() {
 369                         addDefaultTool();
 370                     }
 371                 });
 372             return;
 373         }
 374 
 375         for (int i = 0; i < toolManagers.length; i++) {
 376             ToolManager m = toolManagers[i];
 377             if (m.getClass().getName().equals(defaultToolManager)) {
 378                 m.startTool();
 379                 return;
 380             }
 381         }
 382     }
 383 
 384 
 385     /**
 386      * Add a new default tool to the desktop. The default can be set via the
 387      * system property "javatest.desktop.defaultTool", which should identify
 388      * the class name of an appropriate tool manager; if not set, the default
 389      * is com.sun.javatest.exec.ExecManager.
 390      * @param ip a configuration to be passed to the default tool manager's startTool
 391      * method
 392      * @see #removeTool
 393      */
 394     public Tool addDefaultTool(InterviewParameters ip) {
 395         for (int i = 0; i < toolManagers.length; i++) {
 396             ToolManager mgr = toolManagers[i];
 397             if (mgr.getClass().getName().equals(defaultToolManager)) {
 398                 try {
 399                     // this is to avoid a class dependency to exec package, which is
 400                     // normally not allowed in this package
 401                     Method m = mgr.getClass().getMethod("startTool",
 402                                             new Class[] { InterviewParameters.class} );
 403 
 404                     return (Tool) m.invoke(mgr, new Object[] { ip });
 405                 }
 406                 catch (NoSuchMethodException e) {
 407                     // ignore??
 408                     e.printStackTrace();
 409                 }
 410                 catch (IllegalAccessException e) {
 411                     // ignore??
 412                     e.printStackTrace();
 413                 }
 414                 catch (InvocationTargetException e) {
 415                     // ignore??
 416                     unwrap(e).printStackTrace();
 417                 }
 418             }
 419         }
 420         return null;
 421     }
 422 
 423     /**
 424      * Check if a tool is present on the desktop.
 425      * @param t the tool for which to check
 426      * @return true if the specified tool exists on the desktop, and false otherwise
 427      */
 428     public boolean containsTool(Tool t) {
 429         Tool[] tools = getTools();
 430         for (int i = 0; i < tools.length; i++) {
 431             if (t == tools[i])
 432                 return true;
 433         }
 434         return false;
 435     }
 436 
 437     /**
 438      * Get the set of tool managers associated with this desktop.
 439      * The managers are determined from resource files named
 440      * "com.sun.javatest.tool.ToolManager.lst" on the main JT Harness classpath.
 441      * @return the set of tool managers associated with this desktop
 442      */
 443     public ToolManager[] getToolManagers() {
 444         return toolManagers;
 445     }
 446 
 447     /**
 448      * Get the instance of a tool manager for this desktop of a specific class.
 449      * @param c the class of the desired tool manager.
 450      * @return a tool manager of the desired type, or null if none found
 451      */
 452     public ToolManager getToolManager(Class c) {
 453         for (int i = 0; i < toolManagers.length; i++) {
 454             ToolManager m = toolManagers[i];
 455             if (c.isInstance(m))
 456                 return m;
 457         }
 458         return null;
 459     }
 460 
 461     /**
 462      * Get the instance of a tool manager for this desktop of a specific class.
 463      * @param className the name of the class of the desired tool manager.
 464      * @return a tool manager of the desired type, or null if none found
 465      */
 466     public ToolManager getToolManager(String className) {
 467         for (int i = 0; i < toolManagers.length; i++) {
 468             ToolManager m = toolManagers[i];
 469             if (m.getClass().getName().equals(className))
 470                 return m;
 471         }
 472         return null;
 473     }
 474 
 475     /**
 476      * Get the top level frames that make up this desktop. TAB and MDI style
 477      * desktops just have a single frame; An SDI style desktop may have more
 478      * than one frame.
 479      * @return the top level frames of this desktop
 480      */
 481     public JFrame[] getFrames() {
 482         ensureViewInitialized();
 483         return currView.getFrames();
 484     }
 485 
 486     /**
 487      * Get a parent component for a dialog to use.
 488      * @return Component which can be used as a parent, or null if none
 489      *         is available.
 490      */
 491     public Component getDialogParent() {
 492         ensureViewInitialized();
 493         return currView.getDialogParent();
 494     }
 495 
 496     /**
 497      * Add a file and a corresponding file opener to the file history
 498      * that appears on the File menu.
 499      * @param f The file to be added
 500      * @param fo A FileOpener object to be used to open the file if necessary
 501      */
 502     public void addToFileHistory(File f, FileOpener fo) {
 503         // if it is already in the history, remove it
 504         for (Iterator<FileHistoryEntry> i = fileHistory.iterator(); i.hasNext(); ) {
 505             FileHistoryEntry h = i.next();
 506             if (h.fileOpener == fo && h.file.equals(f)) {
 507                 i.remove();
 508                 break;
 509             }
 510         }
 511 
 512         // add it to the front of the list
 513         fileHistory.addFirst(new FileHistoryEntry(fo, f));
 514 
 515         // throw away old entries in the list
 516         while (fileHistory.size() > FILE_HISTORY_MAX_SIZE)
 517             fileHistory.removeLast();
 518     }
 519 
 520     /**
 521      * Get a list of the current entries on the file history associated with this desktop.
 522      * @return  a list of the current entries on the file history associated with this desktop
 523      * @see #addToFileHistory
 524      */
 525     List getFileHistory()
 526     {
 527         return fileHistory;
 528     }
 529 
 530     /**
 531      * Check if the top level windows of the desktop are visible or not.
 532      * @return true if the top level windows are visible; otherwise, return false
 533      * @see #setVisible
 534      */
 535     public boolean isVisible() {
 536         return (currView == null ? false : currView.isVisible());
 537     }
 538 
 539     /**
 540      * Set whether or not the top level windows of the desktop should be visible.
 541      * @param b If true, the top level windows will be made visible; if false, they
 542      * will be hidden.
 543      */
 544     public void setVisible(final boolean b) {
 545         if (!EventQueue.isDispatchThread()) {
 546             invokeOnEventThread(new Runnable() {
 547                     public void run() {
 548                         setVisible(b);
 549                     }
 550                 });
 551             return;
 552         }
 553 
 554         ensureViewInitialized();
 555         currView.setVisible(b);
 556     }
 557 
 558     /**
 559      * Create a dialog.
 560      * @param tool the parent tool for the dialog
 561      * @param uiKey a string which is to be used as the base name for any
 562      * resources that may be required
 563      * @param title the title for the dialog
 564      * @param menuBar the menu bar for the dialog
 565      * @param body the body component for the dialog
 566      * @param bounds the size and position for the dialog
 567      * @return a JDialog or JInternalDialog built from the supplied values.
 568      */
 569     public Container createDialog(Tool tool, String uiKey, String title,
 570                                            JMenuBar menuBar, Container body,
 571                                            Rectangle bounds, int type)
 572     {
 573         ensureViewInitialized();
 574         return currView.createDialog(tool, uiKey, title, menuBar, body, bounds, type);
 575     }
 576 
 577     /**
 578      * Check if the tool's parent Window is the owner of a dialog.
 579      * This may become false if the desktop style is changed after the dialog
 580      * was created.
 581      * @param tool the tool from which to determine the parent Window
 582      * @param dialog the dialog to be checked
 583      * @return true if the tool's parent Window is the owner of the dialog, and
 584      * false otherwise.
 585      */
 586     public boolean isToolOwnerForDialog(Tool tool, Container dialog) {
 587         ensureViewInitialized();
 588         return currView.isToolOwnerForDialog(tool, dialog);
 589     }
 590 
 591     /**
 592      * Check all the tools on the desktop to see if they have open state
 593      * that should be saved or processes running. If there is open state
 594      * or active processes, a confirmation dialog will be displayed.
 595      * If the user confirms OK, or if there was no need to show the
 596      * confirmation dialog, the desktop will be saved and disposed.
 597      * @param parent A parent frame to be used if a confirmation dialog
 598      * is necessary
 599      * @see #isOKToExit
 600      */
 601     public void checkToolsAndExitIfOK(JFrame parent) {
 602         if (isOKToExit(parent)) {
 603             if (getSaveOnExit())
 604                 save();
 605 
 606             dispose();
 607         }
 608     }
 609 
 610     /**
 611      * Check if it is OK to close a tool. If the tool has important
 612      * state that needs to be saved, or any processes running, a confirmation
 613      * dialog will be shown, to allow the user to cancel the operation if
 614      * necessary.
 615      * @param t The tool to be checked
 616      * @param parent A parent frame to be used if a confirmation dialog
 617      * is necessary
 618      * @return true if it is OK to close the tool
 619      */
 620     public boolean isOKToClose(Tool t, JFrame parent) {
 621         if (confirmDialog != null) {
 622             Toolkit.getDefaultToolkit().beep();
 623             confirmDialog.toFront();
 624             return false;
 625         }
 626 
 627         String[] alerts = t.getCloseAlerts();
 628         if (alerts == null || alerts.length == 0)
 629             return true;
 630         else
 631             return isOKToExitOrClose(parent, alerts, CLOSE);
 632     }
 633 
 634     /**
 635      * Check if it is OK to close all tools and exit the desktop.
 636      * If any tools have important state that needs to be saved, or active tasks
 637      * running, a confirmation dialog will be shown to allow the user to
 638      * cancel the operation in progress.
 639      * @param parent A parent frame to be used if a confirmation dialog
 640      * is necessary
 641      * @return true if it is OK to exit the desktop, and false otherwise.
 642      */
 643     public boolean isOKToExit(JFrame parent) {
 644         if (confirmDialog != null) {
 645             Toolkit.getDefaultToolkit().beep();
 646             confirmDialog.toFront();
 647             return false;
 648         }
 649 
 650         Vector<String> v = new Vector<>();
 651 
 652         Tool[] tools = getTools();
 653         for (int ti = 0; ti < tools.length; ti++) {
 654             String[] alerts = tools[ti].getCloseAlerts();
 655             if (alerts != null)
 656                 v.addAll(Arrays.asList(alerts));
 657         }
 658 
 659         if (v.size() == 0)
 660             return true;
 661         else {
 662             String[] allAlerts = new String[v.size()];
 663             v.copyInto(allAlerts);
 664             return isOKToExitOrClose(parent, allAlerts, EXIT);
 665         }
 666     }
 667 
 668     private static final int CLOSE = 0;
 669     private static final int EXIT = 1;
 670     private JDialog confirmDialog;
 671 
 672     private boolean isOKToExitOrClose(JFrame parent, String[] alerts, int mode) {
 673         if (confirmDialog != null) {
 674             Toolkit.getDefaultToolkit().beep();
 675             confirmDialog.toFront();
 676             return false;
 677         }
 678 
 679         Integer m = new Integer(mode);
 680 
 681         if (alerts.length > 0) {
 682             // protect against reentrant calls by setting confirmDialog
 683             // while showing it
 684 
 685             StringWriter sw = new StringWriter();
 686             try {
 687                 HTMLWriterEx out = new HTMLWriterEx(sw, uif.getI18NResourceBundle());
 688                 out.startTag(HTMLWriterEx.HTML);
 689                 out.startTag(HTMLWriterEx.HEAD);
 690                 out.writeContentMeta();
 691                 out.endTag(HTMLWriterEx.HEAD);
 692                 out.startTag(HTMLWriterEx.BODY);
 693                 out.writeStyleAttr("font-family: SansSerif");
 694                 out.startTag(HTMLWriterEx.P);
 695                 out.writeStyleAttr("margin-top:0");
 696                 out.startTag(HTMLWriterEx.B);
 697                 out.writeI18N("dt.confirm.head", m);
 698                 out.endTag(HTMLWriterEx.B);
 699                 out.endTag(HTMLWriterEx.P);
 700                 out.startTag(HTMLWriterEx.P);
 701                 out.startTag(HTMLWriterEx.I);
 702                 out.writeI18N("dt.confirm.warn", m);
 703                 out.endTag(HTMLWriterEx.I);
 704                 out.endTag(HTMLWriterEx.P);
 705                 out.startTag(HTMLWriterEx.UL);
 706                 out.writeStyleAttr("margin-top:0; margin-bottom:0; margin-left:30");
 707                 for (int i = 0; i < alerts.length; i++) {
 708                     out.startTag(HTMLWriterEx.LI);
 709                     out.write(alerts[i]);
 710                 }
 711                 out.endTag(HTMLWriterEx.UL);
 712                 out.startTag(HTMLWriterEx.P);
 713                 out.writeStyleAttr("margin-top:5");
 714                 out.writeI18N("dt.confirm.warn2", m);
 715                 out.startTag(HTMLWriterEx.BR);
 716                 out.writeI18N("dt.confirm.warn3", m);
 717                 out.endTag(HTMLWriterEx.P);
 718                 out.startTag(HTMLWriterEx.P);
 719                 out.writeStyleAttr("margin-bottom:0");
 720                 out.writeI18N("dt.confirm.tail", m);
 721                 out.endTag(HTMLWriterEx.P);
 722                 out.endTag(HTMLWriterEx.BODY);
 723                 out.endTag(HTMLWriterEx.HTML);
 724                 out.close();
 725             }
 726             catch (IOException e) {
 727                 JavaTestError.unexpectedException(e);
 728             }
 729 
 730             JEditorPane body = new JEditorPane();
 731             body.setOpaque(false);
 732             body.setContentType("text/html");
 733             body.setText(sw.toString());
 734             body.setEditable(false);
 735             body.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 736             //body.setSize(new Dimension(3 * uif.getDotsPerInch(), Integer.MAX_VALUE));
 737             //System.err.println("DT.isOK size=" + body.getSize());
 738             //System.err.println("DT.isOK psize=" + body.getPreferredSize());
 739             String title = uif.getI18NString("dt.confirm.title", m);
 740             // can't use JOptionPane convenience methods because we want to set
 741             // default option to "No"
 742 
 743             final JOptionPane pane = new JOptionPane(body, JOptionPane.WARNING_MESSAGE);
 744             ActionListener l = new ActionListener() {
 745                     public void actionPerformed(ActionEvent e) {
 746                         pane.setValue(e.getSource());
 747                         pane.setVisible(false);
 748                     }
 749                 };
 750             JButton yesBtn = uif.createButton("dt.confirm.yes", l);
 751             JButton noBtn = uif.createButton("dt.confirm.no", l);
 752             pane.setOptions(new JComponent[] { yesBtn, noBtn });
 753             pane.setInitialValue(noBtn);
 754 
 755             confirmDialog = pane.createDialog(parent, title);
 756             confirmDialog.setVisible(true);
 757             confirmDialog.dispose();
 758             confirmDialog = null;
 759 
 760             if (pane.getValue() != yesBtn)
 761                 return false;
 762         }
 763 
 764         return true;
 765     }
 766 
 767     /**
 768      * Check if it is OK to automatically exit JT Harness.
 769      * A warning dialog is posted to the user for a reasonable but short while
 770      * allowing the user to cancel the exit.
 771      * @return true if the user does not respond within the available time,
 772      * or if the user allows the request; and false otherwise
 773      */
 774     public boolean isOKToAutoExit() {
 775         final int delay = 30/*seconds*/;
 776         final JTextArea body = new JTextArea();
 777         body.setOpaque(false);
 778         body.setText(uif.getI18NString("dt.autoExit.txt", new Integer(delay)));
 779         body.setEditable(false);
 780         body.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 781         body.setSize(new Dimension(4 * uif.getDotsPerInch(), Integer.MAX_VALUE));
 782 
 783         final JOptionPane pane = new JOptionPane(body, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
 784         String title = uif.getI18NString("dt.confirm.title", new Integer(EXIT));
 785         final JDialog dialog = pane.createDialog(null, title);
 786 
 787         final Timer timer = new Timer(1000, new ActionListener() {
 788                 public void actionPerformed(ActionEvent e) {
 789                     if (--timeRemaining == 0) {
 790                         pane.setValue(new Integer(JOptionPane.OK_OPTION));
 791                         dialog.setVisible(false);
 792                     }
 793                     else
 794                         body.setText(uif.getI18NString("dt.autoExit.txt", new Integer(timeRemaining)));
 795                 }
 796 
 797                 private int timeRemaining = delay;
 798             });
 799 
 800         timer.start();
 801         dialog.setVisible(true);
 802         timer.stop();
 803 
 804         Object value = pane.getValue();
 805         return (value != null && value.equals(new Integer(JOptionPane.OK_OPTION)));
 806     }
 807 
 808 
 809     /**
 810      * Save the current state of the desktop in the user's standard desktop file.
 811      */
 812     public void save() {
 813         save(getDesktopFile());
 814     }
 815 
 816     /**
 817      * Save the current state of the desktop in a specified desktop file.
 818      * @param f the file in which to save the desktop
 819      */
 820     public void save(File f) {
 821         //System.err.println("DT: save to " + f);
 822         if (f == null)
 823             return;
 824 
 825         SortedMap<String, String> p = new TreeMap<>();
 826 
 827         int s = getStyle();
 828         if (s < NUM_STYLES) {
 829             p.put("dt.style", styleNames[s]);
 830             // backwards compatibility for JT3.1.x
 831             p.put("dt.class", jt31StyleClassNames[s]);
 832         }
 833 
 834         ensureViewInitialized();
 835         currView.saveDesktop(p);
 836         Preferences.access().save();
 837 
 838         p.put("file.count", String.valueOf(fileHistory.size()));
 839         int n = 0;
 840         for (Iterator<FileHistoryEntry> i = fileHistory.iterator(); i.hasNext(); ) {
 841             FileHistoryEntry h = i.next();
 842             p.put("fileHistory." + n + ".type", h.fileOpener.getFileType());
 843             p.put("fileHistory." + n + ".path", h.file.getPath());
 844             n++;
 845         }
 846 
 847         try {
 848             File dir = f.getParentFile();
 849             if (dir != null && !dir.exists())
 850                 dir.mkdirs();
 851             try (FileOutputStream fos = new FileOutputStream(f);
 852                  OutputStream out = new BufferedOutputStream(fos)) {
 853                 Properties.store(p, out, "JT Harness Desktop");
 854             }
 855         }
 856         catch (IOException e) {
 857             System.err.println(uif.getI18NString("dt.cantWriteDt.txt", e.getMessage()));
 858             //System.err.println("Error writing desktop file: " + e);
 859         }
 860     }
 861 
 862     /**
 863      * Restore the desktop from information in a saved desktop file.
 864      * If no such file exists, or if no tools are successfully started
 865      * from the info in the file, add a default tool.
 866      * The work will automatically performed on the main AWT EventQueue
 867      * thread.
 868      */
 869     public void restore() {
 870         restore(getDesktopFile());
 871     }
 872 
 873     /**
 874      * Restore the desktop from information in a specified file.
 875      * If no such file exists, or if no tools are successfully started
 876      * from the info in the file, add a default tool.
 877      * The work will automatically performed on the main AWT EventQueue
 878      * thread.
 879      * @param file the file from which to load the data
 880      */
 881     public void restore(final File file) {
 882         Map<String, String> p = getPreviousDesktop(file);
 883         restore0(p);
 884     }
 885 
 886     public void restoreHistory() {
 887         restoreHistory(getPreviousDesktop(getDesktopFile()));
 888     }
 889 
 890     private void restore0(final Map<String, String> p) {
 891         //System.err.println("DT: restore " + file);
 892         if (!EventQueue.isDispatchThread()) {
 893             invokeOnEventThread(new Runnable() {
 894                     public void run() {
 895                         restore0(p);
 896                     }
 897                 });
 898             return;
 899         }
 900 
 901         restoreHistory(p);
 902 
 903         // ALERT!! NEEDS FIXING!
 904         // should use saved view info
 905         /*
 906         String dtClassName = (String) p.getProperty("dt.class");
 907         if (dtClassName != null) {
 908             try {
 909                 if (theOne != null)
 910                     theOne.dispose();
 911                 theOne = (Desktop) (Class.forName(dtClassName).newInstance());
 912             }
 913             catch (Throwable e) {
 914                 // I18N
 915                 System.err.println("Error loading saved desktop class: " + e);
 916             }
 917         }
 918         */
 919 /*
 920         if (currView != null) {
 921             style = currView.getStyle(); // set default in case no valid style in desktop file
 922             currView.dispose();
 923         }
 924 
 925         int savedStyle;
 926         String s = (String) (p.get("dt.style"));
 927         if (s != null)
 928             savedStyle = indexOf(s, styleNames);
 929         else {
 930             // javatest 3.1 compatibility
 931             String c = (String) (p.get("dt.class"));
 932             savedStyle = (c == null ? -1 : indexOf(c, jt31StyleClassNames));
 933         }
 934 
 935         if (savedStyle != -1)
 936             style = savedStyle;
 937 */
 938 
 939         ensureViewInitialized();
 940         currView.restoreDesktop(p);
 941 
 942         if (getTools().length == 0)
 943             addDefaultTool();
 944 
 945         // select the previously selected tool, if given
 946         // else a default, if available
 947         Tool t = getSelectedTool();
 948         if (t == null) {
 949             Tool[] tools = getTools();
 950             if (tools.length > 0)
 951                 t = tools[0];
 952         }
 953         if (t != null)
 954             setSelectedTool(t);
 955 
 956         //System.err.println("DT.restore: set visible");
 957         setVisible(true);
 958         //System.err.println("DT: restore done");
 959 
 960     }
 961 
 962     private void restoreHistory(Map<String, String> p) {
 963         HashMap<String, FileOpener> allOpeners  = new HashMap<>();
 964         for (int i = 0; i < toolManagers.length; i++) {
 965             ToolManager m = toolManagers[i];
 966             FileOpener[] mgrOpeners = m.getFileOpeners();
 967             if (mgrOpeners != null) {
 968                 for (int j = 0; j < mgrOpeners.length; j++) {
 969                     FileOpener fo = mgrOpeners[j];
 970                     allOpeners.put(fo.getFileType(), fo);
 971                 }
 972             }
 973         }
 974 
 975         try {
 976             fileHistory.clear();
 977             String c = p.get("file.count");
 978             if (c != null) {
 979                 int count = Integer.parseInt(c);
 980                 for (int i = 0; i < count; i++) {
 981                     try {
 982                         String ft = p.get("fileHistory." + i + ".type");
 983                         FileOpener fo = allOpeners.get(ft);
 984                         if (fo != null) {
 985                             String path = p.get("fileHistory." + i + ".path");
 986                             if (path != null && path.length() > 0)
 987                                 fileHistory.add(new FileHistoryEntry(fo, new File(path)));
 988                         }
 989                     }
 990                     catch (Throwable e) {
 991                         // I18N
 992                         //System.err.println("Error loading saved file: " + e);
 993                         System.err.println(uif.getI18NString("dt.cantLoadHist.txt"));
 994                         e.printStackTrace();
 995                     }
 996                 }
 997             }
 998         }
 999         catch (NumberFormatException ignore) {
1000             // ignore, for now
1001         }
1002     }
1003 
1004     static Map<String, String> getPreviousDesktop(File file) {
1005         if (file == null)
1006             file = getDesktopFile();
1007 
1008         Map<String, String> stringPropsMap = new HashMap<>();
1009 
1010         if (file != null && file.exists()) {
1011             try (FileInputStream fis = new FileInputStream(file);
1012                  InputStream in = new BufferedInputStream(fis)) {
1013                 stringPropsMap = Properties.load(in);
1014             }
1015             catch (IOException e) {
1016                 // I18N
1017                 System.err.println("Error reading desktop file: " + e);
1018             }
1019         }
1020 
1021         return stringPropsMap;
1022     }
1023 
1024     /**
1025      * Show a Preferences window.
1026      * @param parent the parent frame to be used for the preferences dialog
1027      */
1028     public void showPreferences(JFrame parent) {
1029         if (prefsPane == null)
1030             prefsPane = new DesktopPrefsPane(this, uif);
1031         if(colorPane == null)
1032             colorPane = new ColorPrefsPane(uif);
1033 
1034         Vector<PreferencesPane> v = new Vector<>();
1035         v.addElement(prefsPane);
1036         v.addElement(colorPane);
1037         for (int i = 0; i < toolManagers.length; i++) {
1038             ToolManager m = toolManagers[i];
1039             PreferencesPane p = m.getPrefsPane();
1040             if (p != null)
1041                 v.addElement(p);
1042         }
1043 
1044         PreferencesPane[] custom = getCustomPreferences();
1045         if (custom != null)
1046            for (int i = 0; i < custom.length; i++)
1047                v.add(custom[i]);
1048 
1049         PreferencesPane[] panes = new PreferencesPane[v.size()];
1050         v.copyInto(panes);
1051         PreferencesPane.showDialog(parent, preferences, panes, helpBroker);
1052     }
1053 
1054     /**
1055      * Allow for other custom prefs panes.  Current implementation scans
1056      * test suite properties for one called "prefsPane", which should be a
1057      * class name referring to a class which subclasses <code>Preferences.Pane</code>.
1058      * That class will be loaded and instantiated using the test suite's class loader.
1059      * @return A set of prefs panes, beyond that of the currently active Tools.
1060      *         Null if none.
1061      */
1062     private PreferencesPane[] getCustomPreferences() {
1063         List<PreferencesPane> al = new ArrayList<>();
1064 
1065         Set<String> customPrefsClasses = new HashSet<>();
1066 
1067         Tool[] tools = getTools();
1068         for (int i = 0; i < tools.length; i++) {
1069             TestSuite[] tss = tools[i].getLoadedTestSuites();
1070             if (tss != null && tss.length > 0) {
1071                 for (int j = 0; j < tss.length; j++) {
1072                     // only process each test suite once
1073                     if (customPrefsClasses.contains(tss[j].getID()))
1074                         continue;
1075                     else
1076                         customPrefsClasses.add(tss[j].getID());
1077 
1078                     String cls = tss[j].getTestSuiteInfo("prefsPane");
1079                     try {
1080                         if (cls != null) {
1081                             PreferencesPane pane =
1082                                 (PreferencesPane)((Class.forName(cls, true,
1083                                 tss[j].getClassLoader())).newInstance());
1084                             al.add(pane);
1085                         }
1086                     }
1087                     catch (ClassNotFoundException e) {
1088                         e.printStackTrace();        // XXX rm
1089                         // should print log entry
1090                     }
1091                     catch (InstantiationException e) {
1092                         e.printStackTrace();        // XXX rm
1093                         // should print log entry
1094                     }
1095                     catch (IllegalAccessException e) {
1096                         e.printStackTrace();        // XXX rm
1097                         // should print log entry
1098                     }   // try
1099                     finally {
1100                     }
1101                 }   // inner for j
1102             }
1103         }   // for i
1104 
1105         if (al.size() > 0) {
1106             PreferencesPane[] panes = new PreferencesPane[al.size()];
1107             al.toArray(panes);
1108             return panes;
1109         }
1110         else
1111             return null;
1112     }
1113 
1114     /**
1115      * Get an icon containing the JT Harness logo.
1116      * @return an icon containing the JT Harness logo
1117      */
1118     public Icon getLogo() {
1119         return uif.createIcon("dt.logo");
1120     }
1121 
1122     /**
1123      * Dispose of any resources used by this object.
1124      */
1125     public void dispose() {
1126         if (currView != null)
1127             currView.dispose();
1128     }
1129 
1130     /**
1131      * Print a text message to the desktop logfile.
1132      * A single line of text which is as short as possible is highly
1133      * recommended for readability purposes.
1134      *
1135      * @param i18n a resource bundle containing the localized messages
1136      * @param key a key into the resource bundle for the required message
1137      *
1138      * @since 3.0.1
1139      */
1140     public void log(I18NResourceBundle i18n, String key) {
1141         ensureLogFileInitialized();
1142         logFile.log(i18n, key);
1143     }
1144 
1145     /**
1146      * Print a text message to the desktop logfile.
1147      * A single line of text which is as short as possible is highly
1148      * recommended for readability purposes.
1149      *
1150      * @param i18n a resource bundle containing the localized messages
1151      * @param key a key into the resource bundle for the required message
1152      * @param arg An argument to be formatted into the specified message.
1153      *          If this is a <code>Throwable</code>, its stack trace
1154      *          will be included in the log.
1155      * @since 3.0.1
1156      */
1157     public void log(I18NResourceBundle i18n, String key, Object arg) {
1158         ensureLogFileInitialized();
1159         logFile.log(i18n, key, arg);
1160     }
1161 
1162     /**
1163      * Print a text message to the desktop logfile.
1164      * A single line of text which is as short as possible is highly
1165      * recommended for readability purposes.
1166      *
1167      * @param i18n a resource bundle containing the localized messages
1168      * @param key a key into the resource bundle for the required message
1169      * @param args An array of arguments to be formatted into the specified message.
1170      *          If the first arg is a <code>Throwable</code>, its stack
1171      *          trace will be included in the log.
1172      * @since 3.0.1
1173      */
1174     public void log(I18NResourceBundle i18n, String key, Object[] args) {
1175         ensureLogFileInitialized();
1176         logFile.log(i18n, key, args);
1177     }
1178 
1179     private void ensureLogFileInitialized() {
1180         if (logFile == null) {
1181             File f;
1182             String s = System.getProperty("javatest.desktop.log");
1183             if (s == null) {
1184                 File jtDir = Preferences.getPrefsDir();
1185                 f = new File(jtDir, "log.txt");
1186             }
1187             else if (s.equals("NONE")) {
1188                 f = null;
1189             }
1190             else
1191                 f = new File(s);
1192 
1193             try {
1194                 BackupPolicy p = BackupPolicy.simpleBackups(5);
1195                 p.backup(f);
1196             }
1197             catch (IOException e) {
1198                 // ignore? or save exception to write to logFile
1199             }
1200 
1201             logFile = (f == null ? new LogFile() : new LogFile(f));
1202         }
1203     }
1204 
1205     // the order of the styles is the presentation order in the preferences panel
1206     /**
1207      * A constant to indicate the tabbed-style desktop:
1208      * a single window for the desktop, using a tabbed pane for the tools.
1209      */
1210     public static final int TAB_STYLE = 0;
1211 
1212     /**
1213      * A constant to indicate the MDI-style desktop:
1214      * a single window for the desktop, containing multiple internal windows, one per tool.
1215      */
1216     public static final int MDI_STYLE = 1;
1217 
1218     /**
1219      * A constant to indicate the SDI-style desktop:
1220      * multiple top-level windows, one per tool.
1221      */
1222     public static final int SDI_STYLE = 2;
1223 
1224     static final int NUM_STYLES = 3;
1225     static final String[] styleNames = {"tab", "mdi", "sdi"};
1226 
1227     private static final String[] jt31StyleClassNames = {
1228         "com.sun.javatest.tool.TabDesktop",
1229         "com.sun.javatest.tool.MDIDesktop",
1230         "com.sun.javatest.tool.SDIDesktop"
1231     };
1232 
1233     /**
1234      * Check whether or not the desktop will save its state when the VM exits.
1235      * @return true if the desktop will save its state when the VM exits, and false otherwise
1236      * @see #setSaveOnExit
1237      */
1238     public boolean getSaveOnExit() {
1239         return saveOnExit;
1240     }
1241 
1242     /**
1243      * Specify whether or not the desktop will save its state when the VM exits.
1244      * @param b true if the desktop should save its state when the VM exits, and false otherwise
1245      * @see #getSaveOnExit
1246      */
1247     public void setSaveOnExit(boolean b) {
1248         saveOnExit = b;
1249     }
1250 
1251     /**
1252      * Check whether or not the desk view should restore saved tools state when the Harness is starting.
1253      * @return true if the desk will restore its tools when the Harness is starting, and false otherwise
1254      * @see #setRestoreOnStart(boolean)
1255      */
1256     public boolean getRestoreOnStart() {
1257         return restoreOnStart;
1258     }
1259 
1260     /**
1261      * Specify whether or not the desk view should restore saved tools state when the Harness is starting.
1262      * @param restoreOnStart true if the desk will restore its tools when the Harness is starting, and false otherwise
1263      * @see #getRestoreOnStart()
1264      */
1265     public void setRestoreOnStart(boolean restoreOnStart) {
1266         this.restoreOnStart = restoreOnStart;
1267     }
1268 
1269     /**
1270      * Get Tooltip delay from prefs in ms.
1271      * @return Range is 0-Integer.MAX_VALUE
1272      */
1273     static int getTooltipDelay(Preferences p) {
1274         String val = p.getPreference(TTIP_DELAY);
1275         int result = TTIP_DELAY_DEFAULT;
1276 
1277         try {
1278             // expected range from prefs in 0-Integer.MAX_VALUE
1279             result = Integer.parseInt(val);
1280         }
1281         catch (NumberFormatException e) {
1282             // default to no delay
1283             result = TTIP_DELAY_DEFAULT;
1284         }
1285 
1286         if (result < 0)
1287             result = TTIP_DELAY_DEFAULT;;
1288 
1289         return result;
1290     }
1291 
1292     /**
1293      * Get tooltip duration from prefs in ms.
1294      * This is the translated value, so the "forever" value has been
1295      * transformed into something useful.
1296      * @return Range is 0-Integer.MAX_VALUE
1297      */
1298     static int getTooltipDuration(Preferences p) {
1299         String val = p.getPreference(TTIP_DURATION);
1300         int result = TTIP_DURATION_DEFAULT;
1301 
1302         try {
1303             // expected range from prefs in -1-Integer.MAX_VALUE
1304             result = Integer.parseInt(val);
1305         }
1306         catch (NumberFormatException e) {
1307             // default to no delay
1308             result = TTIP_DURATION_DEFAULT;
1309         }
1310 
1311         if (result < 0)
1312             if (result == TTIP_DURATION_FOREVER)        // indicates forever duration
1313                 result = Integer.MAX_VALUE;
1314             else                        // -2 or less, unknown value
1315                 result = TTIP_DURATION_DEFAULT;
1316         else { }
1317 
1318         return result;
1319     }
1320 
1321     // these are here to be shared with DesktopPrefsPane.
1322     void setTooltipsEnabled(boolean state) {
1323         ToolTipManager.sharedInstance().setEnabled(state);
1324     }
1325 
1326     /**
1327      * Unconditionally set the tooltip delay to the given setting.
1328      * @param delay Delay time in ms or TTIP_DELAY_NONE.
1329      */
1330     void setTooltipDelay(int delay) {
1331         ToolTipManager.sharedInstance().setInitialDelay(delay);
1332     }
1333 
1334     /**
1335      * Unconditionally set the tooltip duration to the given setting.
1336      * @param duration Duration time in ms or TTTIP_DURATION_FOREVER.
1337      */
1338     void setTooltipDuration(int duration) {
1339         if (duration == TTIP_DURATION_FOREVER)
1340             ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
1341         else
1342             ToolTipManager.sharedInstance().setDismissDelay(duration);
1343     }
1344 
1345     public void printSetup() {
1346         ensurePrintAttrsInitialized();
1347         PrinterJob job = PrinterJob.getPrinterJob();
1348         job.pageDialog(printAttrs);
1349     }
1350 
1351     public void print(Printable printable) {
1352 
1353         DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
1354         PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null);
1355 
1356         if(services.length > 0) {
1357             ensurePrintAttrsInitialized();
1358 
1359             Component parent = getDialogParent();
1360             int x = (int)parent.getLocationOnScreen().getX() + parent.getWidth() / 2 - 250;
1361             int y = (int)parent.getLocationOnScreen().getY() + parent.getHeight() / 2 - 250;
1362 
1363             PrintService service = ServiceUI.printDialog(null, x, y, services,
1364                     services[0], flavor, printAttrs);
1365             if(service != null) {
1366                 DocPrintJob job = service.createPrintJob();
1367                 try {
1368                     Doc doc = new SimpleDoc(printable, flavor, null);
1369 
1370                     job.print(doc, printAttrs);
1371                 }
1372                 catch (PrintException e) {
1373                     e.printStackTrace();
1374                 }
1375             }
1376         }
1377 
1378     }
1379 
1380     private void ensurePrintAttrsInitialized() {
1381         if(printAttrs == null) {
1382             printAttrs = new HashPrintRequestAttributeSet();
1383         }
1384     }
1385 
1386     private void initHelpBroker() {
1387         URL u = null;      // URL for help set
1388         ClassLoader theLoader = null;
1389 
1390         if (commandContext != null) {
1391             final Command[] cmds = commandContext.getCommands();
1392             // uses the first custom help loader found.
1393             // use a customized HelpBroker that will exit the VM when closed.
1394             for (int i = 0; i < cmds.length; i++) {
1395                 theLoader = cmds[i].getCustomHelpLoader();
1396                 // could also upgrade this to accept a different help set name
1397                 u = HelpSet.findHelpSet(theLoader, "jthelp.hs");
1398                 if (u != null)
1399                     break;
1400             }   // for
1401         }
1402 
1403         if (u == null) {
1404             // could also upgrade this to accept a different help set name
1405             theLoader = this.getClass().getClassLoader();
1406             u = HelpSet.findHelpSet(theLoader, "com/sun/javatest/help/jthelp.hs");
1407         }
1408 
1409         if (u != null) {
1410             //HelpSet helpSet = new HelpSet(theLoader, u);
1411             //helpBroker = helpSet.createHelpBroker();
1412             helpBroker = new JTHelpBroker();
1413 
1414             // Not safe to run initPresentation in a background
1415             // thread, despite the JavaHelp specification.
1416             // so run it synchronously instead.
1417             // don't even run it synchronously
1418             //helpBroker.initPresentation();
1419             /*
1420             // launch background thread to init help in the
1421             // background, per JavaHelp suggestions
1422             Runnable r = new Runnable() {
1423                 public void run() {
1424                     helpBroker.initPresentation();
1425                 }
1426             };
1427             Thread t = new Thread(r);
1428             t.setPriority(Thread.MIN_PRIORITY + 1);  // not lowest, but pretty low
1429             t.start();
1430              *
1431              */
1432         }
1433     }
1434 
1435     private void initToolManagers() {
1436         // locate init file and load up the managers
1437         //System.err.println("Desktop.initToolManagers");
1438 
1439         try {
1440             ManagerLoader ml = new ManagerLoader(ToolManager.class, System.err);
1441             ml.setManagerConstructorArgs(new Class[] { Desktop.class }, new Object[] { this });
1442             Set s = ml.loadManagers(TOOLMGRLIST);
1443             toolManagers = (ToolManager[]) (s.toArray(new ToolManager[s.size()]));
1444         }
1445         catch (IOException e) {
1446             throw new JavaTestError(uif.getI18NResourceBundle(),
1447                                     "dt.cantAccessResource", new Object[] { TOOLMGRLIST, e } );
1448         }
1449     }
1450 
1451     private void ensureViewInitialized() {
1452         if (currView != null)
1453             return;
1454 
1455         switch (style) {
1456         case MDI_STYLE:
1457             currView = new MDIDeskView(this);
1458             break;
1459 
1460         case SDI_STYLE:
1461             currView = new SDIDeskView(this);
1462             break;
1463 
1464         default:
1465             currView = new TabDeskView(this);
1466             break;
1467         }
1468     }
1469 
1470     private static void appendStrings(StringBuffer sb, String[] msgs) {
1471         if (msgs != null) {
1472             for (int i = 0; i < msgs.length; i++) {
1473                 sb.append(msgs[i]);
1474                 if (!msgs[i].endsWith("\n"))
1475                     sb.append('\n');
1476             }
1477         }
1478     }
1479 
1480     /**
1481      * Get the file in which the desktop is (to be) stored.
1482      * The standard location is the platform equivalent of
1483      * $HOME/.javatest/desktop
1484      * It can be overridden by setting the system property
1485      * "javatest.desktop.file", which can be set to "NONE"
1486      * to disable the feature.
1487      */
1488     private static File getDesktopFile() {
1489         String s = System.getProperty("javatest.desktop.file");
1490         if (s == null) {
1491             File jtDir = Preferences.getPrefsDir();
1492             return new File(jtDir, "desktop");
1493         }
1494         else if (!s.equals("NONE"))
1495             return new File(s);
1496         else
1497             return null;
1498     }
1499 
1500     static void addHelpDebugListener(Component c) {
1501         JComponent root;
1502         if (c instanceof JFrame)
1503             root = ((JFrame) c).getRootPane();
1504         else if (c instanceof JDialog)
1505             root = ((JDialog) c).getRootPane();
1506         else
1507             throw new IllegalArgumentException();
1508 
1509         ActionListener showFocusListener = new ActionListener() {
1510             public void actionPerformed(ActionEvent e) {
1511                 Component src = (Component) e.getSource();
1512                 Component comp = javax.swing.SwingUtilities.findFocusOwner(src);
1513                 System.err.println("ALT-F2: source=" + src);
1514                 System.err.println("ALT-F2:  focus=" + comp);
1515                 String helpId = comp == null ? "(none)" : ContextHelpManager.getHelpIDString(comp);
1516                 helpId = helpId == null ? ContextHelpManager.getHelpIDString(src) : helpId;
1517                 System.err.println("ALT-F2: helpId=" + helpId);
1518             }
1519         };
1520 
1521         root.registerKeyboardAction(showFocusListener,
1522                                     KeyStroke.getKeyStroke("alt F2"),
1523                                     JComponent.WHEN_IN_FOCUSED_WINDOW);
1524     }
1525 
1526     static void addPreferredSizeDebugListener(Component c) {
1527         JComponent root;
1528         if (c instanceof JFrame)
1529             root = ((JFrame) c).getRootPane();
1530         else if (c instanceof JDialog)
1531             root = ((JDialog) c).getRootPane();
1532         else
1533             throw new IllegalArgumentException();
1534 
1535         ActionListener showPrefSizeListener = new ActionListener() {
1536             public void actionPerformed(ActionEvent e) {
1537                 Component src = (Component) e.getSource();
1538                 Component c = javax.swing.SwingUtilities.findFocusOwner(src);
1539                 while (c != null) {
1540                     Dimension d = c.getPreferredSize();
1541                     System.err.println("ALT-1: comp=" + c.getName() + "(" + c.getClass().getName() + ") "
1542                                      + "[w:" + d.width + ",h:" + d.height + "]");
1543                     c = c.getParent();
1544                 }
1545             }
1546         };
1547 
1548         root.registerKeyboardAction(showPrefSizeListener,
1549                                     KeyStroke.getKeyStroke("alt 1"),
1550                                     JComponent.WHEN_IN_FOCUSED_WINDOW);
1551     }
1552 
1553     private static void invokeOnEventThread(Runnable r) {
1554         try {
1555             EventQueue.invokeAndWait(r);
1556         }
1557         catch (InterruptedException e) {
1558         }
1559         catch (InvocationTargetException e) {
1560             Throwable t = e.getTargetException();
1561             if (t instanceof RuntimeException)
1562                 throw ((RuntimeException) t);
1563             else
1564                 throw ((Error) t);
1565         }
1566     }
1567 
1568     private static int indexOf(String s, String[] a) {
1569         for (int i = 0; i < a.length; i++) {
1570             if (s == null ? a[i] == null : s.equals(a[i]))
1571                 return i;
1572         }
1573         return -1;
1574     }
1575 
1576     /**
1577      * Walks down the exception causes until the original exception is found.
1578      * @param t The problem object to decend into.
1579      * @return The original problem object.
1580      */
1581     private static Throwable unwrap(Throwable t) {
1582         if (t == null) {
1583             return t;
1584         }
1585 
1586         Throwable t1;
1587         while (true) {
1588             t1 = t.getCause();
1589             if (t1==null) {
1590                 return t;
1591             } else {
1592                 t = t1;
1593             }
1594         }
1595     }
1596 
1597     private final UIFactory uif;
1598     private CommandContext commandContext;
1599     private DeskView currView;
1600     private int style; // used until currView is set, then style comes from that
1601     private PreferencesPane prefsPane;
1602     private PreferencesPane colorPane;
1603     private ToolManager[] toolManagers;
1604     private HelpBroker helpBroker;
1605     private LogFile logFile;
1606     private boolean firstTime;
1607     private boolean saveOnExit;
1608     private PrintRequestAttributeSet printAttrs;
1609     private boolean restoreOnStart;
1610 
1611     private LinkedList<FileHistoryEntry> fileHistory = new LinkedList<>();
1612     private static final int FILE_HISTORY_MAX_SIZE = 10;
1613 
1614     private static Preferences preferences = Preferences.access();
1615 
1616 
1617     static final String STYLE_PREF = "tool.appearance.style";
1618     static final String TTIP_PREF = "tool.appearance.ttipToggle";
1619     static final String TTIP_DELAY= "tool.appearance.ttipDelay";
1620     static final String TTIP_DURATION = "tool.appearance.ttipDuration";
1621     static final int TTIP_DURATION_FOREVER = -1;
1622     static final int TTIP_DELAY_NONE = 0;
1623     static final int TTIP_DELAY_DEFAULT = 0;
1624     static final int TTIP_DURATION_DEFAULT = 5000;
1625     static final String SAVE_ON_EXIT_PREF = "tool.appearance.saveOnExit";
1626     static final String RESTORE_ON_START_PREF = "tool.appearance.restoreOnStart";
1627 
1628     private static final String TOOLMGRLIST = "META-INF/services/com.sun.javatest.tool.ToolManager.lst";
1629     private static final String defaultToolManager =
1630         System.getProperty("javatest.desktop.defaultToolManager", "com.sun.javatest.exec.ExecToolManager");
1631 
1632 
1633     //-------------------------------------------------------------------------
1634 
1635     /**
1636      * A class for an entry on the file history list.
1637      * It defines a file, and an object to open that file if required.
1638      */
1639     static class FileHistoryEntry {
1640         FileHistoryEntry(FileOpener fo, File f) {
1641             fileOpener = fo;
1642             file = f;
1643         }
1644 
1645         FileOpener fileOpener;
1646         File file;
1647     }
1648 }