1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2004, 2011, 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.FocusTraversalPolicy;
  33 import java.awt.KeyboardFocusManager;
  34 import java.awt.Rectangle;
  35 import java.awt.Window;
  36 import java.awt.event.ActionEvent;
  37 import java.awt.event.ActionListener;
  38 import java.awt.event.WindowAdapter;
  39 import java.awt.event.WindowEvent;
  40 import java.awt.Dialog.ModalityType;
  41 import java.util.Arrays;
  42 import java.util.Comparator;
  43 import java.util.HashSet;
  44 import java.util.Map;
  45 import java.util.Set;
  46 
  47 import javax.swing.Action;
  48 import javax.swing.JDialog;
  49 import javax.swing.JFrame;
  50 import javax.swing.JMenu;
  51 import javax.swing.JMenuBar;
  52 import javax.swing.JMenuItem;
  53 import javax.swing.JTabbedPane;
  54 import javax.swing.SwingConstants;
  55 import javax.swing.event.AncestorEvent;
  56 import javax.swing.event.AncestorListener;
  57 import javax.swing.event.ChangeEvent;
  58 import javax.swing.event.ChangeListener;
  59 import javax.swing.event.MenuEvent;
  60 import javax.swing.event.MenuListener;
  61 
  62 import com.sun.javatest.util.PrefixMap;
  63 import com.sun.javatest.tool.jthelp.ContextHelpManager;
  64 
  65 /**
  66  * A container that presents the current desktop tools in a tabbed pane.
  67  * The main complexity is that when a tool is made current, its menus
  68  * are merged onto the main menu bar, and removed when the tool is no
  69  * longer selected.
  70  */
  71 class TabDeskView extends DeskView {
  72 
  73     TabDeskView(Desktop desktop) {
  74         this(desktop, getDefaultBounds());
  75     }
  76 
  77     TabDeskView(DeskView other) {
  78         this(other.getDesktop(), other.getBounds());
  79         //System.err.println("Tab: create from " + other);
  80         //System.err.println("Tab: create " + other.getTools().length + " tools");
  81 
  82         Tool[] tools = other.getTools();
  83 
  84         // perhaps would be nice to have getTools(Comparator) and have it return a sorted
  85         // array of tools
  86         Arrays.sort(tools, new Comparator() {
  87             public int compare(Object o1, Object o2) {
  88                 Long l1 = new Long(((Tool)o1).getCreationTime());
  89                 Long l2 = new Long(((Tool)o2).getCreationTime());
  90                 return (l1.compareTo(l2));
  91             }
  92         });
  93 
  94         for (int i = 0; i < tools.length; i++)
  95             addTool(tools[i]);
  96 
  97         setVisible(other.isVisible());
  98     }
  99 
 100     private TabDeskView(Desktop desktop, Rectangle bounds) {
 101         super(desktop);
 102         initMainFrame(bounds);
 103         uif.setDialogParent(mainFrame);
 104         JDialog.setDefaultLookAndFeelDecorated(false);
 105     }
 106 
 107     public void dispose() {
 108         mainFrame.setVisible(false);
 109         mainFrame.dispose();
 110         super.dispose();
 111     }
 112 
 113     public boolean isVisible() {
 114         return mainFrame.isVisible();
 115     }
 116 
 117     public void setVisible(boolean v) {
 118         //System.err.println("Tab: setVisible: " + v);
 119         if (v == mainFrame.isVisible())
 120             return;
 121 
 122         mainFrame.setVisible(v);
 123 
 124         if (v) {
 125             Window[] ww = mainFrame.getOwnedWindows();
 126             if (ww != null) {
 127                 for (int i = 0; i < ww.length; i++)
 128                     ww[i].toFront();
 129             }
 130         }
 131     }
 132 
 133     public void addTool(Tool t) {
 134         DeskView view = t.getDeskView();
 135         if (view == this)
 136             return;
 137 
 138         // save info about dialogs before we remove tool from other view
 139         ToolDialog[] tds = t.getToolDialogs();
 140         boolean[] vis = new boolean[tds.length];
 141         for (int i = 0; i < tds.length; i++)
 142             vis[i] = tds[i].isVisible();
 143 
 144         // remove tool from other view (if any)
 145         if (view != null)
 146             view.removeTool(t);
 147 
 148         //System.err.println("Tab: add " + t);
 149         String tabTitle = getUniqueTabTitle(t.getShortTitle(), null);
 150         String tabToolTip = t.getTitle();
 151         contents.addTab(tabTitle, null, t, tabToolTip);
 152         t.addObserver(listener);
 153         closeAction.setEnabled(true);
 154 
 155         t.setDeskView(this);
 156 
 157         // update tool dialogs
 158         for (int i = 0; i < tds.length; i++)
 159             tds[i].initDialog(this, vis[i]);
 160     }
 161 
 162     public boolean isEmpty() {
 163         return (contents.getComponentCount() == 0);
 164     }
 165 
 166     public Tool[] getTools() {
 167         Tool[] tools = new Tool[contents.getComponentCount()];
 168         for (int i = 0; i < tools.length; i++)
 169             tools[i] = (Tool) (contents.getComponentAt(i));
 170         return tools;
 171     }
 172 
 173     public void removeTool(Tool t) {
 174         t.removeObserver(listener);
 175 
 176         // remove the change listener temporarily because of the
 177         // broken semantics
 178         contents.removeChangeListener(listener);
 179 
 180         // remove the tool
 181         contents.remove(t);
 182         t.setDeskView(null);
 183 
 184         // update the selection as appropriate
 185         if (t == selectedTool)
 186             // set a different tool to be selected
 187             setSelectedTool((Tool) contents.getSelectedComponent());
 188         else
 189             // set the selection again, in case the index has changed
 190             contents.setSelectedComponent(selectedTool);
 191 
 192         // ensure there is a valid keyboard focus
 193         KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
 194         Component fo = fm.getPermanentFocusOwner();
 195         if (fo == null || !fo.isShowing()) {
 196             Container target = contents.getTabCount() > 0 ? contents : mainFrame;
 197             Container fcr = (target.isFocusCycleRoot() ? target : target.getFocusCycleRootAncestor());
 198             FocusTraversalPolicy ftp = fcr.getFocusTraversalPolicy();
 199             Component c = (target.isFocusable() ? target : ftp.getComponentAfter(fcr, target));
 200             c.requestFocusInWindow();
 201         }
 202 
 203         // restore the change listener now that the removal has been done
 204         contents.addChangeListener(listener);
 205 
 206         // update actions
 207         closeAction.setEnabled(contents.getTabCount() > 0);
 208     }
 209 
 210     public Tool getSelectedTool() {
 211         return selectedTool;
 212     }
 213 
 214     public void setSelectedTool(Tool t) {
 215         if (t == selectedTool)
 216             // already selected
 217             return;
 218 
 219         // hands off the old selected tool (if any)
 220         if (selectedTool != null) {
 221             //OLD removeToolMenuItemsFromBasicMenuBar(selectedTool);
 222             removeToolMenuItemsFromFrameMenuBar(mainFrame, selectedTool);
 223             selectedTool.removeObserver(listener);
 224         }
 225 
 226         selectedTool = t;
 227 
 228         // hands on the new selected tool (if any)
 229         if (selectedTool == null) {
 230             mainFrame.setTitle(uif.getI18NString("dt.title.txt"));
 231             ContextHelpManager.setHelpIDString(contents, null);
 232         }
 233         else {
 234             //OLD addToolMenuItemsToBasicMenuBar(selectedTool);
 235             addToolMenuItemsToFrameMenuBar(mainFrame, selectedTool);
 236             selectedTool.addObserver(listener);
 237             mainFrame.setTitle(uif.getI18NString("dt.title.tool.txt", selectedTool.getTitle()));
 238             ContextHelpManager.setHelpIDString(contents, ContextHelpManager.getHelpIDString(selectedTool));
 239             contents.setSelectedComponent(selectedTool);
 240         }
 241 
 242     }
 243 
 244     public int getStyle() {
 245         return Desktop.TAB_STYLE;
 246     }
 247 
 248     public JFrame[] getFrames() {
 249         return new JFrame[] { mainFrame };
 250     }
 251 
 252     public Rectangle getBounds() {
 253         return mainFrame.getBounds();
 254     }
 255 
 256     public boolean isToolOwnerForDialog(Tool tool, Container dialog) {
 257         for (ToolDialog td: tool.getToolDialogs()) {
 258             if (td.getDialogParent() == dialog)
 259                 return true;
 260         }
 261         return (dialog != null
 262                 && (dialog.getParent() == mainFrame));
 263     }
 264 
 265     public Window createDialog(Tool tool, String uiKey, String title,
 266                                   JMenuBar menuBar, Container body,
 267                                   Rectangle bounds, int type) {
 268         UIFactory uif = tool.uif;
 269         if ((type & ToolDialog.FRAME) != 0) {
 270             JFrame d = uif.createFrame(uiKey, title, body);
 271             if (menuBar != null)
 272                 d.setJMenuBar(menuBar);
 273 
 274             setBounds(d, bounds);
 275 
 276             return d;
 277         } else /*if ((type & ToolDialog.DIALOG) != 0)*/ {
 278             JFrame owner = mainFrame;
 279             if ((type & ToolDialog.FREE) != 0) {
 280                 owner = null;
 281             }
 282 
 283             JDialog d = uif.createDialog(uiKey, owner, title, body);
 284 
 285             if ((type & ToolDialog.MODAL) != 0) {
 286                 if ((type & ToolDialog.MODAL_TOOLKIT) == ToolDialog.MODAL_TOOLKIT) {
 287                     d.setModalityType(ModalityType.TOOLKIT_MODAL);
 288                 } else if ((type & ToolDialog.MODAL_DOCUMENT) == ToolDialog.MODAL_DOCUMENT) {
 289                     d.setModalityType(ModalityType.DOCUMENT_MODAL);
 290                 } else if ((type & ToolDialog.MODAL_APPLICATION) == ToolDialog.MODAL_APPLICATION) {
 291                     d.setModalityType(ModalityType.APPLICATION_MODAL);
 292                 } else {
 293                     d.setModal(true);
 294                 }
 295             }
 296             if (menuBar != null)
 297                 d.setJMenuBar(menuBar);
 298 
 299             setBounds(d, bounds);
 300 
 301             return d;
 302         }
 303     }
 304 
 305     private void setBounds(Window d, Rectangle bounds) {
 306         if (bounds == null) {
 307             d.pack();
 308             // for some reason the first call of pack seems to yield small results
 309             // so we need to pack it again to get the real results.  Additional calls
 310             // seem to have no effect, so after 2 calls we seem to have stable results.
 311             d.pack();
 312             d.setLocationRelativeTo(mainFrame);
 313         } else {
 314             d.setBounds(bounds);
 315         }
 316     }
 317 
 318     public Container createDialog(Tool tool, String uiKey, String title,
 319                                   JMenuBar menuBar, Container body,
 320                                   Rectangle bounds) {
 321         UIFactory uif = tool.uif;
 322         JDialog d = uif.createDialog(uiKey, mainFrame, title, body);
 323         if (menuBar != null)
 324             d.setJMenuBar(menuBar);
 325 
 326         setBounds(d, bounds);
 327 
 328         return d;
 329     }
 330 
 331     protected void saveDesktop(Map<String, String> m) {
 332         saveBounds(mainFrame, new PrefixMap<>(m, "dt"));
 333         saveTools(m);
 334         int sel = contents.getSelectedIndex();
 335         if (sel >= 0)
 336             m.put("dt.selected", String.valueOf(sel));
 337     }
 338 
 339     protected void restoreDesktop(Map<String, String> m) {
 340         restoreBounds(mainFrame, new PrefixMap<>(m, "dt"));
 341         if (getDesktop().getRestoreOnStart()) {
 342             restoreTools(m);
 343 
 344             try {
 345                 String s = m.get("dt.selected");
 346                 if (s != null) {
 347                     int sel = Integer.parseInt(s);
 348                     if (0 <= sel && sel < contents.getTabCount()) {
 349                         contents.setSelectedIndex(sel);
 350                     }
 351                 }
 352             } catch (NumberFormatException e) {
 353                 // ignore
 354             }
 355         }
 356     }
 357 
 358     // internal
 359 
 360     private void initMainFrame(Rectangle bounds) {
 361         //System.err.println("Tab: create");
 362         mainFrame = createFrame(listener, closeAction, "tdi.main");
 363         //OLD basicMenuBar = mainFrame.getJMenuBar();
 364 
 365         contents = uif.createTabbedPane("tdi.desk");
 366         contents.setOpaque(true);
 367         contents.setPreferredSize(new Dimension(bounds.width, bounds.height));
 368 
 369         // this could be a user preference (deck tab placement)
 370         contents.setTabPlacement(SwingConstants.BOTTOM);
 371         contents.addChangeListener(listener);
 372         contents.addAncestorListener(listener);
 373         mainFrame.setContentPane(contents);
 374         mainFrame.setBounds(bounds);
 375 
 376         mainFrame.addWindowListener(new WindowAdapter() {
 377             public void windowClosing(WindowEvent e) {
 378                 getDesktop().checkToolsAndExitIfOK(mainFrame);
 379             }
 380         });
 381     }
 382 
 383     private String getUniqueTabTitle(String base, Component ignoreable) {
 384         Set<String> s = new HashSet<>();
 385         for (int i = 0; i < contents.getTabCount(); i++) {
 386             if (contents.getComponentAt(i) != ignoreable)
 387                 s.add(contents.getTitleAt(i));
 388         }
 389 
 390         if (s.contains(base)) {
 391             for (int i = 0; i <= s.size(); i++) {
 392                 // checking s.size() + 1 cases: at least one must be free
 393                 String v = base + " [" + (i + 2) + "]";
 394                 if (!s.contains(v))
 395                     return v;
 396             }
 397         }
 398 
 399         return base;
 400     }
 401 
 402     private JFrame mainFrame;
 403     // OLD private JMenuBar basicMenuBar;
 404     private JTabbedPane contents;
 405     private Tool selectedTool;
 406 
 407     private Listener listener = new Listener();
 408 
 409     private Action closeAction = new CloseAction();
 410 
 411     private class CloseAction extends ToolAction {
 412         CloseAction() {
 413             super(uif, "tdi.file.close");
 414             setEnabled(false);  // enabled if/when there are tools
 415         }
 416 
 417         public void actionPerformed(ActionEvent e) {
 418             Tool t = (Tool) (contents.getSelectedComponent());
 419             if (t != null)
 420                 // should never be null because action should be disabled if there
 421                 // are no tabs
 422                 if (getDesktop().isOKToClose(t, mainFrame)) {
 423                     removeTool(t);
 424                     t.dispose();
 425                 }
 426         }
 427     };
 428 
 429     private class Listener
 430         implements ActionListener, AncestorListener,
 431                    ChangeListener, MenuListener,
 432                    Tool.Observer
 433     {
 434         // --------- ActionListener  ---------
 435 
 436         public void actionPerformed(ActionEvent e) {
 437             JMenuItem mi = (JMenuItem) (e.getSource());
 438             Object o = mi.getClientProperty(this);
 439             if (o instanceof Window)
 440                 ((Window) o).toFront();
 441             else if (o instanceof Tool)
 442                 setSelectedTool((Tool) o);
 443         }
 444 
 445         // ---------- AncestorListener ----------
 446 
 447         public void ancestorAdded(AncestorEvent event) {
 448             Tool t = (Tool)(contents.getSelectedComponent());
 449             if (t != null) {
 450                 // OLD addToolMenuItemsToBasicMenuBar(t);
 451                 addToolMenuItemsToFrameMenuBar(mainFrame, t);
 452                 t.addObserver(this);
 453                 mainFrame.setTitle(uif.getI18NString("dt.title.tool.txt", t.getTitle()));
 454             }
 455         }
 456 
 457         public void ancestorMoved(AncestorEvent event) { }
 458 
 459         public void ancestorRemoved(AncestorEvent event) {
 460             // // update menubar
 461             // removeToolMenuItemsFromBasicMenuBar();
 462             // stop observing current tool
 463             Tool t = (Tool) (contents.getSelectedComponent());
 464             if (t != null)
 465                 t.removeObserver(this);
 466             mainFrame.setTitle(uif.getI18NString("dt.title.txt"));
 467         }
 468 
 469         // ---------- ChangeListener ----------
 470 
 471         public void stateChanged(ChangeEvent e) {
 472             setSelectedTool((Tool) (contents.getSelectedComponent()));
 473         }
 474 
 475         // --------- MenuListener ---------
 476 
 477         // note this is for Windows menu only
 478         public void menuSelected(MenuEvent e) {
 479             Tool[] tools = getTools();
 480 
 481             JMenu m = (JMenu) (e.getSource());
 482             m.removeAll();
 483 
 484             /*
 485             JMenu winOpenMenu = getWindowOpenMenu();
 486             if (winOpenMenu.getItemCount() > 0) {
 487                 m.add(getWindowOpenMenu());
 488                 m.addSeparator();
 489             }
 490             */
 491 
 492             // add entries for all current tools
 493             if (tools.length == 0) {
 494                 JMenuItem mi = new JMenuItem(uif.getI18NString("dt.windows.noWindows.mit"));
 495                 mi.setEnabled(false);
 496                 m.add(mi);
 497             }
 498             else {
 499                 int n = 0;
 500 
 501                 // add entries for all current tools
 502                 for (int i = 0; i < tools.length; i++) {
 503                     Tool tool = tools[i];
 504                     addMenuItem(m, n++, tool.getTitle(), tool);
 505                 }
 506 
 507                 // add entries for any dialogs
 508                 Window[] ownedWindows = mainFrame.getOwnedWindows();
 509                 for (int i = 0; i < ownedWindows.length; i++) {
 510                     Window w = ownedWindows[i];
 511                     if (w.isVisible()) {
 512                         if (w instanceof JDialog)
 513                             addMenuItem(m, n++, ((JDialog) w).getTitle(), w);
 514                         if (w instanceof JFrame)
 515                             addMenuItem(m, n++, ((JFrame) w).getTitle(), w);
 516                     }
 517                 }
 518             }
 519         }
 520 
 521         private void addMenuItem(JMenu m, int n, String s, Object o) {
 522             JMenuItem mi = new JMenuItem(uif.getI18NString("dt.windows.toolX.mit",
 523                                                  new Object[] { new Integer(n), s }));
 524             if (n < 10)
 525                 mi.setMnemonic(Character.forDigit(n, 10));
 526             mi.addActionListener(this);
 527             mi.putClientProperty(this, o);
 528             m.add(mi);
 529         }
 530 
 531         public void menuDeselected(MenuEvent e) {
 532             // it's not so much the menu items we want to get rid of, as the
 533             // client properties on those items
 534             JMenu m = (JMenu) (e.getSource());
 535             m.removeAll();
 536         }
 537 
 538         public void menuCanceled(MenuEvent e) {
 539             // it's not so much the menu items we want to get rid of, as the
 540             // client properties on those items
 541             JMenu m = (JMenu) (e.getSource());
 542             m.removeAll();
 543         }
 544 
 545         // ---------- Tool.Observer ----------
 546 
 547         public void shortTitleChanged(Tool src, String newValue) {
 548             for (int i = 0; i < contents.getTabCount(); i++) {
 549                 if (contents.getComponentAt(i) == src) {
 550                     String tabTitle = getUniqueTabTitle(newValue, src);
 551                     contents.setTitleAt(i, tabTitle);
 552                     break;
 553                 }
 554             }
 555         }
 556 
 557         public void titleChanged(Tool src, String newValue) {
 558             if (src == contents.getSelectedComponent())
 559                 mainFrame.setTitle(uif.getI18NString("dt.title.tool.txt", newValue));
 560 
 561             for (int i = 0; i < contents.getTabCount(); i++) {
 562                 if (contents.getComponentAt(i) == src) {
 563                     contents.setToolTipTextAt(i, newValue);
 564                     break;
 565                 }
 566             }
 567             //System.err.println("Tool title changed: " + newValue);
 568         }
 569 
 570         public void toolDisposed(Tool src) { }
 571     };
 572 }