001    /* DefaultDesktopManager.java --
002       Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.Component;
042    import java.awt.Container;
043    import java.awt.Dimension;
044    import java.awt.Insets;
045    import java.awt.Rectangle;
046    import java.beans.PropertyVetoException;
047    import java.io.Serializable;
048    
049    import javax.swing.JInternalFrame.JDesktopIcon;
050    
051    /**
052     * The default implementation of DesktopManager for
053     * Swing. It implements the basic beaviours for JInternalFrames in arbitrary
054     * parents. The methods provided by the class are not meant to be called by
055     * the user, instead, the JInternalFrame methods will call these methods.
056     */
057    public class DefaultDesktopManager implements DesktopManager, Serializable
058    {
059      /** DOCUMENT ME! */
060      private static final long serialVersionUID = 4657624909838017887L;
061    
062      /** The property change event fired when the wasIcon property changes. */
063      static final String WAS_ICON_ONCE_PROPERTY = "wasIconOnce";
064    
065      /**
066       * The method of dragging used by the JDesktopPane that parents the
067       * JInternalFrame that is being dragged.
068       */
069      private int currentDragMode = 0;
070    
071      /**
072       * The cache of the bounds used to draw the outline rectangle when
073       * OUTLINE_DRAG_MODE is used.
074       */
075      private transient Rectangle dragCache = new Rectangle();
076    
077      /**
078       * A cached JDesktopPane that is stored when the JInternalFrame is initially
079       * dragged.
080       */
081      private transient Container pane;
082    
083      /**
084       * An array of Rectangles that holds the bounds of the JDesktopIcons in the
085       * JDesktopPane when looking for where to place a new icon.
086       */
087      private transient Rectangle[] iconRects;
088    
089      /**
090       * This creates a new DefaultDesktopManager object.
091       */
092      public DefaultDesktopManager()
093      {
094        // Nothing to do here.
095      }
096    
097      /**
098       * This method is not normally called since the user will typically add the
099       * JInternalFrame to a Container. If this is called, it will try to
100       * determine the parent of the JInternalFrame and remove any icon that
101       * represents this JInternalFrame and add this JInternalFrame.
102       *
103       * @param frame The JInternalFrame to open.
104       */
105      public void openFrame(JInternalFrame frame)
106      {
107        Container c = frame.getParent();
108        if (c == null)
109          c = frame.getDesktopIcon().getParent();
110        if (c == null)
111          return;
112    
113        c.remove(frame.getDesktopIcon());
114        c.add(frame);
115        frame.setVisible(true);
116      }
117    
118      /**
119       * This method removes the JInternalFrame and JDesktopIcon (if one is
120       * present) from their parents.
121       *
122       * @param frame The JInternalFrame to close.
123       */
124      public void closeFrame(JInternalFrame frame)
125      {
126        Container c = frame.getParent();
127        if (c != null)
128          {
129            if (frame.isIcon())
130              c.remove(frame.getDesktopIcon());
131            else
132              c.remove(frame);
133            c.repaint();
134          }
135      }
136    
137      /**
138       * This method resizes the JInternalFrame to match its parent's bounds.
139       *
140       * @param frame The JInternalFrame to maximize.
141       */
142      public void maximizeFrame(JInternalFrame frame)
143      {
144        // Can't maximize from iconified state.
145        // It can only return to maximized state, but that would fall under
146        // deiconify.
147        if (frame.isIcon())
148          return;
149        frame.setNormalBounds(frame.getBounds());
150    
151        Container p = frame.getParent();
152        if (p != null)
153          {
154            Rectangle pBounds = p.getBounds();
155            Insets insets = p.getInsets();
156            pBounds.width -= insets.left + insets.right;
157            pBounds.height -= insets.top + insets.bottom;
158    
159            setBoundsForFrame(frame, 0, 0, pBounds.width, pBounds.height);
160          }
161        if (p instanceof JDesktopPane)
162          ((JDesktopPane) p).setSelectedFrame(frame);
163        else
164          {
165            try
166              {
167                frame.setSelected(true);
168              }
169            catch (PropertyVetoException e)
170              {
171                // Do nothing.
172              }
173          }
174      }
175    
176      /**
177       * This method restores the JInternalFrame's bounds to what they were
178       * previous to the setMaximize call.
179       *
180       * @param frame The JInternalFrame to minimize.
181       */
182      public void minimizeFrame(JInternalFrame frame)
183      {
184        Rectangle normalBounds = frame.getNormalBounds();
185    
186        JDesktopPane p = frame.getDesktopPane();
187        if (p != null)
188          p.setSelectedFrame(frame);
189        else
190          {
191            try
192              {
193                frame.setSelected(true);
194              }
195            catch (PropertyVetoException e)
196              {
197                // Do nothing.
198              }
199          }
200    
201        setBoundsForFrame(frame, normalBounds.x, normalBounds.y,
202                          normalBounds.width, normalBounds.height);
203      }
204    
205      /**
206       * This method removes the JInternalFrame from its parent and adds its
207       * JDesktopIcon representation.
208       *
209       * @param frame The JInternalFrame to iconify.
210       */
211      public void iconifyFrame(JInternalFrame frame)
212      {
213        JDesktopPane p = frame.getDesktopPane();
214        JDesktopIcon icon = frame.getDesktopIcon();
215        if (p != null && p.getSelectedFrame() == frame)
216          p.setSelectedFrame(null);
217        else
218          {
219            try
220              {
221                frame.setSelected(false);
222              }
223            catch (PropertyVetoException e)
224              {
225                // Do nothing if attempt is vetoed.
226              }
227          }
228    
229        Container c = frame.getParent();
230    
231        if (!wasIcon(frame))
232          {
233            Rectangle r = getBoundsForIconOf(frame);
234            icon.setBounds(r);
235            setWasIcon(frame, Boolean.TRUE);
236          }
237    
238        if (c != null)
239          {
240            if (icon != null)
241              {
242                c.add(icon);
243                icon.setVisible(true);
244              }
245            Rectangle b = frame.getBounds();
246            c.remove(frame);
247            c.repaint(b.x, b.y, b.width, b.height);
248          }
249      }
250    
251      /**
252       * This method removes the JInternalFrame's JDesktopIcon representation and
253       * adds the JInternalFrame back to its parent.
254       *
255       * @param frame The JInternalFrame to deiconify.
256       */
257      public void deiconifyFrame(JInternalFrame frame)
258      {
259        JDesktopIcon icon = frame.getDesktopIcon();
260        Container c = icon.getParent();
261    
262        removeIconFor(frame);
263        c.add(frame);
264        frame.setVisible(true);
265    
266        if (!frame.isSelected())
267          {
268            JDesktopPane p = frame.getDesktopPane();
269            if (p != null)
270              p.setSelectedFrame(frame);
271            else
272              {
273                try
274                  {
275                    frame.setSelected(true);
276                  }
277                catch (PropertyVetoException e)
278                  {
279                    // Do nothing.
280                  }
281              }
282          }
283    
284        c.invalidate();
285      }
286    
287      /**
288       * This method activates the JInternalFrame by moving it to the front and
289       * selecting it.
290       *
291       * @param frame The JInternalFrame to activate.
292       */
293      public void activateFrame(JInternalFrame frame)
294      {
295        JDesktopPane p = frame.getDesktopPane();
296        JInternalFrame active = null;
297        if (p != null)
298          active = p.getSelectedFrame();
299        if (active == null)
300          {
301            if (p != null)
302              {
303                p.setSelectedFrame(frame);
304              }
305          }
306        else if (active != frame)
307          {
308            if (active.isSelected())
309              {
310                try
311                  {
312                    active.setSelected(false);
313                  }
314                catch (PropertyVetoException ex)
315                  {
316                    // Not allowed.
317                  }
318              }
319            if (p != null)
320              {
321                p.setSelectedFrame(frame);
322              }
323    
324          }
325        frame.toFront();
326      }
327    
328      /**
329       * This method is called when the JInternalFrame loses focus.
330       *
331       * @param frame The JInternalFram to deactivate.
332       */
333      public void deactivateFrame(JInternalFrame frame)
334      {
335        JDesktopPane p = frame.getDesktopPane();
336        if (p != null)
337          {
338            if (p.getSelectedFrame() == frame)
339              p.setSelectedFrame(null);
340          }
341        else
342          {
343            try
344              {
345                frame.setSelected(false);
346              }
347            catch (PropertyVetoException e)
348              {
349                // Do nothing if attempt is vetoed.
350              }
351          }
352      }
353    
354      /**
355       * This method is called to indicate that the DesktopManager should prepare
356       * to drag the JInternalFrame. Any state information needed to drag the
357       * frame will be prepared now.
358       *
359       * @param component The JComponent to drag, usually a JInternalFrame.
360       */
361      public void beginDraggingFrame(JComponent component)
362      {
363        if (component instanceof JDesktopIcon)
364          pane = ((JDesktopIcon) component).getInternalFrame().getDesktopPane();
365        else
366          pane = ((JInternalFrame) component).getDesktopPane();
367        if (pane == null)
368          return;
369    
370        dragCache = component.getBounds();
371    
372        if (! (pane instanceof JDesktopPane))
373          currentDragMode = JDesktopPane.LIVE_DRAG_MODE;
374        else
375          currentDragMode = ((JDesktopPane) pane).getDragMode();
376      }
377    
378      /**
379       * This method is called to drag the JInternalFrame to a new location.
380       *
381       * @param component The JComponent to drag, usually a JInternalFrame.
382       *
383       * @param newX The new x coordinate.
384       * @param newY The new y coordinate.
385       */
386      public void dragFrame(JComponent component, int newX, int newY)
387      {
388        if (currentDragMode == JDesktopPane.OUTLINE_DRAG_MODE)
389          {
390            // FIXME: Do outline drag mode painting.
391          }
392        else
393          {
394            Rectangle b = component.getBounds();
395            if (component instanceof JDesktopIcon)
396              component.setBounds(newX, newY, b.width, b.height);
397            else
398              setBoundsForFrame((JInternalFrame) component, newX, newY, b.width,
399                                b.height);
400          }
401      }
402    
403      /**
404       * This method indicates that the dragging is done. Any state information
405       * stored by the DesktopManager can be cleared.
406       *
407       * @param component The JComponent that has finished dragging.
408       */
409      public void endDraggingFrame(JComponent component)
410      {
411        if (currentDragMode == JDesktopPane.OUTLINE_DRAG_MODE)
412          {
413            setBoundsForFrame((JInternalFrame) component, dragCache.x, dragCache.y,
414                              dragCache.width, dragCache.height);
415            pane = null;
416            dragCache = null;
417            component.repaint();
418          }
419      }
420    
421      /**
422       * This method is called to indicate that the given JComponent will be
423       * resized. Any state information necessary to resize the JComponent will
424       * be prepared now.
425       *
426       * @param component The JComponent to resize, usually a JInternalFrame.
427       * @param direction The direction to drag in (a SwingConstant).
428       */
429      public void beginResizingFrame(JComponent component, int direction)
430      {
431        pane = ((JInternalFrame) component).getDesktopPane();
432        if (pane == null)
433          return;
434    
435        dragCache = component.getBounds();
436        if (! (pane instanceof JDesktopPane))
437          currentDragMode = JDesktopPane.LIVE_DRAG_MODE;
438        else
439          currentDragMode = ((JDesktopPane) pane).getDragMode();
440      }
441    
442      /**
443       * This method resizes the give JComponent.
444       *
445       * @param component The JComponent to resize.
446       * @param newX The new x coordinate.
447       * @param newY The new y coordinate.
448       * @param newWidth The new width.
449       * @param newHeight The new height.
450       */
451      public void resizeFrame(JComponent component, int newX, int newY,
452                              int newWidth, int newHeight)
453      {
454        dragCache.setBounds(newX, newY, newWidth, newHeight);
455    
456        if (currentDragMode == JDesktopPane.OUTLINE_DRAG_MODE)
457          {
458            // FIXME: Do outline drag painting.
459          }
460        else
461          setBoundsForFrame(component, dragCache.x, dragCache.y, dragCache.width,
462                            dragCache.height);
463      }
464    
465      /**
466       * This method is called to indicate that the given JComponent has finished
467       * dragging. Any state information stored by the DesktopManager can be
468       * cleared.
469       *
470       * @param component The JComponent that finished resizing.
471       */
472      public void endResizingFrame(JComponent component)
473      {
474        if (currentDragMode == JDesktopPane.OUTLINE_DRAG_MODE)
475          {
476            setBoundsForFrame((JInternalFrame) component, dragCache.x, dragCache.y,
477                              dragCache.width, dragCache.height);
478            pane = null;
479            dragCache = null;
480            component.repaint();
481          }
482      }
483    
484      /**
485       * This method calls setBounds with the given parameters and repaints the
486       * JComponent.
487       *
488       * @param component The JComponent to set bounds for.
489       * @param newX The new x coordinate.
490       * @param newY The new y coordinate.
491       * @param newWidth The new width.
492       * @param newHeight The new height.
493       */
494      public void setBoundsForFrame(JComponent component, int newX, int newY,
495                                    int newWidth, int newHeight)
496      {
497        component.setBounds(newX, newY, newWidth, newHeight);
498      }
499    
500      /**
501       * This is a helper method that removes the JDesktopIcon of the given
502       * JInternalFrame from the parent.
503       *
504       * @param frame The JInternalFrame to remove an icon for.
505       */
506      protected void removeIconFor(JInternalFrame frame)
507      {
508        JDesktopIcon icon = frame.getDesktopIcon();
509        Container c = icon.getParent();
510        if (c != null && icon != null)
511          {
512            Rectangle b = icon.getBounds();
513            c.remove(icon);
514            c.repaint(b.x, b.y, b.width, b.height);
515          }
516      }
517    
518      /**
519       * This method is called by iconifyFrame to determine the bounds of the
520       * JDesktopIcon for the given JInternalFrame.
521       *
522       * @param frame The JInternalFrame to find the bounds of its JDesktopIcon
523       *        for.
524       *
525       * @return The bounds of the JDesktopIcon.
526       */
527      protected Rectangle getBoundsForIconOf(JInternalFrame frame)
528      {
529        // IconRects has no order to it.
530        // The icon _must_ be placed in the first free slot (working from
531        // the bottom left corner)
532        // The icon also must not be placed where another icon is placed
533        // (regardless whether that frame is an icon currently or not)
534        JDesktopPane desktopPane = frame.getDesktopPane();
535    
536        if (desktopPane == null)
537          return frame.getDesktopIcon().getBounds();
538    
539        Rectangle paneBounds = desktopPane.getBounds();
540        Insets insets = desktopPane.getInsets();
541        Dimension pref = frame.getDesktopIcon().getPreferredSize();
542    
543        Component[] frames = desktopPane.getComponents();
544    
545        int count = 0;
546        for (int i = 0, j = 0; i < frames.length; i++)
547          if (frames[i] instanceof JDesktopIcon
548              || frames[i] instanceof JInternalFrame
549              && ((JInternalFrame) frames[i]).getWasIcon() && frames[i] != frame)
550            count++;
551        iconRects = new Rectangle[count];
552        for (int i = 0, j = 0; i < frames.length; i++)
553          if (frames[i] instanceof JDesktopIcon)
554            iconRects[--count] = frames[i].getBounds();
555          else if (frames[i] instanceof JInternalFrame
556                   && ((JInternalFrame) frames[i]).getWasIcon()
557                   && frames[i] != frame)
558            iconRects[--count] = ((JInternalFrame) frames[i])
559                                                     .getDesktopIcon().getBounds();
560    
561        int startingX = insets.left;
562        int startingY = paneBounds.height - insets.bottom - pref.height;
563        Rectangle ideal = new Rectangle(startingX, startingY, pref.width,
564                                        pref.height);
565        boolean clear = true;
566    
567        while (iconRects.length > 0)
568          {
569            clear = true;
570            for (int i = 0; i < iconRects.length; i++)
571              {
572                if (iconRects[i] != null && iconRects[i].intersects(ideal))
573                  {
574                    clear = false;
575                    break;
576                  }
577              }
578            if (clear)
579              return ideal;
580    
581            startingX += pref.width;
582            if (startingX + pref.width > paneBounds.width - insets.right)
583              {
584                startingX = insets.left;
585                startingY -= pref.height;
586              }
587            ideal.setBounds(startingX, startingY, pref.width, pref.height);
588          }
589    
590        return ideal;
591      }
592    
593      /**
594       * This method sets the bounds of the JInternalFrame right before the
595       * maximizeFrame call.
596       *
597       * @param frame The JInternalFrame being maximized.
598       * @param rect The normal bounds.
599       */
600      protected void setPreviousBounds(JInternalFrame frame, Rectangle rect)
601      {
602        frame.setNormalBounds(rect);
603      }
604    
605      /**
606       * This method returns the normal bounds of the JInternalFrame from before
607       * the maximize call.
608       *
609       * @param frame The JInternalFrame that is being restored.
610       *
611       * @return The previous bounds of the JInternalFrame.
612       */
613      protected Rectangle getPreviousBounds(JInternalFrame frame)
614      {
615        return frame.getNormalBounds();
616      }
617    
618      /**
619       * This method sets the value to true if the given JInternalFrame has been
620       * iconized and the bounds of its DesktopIcon are valid.
621       *
622       * @param frame The JInternalFrame for the JDesktopIcon.
623       * @param value True if the JInternalFrame has been iconized and the bounds
624       *        of the JDesktopIcon are valid.
625       */
626      protected void setWasIcon(JInternalFrame frame, Boolean value)
627      {
628        frame.setWasIcon(value.booleanValue(), WAS_ICON_ONCE_PROPERTY);
629      }
630    
631      /**
632       * This method returns true if the given JInternalFrame has been iconized
633       * and the bounds of its DesktopIcon are valid.
634       *
635       * @param frame The JInternalFrame for the JDesktopIcon.
636       *
637       * @return True if the given JInternalFrame has been iconized and the bounds
638       *         of its DesktopIcon are valid.
639       */
640      protected boolean wasIcon(JInternalFrame frame)
641      {
642        return frame.getWasIcon();
643      }
644    }