001/* BasicToolBarUI.java --
002   Copyright (C) 2004, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.BorderLayout;
042import java.awt.Color;
043import java.awt.Component;
044import java.awt.Container;
045import java.awt.Dimension;
046import java.awt.Graphics;
047import java.awt.Insets;
048import java.awt.Point;
049import java.awt.Rectangle;
050import java.awt.Window;
051import java.awt.event.ActionEvent;
052import java.awt.event.ContainerEvent;
053import java.awt.event.ContainerListener;
054import java.awt.event.FocusEvent;
055import java.awt.event.FocusListener;
056import java.awt.event.MouseEvent;
057import java.awt.event.WindowAdapter;
058import java.awt.event.WindowEvent;
059import java.awt.event.WindowListener;
060import java.beans.PropertyChangeEvent;
061import java.beans.PropertyChangeListener;
062import java.util.Hashtable;
063
064import javax.swing.AbstractAction;
065import javax.swing.AbstractButton;
066import javax.swing.Action;
067import javax.swing.ActionMap;
068import javax.swing.InputMap;
069import javax.swing.JButton;
070import javax.swing.JComponent;
071import javax.swing.JDialog;
072import javax.swing.JFrame;
073import javax.swing.JToolBar;
074import javax.swing.KeyStroke;
075import javax.swing.LookAndFeel;
076import javax.swing.RootPaneContainer;
077import javax.swing.SwingConstants;
078import javax.swing.SwingUtilities;
079import javax.swing.UIManager;
080import javax.swing.border.Border;
081import javax.swing.border.CompoundBorder;
082import javax.swing.event.MouseInputListener;
083import javax.swing.plaf.ActionMapUIResource;
084import javax.swing.plaf.ComponentUI;
085import javax.swing.plaf.ToolBarUI;
086import javax.swing.plaf.UIResource;
087import javax.swing.plaf.basic.BasicBorders.ButtonBorder;
088
089/**
090 * This is the Basic Look and Feel UI class for JToolBar.
091 */
092public class BasicToolBarUI extends ToolBarUI implements SwingConstants
093{
094
095  /**
096   * Implements the keyboard actions for JToolBar.
097   */
098  static class ToolBarAction
099    extends AbstractAction
100  {
101    /**
102     * Performs the action.
103     */
104    public void actionPerformed(ActionEvent event)
105    {
106      Object cmd = getValue("__command__");
107      JToolBar toolBar = (JToolBar) event.getSource();
108      BasicToolBarUI ui = (BasicToolBarUI) toolBar.getUI();
109
110      if (cmd.equals("navigateRight"))
111        ui.navigateFocusedComp(EAST);
112      else if (cmd.equals("navigateLeft"))
113          ui.navigateFocusedComp(WEST);
114      else if (cmd.equals("navigateUp"))
115          ui.navigateFocusedComp(NORTH);
116      else if (cmd.equals("navigateDown"))
117        ui.navigateFocusedComp(SOUTH);
118      else
119        assert false : "Shouldn't reach here";
120    }
121  }
122
123  /** Static owner of all DragWindows.
124   * This is package-private to avoid an accessor method.  */
125  static JFrame owner = new JFrame();
126
127  /** The border used when the JToolBar is in nonrollover mode. */
128  private static Border nonRolloverBorder;
129
130  /** The border used when the JToolBar is in rollover mode. */
131  private static Border rolloverBorder;
132
133  /** The last known BorderLayout constraint before floating. */
134  protected String constraintBeforeFloating;
135
136  /** The last known orientation of the JToolBar before floating.
137   * This is package-private to avoid an accessor method.  */
138  int lastGoodOrientation;
139
140  /** The color of the border when it is dockable. */
141  protected Color dockingBorderColor;
142
143  /** The background color of the JToolBar when it is dockable. */
144  protected Color dockingColor;
145
146  /** The docking listener responsible for mouse events on the JToolBar. */
147  protected MouseInputListener dockingListener;
148
149  /** The window used for dragging the JToolBar. */
150  protected BasicToolBarUI.DragWindow dragWindow;
151
152  /** The color of the border when it is not dockable. */
153  protected Color floatingBorderColor;
154
155  /** The background color of the JToolBar when it is not dockable. */
156  protected Color floatingColor;
157
158  /** The index of the focused component. */
159  protected int focusedCompIndex;
160
161  /** The PropertyChangeListener for the JToolBar. */
162  protected PropertyChangeListener propertyListener;
163
164  /** The JToolBar this UI delegate is responsible for. */
165  protected JToolBar toolBar;
166
167  /** The Container listener for the JToolBar. */
168  protected ContainerListener toolBarContListener;
169
170  /** The Focus listener for the JToolBar. */
171  protected FocusListener toolBarFocusListener;
172
173  /**
174   * @deprecated since JDK1.3.
175   */
176  protected KeyStroke leftKey;
177
178  /**
179   * @deprecated since JDK1.3.
180   */
181  protected KeyStroke rightKey;
182
183  /**
184   * @deprecated since JDK1.3.
185   */
186  protected KeyStroke upKey;
187
188  /**
189   * @deprecated since JDK1.3.
190   */
191  protected KeyStroke downKey;
192
193  /**
194   * The floating window that is responsible for holding the JToolBar when it
195   * is dragged outside of its original parent.
196   */
197  private transient Window floatFrame;
198
199  /** The original parent of the JToolBar.
200   * This is package-private to avoid an accessor method.  */
201  transient Container origParent;
202
203  /** A hashtable of components and their original borders.
204   * This is package-private to avoid an accessor method.  */
205  transient Hashtable borders;
206
207  /** A window listener for the floatable frame. */
208  private transient WindowListener windowListener;
209
210  /** A set of cached bounds of the JToolBar.
211   * This is package-private to avoid an accessor method.  */
212  transient Dimension cachedBounds;
213
214  /** The cached orientation of the JToolBar.
215   * This is package-private to avoid an accessor method.  */
216  transient int cachedOrientation;
217
218  /**
219   * This method creates a new <code>BasicToolBarUI</code> object for the given JToolBar.
220   */
221  public BasicToolBarUI()
222  {
223    // Do nothing here.
224  }
225
226  /**
227   * This method returns whether the JToolBar can dock at the given position.
228   *
229   * @param c The component to try to dock in.
230   * @param p The position of the mouse cursor relative to the given
231   *        component.
232   *
233   * @return Whether the JToolBar can dock.
234   */
235  public boolean canDock(Component c, Point p)
236  {
237    return areaOfClick(c, p) != -1;
238  }
239
240  /**
241   * This helper method returns the position of the JToolBar if it can dock.
242   *
243   * @param c The component to try to dock in.
244   * @param p The position of the mouse cursor relative to the given
245   *        component.
246   *
247   * @return One of the SwingConstants directions or -1 if the JToolBar can't
248   *         dock.
249   */
250  private int areaOfClick(Component c, Point p)
251  {
252    // Has to dock in immediate parent, not eventual root container.
253    Rectangle pBounds = c.getBounds();
254
255    // XXX: In Sun's implementation, the space the toolbar has to dock is dependent on the size it had last.
256    Dimension d = toolBar.getSize();
257    int limit = Math.min(d.width, d.height);
258
259    // The order of checking is 1. top 2. bottom 3. left 4. right
260    if (! pBounds.contains(p))
261      return -1;
262
263    if (p.y < limit)
264      return SwingConstants.NORTH;
265
266    if (p.y > (pBounds.height - limit))
267      return SwingConstants.SOUTH;
268
269    if (p.x < limit)
270      return SwingConstants.WEST;
271
272    if (p.x > (pBounds.width - limit))
273      return SwingConstants.EAST;
274
275    return -1;
276  }
277
278  /**
279   * This method creates a new DockingListener for the JToolBar.
280   *
281   * @return A new DockingListener for the JToolBar.
282   */
283  protected MouseInputListener createDockingListener()
284  {
285    return new DockingListener(toolBar);
286  }
287
288  /**
289   * This method creates a new DragWindow for the given JToolBar.
290   *
291   * @param toolbar The JToolBar to create a DragWindow for.
292   *
293   * @return A new DragWindow.
294   */
295  protected BasicToolBarUI.DragWindow createDragWindow(JToolBar toolbar)
296  {
297    return new DragWindow();
298  }
299
300  /**
301   * This method creates a new floating frame for the JToolBar. By default,
302   * this UI uses createFloatingWindow instead. This method of creating a
303   * floating frame is deprecated.
304   *
305   * @param toolbar The JToolBar to create a floating frame for.
306   *
307   * @return A new floating frame.
308   */
309  protected JFrame createFloatingFrame(JToolBar toolbar)
310  {
311    // FIXME: Though deprecated, this should still work.
312    return null;
313  }
314
315  /**
316   * This method creates a new floating window for the JToolBar. This is the
317   * method used by default to create a floating container for the JToolBar.
318   *
319   * @param toolbar The JToolBar to create a floating window for.
320   *
321   * @return A new floating window.
322   */
323  protected RootPaneContainer createFloatingWindow(JToolBar toolbar)
324  {
325    // This one is used by default though.
326    return new ToolBarDialog();
327  }
328
329  /**
330   * This method creates a new WindowListener for the JToolBar.
331   *
332   * @return A new WindowListener.
333   */
334  protected WindowListener createFrameListener()
335  {
336    return new FrameListener();
337  }
338
339  /**
340   * This method creates a new nonRolloverBorder for JButtons when the
341   * JToolBar's rollover property is set to false.
342   *
343   * @return A new NonRolloverBorder.
344   */
345  protected Border createNonRolloverBorder()
346  {
347    Border b = UIManager.getBorder("ToolBar.nonrolloverBorder");
348
349    if (b == null)
350      {
351        b = new CompoundBorder(
352            new ButtonBorder(UIManager.getColor("Button.shadow"),
353                             UIManager.getColor("Button.darkShadow"),
354                             UIManager.getColor("Button.light"),
355                             UIManager.getColor("Button.highlight")),
356            BasicBorders.getMarginBorder());
357      }
358
359    return b;  }
360
361  /**
362   * This method creates a new PropertyChangeListener for the JToolBar.
363   *
364   * @return A new PropertyChangeListener.
365   */
366  protected PropertyChangeListener createPropertyListener()
367  {
368    return new PropertyListener();
369  }
370
371  /**
372   * This method creates a new rollover border for JButtons when the
373   * JToolBar's rollover property is set to true.
374   *
375   * @return A new rollover border.
376   */
377  protected Border createRolloverBorder()
378  {
379    Border b = UIManager.getBorder("ToolBar.rolloverBorder");
380
381    if (b == null)
382      {
383        b = new CompoundBorder(
384            new ButtonBorder(UIManager.getColor("Button.shadow"),
385                             UIManager.getColor("Button.darkShadow"),
386                             UIManager.getColor("Button.light"),
387                             UIManager.getColor("Button.highlight")),
388            BasicBorders.getMarginBorder());
389      }
390
391    return b;
392  }
393
394  /**
395   * This method creates a new Container listener for the JToolBar.
396   *
397   * @return A new Container listener.
398   */
399  protected ContainerListener createToolBarContListener()
400  {
401    return new ToolBarContListener();
402  }
403
404  /**
405   * This method creates a new FocusListener for the JToolBar.
406   *
407   * @return A new FocusListener for the JToolBar.
408   */
409  protected FocusListener createToolBarFocusListener()
410  {
411    return new ToolBarFocusListener();
412  }
413
414  /**
415   * This method creates a new UI delegate for the given JComponent.
416   *
417   * @param c The JComponent to create a UI delegate for.
418   *
419   * @return A new UI delegate.
420   */
421  public static ComponentUI createUI(JComponent c)
422  {
423    return new BasicToolBarUI();
424  }
425
426  /**
427   * This method is called to drag the DragWindow around when the JToolBar is
428   * being dragged around.
429   *
430   * @param position The mouse cursor coordinates relative to the JToolBar.
431   * @param origin The screen position of the JToolBar.
432   */
433  protected void dragTo(Point position, Point origin)
434  {
435    int loc = areaOfClick(origParent,
436                          SwingUtilities.convertPoint(toolBar, position,
437                                                      origParent));
438
439    if (loc != -1)
440      {
441        dragWindow.setBorderColor(dockingBorderColor);
442        dragWindow.setBackground(dockingColor);
443      }
444    else
445      {
446        dragWindow.setBorderColor(floatingBorderColor);
447        dragWindow.setBackground(floatingColor);
448      }
449
450    int w = 0;
451    int h = 0;
452
453    boolean tmp = (loc == SwingConstants.NORTH)
454                  || (loc == SwingConstants.SOUTH) || (loc == -1);
455
456    cachedOrientation = toolBar.getOrientation();
457    cachedBounds = toolBar.getSize();
458    if (((cachedOrientation == SwingConstants.HORIZONTAL) && tmp)
459        || ((cachedOrientation == VERTICAL) && ! tmp))
460      {
461        w = cachedBounds.width;
462        h = cachedBounds.height;
463      }
464    else
465      {
466        w = cachedBounds.height;
467        h = cachedBounds.width;
468      }
469
470    Point p = dragWindow.getOffset();
471    Insets insets = toolBar.getInsets();
472
473    dragWindow.setBounds((origin.x + position.x) - p.x
474                         - ((insets.left + insets.right) / 2),
475                         (origin.y + position.y) - p.y
476                         - ((insets.top + insets.bottom) / 2), w, h);
477
478    if (! dragWindow.isVisible())
479      dragWindow.show();
480  }
481
482  /**
483   * This method is used at the end of a drag session to place the frame in
484   * either its original parent as a docked JToolBar or in its floating
485   * frame.
486   *
487   * @param position The position of the mouse cursor relative to the
488   *        JToolBar.
489   * @param origin The screen position of the JToolBar before the drag session
490   *        started.
491   */
492  protected void floatAt(Point position, Point origin)
493  {
494    Point p = new Point(position);
495    int aoc = areaOfClick(origParent,
496                          SwingUtilities.convertPoint(toolBar, p, origParent));
497
498    Container oldParent = toolBar.getParent();
499
500    oldParent.remove(toolBar);
501    oldParent.doLayout();
502    oldParent.repaint();
503
504    Container newParent;
505
506    if (aoc == -1)
507      newParent = ((RootPaneContainer) floatFrame).getContentPane();
508    else
509      {
510        floatFrame.hide();
511        newParent = origParent;
512      }
513
514    String constraint;
515    switch (aoc)
516      {
517      case SwingConstants.EAST:
518        constraint = BorderLayout.EAST;
519        break;
520      case SwingConstants.NORTH:
521        constraint = BorderLayout.NORTH;
522        break;
523      case SwingConstants.SOUTH:
524        constraint = BorderLayout.SOUTH;
525        break;
526      case SwingConstants.WEST:
527        constraint = BorderLayout.WEST;
528        break;
529      default:
530        constraint = BorderLayout.CENTER;
531        break;
532      }
533
534    int newOrientation = SwingConstants.HORIZONTAL;
535    if ((aoc != -1)
536        && ((aoc == SwingConstants.EAST) || (aoc == SwingConstants.WEST)))
537      newOrientation = SwingConstants.VERTICAL;
538
539    if (aoc != -1)
540      {
541        constraintBeforeFloating = constraint;
542        lastGoodOrientation = newOrientation;
543      }
544
545    newParent.add(toolBar, constraint);
546
547    setFloating(aoc == -1, null);
548    toolBar.setOrientation(newOrientation);
549
550    Insets insets = floatFrame.getInsets();
551    Dimension dims = toolBar.getPreferredSize();
552    p = dragWindow.getOffset();
553    setFloatingLocation((position.x + origin.x) - p.x
554                        - ((insets.left + insets.right) / 2),
555                        (position.y + origin.y) - p.y
556                        - ((insets.top + insets.bottom) / 2));
557
558    if (aoc == -1)
559      {
560        floatFrame.pack();
561        floatFrame.setSize(dims.width + insets.left + insets.right,
562                           dims.height + insets.top + insets.bottom);
563        floatFrame.show();
564      }
565
566    newParent.invalidate();
567    newParent.validate();
568    newParent.repaint();
569  }
570
571  /**
572   * This method returns the docking color.
573   *
574   * @return The docking color.
575   */
576  public Color getDockingColor()
577  {
578    return dockingColor;
579  }
580
581  /**
582   * This method returns the Color which is displayed when over a floating
583   * area.
584   *
585   * @return The color which is displayed when over a floating area.
586   */
587  public Color getFloatingColor()
588  {
589    return floatingColor;
590  }
591
592  /**
593   * This method returns the maximum size of the given JComponent for this UI.
594   *
595   * @param c The JComponent to find the maximum size for.
596   *
597   * @return The maximum size for this UI.
598   */
599  public Dimension getMaximumSize(JComponent c)
600  {
601    return getPreferredSize(c);
602  }
603
604  /**
605   * This method returns the minimum size of the given JComponent for this UI.
606   *
607   * @param c The JComponent to find a minimum size for.
608   *
609   * @return The minimum size for this UI.
610   */
611  public Dimension getMinimumSize(JComponent c)
612  {
613    return getPreferredSize(c);
614  }
615
616  /**
617   * This method installs the needed components for the JToolBar.
618   */
619  protected void installComponents()
620  {
621    floatFrame = (Window) createFloatingWindow(toolBar);
622
623    dragWindow = createDragWindow(toolBar);
624
625    nonRolloverBorder = createNonRolloverBorder();
626    rolloverBorder = createRolloverBorder();
627
628    borders = new Hashtable();
629    setRolloverBorders(toolBar.isRollover());
630
631    fillHashtable();
632  }
633
634  /**
635   * This method installs the defaults as specified by the look and feel.
636   */
637  protected void installDefaults()
638  {
639    LookAndFeel.installBorder(toolBar, "ToolBar.border");
640    LookAndFeel.installColorsAndFont(toolBar, "ToolBar.background",
641                                     "ToolBar.foreground", "ToolBar.font");
642
643    dockingBorderColor = UIManager.getColor("ToolBar.dockingForeground");
644    dockingColor = UIManager.getColor("ToolBar.dockingBackground");
645
646    floatingBorderColor = UIManager.getColor("ToolBar.floatingForeground");
647    floatingColor = UIManager.getColor("ToolBar.floatingBackground");
648  }
649
650  /**
651   * This method installs the keyboard actions for the JToolBar as specified
652   * by the look and feel.
653   */
654  protected void installKeyboardActions()
655  {
656    // Install the input map.
657    InputMap inputMap =
658      (InputMap) SharedUIDefaults.get("ToolBar.ancestorInputMap");
659    SwingUtilities.replaceUIInputMap(toolBar,
660                                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
661                                 inputMap);
662
663    // FIXME: The JDK uses a LazyActionMap for parentActionMap
664    SwingUtilities.replaceUIActionMap(toolBar, getActionMap());
665  }
666
667  /**
668   * Fetches the action map from  the UI defaults, or create a new one
669   * if the action map hasn't been initialized.
670   *
671   * @return the action map
672   */
673  private ActionMap getActionMap()
674  {
675    ActionMap am = (ActionMap) UIManager.get("ToolBar.actionMap");
676    if (am == null)
677      {
678        am = createDefaultActions();
679        UIManager.getLookAndFeelDefaults().put("ToolBar.actionMap", am);
680      }
681    return am;
682  }
683
684  private ActionMap createDefaultActions()
685  {
686    ActionMapUIResource am = new ActionMapUIResource();
687    Action action = new ToolBarAction();
688
689    am.put("navigateLeft", action);
690    am.put("navigateRight", action);
691    am.put("navigateUp", action);
692    am.put("navigateDown", action);
693
694    return am;
695  }
696
697  /**
698   * This method installs listeners for the JToolBar.
699   */
700  protected void installListeners()
701  {
702    dockingListener = createDockingListener();
703    toolBar.addMouseListener(dockingListener);
704    toolBar.addMouseMotionListener(dockingListener);
705
706    propertyListener = createPropertyListener();
707    toolBar.addPropertyChangeListener(propertyListener);
708
709    toolBarContListener = createToolBarContListener();
710    toolBar.addContainerListener(toolBarContListener);
711
712    windowListener = createFrameListener();
713    floatFrame.addWindowListener(windowListener);
714
715    toolBarFocusListener = createToolBarFocusListener();
716    if (toolBarFocusListener != null)
717      {
718        int count = toolBar.getComponentCount();
719        for (int i = 0; i < count; i++)
720          toolBar.getComponent(i).addFocusListener(toolBarFocusListener);
721      }
722  }
723
724  /**
725   * This method installs non rollover borders for each component inside the
726   * given JComponent.
727   *
728   * @param c The JComponent whose children need to have non rollover borders
729   *        installed.
730   */
731  protected void installNonRolloverBorders(JComponent c)
732  {
733    Component[] components = toolBar.getComponents();
734
735    for (int i = 0; i < components.length; i++)
736      setBorderToNonRollover(components[i]);
737  }
738
739  /**
740   * This method installs normal (or their original) borders for each
741   * component inside the given JComponent.
742   *
743   * @param c The JComponent whose children need to have their original
744   *        borders installed.
745   */
746  protected void installNormalBorders(JComponent c)
747  {
748    Component[] components = toolBar.getComponents();
749
750    for (int i = 0; i < components.length; i++)
751      setBorderToNormal(components[i]);
752  }
753
754  /**
755   * This method install rollover borders for each component inside the given
756   * JComponent.
757   *
758   * @param c The JComponent whose children need to have rollover borders
759   *        installed.
760   */
761  protected void installRolloverBorders(JComponent c)
762  {
763    Component[] components = toolBar.getComponents();
764
765    for (int i = 0; i < components.length; i++)
766      setBorderToRollover(components[i]);
767  }
768
769  /**
770   * This method fills the borders hashtable with a list of components that
771   * are JButtons and their borders.
772   */
773  private void fillHashtable()
774  {
775    Component[] c = toolBar.getComponents();
776
777    for (int i = 0; i < c.length; i++)
778      {
779        if (c[i] instanceof JButton)
780          {
781            // Don't really care about anything other than JButtons
782            JButton b = (JButton) c[i];
783
784            if (b.getBorder() != null)
785              borders.put(b, b.getBorder());
786          }
787      }
788  }
789
790  /**
791   * This method installs the UI for the given JComponent.
792   *
793   * @param c The JComponent to install a UI for.
794   */
795  public void installUI(JComponent c)
796  {
797    super.installUI(c);
798
799    if (c instanceof JToolBar)
800      {
801        toolBar = (JToolBar) c;
802    installDefaults();
803    installComponents();
804        installListeners();
805        installKeyboardActions();
806      }
807  }
808
809  /**
810   * This method returns whether the JToolBar is floating.
811   *
812   * @return Whether the JToolBar is floating.
813   */
814  public boolean isFloating()
815  {
816    return floatFrame.isVisible();
817  }
818
819  /**
820   * This method returns whether rollover borders have been set.
821   *
822   * @return Whether rollover borders have been set.
823   */
824  public boolean isRolloverBorders()
825  {
826    return toolBar.isRollover();
827  }
828
829  /**
830   * This method navigates in the given direction giving focus to the next
831   * component in the given direction.
832   *
833   * @param direction The direction to give focus to.
834   */
835  protected void navigateFocusedComp(int direction)
836  {
837    int count = toolBar.getComponentCount();
838    switch (direction)
839    {
840      case EAST:
841      case SOUTH:
842        if (focusedCompIndex >= 0 && focusedCompIndex < count)
843          {
844            int i = focusedCompIndex + 1;
845            boolean focusRequested = false;
846            // Find component to focus and request focus on it.
847            while (i != focusedCompIndex && ! focusRequested)
848              {
849                if (i >= count)
850                  i = 0;
851                Component comp = toolBar.getComponentAtIndex(i++);
852                if (comp != null && comp.isFocusable()
853                    && comp.isEnabled())
854                  {
855                    comp.requestFocus();
856                    focusRequested = true;
857                  }
858              }
859          }
860        break;
861      case WEST:
862      case NORTH:
863        if (focusedCompIndex >= 0 && focusedCompIndex < count)
864          {
865            int i = focusedCompIndex - 1;
866            boolean focusRequested = false;
867            // Find component to focus and request focus on it.
868            while (i != focusedCompIndex && ! focusRequested)
869              {
870                if (i < 0)
871                  i = count - 1;
872                Component comp = toolBar.getComponentAtIndex(i--);
873                if (comp != null && comp.isFocusable()
874                    && comp.isEnabled())
875                  {
876                    comp.requestFocus();
877                    focusRequested = true;
878                  }
879              }
880          }
881        break;
882      default:
883        break;
884    }
885  }
886
887  /**
888   * This method sets the border of the given component to a non rollover
889   * border.
890   *
891   * @param c The Component whose border needs to be set.
892   */
893  protected void setBorderToNonRollover(Component c)
894  {
895    if (c instanceof AbstractButton)
896      {
897        AbstractButton b = (AbstractButton) c;
898        b.setRolloverEnabled(false);
899
900        // Save old border in hashtable.
901        if (b.getBorder() != null)
902          borders.put(b, b.getBorder());
903
904        b.setBorder(nonRolloverBorder);
905      }
906  }
907
908  /**
909   * This method sets the border of the given component to its original value.
910   *
911   * @param c The Component whose border needs to be set.
912   */
913  protected void setBorderToNormal(Component c)
914  {
915    if (c instanceof AbstractButton)
916      {
917        AbstractButton b = (AbstractButton) c;
918        b.setRolloverEnabled(true);
919        b.setBorder((Border) borders.remove(b));
920      }
921  }
922
923  /**
924   * This method sets the border of the given component to a rollover border.
925   *
926   * @param c The Component whose border needs to be set.
927   */
928  protected void setBorderToRollover(Component c)
929  {
930    if (c instanceof AbstractButton)
931      {
932        AbstractButton b = (AbstractButton) c;
933        b.setRolloverEnabled(false);
934
935        // Save old border in hashtable.
936        if (b.getBorder() != null)
937          borders.put(b, b.getBorder());
938
939        b.setBorder(rolloverBorder);
940      }
941  }
942
943  /**
944   * This method sets the docking color.
945   *
946   * @param c The docking color.
947   */
948  public void setDockingColor(Color c)
949  {
950    dockingColor = c;
951  }
952
953  /**
954   * This method sets the floating property for the JToolBar.
955   *
956   * @param b Whether the JToolBar is floating.
957   * @param p FIXME
958   */
959  public void setFloating(boolean b, Point p)
960  {
961    // FIXME: use p for something. It's not location
962    // since we already have setFloatingLocation.
963    floatFrame.setVisible(b);
964  }
965
966  /**
967   * This method sets the color displayed when the JToolBar is not in a
968   * dockable area.
969   *
970   * @param c The floating color.
971   */
972  public void setFloatingColor(Color c)
973  {
974    floatingColor = c;
975  }
976
977  /**
978   * This method sets the floating location of the JToolBar.
979   *
980   * @param x The x coordinate for the floating frame.
981   * @param y The y coordinate for the floating frame.
982   */
983  public void setFloatingLocation(int x, int y)
984  {
985    // x,y are the coordinates of the new JFrame created to store the toolbar
986    // XXX: The floating location is bogus is not floating.
987    floatFrame.setLocation(x, y);
988    floatFrame.invalidate();
989    floatFrame.validate();
990    floatFrame.repaint();
991  }
992
993  /**
994   * This is a convenience method for changing the orientation of the
995   * JToolBar.
996   *
997   * @param orientation The new orientation.
998   */
999  public void setOrientation(int orientation)
1000  {
1001    toolBar.setOrientation(orientation);
1002  }
1003
1004  /**
1005   * This method changes the child components to have rollover borders if the
1006   * given parameter is true. Otherwise, the components are set to have non
1007   * rollover borders.
1008   *
1009   * @param rollover Whether the children will have rollover borders.
1010   */
1011  public void setRolloverBorders(boolean rollover)
1012  {
1013    if (rollover)
1014      installRolloverBorders(toolBar);
1015    else
1016      installNonRolloverBorders(toolBar);
1017  }
1018
1019  /**
1020   * This method uninstall UI installed components from the JToolBar.
1021   */
1022  protected void uninstallComponents()
1023  {
1024    installNormalBorders(toolBar);
1025    borders = null;
1026    cachedBounds = null;
1027
1028    floatFrame = null;
1029    dragWindow = null;
1030  }
1031
1032  /**
1033   * This method removes the defaults installed by the Look and Feel.
1034   */
1035  protected void uninstallDefaults()
1036  {
1037    toolBar.setBackground(null);
1038    toolBar.setForeground(null);
1039    toolBar.setFont(null);
1040
1041    dockingBorderColor = null;
1042    dockingColor = null;
1043    floatingBorderColor = null;
1044    floatingColor = null;
1045  }
1046
1047  /**
1048   * This method uninstalls keyboard actions installed by the UI.
1049   */
1050  protected void uninstallKeyboardActions()
1051  {
1052    SwingUtilities.replaceUIInputMap(toolBar, JComponent.
1053                                     WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1054    SwingUtilities.replaceUIActionMap(toolBar, null);
1055  }
1056
1057  /**
1058   * This method uninstalls listeners installed by the UI.
1059   */
1060  protected void uninstallListeners()
1061  {
1062    if (toolBarFocusListener != null)
1063      {
1064        int count = toolBar.getComponentCount();
1065        for (int i = 0; i < count; i++)
1066          toolBar.getComponent(i).removeFocusListener(toolBarFocusListener);
1067        toolBarFocusListener = null;
1068      }
1069
1070    floatFrame.removeWindowListener(windowListener);
1071    windowListener = null;
1072
1073    toolBar.removeContainerListener(toolBarContListener);
1074    toolBarContListener = null;
1075
1076    toolBar.removeMouseMotionListener(dockingListener);
1077    toolBar.removeMouseListener(dockingListener);
1078    dockingListener = null;
1079  }
1080
1081  /**
1082   * This method uninstalls the UI.
1083   *
1084   * @param c The JComponent that is having this UI removed.
1085   */
1086  public void uninstallUI(JComponent c)
1087  {
1088    uninstallKeyboardActions();
1089    uninstallListeners();
1090    uninstallComponents();
1091    uninstallDefaults();
1092    toolBar = null;
1093  }
1094
1095  /**
1096   * This is the MouseHandler class that allows the user to drag the JToolBar
1097   * in and out of the parent and dock it if it can.
1098   */
1099  public class DockingListener implements MouseInputListener
1100  {
1101    /** Whether the JToolBar is being dragged. */
1102    protected boolean isDragging;
1103
1104    /**
1105     * The origin point. This point is saved from the beginning press and is
1106     * used until the end of the drag session.
1107     */
1108    protected Point origin;
1109
1110    /** The JToolBar being dragged. */
1111    protected JToolBar toolBar;
1112
1113    /**
1114     * Creates a new DockingListener object.
1115     *
1116     * @param t The JToolBar this DockingListener is being used for.
1117     */
1118    public DockingListener(JToolBar t)
1119    {
1120      toolBar = t;
1121    }
1122
1123    /**
1124     * This method is called when the mouse is clicked.
1125     *
1126     * @param e The MouseEvent.
1127     */
1128    public void mouseClicked(MouseEvent e)
1129    {
1130      // Nothing to do here.
1131    }
1132
1133    /**
1134     * This method is called when the mouse is dragged. It delegates the drag
1135     * painting to the dragTo method.
1136     *
1137     * @param e The MouseEvent.
1138     */
1139    public void mouseDragged(MouseEvent e)
1140    {
1141      if (isDragging)
1142        dragTo(e.getPoint(), origin);
1143    }
1144
1145    /**
1146     * This method is called when the mouse enters the JToolBar.
1147     *
1148     * @param e The MouseEvent.
1149     */
1150    public void mouseEntered(MouseEvent e)
1151    {
1152      // Nothing to do here.
1153    }
1154
1155    /**
1156     * This method is called when the mouse exits the JToolBar.
1157     *
1158     * @param e The MouseEvent.
1159     */
1160    public void mouseExited(MouseEvent e)
1161    {
1162      // Nothing to do here.
1163    }
1164
1165    /**
1166     * This method is called when the mouse is moved in the JToolBar.
1167     *
1168     * @param e The MouseEvent.
1169     */
1170    public void mouseMoved(MouseEvent e)
1171    {
1172      // Nothing to do here.
1173    }
1174
1175    /**
1176     * This method is called when the mouse is pressed in the JToolBar. If the
1177     * press doesn't occur in a place where it causes the JToolBar to be
1178     * dragged, it returns. Otherwise, it starts a drag session.
1179     *
1180     * @param e The MouseEvent.
1181     */
1182    public void mousePressed(MouseEvent e)
1183    {
1184      if (! toolBar.isFloatable())
1185        return;
1186
1187      Point ssd = e.getPoint();
1188      Insets insets = toolBar.getInsets();
1189
1190      // Verify that this click occurs in the top inset.
1191      if (toolBar.getOrientation() == SwingConstants.HORIZONTAL)
1192        {
1193          if (e.getX() > insets.left)
1194            return;
1195        }
1196      else
1197        {
1198          if (e.getY() > insets.top)
1199            return;
1200        }
1201
1202      origin = new Point(0, 0);
1203      if (toolBar.isShowing())
1204        SwingUtilities.convertPointToScreen(ssd, toolBar);
1205
1206      if (! (SwingUtilities.getAncestorOfClass(Window.class, toolBar) instanceof UIResource))
1207        // Need to know who keeps the toolBar if it gets dragged back into it.
1208        origParent = toolBar.getParent();
1209
1210      if (toolBar.isShowing())
1211        SwingUtilities.convertPointToScreen(origin, toolBar);
1212
1213      isDragging = true;
1214
1215      if (dragWindow != null)
1216        dragWindow.setOffset(new Point(cachedBounds.width / 2,
1217            cachedBounds.height / 2));
1218
1219      dragTo(e.getPoint(), origin);
1220    }
1221
1222    /**
1223     * This method is called when the mouse is released from the JToolBar.
1224     *
1225     * @param e The MouseEvent.
1226     */
1227    public void mouseReleased(MouseEvent e)
1228    {
1229      if (! isDragging || ! toolBar.isFloatable())
1230        return;
1231
1232      isDragging = false;
1233      floatAt(e.getPoint(), origin);
1234      dragWindow.hide();
1235    }
1236  }
1237
1238  /**
1239   * This is the window that appears when the JToolBar is being dragged
1240   * around.
1241   */
1242  protected class DragWindow extends Window
1243  {
1244    /**
1245     * The current border color. It changes depending on whether the JToolBar
1246     * is over a place that allows it to dock.
1247     */
1248    private Color borderColor;
1249
1250    /** The between the mouse and the top left corner of the window. */
1251    private Point offset;
1252
1253    /**
1254     * Creates a new DragWindow object.
1255     * This is package-private to avoid an accessor method.
1256     */
1257    DragWindow()
1258    {
1259      super(owner);
1260    }
1261
1262    /**
1263     * The color that the border should be.
1264     *
1265     * @return The border color.
1266     */
1267    public Color getBorderColor()
1268    {
1269      if (borderColor == null)
1270        return Color.BLACK;
1271
1272      return borderColor;
1273    }
1274
1275    /**
1276     * This method returns the insets for the DragWindow.
1277     *
1278     * @return The insets for the DragWindow.
1279     */
1280    public Insets getInsets()
1281    {
1282      // This window has no decorations, so insets are empty.
1283      return new Insets(0, 0, 0, 0);
1284    }
1285
1286    /**
1287     * This method returns the mouse offset from the top left corner of the
1288     * DragWindow.
1289     *
1290     * @return The mouse offset.
1291     */
1292    public Point getOffset()
1293    {
1294      return offset;
1295    }
1296
1297    /**
1298     * This method paints the DragWindow.
1299     *
1300     * @param g The Graphics object to paint with.
1301     */
1302    public void paint(Graphics g)
1303    {
1304      //  No visiting children necessary.
1305      Color saved = g.getColor();
1306      Rectangle b = getBounds();
1307
1308      g.setColor(getBorderColor());
1309      g.drawRect(0, 0, b.width - 1, b.height - 1);
1310
1311      g.setColor(saved);
1312    }
1313
1314    /**
1315     * This method changes the border color.
1316     *
1317     * @param c The new border color.
1318     */
1319    public void setBorderColor(Color c)
1320    {
1321      borderColor = c;
1322    }
1323
1324    /**
1325     * This method changes the mouse offset.
1326     *
1327     * @param p The new mouse offset.
1328     */
1329    public void setOffset(Point p)
1330    {
1331      offset = p;
1332    }
1333
1334    /**
1335     * Sets the orientation of the toolbar and the
1336     * drag window.
1337     *
1338     * @param o - the new orientation of the toolbar and drag
1339     * window.
1340     */
1341    public void setOrientation(int o)
1342    {
1343      toolBar.setOrientation(o);
1344      if (dragWindow != null)
1345        dragWindow.setOrientation(o);
1346    }
1347  }
1348
1349  /**
1350   * This helper class listens for Window events from the floatable window and
1351   * if it is closed, returns the JToolBar to the last known good location.
1352   */
1353  protected class FrameListener extends WindowAdapter
1354  {
1355    /**
1356     * This method is called when the floating window is closed.
1357     *
1358     * @param e The WindowEvent.
1359     */
1360    public void windowClosing(WindowEvent e)
1361    {
1362      Container parent = toolBar.getParent();
1363      parent.remove(toolBar);
1364
1365      if (origParent != null)
1366        {
1367          origParent.add(toolBar,
1368                         (constraintBeforeFloating != null)
1369                         ? constraintBeforeFloating : BorderLayout.NORTH);
1370          toolBar.setOrientation(lastGoodOrientation);
1371        }
1372
1373      origParent.invalidate();
1374      origParent.validate();
1375      origParent.repaint();
1376    }
1377  }
1378
1379  /**
1380   * This helper class listens for PropertyChangeEvents from the JToolBar.
1381   */
1382  protected class PropertyListener implements PropertyChangeListener
1383  {
1384    /**
1385     * This method is called when a property from the JToolBar is changed.
1386     *
1387     * @param e The PropertyChangeEvent.
1388     */
1389    public void propertyChange(PropertyChangeEvent e)
1390    {
1391      // FIXME: need name properties so can change floatFrame title.
1392      if (e.getPropertyName().equals("rollover") && toolBar != null)
1393        setRolloverBorders(toolBar.isRollover());
1394    }
1395  }
1396
1397  /**
1398   * This helper class listens for components added to and removed from the
1399   * JToolBar.
1400   */
1401  protected class ToolBarContListener implements ContainerListener
1402  {
1403    /**
1404     * This method is responsible for setting rollover or non rollover for new
1405     * buttons added to the JToolBar.
1406     *
1407     * @param e The ContainerEvent.
1408     */
1409    public void componentAdded(ContainerEvent e)
1410    {
1411      if (e.getChild() instanceof JButton)
1412        {
1413          JButton b = (JButton) e.getChild();
1414
1415          if (b.getBorder() != null)
1416            borders.put(b, b.getBorder());
1417        }
1418
1419      if (isRolloverBorders())
1420        setBorderToRollover(e.getChild());
1421      else
1422        setBorderToNonRollover(e.getChild());
1423
1424      cachedBounds = toolBar.getPreferredSize();
1425      cachedOrientation = toolBar.getOrientation();
1426
1427      Component c = e.getChild();
1428      if (toolBarFocusListener != null)
1429        c.addFocusListener(toolBarFocusListener);
1430    }
1431
1432    /**
1433     * This method is responsible for giving the child components their
1434     * original borders when they are removed.
1435     *
1436     * @param e The ContainerEvent.
1437     */
1438    public void componentRemoved(ContainerEvent e)
1439    {
1440      setBorderToNormal(e.getChild());
1441      cachedBounds = toolBar.getPreferredSize();
1442      cachedOrientation = toolBar.getOrientation();
1443
1444      Component c = e.getChild();
1445      if (toolBarFocusListener != null)
1446        c.removeFocusListener(toolBarFocusListener);
1447    }
1448  }
1449
1450  /**
1451   * This is the floating window that is returned when getFloatingWindow is
1452   * called.
1453   */
1454  private class ToolBarDialog extends JDialog implements UIResource
1455  {
1456    /**
1457     * Creates a new ToolBarDialog object with the name given by the JToolBar.
1458     */
1459    public ToolBarDialog()
1460    {
1461      super();
1462      setName((toolBar.getName() != null) ? toolBar.getName() : "");
1463    }
1464  }
1465
1466  /**
1467   * DOCUMENT ME!
1468   */
1469  protected class ToolBarFocusListener implements FocusListener
1470  {
1471    /**
1472     * Creates a new ToolBarFocusListener object.
1473     */
1474    protected ToolBarFocusListener()
1475    {
1476      // Nothing to do here.
1477    }
1478
1479    /**
1480     * Receives notification when the toolbar or one of it's component
1481     * receives the keyboard input focus.
1482     *
1483     * @param e the focus event
1484     */
1485    public void focusGained(FocusEvent e)
1486    {
1487      Component c = e.getComponent();
1488      focusedCompIndex = toolBar.getComponentIndex(c);
1489    }
1490
1491    /**
1492     * Receives notification when the toolbar or one of it's component
1493     * looses the keyboard input focus.
1494     *
1495     * @param e the focus event
1496     */
1497    public void focusLost(FocusEvent e)
1498    {
1499      // Do nothing here.
1500    }
1501  }
1502
1503  /**
1504   * This helper class acts as the border for the JToolBar.
1505   */
1506  private static class ToolBarBorder implements Border
1507  {
1508    /** The size of the larger, draggable side of the border. */
1509    private static final int offset = 10;
1510
1511    /** The other sides. */
1512    private static final int regular = 2;
1513
1514    /**
1515     * This method returns the border insets for the JToolBar.
1516     *
1517     * @param c The Component to find insets for.
1518     *
1519     * @return The border insets.
1520     */
1521    public Insets getBorderInsets(Component c)
1522    {
1523      if (c instanceof JToolBar)
1524        {
1525          JToolBar tb = (JToolBar) c;
1526          int orientation = tb.getOrientation();
1527
1528          if (! tb.isFloatable())
1529            return new Insets(regular, regular, regular, regular);
1530          else if (orientation == SwingConstants.HORIZONTAL)
1531            return new Insets(regular, offset, regular, regular);
1532          else
1533            return new Insets(offset, regular, regular, regular);
1534        }
1535
1536      return new Insets(0, 0, 0, 0);
1537    }
1538
1539    /**
1540     * This method returns whether the border is opaque.
1541     *
1542     * @return Whether the border is opaque.
1543     */
1544    public boolean isBorderOpaque()
1545    {
1546      return false;
1547    }
1548
1549    /**
1550     * This method paints the ribbed area of the border.
1551     *
1552     * @param g The Graphics object to paint with.
1553     * @param x The x coordinate of the area.
1554     * @param y The y coordinate of the area.
1555     * @param w The width of the area.
1556     * @param h The height of the area.
1557     * @param size The size of the bump.
1558     * @param c The color of the bumps.
1559     */
1560    private void paintBumps(Graphics g, int x, int y, int w, int h, int size,
1561                            Color c)
1562    {
1563      Color saved = g.getColor();
1564      g.setColor(c);
1565
1566      int hgap = 2 * size;
1567      int vgap = 4 * size;
1568      int count = 0;
1569
1570      for (int i = x; i < (w + x); i += hgap)
1571        for (int j = ((count++ % 2) == 0) ? y : (y + (2 * size)); j < (h + y);
1572             j += vgap)
1573          g.fillRect(i, j, size, size);
1574
1575      g.setColor(saved);
1576    }
1577
1578    /**
1579     * This method paints the border around the given Component.
1580     *
1581     * @param c The Component whose border is being painted.
1582     * @param g The Graphics object to paint with.
1583     * @param x The x coordinate of the component.
1584     * @param y The y coordinate of the component.
1585     * @param width The width of the component.
1586     * @param height The height of the component.
1587     */
1588    public void paintBorder(Component c, Graphics g, int x, int y, int width,
1589                            int height)
1590    {
1591      if (c instanceof JToolBar)
1592        {
1593          JToolBar tb = (JToolBar) c;
1594
1595          int orientation = tb.getOrientation();
1596
1597          if (orientation == SwingConstants.HORIZONTAL)
1598            {
1599              paintBumps(g, x, y, offset, height, 1, Color.WHITE);
1600              paintBumps(g, x + 1, y + 1, offset - 1, height - 1, 1, Color.GRAY);
1601            }
1602          else
1603            {
1604              paintBumps(g, x, y, width, offset, 1, Color.WHITE);
1605              paintBumps(g, x + 1, y + 1, width - 1, offset - 1, 1, Color.GRAY);
1606            }
1607        }
1608    }
1609  }
1610}