Integration of OCCT 6.5.0 from SVN
[occt.git] / samples / java / java / HeavyToolTipManager.java
1 /*
2  * @(#)ToolTipManager.java      1.40 99/04/22
3  *
4  * Copyright 1997-1999 by Sun Microsystems, Inc.,
5  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
6  * All rights reserved.
7  * 
8  * This software is the confidential and proprietary information
9  * of Sun Microsystems, Inc. ("Confidential Information").  You
10  * shall not disclose such Confidential Information and shall use
11  * it only in accordance with the terms of the license agreement
12  * you entered into with Sun.
13  */
14
15
16 import javax.swing.*;
17 import java.awt.event.*;
18 import java.applet.*;
19 import java.awt.*;
20
21 /**
22  * Manages all the ToolTips in the system.
23  *
24  * @see JComponent#createToolTip
25  * @version 1.40 04/22/99
26  * @author Dave Moore
27  * @author Rich Schiavi
28  */
29 public class HeavyToolTipManager extends MouseAdapter
30                                  implements MouseMotionListener
31 {
32   Timer enterTimer, exitTimer, insideTimer;
33   String toolTipText;
34   Point  preferredLocation;
35   JComponent insideComponent;
36   MouseEvent mouseEvent;
37   boolean showImmediately;
38   Popup tipWindow;
39   JToolTip tip;
40
41   private Rectangle popupRect = null;
42   private Rectangle popupFrameRect = null;
43
44   boolean enabled = true;
45   boolean mouseAboveToolTip = false;
46   private boolean tipShowing = false;
47   private long timerEnter = 0;
48
49   private KeyStroke postTip,hideTip;
50   private AbstractAction postTipAction, hideTipAction;
51
52   private FocusListener focusChangeListener = null;
53
54   protected boolean lightWeightPopupEnabled = true;
55   protected boolean heavyWeightPopupEnabled = false;
56
57   final static HeavyToolTipManager sharedInstance = new HeavyToolTipManager();
58   /** Returns a shared HeavyToolTipManager instance. */
59   public static HeavyToolTipManager sharedInstance()
60   {
61       return sharedInstance;
62   }
63
64
65 //=======================================================================//
66 // Constructor
67 //=======================================================================//
68   HeavyToolTipManager()
69   {
70     enterTimer = new Timer(750, new insideTimerAction());
71     enterTimer.setRepeats(false);
72     exitTimer = new Timer(500, new outsideTimerAction());
73     exitTimer.setRepeats(false);
74     insideTimer = new Timer(4000, new stillInsideTimerAction());
75     insideTimer.setRepeats(false);
76
77           // create accessibility actions
78         postTip = KeyStroke.getKeyStroke(KeyEvent.VK_F1,Event.CTRL_MASK);
79     postTipAction = new AbstractAction()
80     {
81             public void actionPerformed(ActionEvent e)
82       {
83               if (tipWindow != null) // showing we unshow
84                 hideTipWindow();
85             else
86         {
87                 hideTipWindow(); // be safe
88               enterTimer.stop();
89               exitTimer.stop();
90               insideTimer.stop();
91               insideComponent = (JComponent)e.getSource();
92               if (insideComponent != null)
93           {
94                         toolTipText = insideComponent.getToolTipText();
95                         preferredLocation = new Point(10,insideComponent.getHeight()+10);  // manual set
96                         showTipWindow();
97                         // put a focuschange listener on to bring the tip down
98                         if (focusChangeListener == null)
99                           focusChangeListener = createFocusChangeListener();
100                                 insideComponent.addFocusListener(focusChangeListener);
101               }
102             }
103           }
104         };
105
106         hideTip = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0);
107         hideTipAction = new AbstractAction()
108     {
109           public void actionPerformed(ActionEvent e)
110       {
111             hideTipWindow();
112             JComponent jc = (JComponent)e.getSource();
113             jc.removeFocusListener(focusChangeListener);
114             preferredLocation = null;
115       }
116           public boolean isEnabled()
117       {
118               // Only enable when the tooltip is showing, otherwise
119               // we will get in the way of any UI actions.
120               return tipShowing;
121             }
122           };
123   }
124
125 //=======================================================================//
126 // Properties
127 //=======================================================================//
128   /** Enables or disables the tooltip. */
129   public void setEnabled(boolean flag)
130   {
131     enabled = flag;
132     if (!flag) hideTipWindow();
133   }
134
135   /** Returns true if this object is enabled. */
136   public boolean isEnabled()
137   {
138     return enabled;
139   }
140
141   /** Specifies the initial delay value. */
142   public void setInitialDelay(int microSeconds)
143   {
144     enterTimer.setInitialDelay(microSeconds);
145   }
146
147   /** Returns the initial delay value. */
148   public int getInitialDelay()
149   {
150     return enterTimer.getInitialDelay();
151   }
152
153   /** Specifies the dismisal delay value. */
154   public void setDismissDelay(int microSeconds)
155   {
156       insideTimer.setInitialDelay(microSeconds);
157   }
158
159   /** Returns the dismisal delay value. */
160   public int getDismissDelay()
161   {
162     return insideTimer.getInitialDelay();
163   }
164
165   /** Specifies the time to delay before reshowing the tooltip. */
166   public void setReshowDelay(int microSeconds)
167   {
168     exitTimer.setInitialDelay(microSeconds);
169   }
170
171   /** Returns the reshow delay value. */
172   public int getReshowDelay()
173   {
174     return exitTimer.getInitialDelay();
175   }
176
177 //=======================================================================//
178 // Action
179 //=======================================================================//
180   void showTipWindow()
181   {
182     if (insideComponent == null || !insideComponent.isShowing())
183       return;
184
185     if (enabled)
186     {
187       Dimension size;
188       Point screenLocation = insideComponent.getLocationOnScreen();
189       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
190       Point location = new Point();
191
192       // Just to be paranoid
193       hideTipWindow();
194
195       tip = insideComponent.createToolTip();
196       tip.setTipText(toolTipText);
197       size = tip.getPreferredSize();
198
199       // support only heavy tooltips
200       tipWindow = new WindowPopup((frameForComponent(insideComponent)),tip,size);
201
202       tipWindow.addMouseListener(this);
203
204       if (preferredLocation != null)
205       {
206         location.x = screenLocation.x + preferredLocation.x;
207         location.y = screenLocation.y + preferredLocation.y;
208       }
209       else
210       {
211         location.x = screenLocation.x + mouseEvent.getX();
212         location.y = screenLocation.y + mouseEvent.getY() + 20;
213
214         if (location.x + size.width > screenSize.width)
215           location.x -= size.width;
216         if (location.y + size.height > screenSize.height)
217           location.y -= (size.height + 20);
218       }
219
220             tipWindow.show(insideComponent,location.x,location.y);
221       insideTimer.start();
222             timerEnter = System.currentTimeMillis();
223             tipShowing = true;
224     }
225   }
226
227   void hideTipWindow()
228   {
229     if (tipWindow != null)
230     {
231       tipWindow.removeMouseListener(this);
232             tipWindow.hide();
233             tipWindow = null;
234             tipShowing = false;
235             timerEnter = 0;
236             (tip.getUI()).uninstallUI(tip);
237       tip = null;
238       insideTimer.stop();
239     }
240   }
241
242   /**
243    * Register a component for tooltip management.
244    * <p>This will register key bindings to show and hide the tooltip text
245    * only if <code>component</code> has focus bindings. This is done
246    * so that components that are not normally focus traversable, such
247    * as JLabel, are not made focus traversable as a result of invoking
248    * this method.
249    */
250   public void registerComponent(JComponent component)
251   {
252     component.removeMouseListener(this);
253     component.addMouseListener(this);
254         if (shouldRegisterBindings(component))
255     {
256             // register our accessibility keybindings for this component
257             // this will apply globally across L&F
258             // Post Tip: Ctrl+F1
259             // Unpost Tip: Esc and Ctrl+F1
260             InputMap inputMap = component.getInputMap(JComponent.WHEN_FOCUSED);
261             ActionMap actionMap = component.getActionMap();
262
263             if (inputMap != null && actionMap != null)
264       {
265                 inputMap.put(postTip, "postTip");
266                 inputMap.put(hideTip, "hideTip");
267                 actionMap.put("postTip", postTipAction);
268                 actionMap.put("hideTip", hideTipAction);
269             }
270     }
271   }
272
273   /** Remove a component from tooltip control. */
274   public void unregisterComponent(JComponent component)
275   {
276     component.removeMouseListener(this);
277         if (shouldRegisterBindings(component))
278     {
279             InputMap inputMap = component.getInputMap(JComponent.WHEN_FOCUSED);
280             ActionMap actionMap = component.getActionMap();
281
282             if (inputMap != null && actionMap != null)
283       {
284                 inputMap.remove(postTip);
285                 inputMap.remove(hideTip);
286                 actionMap.remove("postTip");
287                 actionMap.remove("hideTip");
288             }
289         }
290   }
291
292   /**
293    * Returns whether or not bindings should be registered on the given
294    * Component. This is implemented to return true if the receiver has
295    * a binding in any one of the InputMaps registered under the condition
296    * <code>WHEN_FOCUSED</code>.
297    * <p>
298    * This does not use <code>isFocusTraversable</code> as
299    * some components may override <code>isFocusTraversable</code> and
300    * base the return value on something other than bindings. For example,
301    * JButton bases its return value on its enabled state.
302    */
303   private boolean shouldRegisterBindings(JComponent component)
304   {
305         InputMap inputMap = component.getInputMap(JComponent.WHEN_FOCUSED);
306         while (inputMap != null && inputMap.size() == 0)
307     {
308             inputMap = inputMap.getParent();
309         }
310           return (inputMap != null);
311   }
312
313 //=======================================================================//
314 // Subsidiary functions
315 //=======================================================================//
316   static Frame frameForComponent(Component component)
317   {
318     while (!(component instanceof Frame))
319       component = component.getParent();
320     return (Frame)component;
321   }
322
323   private FocusListener createFocusChangeListener()
324   {
325     return new FocusAdapter()
326     {
327       public void focusLost(FocusEvent evt)
328       {
329         hideTipWindow();
330         JComponent c = (JComponent)evt.getSource();
331         c.removeFocusListener(focusChangeListener);
332       }
333     };
334   }
335
336
337 //=======================================================================//
338 // MouseListener interface
339 //=======================================================================//
340   public void mouseEntered(MouseEvent event)
341   {
342     // this is here for a workaround for a Solaris *application* only bug
343     // in which an extra MouseExit/Enter events are generated when a Panel
344     // initially is shown
345     if (tipShowing)
346           {
347             if (System.currentTimeMillis() - timerEnter < 200)
348                 return;
349         }
350
351     if (event.getSource() == tipWindow)
352       return;
353
354     JComponent component = (JComponent)event.getSource();
355     toolTipText = component.getToolTipText(event);
356     preferredLocation = component.getToolTipLocation(event);
357
358     exitTimer.stop();
359
360         Point location = event.getPoint();
361         // ensure tooltip shows only in proper place
362         if (location.x < 0 ||
363               location.x >=component.getWidth() ||
364               location.y < 0 ||
365               location.y >= component.getHeight())
366           {
367             return;
368           }
369
370     if (insideComponent != null)
371     {
372       enterTimer.stop();
373       insideComponent = null;
374     }
375
376     component.addMouseMotionListener(this);
377
378     insideComponent = component;
379         if (tipWindow != null)
380             return;
381
382   }
383
384   public void mouseExited(MouseEvent event)
385   {
386     // this is here for a workaround for a Solaris *application* only bug
387     //  when Panels are used
388     if (tipShowing)
389           {
390             if (System.currentTimeMillis() - timerEnter < 200)
391                 return;
392         }
393
394     boolean shouldHide = true;
395
396     if(event.getSource() == tipWindow)
397     {
398             // if we get an exit and have a heavy window
399           // we need to check if it if overlapping the inside component
400       Container insideComponentWindow = insideComponent.getTopLevelAncestor();
401       Rectangle b = tipWindow.getBounds();
402       Point location = event.getPoint();
403       location.x += b.x;
404       location.y += b.y;
405
406       b = insideComponentWindow.getBounds();
407       location.x -= b.x;
408       location.y -= b.y;
409
410       location = SwingUtilities.convertPoint(null,location,insideComponent);
411       if (location.x >= 0 && location.x < insideComponent.getWidth() &&
412           location.y >= 0 && location.y < insideComponent.getHeight())
413         shouldHide = false;
414       else
415               shouldHide = true;
416     }
417     else if(event.getSource() == insideComponent && tipWindow != null)
418     {
419       Point location = SwingUtilities.convertPoint(insideComponent, event.getPoint(), null);
420       Rectangle bounds = insideComponent.getTopLevelAncestor().getBounds();
421       location.x += bounds.x;
422       location.y += bounds.y;
423
424       bounds = tipWindow.getBounds();
425       if (location.x >= bounds.x && location.x < (bounds.x + bounds.width) &&
426           location.y >= bounds.y && location.y < (bounds.y + bounds.height))
427         shouldHide = false;
428       else
429         shouldHide = true;
430     }
431
432     if(shouldHide)
433     {
434       enterTimer.stop();
435             if (insideComponent != null)
436               insideComponent.removeMouseMotionListener(this);
437       insideComponent = null;
438       toolTipText = null;
439       mouseEvent = null;
440       hideTipWindow();
441       exitTimer.start();
442     }
443   }
444
445   public void mousePressed(MouseEvent event)
446   {
447     hideTipWindow();
448     enterTimer.stop();
449     showImmediately = false;
450   }
451
452 //=======================================================================//
453 // MouseMotionListener interface
454 //=======================================================================//
455   public void mouseDragged(MouseEvent event)
456   {
457   }
458
459   public void mouseMoved(MouseEvent event)
460   {
461     JComponent component = (JComponent)event.getSource();
462     String newText = component.getToolTipText(event);
463     Point  newPreferredLocation = component.getToolTipLocation(event);
464
465     if (newText != null || newPreferredLocation != null)
466     {
467       mouseEvent = event;
468       if (((newText != null && newText.equals(toolTipText)) || newText == null) &&
469           ((newPreferredLocation != null && newPreferredLocation.equals(preferredLocation))
470           || newPreferredLocation == null))
471       {
472         if (tipWindow != null)
473           insideTimer.restart();
474         else
475           enterTimer.restart();
476       }
477       else
478       {
479         toolTipText = newText;
480         preferredLocation = newPreferredLocation;
481         if (showImmediately)
482         {
483           hideTipWindow();
484           showTipWindow();
485         }
486         else
487           enterTimer.restart();
488       }
489     }
490     else
491     {
492       toolTipText = null;
493       preferredLocation = null;
494       mouseEvent = null;
495       hideTipWindow();
496       enterTimer.stop();
497       exitTimer.start();
498     }
499   }
500
501 //=======================================================================//
502 // Class insideTimerAction
503 //=======================================================================//
504   protected class insideTimerAction implements ActionListener
505   {
506     public void actionPerformed(ActionEvent e)
507     {
508       if (insideComponent != null && insideComponent.isShowing())
509       {
510         showImmediately = true;
511         showTipWindow();
512       }
513     }
514   }
515
516 //=======================================================================//
517 // Class insideTimerAction
518 //=======================================================================//
519   protected class outsideTimerAction implements ActionListener
520   {
521     public void actionPerformed(ActionEvent e)
522     {
523       showImmediately = false;
524     }
525   }
526
527 //=======================================================================//
528 // Class insideTimerAction
529 //=======================================================================//
530   protected class stillInsideTimerAction implements ActionListener
531   {
532     public void actionPerformed(ActionEvent e)
533     {
534       hideTipWindow();
535       enterTimer.stop();
536       showImmediately = false;
537     }
538   }
539
540
541 //=======================================================================//
542 // Interface Popup
543 //=======================================================================//
544   /*
545    * The following interface describes what a popup should implement.
546    * We do this because the ToolTip manager uses popup that can be windows or
547    * panels. The reason is two-fold: We'd like to use panels mostly, but when the
548    * panel (or tooltip) would not fit, we need to use a Window to avoid the panel
549    * being clipped or not shown.
550    *
551    */
552   private interface Popup
553   {
554     public void show(JComponent invoker, int x, int y);
555     public void hide();
556     public void addMouseListener(HeavyToolTipManager c);
557     public void removeMouseListener(HeavyToolTipManager c);
558     public Rectangle getBounds();
559   }
560
561 //=======================================================================//
562 // Class WindowPopup
563 //=======================================================================//
564   class WindowPopup extends Window implements Popup
565   {
566     boolean  firstShow = true;
567     JComponent tip;
568     Frame frame;
569
570     public WindowPopup(Frame f,JComponent t, Dimension size)
571     {
572       super(f);
573       this.tip = t;
574       this.frame = f;
575       add(t, BorderLayout.CENTER);
576       pack();
577       // setSize(size);
578     }
579
580     public Rectangle getBounds()
581     {
582         return super.getBounds();
583     }
584
585     public void show(JComponent invoker, int x, int y)
586     {
587       this.setLocation(x,y);
588       this.setVisible(true);
589
590       /** This hack is to workaround a bug on Solaris where the windows does not really show
591        *  the first time
592        *  It causes a side effect of MS JVM reporting IllegalArumentException: null source
593        *  fairly frequently - also happens if you use HeavyWeight JPopup, ie JComboBox
594        */
595       if(firstShow)
596       {
597         this.hide();
598         this.setVisible(true);
599         firstShow = false;
600       }
601     }
602
603     public void hide()
604     {
605       super.hide();
606       /** We need to call removeNotify() here because hide() does something only if
607        *  Component.visible is true. When the app frame is miniaturized, the parent
608        *  frame of this frame is invisible, causing AWT to believe that this frame
609        *  is invisible and causing hide() to do nothing
610        */
611       removeNotify();
612     }
613
614     public void addMouseListener(HeavyToolTipManager c)
615     {
616         super.addMouseListener(c);
617     }
618
619     public void removeMouseListener(HeavyToolTipManager c)
620     {
621         super.removeMouseListener(c);
622     }
623
624   }
625
626 }