1/*
2 * Copyright (c) 1997, 2015, 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 */
25package javax.swing;
26
27import java.util.List;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.Iterator;
31import javax.swing.plaf.*;
32import javax.accessibility.*;
33
34import java.awt.Component;
35import java.awt.Container;
36import java.beans.JavaBean;
37import java.beans.BeanProperty;
38import java.io.ObjectOutputStream;
39import java.io.IOException;
40import java.beans.PropertyVetoException;
41import java.util.Set;
42import java.util.TreeSet;
43import java.util.LinkedHashSet;
44
45/**
46 * A container used to create a multiple-document interface or a virtual desktop.
47 * You create <code>JInternalFrame</code> objects and add them to the
48 * <code>JDesktopPane</code>. <code>JDesktopPane</code> extends
49 * <code>JLayeredPane</code> to manage the potentially overlapping internal
50 * frames. It also maintains a reference to an instance of
51 * <code>DesktopManager</code> that is set by the UI
52 * class for the current look and feel (L&amp;F).  Note that <code>JDesktopPane</code>
53 * does not support borders.
54 * <p>
55 * This class is normally used as the parent of <code>JInternalFrames</code>
56 * to provide a pluggable <code>DesktopManager</code> object to the
57 * <code>JInternalFrames</code>. The <code>installUI</code> of the
58 * L&amp;F specific implementation is responsible for setting the
59 * <code>desktopManager</code> variable appropriately.
60 * When the parent of a <code>JInternalFrame</code> is a <code>JDesktopPane</code>,
61 * it should delegate most of its behavior to the <code>desktopManager</code>
62 * (closing, resizing, etc).
63 * <p>
64 * For further documentation and examples see
65 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/internalframe.html">How to Use Internal Frames</a>,
66 * a section in <em>The Java Tutorial</em>.
67 * <p>
68 * <strong>Warning:</strong> Swing is not thread safe. For more
69 * information see <a
70 * href="package-summary.html#threading">Swing's Threading
71 * Policy</a>.
72 * <p>
73 * <strong>Warning:</strong>
74 * Serialized objects of this class will not be compatible with
75 * future Swing releases. The current serialization support is
76 * appropriate for short term storage or RMI between applications running
77 * the same version of Swing.  As of 1.4, support for long term storage
78 * of all JavaBeans&trade;
79 * has been added to the <code>java.beans</code> package.
80 * Please see {@link java.beans.XMLEncoder}.
81 *
82 * @see JInternalFrame
83 * @see JInternalFrame.JDesktopIcon
84 * @see DesktopManager
85 *
86 * @author David Kloba
87 * @since 1.2
88 */
89@JavaBean(defaultProperty = "UI")
90@SuppressWarnings("serial") // Same-version serialization only
91public class JDesktopPane extends JLayeredPane implements Accessible
92{
93    /**
94     * @see #getUIClassID
95     * @see #readObject
96     */
97    private static final String uiClassID = "DesktopPaneUI";
98
99    transient DesktopManager desktopManager;
100
101    private transient JInternalFrame selectedFrame = null;
102
103    /**
104      * Indicates that the entire contents of the item being dragged
105      * should appear inside the desktop pane.
106      *
107      * @see #OUTLINE_DRAG_MODE
108      * @see #setDragMode
109      */
110    public static final int LIVE_DRAG_MODE = 0;
111
112    /**
113      * Indicates that an outline only of the item being dragged
114      * should appear inside the desktop pane.
115      *
116      * @see #LIVE_DRAG_MODE
117      * @see #setDragMode
118      */
119    public static final int OUTLINE_DRAG_MODE = 1;
120
121    private int dragMode = LIVE_DRAG_MODE;
122    private boolean dragModeSet = false;
123    private transient List<JInternalFrame> framesCache;
124    private boolean componentOrderCheckingEnabled = true;
125    private boolean componentOrderChanged = false;
126
127    /**
128     * Creates a new <code>JDesktopPane</code>.
129     */
130    public JDesktopPane() {
131        setUIProperty("opaque", Boolean.TRUE);
132        setFocusCycleRoot(true);
133
134        setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
135            public Component getDefaultComponent(Container c) {
136                JInternalFrame jifArray[] = getAllFrames();
137                Component comp = null;
138                for (JInternalFrame jif : jifArray) {
139                    comp = jif.getFocusTraversalPolicy().getDefaultComponent(jif);
140                    if (comp != null) {
141                        break;
142                    }
143                }
144                return comp;
145            }
146        });
147        updateUI();
148    }
149
150    /**
151     * Returns the L&amp;F object that renders this component.
152     *
153     * @return the <code>DesktopPaneUI</code> object that
154     *   renders this component
155     */
156    public DesktopPaneUI getUI() {
157        return (DesktopPaneUI)ui;
158    }
159
160    /**
161     * Sets the L&amp;F object that renders this component.
162     *
163     * @param ui  the DesktopPaneUI L&amp;F object
164     * @see UIDefaults#getUI
165     */
166    @BeanProperty(hidden = true, visualUpdate = true, description
167            = "The UI object that implements the Component's LookAndFeel.")
168    public void setUI(DesktopPaneUI ui) {
169        super.setUI(ui);
170    }
171
172    /**
173     * Sets the "dragging style" used by the desktop pane.
174     * You may want to change to one mode or another for
175     * performance or aesthetic reasons.
176     *
177     * @param dragMode the style of drag to use for items in the Desktop
178     *
179     * @see #LIVE_DRAG_MODE
180     * @see #OUTLINE_DRAG_MODE
181     *
182     * @since 1.3
183     */
184    @BeanProperty(enumerationValues = {
185            "JDesktopPane.LIVE_DRAG_MODE",
186            "JDesktopPane.OUTLINE_DRAG_MODE"}, description
187            = "Dragging style for internal frame children.")
188    public void setDragMode(int dragMode) {
189        int oldDragMode = this.dragMode;
190        this.dragMode = dragMode;
191        firePropertyChange("dragMode", oldDragMode, this.dragMode);
192        dragModeSet = true;
193     }
194
195    /**
196     * Gets the current "dragging style" used by the desktop pane.
197     * @return either <code>Live_DRAG_MODE</code> or
198     *   <code>OUTLINE_DRAG_MODE</code>
199     * @see #setDragMode
200     * @since 1.3
201     */
202     public int getDragMode() {
203         return dragMode;
204     }
205
206    /**
207     * Returns the {@code DesktopManger} that handles
208     * desktop-specific UI actions.
209     *
210     * @return the {@code DesktopManger} that handles desktop-specific
211     *         UI actions
212     */
213    public DesktopManager getDesktopManager() {
214        return desktopManager;
215    }
216
217    /**
218     * Sets the <code>DesktopManger</code> that will handle
219     * desktop-specific UI actions. This may be overridden by
220     * {@code LookAndFeel}.
221     *
222     * @param d the <code>DesktopManager</code> to use
223     */
224    @BeanProperty(description
225            = "Desktop manager to handle the internal frames in the desktop pane.")
226    public void setDesktopManager(DesktopManager d) {
227        DesktopManager oldValue = desktopManager;
228        desktopManager = d;
229        firePropertyChange("desktopManager", oldValue, desktopManager);
230    }
231
232    /**
233     * Notification from the <code>UIManager</code> that the L&amp;F has changed.
234     * Replaces the current UI object with the latest version from the
235     * <code>UIManager</code>.
236     *
237     * @see JComponent#updateUI
238     */
239    public void updateUI() {
240        setUI((DesktopPaneUI)UIManager.getUI(this));
241    }
242
243
244    /**
245     * Returns the name of the L&amp;F class that renders this component.
246     *
247     * @return the string "DesktopPaneUI"
248     * @see JComponent#getUIClassID
249     * @see UIDefaults#getUI
250     */
251    @BeanProperty(bound = false)
252    public String getUIClassID() {
253        return uiClassID;
254    }
255
256    /**
257     * Returns all <code>JInternalFrames</code> currently displayed in the
258     * desktop. Returns iconified frames as well as expanded frames.
259     *
260     * @return an array of <code>JInternalFrame</code> objects
261     */
262    @BeanProperty(bound = false)
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 LinkedHashSet<>();
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        checkComponentAttributes(comp);
484        super.addImpl(comp, constraints, index);
485        if (componentOrderCheckingEnabled) {
486            if (comp instanceof JInternalFrame ||
487                comp instanceof JInternalFrame.JDesktopIcon) {
488                componentOrderChanged = true;
489            }
490        }
491    }
492
493    private void checkComponentAttributes(Component comp) {
494        if (comp instanceof JInternalFrame && ((JInternalFrame) comp).isIcon()) {
495            ((JInternalFrame) comp).putClientProperty("wasIconOnce", Boolean.FALSE);
496        }
497    }
498
499    /**
500     * {@inheritDoc}
501     * @since 1.6
502     */
503    public void remove(int index) {
504        if (componentOrderCheckingEnabled) {
505            Component comp = getComponent(index);
506            if (comp instanceof JInternalFrame ||
507                comp instanceof JInternalFrame.JDesktopIcon) {
508                componentOrderChanged = true;
509            }
510        }
511        super.remove(index);
512    }
513
514    /**
515     * {@inheritDoc}
516     * @since 1.6
517     */
518    public void removeAll() {
519        if (componentOrderCheckingEnabled) {
520            int count = getComponentCount();
521            for (int i = 0; i < count; i++) {
522                Component comp = getComponent(i);
523                if (comp instanceof JInternalFrame ||
524                    comp instanceof JInternalFrame.JDesktopIcon) {
525                    componentOrderChanged = true;
526                    break;
527                }
528            }
529        }
530        super.removeAll();
531    }
532
533    /**
534     * {@inheritDoc}
535     * @since 1.6
536     */
537    public void setComponentZOrder(Component comp, int index) {
538        super.setComponentZOrder(comp, index);
539        if (componentOrderCheckingEnabled) {
540            if (comp instanceof JInternalFrame ||
541                comp instanceof JInternalFrame.JDesktopIcon) {
542                componentOrderChanged = true;
543            }
544        }
545    }
546
547    /**
548     * See readObject() and writeObject() in JComponent for more
549     * information about serialization in Swing.
550     */
551    private void writeObject(ObjectOutputStream s) throws IOException {
552        s.defaultWriteObject();
553        if (getUIClassID().equals(uiClassID)) {
554            byte count = JComponent.getWriteObjCounter(this);
555            JComponent.setWriteObjCounter(this, --count);
556            if (count == 0 && ui != null) {
557                ui.installUI(this);
558            }
559        }
560    }
561
562    void setUIProperty(String propertyName, Object value) {
563        if (propertyName == "dragMode") {
564            if (!dragModeSet) {
565                setDragMode(((Integer)value).intValue());
566                dragModeSet = false;
567            }
568        } else {
569            super.setUIProperty(propertyName, value);
570        }
571    }
572
573    /**
574     * Returns a string representation of this <code>JDesktopPane</code>.
575     * This method is intended to be used only for debugging purposes, and the
576     * content and format of the returned string may vary between
577     * implementations. The returned string may be empty but may not
578     * be <code>null</code>.
579     *
580     * @return  a string representation of this <code>JDesktopPane</code>
581     */
582    protected String paramString() {
583        String desktopManagerString = (desktopManager != null ?
584                                       desktopManager.toString() : "");
585
586        return super.paramString() +
587        ",desktopManager=" + desktopManagerString;
588    }
589
590/////////////////
591// Accessibility support
592////////////////
593
594    /**
595     * Gets the <code>AccessibleContext</code> associated with this
596     * <code>JDesktopPane</code>. For desktop panes, the
597     * <code>AccessibleContext</code> takes the form of an
598     * <code>AccessibleJDesktopPane</code>.
599     * A new <code>AccessibleJDesktopPane</code> instance is created if necessary.
600     *
601     * @return an <code>AccessibleJDesktopPane</code> that serves as the
602     *         <code>AccessibleContext</code> of this <code>JDesktopPane</code>
603     */
604    @BeanProperty(bound = false)
605    public AccessibleContext getAccessibleContext() {
606        if (accessibleContext == null) {
607            accessibleContext = new AccessibleJDesktopPane();
608        }
609        return accessibleContext;
610    }
611
612    /**
613     * This class implements accessibility support for the
614     * <code>JDesktopPane</code> class.  It provides an implementation of the
615     * Java Accessibility API appropriate to desktop pane user-interface
616     * elements.
617     * <p>
618     * <strong>Warning:</strong>
619     * Serialized objects of this class will not be compatible with
620     * future Swing releases. The current serialization support is
621     * appropriate for short term storage or RMI between applications running
622     * the same version of Swing.  As of 1.4, support for long term storage
623     * of all JavaBeans&trade;
624     * has been added to the <code>java.beans</code> package.
625     * Please see {@link java.beans.XMLEncoder}.
626     */
627    @SuppressWarnings("serial") // Same-version serialization only
628    protected class AccessibleJDesktopPane extends AccessibleJComponent {
629
630        /**
631         * Get the role of this object.
632         *
633         * @return an instance of AccessibleRole describing the role of the
634         * object
635         * @see AccessibleRole
636         */
637        public AccessibleRole getAccessibleRole() {
638            return AccessibleRole.DESKTOP_PANE;
639        }
640    }
641}
642