2
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.flamingo.api.common;
33
import java.awt.event.*;
34
import java.util.List;
38
import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
39
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
40
import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
41
import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel;
43
public class RichToolTipManager extends MouseAdapter implements
45
private Timer initialDelayTimer;
47
private Timer dismissTimer;
49
private RichTooltip richTooltip;
51
private JTrackableComponent insideComponent;
53
private MouseEvent mouseEvent;
55
final static RichToolTipManager sharedInstance = new RichToolTipManager();
57
private Popup tipWindow;
59
private JRichTooltipPanel tip;
61
private boolean tipShowing = false;
63
private static final String TRACKED_FOR_RICH_TOOLTIP = "flamingo.internal.trackedForRichTooltip";
65
public static abstract class JTrackableComponent extends JComponent {
66
public abstract RichTooltip getRichTooltip(MouseEvent mouseEvent);
69
RichToolTipManager() {
70
initialDelayTimer = new Timer(750, new InitialDelayTimerAction());
71
initialDelayTimer.setRepeats(false);
72
dismissTimer = new Timer(20000, new DismissTimerAction());
73
dismissTimer.setRepeats(false);
77
* Specifies the initial delay value.
80
* the number of milliseconds to delay (after the cursor has
81
* paused) before displaying the tooltip
82
* @see #getInitialDelay
84
public void setInitialDelay(int milliseconds) {
85
initialDelayTimer.setInitialDelay(milliseconds);
89
* Returns the initial delay value.
91
* @return an integer representing the initial delay value, in milliseconds
92
* @see #setInitialDelay(int)
94
public int getInitialDelay() {
95
return initialDelayTimer.getInitialDelay();
99
* Specifies the dismissal delay value.
101
* @param milliseconds
102
* the number of milliseconds to delay before taking away the
104
* @see #getDismissDelay
106
public void setDismissDelay(int milliseconds) {
107
dismissTimer.setInitialDelay(milliseconds);
111
* Returns the dismissal delay value.
113
* @return an integer representing the dismissal delay value, in
115
* @see #setDismissDelay(int)
117
public int getDismissDelay() {
118
return dismissTimer.getInitialDelay();
121
void showTipWindow(MouseEvent mouseEvent) {
122
if (insideComponent == null || !insideComponent.isShowing())
125
Point screenLocation = insideComponent.getLocationOnScreen();
126
Point location = new Point();
127
GraphicsConfiguration gc;
128
gc = insideComponent.getGraphicsConfiguration();
129
Rectangle sBounds = gc.getBounds();
130
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
131
// Take into account screen insets, decrease viewport
132
sBounds.x += screenInsets.left;
133
sBounds.y += screenInsets.top;
134
sBounds.width -= (screenInsets.left + screenInsets.right);
135
sBounds.height -= (screenInsets.top + screenInsets.bottom);
137
// Just to be paranoid
140
tip = new JRichTooltipPanel(insideComponent.getRichTooltip(mouseEvent));
142
.applyComponentOrientation(insideComponent
143
.getComponentOrientation());
144
size = tip.getPreferredSize();
146
AbstractRibbonBand<?> ribbonBand = (AbstractRibbonBand<?>) SwingUtilities
147
.getAncestorOfClass(AbstractRibbonBand.class, insideComponent);
148
boolean ltr = tip.getComponentOrientation().isLeftToRight();
149
boolean isInRibbonBand = (ribbonBand != null);
150
if (isInRibbonBand) {
151
// display directly below or above ribbon band
152
location.x = ltr ? screenLocation.x : screenLocation.x
153
+ insideComponent.getWidth() - size.width;
154
Point bandLocationOnScreen = ribbonBand.getLocationOnScreen();
155
location.y = bandLocationOnScreen.y + ribbonBand.getHeight() + 4;
156
if ((location.y + size.height) > (sBounds.y + sBounds.height)) {
157
location.y = bandLocationOnScreen.y - size.height;
160
// display directly below or above it
161
location.x = ltr ? screenLocation.x : screenLocation.x
162
+ insideComponent.getWidth() - size.width;
163
location.y = screenLocation.y + insideComponent.getHeight();
164
if ((location.y + size.height) > (sBounds.y + sBounds.height)) {
165
location.y = screenLocation.y - size.height;
169
// Tweak the X location to not overflow the screen
170
if (location.x < sBounds.x) {
171
location.x = sBounds.x;
172
} else if (location.x - sBounds.x + size.width > sBounds.width) {
173
location.x = sBounds.x + Math.max(0, sBounds.width - size.width);
176
PopupFactory popupFactory = PopupFactory.getSharedInstance();
177
tipWindow = popupFactory.getPopup(insideComponent, tip, location.x,
181
dismissTimer.start();
185
void hideTipWindow() {
186
if (tipWindow != null) {
196
* Returns a shared <code>ToolTipManager</code> instance.
198
* @return a shared <code>ToolTipManager</code> object
200
public static RichToolTipManager sharedInstance() {
201
return sharedInstance;
205
* Registers a component for tooltip management.
207
* This will register key bindings to show and hide the tooltip text only if
208
* <code>component</code> has focus bindings. This is done so that
209
* components that are not normally focus traversable, such as
210
* <code>JLabel</code>, are not made focus traversable as a result of
211
* invoking this method.
214
* a <code>JComponent</code> object to add
215
* @see JComponent#isFocusTraversable
217
public void registerComponent(JTrackableComponent comp) {
218
if (Boolean.TRUE.equals(comp
219
.getClientProperty(TRACKED_FOR_RICH_TOOLTIP)))
221
comp.addMouseListener(this);
222
// commandButton.addMouseMotionListener(moveBeforeEnterListener);
223
comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, Boolean.TRUE);
227
* Removes a component from tooltip control.
230
* a <code>JComponent</code> object to remove
232
public void unregisterComponent(JTrackableComponent comp) {
233
comp.removeMouseListener(this);
234
comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, null);
238
public void mouseEntered(MouseEvent event) {
239
initiateToolTip(event);
242
private void initiateToolTip(MouseEvent event) {
243
JTrackableComponent component = (JTrackableComponent) event.getSource();
244
// component.removeMouseMotionListener(moveBeforeEnterListener);
246
Point location = event.getPoint();
247
// ensure tooltip shows only in proper place
248
if (location.x < 0 || location.x >= component.getWidth()
249
|| location.y < 0 || location.y >= component.getHeight()) {
253
// do not show tooltips on components in popup panels that are not
254
// in the last shown one
255
List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
256
.defaultManager().getShownPath();
257
if (popups.size() > 0) {
258
JPopupPanel popupPanel = popups.get(popups.size() - 1)
260
boolean ignore = true;
261
Component c = component;
263
if (c == popupPanel) {
273
if (insideComponent != null) {
274
initialDelayTimer.stop();
276
// A component in an unactive internal frame is sent two
277
// mouseEntered events, make sure we don't end up adding
278
// ourselves an extra time.
279
component.removeMouseMotionListener(this);
280
component.addMouseMotionListener(this);
282
insideComponent = component;
284
initialDelayTimer.start();
288
public void mouseExited(MouseEvent event) {
289
initialDelayTimer.stop();
290
if (insideComponent != null) {
291
insideComponent.removeMouseMotionListener(this);
293
insideComponent = null;
300
public void mousePressed(MouseEvent event) {
302
initialDelayTimer.stop();
303
insideComponent = null;
308
public void mouseDragged(MouseEvent event) {
312
public void mouseMoved(MouseEvent event) {
314
checkForTipChange(event);
316
// Lazily lookup the values from within insideTimerAction
317
insideComponent = (JTrackableComponent) event.getSource();
320
initialDelayTimer.restart();
324
private void checkForTipChange(MouseEvent event) {
325
JTrackableComponent component = (JTrackableComponent) event.getSource();
326
RichTooltip newTooltip = component.getRichTooltip(event);
329
boolean isDifferent = (richTooltip != newTooltip);
332
if (newTooltip != null) {
333
richTooltip = newTooltip;
334
initialDelayTimer.restart();
339
protected class InitialDelayTimerAction implements ActionListener {
341
public void actionPerformed(ActionEvent e) {
342
if (insideComponent != null && insideComponent.isShowing()) {
344
if (richTooltip == null && mouseEvent != null) {
345
richTooltip = insideComponent.getRichTooltip(mouseEvent);
347
if (richTooltip != null) {
348
boolean showRichTooltip = true;
349
// check that no visible popup is originating in this
351
for (PopupPanelManager.PopupInfo pi : PopupPanelManager
352
.defaultManager().getShownPath()) {
353
if (pi.getPopupOriginator() == insideComponent) {
354
showRichTooltip = false;
359
if (showRichTooltip) {
360
showTipWindow(mouseEvent);
363
insideComponent = null;
372
protected class DismissTimerAction implements ActionListener {
374
public void actionPerformed(ActionEvent e) {
376
initialDelayTimer.stop();
377
insideComponent = null;