View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.swing;
27  
28  import java.util.List;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Iterator;
32  import javax.swing.plaf.*;
33  import javax.accessibility.*;
34  
35  import java.awt.Component;
36  import java.awt.Container;
37  import java.awt.DefaultFocusTraversalPolicy;
38  import java.awt.FocusTraversalPolicy;
39  import java.awt.Window;
40  import java.io.ObjectOutputStream;
41  import java.io.ObjectInputStream;
42  import java.io.IOException;
43  import java.beans.PropertyVetoException;
44  import java.util.Set;
45  import java.util.TreeSet;
46  /**
47   * A container used to create a multiple-document interface or a virtual desktop.
48   * You create <code>JInternalFrame</code> objects and add them to the
49   * <code>JDesktopPane</code>. <code>JDesktopPane</code> extends
50   * <code>JLayeredPane</code> to manage the potentially overlapping internal
51   * frames. It also maintains a reference to an instance of
52   * <code>DesktopManager</code> that is set by the UI
53   * class for the current look and feel (L&amp;F).  Note that <code>JDesktopPane</code>
54   * does not support borders.
55   * <p>
56   * This class is normally used as the parent of <code>JInternalFrames</code>
57   * to provide a pluggable <code>DesktopManager</code> object to the
58   * <code>JInternalFrames</code>. The <code>installUI</code> of the
59   * L&amp;F specific implementation is responsible for setting the
60   * <code>desktopManager</code> variable appropriately.
61   * When the parent of a <code>JInternalFrame</code> is a <code>JDesktopPane</code>,
62   * it should delegate most of its behavior to the <code>desktopManager</code>
63   * (closing, resizing, etc).
64   * <p>
65   * For further documentation and examples see
66   * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/internalframe.html">How to Use Internal Frames</a>,
67   * a section in <em>The Java Tutorial</em>.
68   * <p>
69   * <strong>Warning:</strong> Swing is not thread safe. For more
70   * information see <a
71   * href="package-summary.html#threading">Swing's Threading
72   * Policy</a>.
73   * <p>
74   * <strong>Warning:</strong>
75   * Serialized objects of this class will not be compatible with
76   * future Swing releases. The current serialization support is
77   * appropriate for short term storage or RMI between applications running
78   * the same version of Swing.  As of 1.4, support for long term storage
79   * of all JavaBeans&trade;
80   * has been added to the <code>java.beans</code> package.
81   * Please see {@link java.beans.XMLEncoder}.
82   *
83   * @see JInternalFrame
84   * @see JInternalFrame.JDesktopIcon
85   * @see DesktopManager
86   *
87   * @author David Kloba
88   */
89  public class JDesktopPane extends JLayeredPane implements Accessible
90  {
91      /**
92       * @see #getUIClassID
93       * @see #readObject
94       */
95      private static final String uiClassID = "DesktopPaneUI";
96  
97      transient DesktopManager desktopManager;
98  
99      private transient JInternalFrame selectedFrame = null;
100 
101     /**
102       * Indicates that the entire contents of the item being dragged
103       * should appear inside the desktop pane.
104       *
105       * @see #OUTLINE_DRAG_MODE
106       * @see #setDragMode
107       */
108     public static final int LIVE_DRAG_MODE = 0;
109 
110     /**
111       * Indicates that an outline only of the item being dragged
112       * should appear inside the desktop pane.
113       *
114       * @see #LIVE_DRAG_MODE
115       * @see #setDragMode
116       */
117     public static final int OUTLINE_DRAG_MODE = 1;
118 
119     private int dragMode = LIVE_DRAG_MODE;
120     private boolean dragModeSet = false;
121     private transient List<JInternalFrame> framesCache;
122     private boolean componentOrderCheckingEnabled = true;
123     private boolean componentOrderChanged = false;
124 
125     /**
126      * Creates a new <code>JDesktopPane</code>.
127      */
128     public JDesktopPane() {
129         setUIProperty("opaque", Boolean.TRUE);
130         setFocusCycleRoot(true);
131 
132         setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
133             public Component getDefaultComponent(Container c) {
134                 JInternalFrame jifArray[] = getAllFrames();
135                 Component comp = null;
136                 for (JInternalFrame jif : jifArray) {
137                     comp = jif.getFocusTraversalPolicy().getDefaultComponent(jif);
138                     if (comp != null) {
139                         break;
140                     }
141                 }
142                 return comp;
143             }
144         });
145         updateUI();
146     }
147 
148     /**
149      * Returns the L&amp;F object that renders this component.
150      *
151      * @return the <code>DesktopPaneUI</code> object that
152      *   renders this component
153      */
154     public DesktopPaneUI getUI() {
155         return (DesktopPaneUI)ui;
156     }
157 
158     /**
159      * Sets the L&amp;F object that renders this component.
160      *
161      * @param ui  the DesktopPaneUI L&amp;F object
162      * @see UIDefaults#getUI
163      * @beaninfo
164      *        bound: true
165      *       hidden: true
166      *    attribute: visualUpdate true
167      *  description: The UI object that implements the Component's LookAndFeel.
168      */
169     public void setUI(DesktopPaneUI ui) {
170         super.setUI(ui);
171     }
172 
173     /**
174      * Sets the "dragging style" used by the desktop pane.
175      * You may want to change to one mode or another for
176      * performance or aesthetic reasons.
177      *
178      * @param dragMode the style of drag to use for items in the Desktop
179      *
180      * @see #LIVE_DRAG_MODE
181      * @see #OUTLINE_DRAG_MODE
182      *
183      * @beaninfo
184      *        bound: true
185      *  description: Dragging style for internal frame children.
186      *         enum: LIVE_DRAG_MODE JDesktopPane.LIVE_DRAG_MODE
187      *               OUTLINE_DRAG_MODE JDesktopPane.OUTLINE_DRAG_MODE
188      * @since 1.3
189      */
190     public void setDragMode(int dragMode) {
191         int oldDragMode = this.dragMode;
192         this.dragMode = dragMode;
193         firePropertyChange("dragMode", oldDragMode, this.dragMode);
194         dragModeSet = true;
195      }
196 
197     /**
198      * Gets the current "dragging style" used by the desktop pane.
199      * @return either <code>Live_DRAG_MODE</code> or
200      *   <code>OUTLINE_DRAG_MODE</code>
201      * @see #setDragMode
202      * @since 1.3
203      */
204      public int getDragMode() {
205          return dragMode;
206      }
207 
208     /**
209      * Returns the <code>DesktopManger</code> that handles
210      * desktop-specific UI actions.
211      */
212     public DesktopManager getDesktopManager() {
213         return desktopManager;
214     }
215 
216     /**
217      * Sets the <code>DesktopManger</code> that will handle
218      * desktop-specific UI actions. This may be overridden by
219      * {@code LookAndFeel}.
220      *
221      * @param d the <code>DesktopManager</code> to use
222      *
223      * @beaninfo
224      *        bound: true
225      *  description: Desktop manager to handle the internal frames in the
226      *               desktop pane.
227      */
228     public void setDesktopManager(DesktopManager d) {
229         DesktopManager oldValue = desktopManager;
230         desktopManager = d;
231         firePropertyChange("desktopManager", oldValue, desktopManager);
232     }
233 
234     /**
235      * Notification from the <code>UIManager</code> that the L&amp;F has changed.
236      * Replaces the current UI object with the latest version from the
237      * <code>UIManager</code>.
238      *
239      * @see JComponent#updateUI
240      */
241     public void updateUI() {
242         setUI((DesktopPaneUI)UIManager.getUI(this));
243     }
244 
245 
246     /**
247      * Returns the name of the L&amp;F class that renders this component.
248      *
249      * @return the string "DesktopPaneUI"
250      * @see JComponent#getUIClassID
251      * @see UIDefaults#getUI
252      */
253     public String getUIClassID() {
254         return uiClassID;
255     }
256 
257     /**
258      * Returns all <code>JInternalFrames</code> currently displayed in the
259      * desktop. Returns iconified frames as well as expanded frames.
260      *
261      * @return an array of <code>JInternalFrame</code> objects
262      */
263     public JInternalFrame[] getAllFrames() {
264         return getAllFrames(this).toArray(new JInternalFrame[0]);
265     }
266 
267     private static Collection<JInternalFrame> getAllFrames(Container parent) {
268         int i, count;
269         Collection<JInternalFrame> results = new ArrayList<JInternalFrame>();
270         count = parent.getComponentCount();
271         for (i = 0; i < count; i++) {
272             Component next = parent.getComponent(i);
273             if (next instanceof JInternalFrame) {
274                 results.add((JInternalFrame) next);
275             } else if (next instanceof JInternalFrame.JDesktopIcon) {
276                 JInternalFrame tmp = ((JInternalFrame.JDesktopIcon) next).getInternalFrame();
277                 if (tmp != null) {
278                     results.add(tmp);
279                 }
280             } else if (next instanceof Container) {
281                 results.addAll(getAllFrames((Container) next));
282             }
283         }
284         return results;
285     }
286 
287     /** Returns the currently active <code>JInternalFrame</code>
288       * in this <code>JDesktopPane</code>, or <code>null</code>
289       * if no <code>JInternalFrame</code> is currently active.
290       *
291       * @return the currently active <code>JInternalFrame</code> or
292       *   <code>null</code>
293       * @since 1.3
294       */
295 
296     public JInternalFrame getSelectedFrame() {
297       return selectedFrame;
298     }
299 
300     /** Sets the currently active <code>JInternalFrame</code>
301      *  in this <code>JDesktopPane</code>. This method is used to bridge
302      *  the package gap between JDesktopPane and the platform implementation
303      *  code and should not be called directly. To visually select the frame
304      *  the client must call JInternalFrame.setSelected(true) to activate
305      *  the frame.
306      *  @see JInternalFrame#setSelected(boolean)
307      *
308      * @param f the internal frame that's currently selected
309      * @since 1.3
310      */
311 
312     public void setSelectedFrame(JInternalFrame f) {
313       selectedFrame = f;
314     }
315 
316     /**
317      * Returns all <code>JInternalFrames</code> currently displayed in the
318      * specified layer of the desktop. Returns iconified frames as well
319      * expanded frames.
320      *
321      * @param layer  an int specifying the desktop layer
322      * @return an array of <code>JInternalFrame</code> objects
323      * @see JLayeredPane
324      */
325     public JInternalFrame[] getAllFramesInLayer(int layer) {
326         Collection<JInternalFrame> allFrames = getAllFrames(this);
327         Iterator<JInternalFrame> iterator = allFrames.iterator();
328         while (iterator.hasNext()) {
329             if (iterator.next().getLayer() != layer) {
330                 iterator.remove();
331             }
332         }
333         return allFrames.toArray(new JInternalFrame[0]);
334     }
335 
336     private List<JInternalFrame> getFrames() {
337         Component c;
338         Set<ComponentPosition> set = new TreeSet<ComponentPosition>();
339         for (int i = 0; i < getComponentCount(); i++) {
340             c = getComponent(i);
341             if (c instanceof JInternalFrame) {
342                 set.add(new ComponentPosition((JInternalFrame)c, getLayer(c),
343                     i));
344             }
345             else if (c instanceof JInternalFrame.JDesktopIcon)  {
346                 c = ((JInternalFrame.JDesktopIcon)c).getInternalFrame();
347                 set.add(new ComponentPosition((JInternalFrame)c, getLayer(c),
348                     i));
349             }
350         }
351         List<JInternalFrame> frames = new ArrayList<JInternalFrame>(
352                 set.size());
353         for (ComponentPosition position : set) {
354             frames.add(position.component);
355         }
356         return frames;
357    }
358 
359     private static class ComponentPosition implements
360         Comparable<ComponentPosition> {
361         private final JInternalFrame component;
362         private final int layer;
363         private final int zOrder;
364 
365         ComponentPosition(JInternalFrame component, int layer, int zOrder) {
366             this.component = component;
367             this.layer = layer;
368             this.zOrder = zOrder;
369         }
370 
371         public int compareTo(ComponentPosition o) {
372             int delta = o.layer - layer;
373             if (delta == 0) {
374                 return zOrder - o.zOrder;
375             }
376             return delta;
377         }
378     }
379 
380     private JInternalFrame getNextFrame(JInternalFrame f, boolean forward) {
381         verifyFramesCache();
382         if (f == null) {
383             return getTopInternalFrame();
384         }
385         int i = framesCache.indexOf(f);
386         if (i == -1 || framesCache.size() == 1) {
387             /* error */
388             return null;
389         }
390         if (forward) {
391             // navigate to the next frame
392             if (++i == framesCache.size()) {
393                 /* wrap */
394                 i = 0;
395             }
396         }
397         else {
398             // navigate to the previous frame
399             if (--i == -1) {
400                 /* wrap */
401                 i = framesCache.size() - 1;
402             }
403         }
404         return framesCache.get(i);
405     }
406 
407     JInternalFrame getNextFrame(JInternalFrame f) {
408         return getNextFrame(f, true);
409     }
410 
411     private JInternalFrame getTopInternalFrame() {
412         if (framesCache.size() == 0) {
413             return null;
414         }
415         return framesCache.get(0);
416     }
417 
418     private void updateFramesCache() {
419         framesCache = getFrames();
420     }
421 
422     private void verifyFramesCache() {
423         // If framesCache is dirty, then recreate it.
424         if (componentOrderChanged) {
425             componentOrderChanged = false;
426             updateFramesCache();
427         }
428     }
429 
430     /**
431      * {@inheritDoc}
432      */
433     @Override
434     public void remove(Component comp) {
435         super.remove(comp);
436         updateFramesCache();
437     }
438 
439     /**
440      * Selects the next <code>JInternalFrame</code> in this desktop pane.
441      *
442      * @param forward a boolean indicating which direction to select in;
443      *        <code>true</code> for forward, <code>false</code> for
444      *        backward
445      * @return the JInternalFrame that was selected or <code>null</code>
446      *         if nothing was selected
447      * @since 1.6
448      */
449     public JInternalFrame selectFrame(boolean forward) {
450         JInternalFrame selectedFrame = getSelectedFrame();
451         JInternalFrame frameToSelect = getNextFrame(selectedFrame, forward);
452         if (frameToSelect == null) {
453             return null;
454         }
455         // Maintain navigation traversal order until an
456         // external stack change, such as a click on a frame.
457         setComponentOrderCheckingEnabled(false);
458         if (forward && selectedFrame != null) {
459             selectedFrame.moveToBack();  // For Windows MDI fidelity.
460         }
461         try { frameToSelect.setSelected(true);
462         } catch (PropertyVetoException pve) {}
463         setComponentOrderCheckingEnabled(true);
464         return frameToSelect;
465     }
466 
467     /*
468      * Sets whether component order checking is enabled.
469      * @param enable a boolean value, where <code>true</code> means
470      * a change in component order will cause a change in the keyboard
471      * navigation order.
472      * @since 1.6
473      */
474     void setComponentOrderCheckingEnabled(boolean enable) {
475         componentOrderCheckingEnabled = enable;
476     }
477 
478     /**
479      * {@inheritDoc}
480      * @since 1.6
481      */
482     protected void addImpl(Component comp, Object constraints, int index) {
483         super.addImpl(comp, constraints, index);
484         if (componentOrderCheckingEnabled) {
485             if (comp instanceof JInternalFrame ||
486                 comp instanceof JInternalFrame.JDesktopIcon) {
487                 componentOrderChanged = true;
488             }
489         }
490     }
491 
492     /**
493      * {@inheritDoc}
494      * @since 1.6
495      */
496     public void remove(int index) {
497         if (componentOrderCheckingEnabled) {
498             Component comp = getComponent(index);
499             if (comp instanceof JInternalFrame ||
500                 comp instanceof JInternalFrame.JDesktopIcon) {
501                 componentOrderChanged = true;
502             }
503         }
504         super.remove(index);
505     }
506 
507     /**
508      * {@inheritDoc}
509      * @since 1.6
510      */
511     public void removeAll() {
512         if (componentOrderCheckingEnabled) {
513             int count = getComponentCount();
514             for (int i = 0; i < count; i++) {
515                 Component comp = getComponent(i);
516                 if (comp instanceof JInternalFrame ||
517                     comp instanceof JInternalFrame.JDesktopIcon) {
518                     componentOrderChanged = true;
519                     break;
520                 }
521             }
522         }
523         super.removeAll();
524     }
525 
526     /**
527      * {@inheritDoc}
528      * @since 1.6
529      */
530     public void setComponentZOrder(Component comp, int index) {
531         super.setComponentZOrder(comp, index);
532         if (componentOrderCheckingEnabled) {
533             if (comp instanceof JInternalFrame ||
534                 comp instanceof JInternalFrame.JDesktopIcon) {
535                 componentOrderChanged = true;
536             }
537         }
538     }
539 
540     /**
541      * See readObject() and writeObject() in JComponent for more
542      * information about serialization in Swing.
543      */
544     private void writeObject(ObjectOutputStream s) throws IOException {
545         s.defaultWriteObject();
546         if (getUIClassID().equals(uiClassID)) {
547             byte count = JComponent.getWriteObjCounter(this);
548             JComponent.setWriteObjCounter(this, --count);
549             if (count == 0 && ui != null) {
550                 ui.installUI(this);
551             }
552         }
553     }
554 
555     void setUIProperty(String propertyName, Object value) {
556         if (propertyName == "dragMode") {
557             if (!dragModeSet) {
558                 setDragMode(((Integer)value).intValue());
559                 dragModeSet = false;
560             }
561         } else {
562             super.setUIProperty(propertyName, value);
563         }
564     }
565 
566     /**
567      * Returns a string representation of this <code>JDesktopPane</code>.
568      * This method is intended to be used only for debugging purposes, and the
569      * content and format of the returned string may vary between
570      * implementations. The returned string may be empty but may not
571      * be <code>null</code>.
572      *
573      * @return  a string representation of this <code>JDesktopPane</code>
574      */
575     protected String paramString() {
576         String desktopManagerString = (desktopManager != null ?
577                                        desktopManager.toString() : "");
578 
579         return super.paramString() +
580         ",desktopManager=" + desktopManagerString;
581     }
582 
583 /////////////////
584 // Accessibility support
585 ////////////////
586 
587     /**
588      * Gets the <code>AccessibleContext</code> associated with this
589      * <code>JDesktopPane</code>. For desktop panes, the
590      * <code>AccessibleContext</code> takes the form of an
591      * <code>AccessibleJDesktopPane</code>.
592      * A new <code>AccessibleJDesktopPane</code> instance is created if necessary.
593      *
594      * @return an <code>AccessibleJDesktopPane</code> that serves as the
595      *         <code>AccessibleContext</code> of this <code>JDesktopPane</code>
596      */
597     public AccessibleContext getAccessibleContext() {
598         if (accessibleContext == null) {
599             accessibleContext = new AccessibleJDesktopPane();
600         }
601         return accessibleContext;
602     }
603 
604     /**
605      * This class implements accessibility support for the
606      * <code>JDesktopPane</code> class.  It provides an implementation of the
607      * Java Accessibility API appropriate to desktop pane user-interface
608      * elements.
609      * <p>
610      * <strong>Warning:</strong>
611      * Serialized objects of this class will not be compatible with
612      * future Swing releases. The current serialization support is
613      * appropriate for short term storage or RMI between applications running
614      * the same version of Swing.  As of 1.4, support for long term storage
615      * of all JavaBeans&trade;
616      * has been added to the <code>java.beans</code> package.
617      * Please see {@link java.beans.XMLEncoder}.
618      */
619     protected class AccessibleJDesktopPane extends AccessibleJComponent {
620 
621         /**
622          * Get the role of this object.
623          *
624          * @return an instance of AccessibleRole describing the role of the
625          * object
626          * @see AccessibleRole
627          */
628         public AccessibleRole getAccessibleRole() {
629             return AccessibleRole.DESKTOP_PANE;
630         }
631     }
632 }