~ubuntu-branches/debian/stretch/insubstantial/stretch

« back to all changes in this revision

Viewing changes to flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichToolTipManager.java

  • Committer: Package Import Robot
  • Author(s): Felix Natter
  • Date: 2016-01-18 20:58:45 UTC
  • Revision ID: package-import@ubuntu.com-20160118205845-crbmrkda61qsi5qa
Tags: upstream-7.3+dfsg2
ImportĀ upstreamĀ versionĀ 7.3+dfsg2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
 
3
 *
 
4
 * Redistribution and use in source and binary forms, with or without 
 
5
 * modification, are permitted provided that the following conditions are met:
 
6
 * 
 
7
 *  o Redistributions of source code must retain the above copyright notice, 
 
8
 *    this list of conditions and the following disclaimer. 
 
9
 *     
 
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. 
 
13
 *     
 
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. 
 
17
 *     
 
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. 
 
29
 */
 
30
package org.pushingpixels.flamingo.api.common;
 
31
 
 
32
import java.awt.*;
 
33
import java.awt.event.*;
 
34
import java.util.List;
 
35
 
 
36
import javax.swing.*;
 
37
 
 
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;
 
42
 
 
43
public class RichToolTipManager extends MouseAdapter implements
 
44
                MouseMotionListener {
 
45
        private Timer initialDelayTimer;
 
46
 
 
47
        private Timer dismissTimer;
 
48
 
 
49
        private RichTooltip richTooltip;
 
50
 
 
51
        private JTrackableComponent insideComponent;
 
52
 
 
53
        private MouseEvent mouseEvent;
 
54
 
 
55
        final static RichToolTipManager sharedInstance = new RichToolTipManager();
 
56
 
 
57
        private Popup tipWindow;
 
58
 
 
59
        private JRichTooltipPanel tip;
 
60
 
 
61
        private boolean tipShowing = false;
 
62
 
 
63
        private static final String TRACKED_FOR_RICH_TOOLTIP = "flamingo.internal.trackedForRichTooltip";
 
64
 
 
65
        public static abstract class JTrackableComponent extends JComponent {
 
66
                public abstract RichTooltip getRichTooltip(MouseEvent mouseEvent);
 
67
        }
 
68
 
 
69
        RichToolTipManager() {
 
70
                initialDelayTimer = new Timer(750, new InitialDelayTimerAction());
 
71
                initialDelayTimer.setRepeats(false);
 
72
                dismissTimer = new Timer(20000, new DismissTimerAction());
 
73
                dismissTimer.setRepeats(false);
 
74
        }
 
75
 
 
76
        /**
 
77
         * Specifies the initial delay value.
 
78
         * 
 
79
         * @param milliseconds
 
80
         *            the number of milliseconds to delay (after the cursor has
 
81
         *            paused) before displaying the tooltip
 
82
         * @see #getInitialDelay
 
83
         */
 
84
        public void setInitialDelay(int milliseconds) {
 
85
                initialDelayTimer.setInitialDelay(milliseconds);
 
86
        }
 
87
 
 
88
        /**
 
89
         * Returns the initial delay value.
 
90
         * 
 
91
         * @return an integer representing the initial delay value, in milliseconds
 
92
         * @see #setInitialDelay(int)
 
93
         */
 
94
        public int getInitialDelay() {
 
95
                return initialDelayTimer.getInitialDelay();
 
96
        }
 
97
 
 
98
        /**
 
99
         * Specifies the dismissal delay value.
 
100
         * 
 
101
         * @param milliseconds
 
102
         *            the number of milliseconds to delay before taking away the
 
103
         *            tooltip
 
104
         * @see #getDismissDelay
 
105
         */
 
106
        public void setDismissDelay(int milliseconds) {
 
107
                dismissTimer.setInitialDelay(milliseconds);
 
108
        }
 
109
 
 
110
        /**
 
111
         * Returns the dismissal delay value.
 
112
         * 
 
113
         * @return an integer representing the dismissal delay value, in
 
114
         *         milliseconds
 
115
         * @see #setDismissDelay(int)
 
116
         */
 
117
        public int getDismissDelay() {
 
118
                return dismissTimer.getInitialDelay();
 
119
        }
 
120
 
 
121
        void showTipWindow(MouseEvent mouseEvent) {
 
122
                if (insideComponent == null || !insideComponent.isShowing())
 
123
                        return;
 
124
                Dimension size;
 
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);
 
136
 
 
137
                // Just to be paranoid
 
138
                hideTipWindow();
 
139
 
 
140
                tip = new JRichTooltipPanel(insideComponent.getRichTooltip(mouseEvent));
 
141
                tip
 
142
                                .applyComponentOrientation(insideComponent
 
143
                                                .getComponentOrientation());
 
144
                size = tip.getPreferredSize();
 
145
 
 
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;
 
158
                        }
 
159
                } else {
 
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;
 
166
                        }
 
167
                }
 
168
 
 
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);
 
174
                }
 
175
 
 
176
                PopupFactory popupFactory = PopupFactory.getSharedInstance();
 
177
                tipWindow = popupFactory.getPopup(insideComponent, tip, location.x,
 
178
                                location.y);
 
179
                tipWindow.show();
 
180
 
 
181
                dismissTimer.start();
 
182
                tipShowing = true;
 
183
        }
 
184
 
 
185
        void hideTipWindow() {
 
186
                if (tipWindow != null) {
 
187
                        tipWindow.hide();
 
188
                        tipWindow = null;
 
189
                        tipShowing = false;
 
190
                        tip = null;
 
191
                        dismissTimer.stop();
 
192
                }
 
193
        }
 
194
 
 
195
        /**
 
196
         * Returns a shared <code>ToolTipManager</code> instance.
 
197
         * 
 
198
         * @return a shared <code>ToolTipManager</code> object
 
199
         */
 
200
        public static RichToolTipManager sharedInstance() {
 
201
                return sharedInstance;
 
202
        }
 
203
 
 
204
        /**
 
205
         * Registers a component for tooltip management.
 
206
         * <p>
 
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.
 
212
         * 
 
213
         * @param comp
 
214
         *            a <code>JComponent</code> object to add
 
215
         * @see JComponent#isFocusTraversable
 
216
         */
 
217
        public void registerComponent(JTrackableComponent comp) {
 
218
                if (Boolean.TRUE.equals(comp
 
219
                                .getClientProperty(TRACKED_FOR_RICH_TOOLTIP)))
 
220
                        return;
 
221
                comp.addMouseListener(this);
 
222
                // commandButton.addMouseMotionListener(moveBeforeEnterListener);
 
223
                comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, Boolean.TRUE);
 
224
        }
 
225
 
 
226
        /**
 
227
         * Removes a component from tooltip control.
 
228
         * 
 
229
         * @param comp
 
230
         *            a <code>JComponent</code> object to remove
 
231
         */
 
232
        public void unregisterComponent(JTrackableComponent comp) {
 
233
                comp.removeMouseListener(this);
 
234
                comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, null);
 
235
        }
 
236
 
 
237
        @Override
 
238
        public void mouseEntered(MouseEvent event) {
 
239
                initiateToolTip(event);
 
240
        }
 
241
 
 
242
        private void initiateToolTip(MouseEvent event) {
 
243
                JTrackableComponent component = (JTrackableComponent) event.getSource();
 
244
                // component.removeMouseMotionListener(moveBeforeEnterListener);
 
245
 
 
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()) {
 
250
                        return;
 
251
                }
 
252
 
 
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)
 
259
                                        .getPopupPanel();
 
260
                        boolean ignore = true;
 
261
                        Component c = component;
 
262
                        while (c != null) {
 
263
                                if (c == popupPanel) {
 
264
                                        ignore = false;
 
265
                                        break;
 
266
                                }
 
267
                                c = c.getParent();
 
268
                        }
 
269
                        if (ignore)
 
270
                                return;
 
271
                }
 
272
 
 
273
                if (insideComponent != null) {
 
274
                        initialDelayTimer.stop();
 
275
                }
 
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);
 
281
 
 
282
                insideComponent = component;
 
283
                mouseEvent = event;
 
284
                initialDelayTimer.start();
 
285
        }
 
286
 
 
287
        @Override
 
288
        public void mouseExited(MouseEvent event) {
 
289
                initialDelayTimer.stop();
 
290
                if (insideComponent != null) {
 
291
                        insideComponent.removeMouseMotionListener(this);
 
292
                }
 
293
                insideComponent = null;
 
294
                richTooltip = null;
 
295
                mouseEvent = null;
 
296
                hideTipWindow();
 
297
        }
 
298
 
 
299
        @Override
 
300
        public void mousePressed(MouseEvent event) {
 
301
                hideTipWindow();
 
302
                initialDelayTimer.stop();
 
303
                insideComponent = null;
 
304
                mouseEvent = null;
 
305
        }
 
306
 
 
307
        @Override
 
308
        public void mouseDragged(MouseEvent event) {
 
309
        }
 
310
 
 
311
        @Override
 
312
        public void mouseMoved(MouseEvent event) {
 
313
                if (tipShowing) {
 
314
                        checkForTipChange(event);
 
315
                } else {
 
316
                        // Lazily lookup the values from within insideTimerAction
 
317
                        insideComponent = (JTrackableComponent) event.getSource();
 
318
                        mouseEvent = event;
 
319
                        richTooltip = null;
 
320
                        initialDelayTimer.restart();
 
321
                }
 
322
        }
 
323
 
 
324
        private void checkForTipChange(MouseEvent event) {
 
325
                JTrackableComponent component = (JTrackableComponent) event.getSource();
 
326
                RichTooltip newTooltip = component.getRichTooltip(event);
 
327
 
 
328
                // is it different?
 
329
                boolean isDifferent = (richTooltip != newTooltip);
 
330
                if (isDifferent) {
 
331
                        hideTipWindow();
 
332
                        if (newTooltip != null) {
 
333
                                richTooltip = newTooltip;
 
334
                                initialDelayTimer.restart();
 
335
                        }
 
336
                }
 
337
        }
 
338
 
 
339
        protected class InitialDelayTimerAction implements ActionListener {
 
340
                @Override
 
341
        public void actionPerformed(ActionEvent e) {
 
342
                        if (insideComponent != null && insideComponent.isShowing()) {
 
343
                                // Lazy lookup
 
344
                                if (richTooltip == null && mouseEvent != null) {
 
345
                                        richTooltip = insideComponent.getRichTooltip(mouseEvent);
 
346
                                }
 
347
                                if (richTooltip != null) {
 
348
                                        boolean showRichTooltip = true;
 
349
                                        // check that no visible popup is originating in this
 
350
                                        // component
 
351
                                        for (PopupPanelManager.PopupInfo pi : PopupPanelManager
 
352
                                                        .defaultManager().getShownPath()) {
 
353
                                                if (pi.getPopupOriginator() == insideComponent) {
 
354
                                                        showRichTooltip = false;
 
355
                                                        break;
 
356
                                                }
 
357
                                        }
 
358
 
 
359
                                        if (showRichTooltip) {
 
360
                                                showTipWindow(mouseEvent);
 
361
                                        }
 
362
                                } else {
 
363
                                        insideComponent = null;
 
364
                                        richTooltip = null;
 
365
                                        mouseEvent = null;
 
366
                                        hideTipWindow();
 
367
                                }
 
368
                        }
 
369
                }
 
370
        }
 
371
 
 
372
        protected class DismissTimerAction implements ActionListener {
 
373
                @Override
 
374
        public void actionPerformed(ActionEvent e) {
 
375
                        hideTipWindow();
 
376
                        initialDelayTimer.stop();
 
377
                        insideComponent = null;
 
378
                        mouseEvent = null;
 
379
                }
 
380
        }
 
381
}