2
* Copyright (c) 2005-2010 Laf-Widget 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 Laf-Widget 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.lafwidget;
33
import java.awt.geom.GeneralPath;
34
import java.awt.image.BufferedImage;
35
import java.beans.PropertyChangeEvent;
36
import java.beans.PropertyChangeListener;
40
import javax.swing.text.JTextComponent;
42
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
43
import org.pushingpixels.lafwidget.animation.AnimationFacet;
46
* Various utility functions.
48
* @author Kirill Grouchnikov
51
public class LafWidgetUtilities {
53
* Name for the internal client property that marks a component as
56
public static final String PREVIEW_MODE = "lafwidgets.internal.previewMode";
59
* Private constructor. Is here to enforce using static methods only.
61
private LafWidgetUtilities() {
65
* Retrieves transparent image of specified dimension.
71
* @return Transparent image of specified dimension.
73
public static BufferedImage getBlankImage(int width, int height) {
74
BufferedImage image = new BufferedImage(width, height,
75
BufferedImage.TYPE_INT_ARGB);
77
// get graphics and set hints
78
Graphics2D graphics = (Graphics2D) image.getGraphics().create();
79
graphics.setColor(new Color(0, 0, 0, 0));
80
graphics.setComposite(AlphaComposite.Src);
81
graphics.fillRect(0, 0, width, height);
88
* Creates a compatible image (for efficient processing and drawing).
92
* @return Compatible version of the original image.
95
public static BufferedImage createCompatibleImage(BufferedImage image) {
96
GraphicsEnvironment e = GraphicsEnvironment
97
.getLocalGraphicsEnvironment();
98
GraphicsDevice d = e.getDefaultScreenDevice();
99
GraphicsConfiguration c = d.getDefaultConfiguration();
100
BufferedImage compatibleImage = c.createCompatibleImage(image
101
.getWidth(), image.getHeight());
102
Graphics g = compatibleImage.getGraphics();
103
g.drawImage(image, 0, 0, null);
105
return compatibleImage;
109
* Creates a thumbnail of the specified width.
112
* The original image.
113
* @param requestedThumbWidth
114
* The width of the resulting thumbnail.
115
* @return Thumbnail of the specified width.
118
public static BufferedImage createThumbnail(BufferedImage image,
119
int requestedThumbWidth) {
120
float ratio = (float) image.getWidth() / (float) image.getHeight();
121
int width = image.getWidth();
122
BufferedImage thumb = image;
126
if (width < requestedThumbWidth) {
127
width = requestedThumbWidth;
130
BufferedImage temp = new BufferedImage(width,
131
(int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
132
Graphics2D g2 = temp.createGraphics();
133
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
134
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
135
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
139
} while (width != requestedThumbWidth);
145
* Returns search icon.
150
* Indicates the orientation of the resulting icon.
151
* @return Search icon.
153
public static Icon getSearchIcon(int dimension, boolean leftToRight) {
154
BufferedImage result = LafWidgetUtilities.getBlankImage(dimension,
157
Graphics2D graphics = (Graphics2D) result.getGraphics().create();
158
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
159
RenderingHints.VALUE_ANTIALIAS_ON);
161
graphics.setColor(Color.black);
163
graphics.setStroke(new BasicStroke(1.5f));
165
int xc = (int) (0.6 * dimension);
166
int yc = (int) (0.45 * dimension);
167
int r = (int) (0.3 * dimension);
169
graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
171
graphics.setStroke(new BasicStroke(3.0f));
172
GeneralPath handle = new GeneralPath();
173
handle.moveTo((float) (xc - r / Math.sqrt(2.0)), (float) (yc + r
175
handle.lineTo(1.8f, dimension - 2.2f);
176
graphics.draw(handle);
178
int xc = (int) (0.4 * dimension);
179
int yc = (int) (0.45 * dimension);
180
int r = (int) (0.3 * dimension);
182
graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
184
graphics.setStroke(new BasicStroke(3.0f));
185
GeneralPath handle = new GeneralPath();
186
handle.moveTo((float) (xc + r / Math.sqrt(2.0)), (float) (yc + r
188
handle.lineTo(dimension - 2.5f, dimension - 2.2f);
189
graphics.draw(handle);
193
return new ImageIcon(result);
197
* Returns small icon representation of the specified integer value. The
198
* remainder of dividing the integer by 16 is translated to four circles
199
* arranged in 2*2 grid.
202
* Integer value to represent.
203
* @return Icon representation of the specified integer value.
205
public static Icon getHexaMarker(int value) {
206
BufferedImage result = LafWidgetUtilities.getBlankImage(9, 9);
209
Color offColor = Color.gray;
210
Color onColor = Color.black;
212
boolean bit1 = ((value & 0x1) != 0);
213
boolean bit2 = ((value & 0x2) != 0);
214
boolean bit3 = ((value & 0x4) != 0);
215
boolean bit4 = ((value & 0x8) != 0);
217
Graphics2D graphics = (Graphics2D) result.getGraphics().create();
218
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
219
RenderingHints.VALUE_ANTIALIAS_ON);
221
graphics.setColor(bit1 ? onColor : offColor);
222
graphics.fillOval(5, 5, 4, 4);
223
graphics.setColor(bit2 ? onColor : offColor);
224
graphics.fillOval(5, 0, 4, 4);
225
graphics.setColor(bit3 ? onColor : offColor);
226
graphics.fillOval(0, 5, 4, 4);
227
graphics.setColor(bit4 ? onColor : offColor);
228
graphics.fillOval(0, 0, 4, 4);
231
return new ImageIcon(result);
235
* Makes the specified component and all its descendants previewable.
240
* The "snapshot" map that will contain the original
241
* double-buffer status of the specified component and all its
242
* descendants. Key is {@link JComponent}, value is
245
public static void makePreviewable(Component comp,
246
Map<Component, Boolean> dbSnapshot) {
247
if (comp instanceof JComponent) {
248
JComponent jcomp = (JComponent) comp;
249
// if (jcomp.getParent() instanceof CellRendererPane) {
250
// System.out.println(jcomp.getClass().getSimpleName() + ":"
251
// + jcomp.hashCode());
253
dbSnapshot.put(jcomp, jcomp.isDoubleBuffered());
254
jcomp.setDoubleBuffered(false);
255
jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE,
258
if (comp instanceof Container) {
259
Container cont = (Container) comp;
260
for (int i = 0; i < cont.getComponentCount(); i++)
261
LafWidgetUtilities.makePreviewable(cont.getComponent(i),
267
* Restores the regular (non-previewable) status of the specified component
268
* and all its descendants.
273
* The "snapshot" map that contains the original double-buffer
274
* status of the specified component and all its descendants. Key
275
* is {@link JComponent}, value is {@link Boolean}.
277
public static void restorePreviewable(Component comp,
278
Map<Component, Boolean> dbSnapshot) {
279
if (comp instanceof JComponent) {
280
JComponent jcomp = (JComponent) comp;
281
if (dbSnapshot.containsKey(comp)) {
282
// the key may exist, but may be set to null (lovely boxing quirk)
283
// treat null as false, since that is the default
284
Boolean buffered = dbSnapshot.get(comp);
285
jcomp.setDoubleBuffered(buffered == null ? false : buffered);
286
jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE, null);
288
// this can happen in case the application has
289
// renderers (combos, ...). Take the property from the parent
290
Component parent = comp.getParent();
291
if (parent instanceof JComponent && dbSnapshot.containsKey(parent)) {
292
// the key may exist, but may be set to null (lovely boxing quirk)
293
// treat null as false, since that is the default
294
Boolean buffered = dbSnapshot.get(parent);
295
jcomp.setDoubleBuffered(buffered == null ? false : buffered);
296
jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE,
299
// System.out.println("Not found");
300
// Component c = jcomp;
301
// while (c != null) {
302
// System.out.println("\t" + c.getClass().getSimpleName()
303
// + ":" + c.hashCode());
304
// c = c.getParent();
308
if (comp instanceof Container) {
309
Container cont = (Container) comp;
310
for (int i = 0; i < cont.getComponentCount(); i++)
311
LafWidgetUtilities.restorePreviewable(cont.getComponent(i),
317
* Returns a lock icon.
321
public static Icon getSmallLockIcon() {
322
BufferedImage result = LafWidgetUtilities.getBlankImage(6, 8);
324
Color fore = Color.black;
325
Color fill = new Color(208, 208, 48);
327
Graphics2D graphics = (Graphics2D) result.getGraphics().create();
328
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
329
RenderingHints.VALUE_ANTIALIAS_OFF);
331
graphics.setColor(fill);
332
graphics.fillRect(1, 3, 4, 4);
333
graphics.setColor(fore);
334
graphics.drawLine(0, 3, 0, 7);
335
graphics.drawLine(5, 3, 5, 7);
336
graphics.drawLine(0, 7, 5, 7);
337
graphics.drawLine(1, 2, 4, 2);
338
graphics.drawLine(1, 1, 1, 2);
339
graphics.drawLine(4, 1, 4, 2);
340
graphics.drawLine(2, 0, 3, 0);
341
graphics.drawLine(2, 4, 3, 4);
342
graphics.drawLine(2, 5, 3, 5);
345
return new ImageIcon(result);
349
* Checks whether the specified text component has
350
* "select all on focus gain" property.
354
* @return <code>true</code> if the specified text component has "select all
355
* on focus gain" property, <code>false</code> otherwise.
357
public static boolean hasTextFocusSelectAllProperty(JTextComponent textComp) {
358
Component comp = textComp;
359
while (comp != null) {
360
if (comp instanceof JComponent) {
361
Object textFocusSelectAllProperty = ((JComponent) comp)
362
.getClientProperty(LafWidget.TEXT_SELECT_ON_FOCUS);
363
if (Boolean.TRUE.equals(textFocusSelectAllProperty))
365
if (Boolean.FALSE.equals(textFocusSelectAllProperty))
368
comp = comp.getParent();
370
return (Boolean.TRUE.equals(UIManager
371
.get(LafWidget.TEXT_SELECT_ON_FOCUS)));
375
* Checks whether the specified text component has "flip select on escape"
380
* @return <code>true</code> if the specified text component has "flip
381
* select on escape" property, <code>false</code> otherwise.
383
public static boolean hasTextFlipSelectOnEscapeProperty(
384
JTextComponent textComp) {
385
Object textFocusSelectAllProperty = textComp
386
.getClientProperty(LafWidget.TEXT_FLIP_SELECT_ON_ESCAPE);
387
return (Boolean.TRUE.equals(textFocusSelectAllProperty));
391
* Checks whether the specified text component has edit context menu
396
* @return <code>true</code> if the specified text component has edit
397
* context menu property, <code>false</code> otherwise.
399
public static boolean hasTextEditContextMenu(JTextComponent textComp) {
400
Object textEditContextMenuProperty = textComp
401
.getClientProperty(LafWidget.TEXT_EDIT_CONTEXT_MENU);
402
if (Boolean.TRUE.equals(textEditContextMenuProperty))
404
if (Boolean.FALSE.equals(textEditContextMenuProperty))
406
return (Boolean.TRUE.equals(UIManager
407
.get(LafWidget.TEXT_EDIT_CONTEXT_MENU)));
411
* Checks whether the specified scroll pane supports auto scroll.
414
* Scroll pane component.
415
* @return <code>true</code> if the specified scroll pane supports auto
416
* scroll, <code>false</code> otherwise.
418
public static boolean hasAutoScroll(JScrollPane scrollPane) {
419
Object compProperty = scrollPane
420
.getClientProperty(LafWidget.AUTO_SCROLL);
421
if (Boolean.TRUE.equals(compProperty))
423
if (Boolean.FALSE.equals(compProperty))
425
return (Boolean.TRUE.equals(UIManager.get(LafWidget.AUTO_SCROLL)));
429
* Checks whether the specified tree component has automatic drag and drop
434
* @return <code>true</code> if the specified text component has automatic
435
* drag and drop support, <code>false</code> otherwise.
437
public static boolean hasAutomaticDnDSupport(JTree tree) {
438
Object dndProperty = tree
439
.getClientProperty(LafWidget.TREE_AUTO_DND_SUPPORT);
440
if (Boolean.TRUE.equals(dndProperty))
442
if (Boolean.FALSE.equals(dndProperty))
444
return (Boolean.TRUE.equals(UIManager
445
.get(LafWidget.TREE_AUTO_DND_SUPPORT)));
449
* Checks whether the label lookup should use component-specific locale on
450
* the specified component.
454
* @return <code>true</code> if the custom labels should be looked up based
455
* on the component locale as returned by
456
* {@link JComponent#getLocale()}, <code>false</code> if the custom
457
* labels should be looked up based on the global locale as returned
458
* by {@link Locale#getDefault()}.
460
public static boolean toIgnoreGlobalLocale(JComponent jcomp) {
463
return Boolean.TRUE.equals(jcomp
464
.getClientProperty(LafWidget.IGNORE_GLOBAL_LOCALE));
468
* Returns the resource bundle for the specified component.
472
* @return Resource bundle for the specified component.
474
public static ResourceBundle getResourceBundle(JComponent jcomp) {
475
if (toIgnoreGlobalLocale(jcomp)) {
476
return LafWidgetRepository.getLabelBundle(jcomp.getLocale());
478
return LafWidgetRepository.getLabelBundle();
483
* Checks whether the specified component has been configured (specifically
484
* or globally) to have no animations of the specific facet. Can be used to
485
* cull unnecessary code in animation listeners on large tables and lists.
489
* @param animationFacet
491
* @return <code>true</code> if the specified component has been configured
492
* (specifically or globally) to have no animations of the specific
493
* facet, <code>false</code> otherwise.
495
public static boolean hasNoAnimations(Component comp,
496
AnimationFacet animationFacet) {
497
return !AnimationConfigurationManager.getInstance().isAnimationAllowed(
498
animationFacet, comp);
502
* Returns the current icon for the specified button. This method is <b>for
503
* internal use only</b>.
507
* @return Icon for the specified button.
509
public static Icon getIcon(AbstractButton b) {
510
Icon icon = b.getIcon();
513
ButtonModel model = b.getModel();
517
if (!model.isEnabled()) {
518
if (model.isSelected()) {
519
tmpIcon = b.getDisabledSelectedIcon();
521
tmpIcon = b.getDisabledIcon();
523
} else if (model.isPressed() && model.isArmed()) {
524
tmpIcon = b.getPressedIcon();
525
} else if (b.isRolloverEnabled() && model.isRollover()) {
526
if (model.isSelected()) {
527
tmpIcon = b.getRolloverSelectedIcon();
529
tmpIcon = b.getRolloverIcon();
531
} else if (model.isSelected()) {
532
tmpIcon = b.getSelectedIcon();
535
if (tmpIcon != null) {
542
public static boolean toIgnoreAnimations(Component comp) {
543
if (comp instanceof JMenuItem)
545
return (SwingUtilities.getAncestorOfClass(CellRendererPane.class, comp) != null);
549
* Tests UI threading violations on changing the state the specified
554
* @throws UiThreadingViolationException
555
* If the component is changing state off Event Dispatch Thread.
557
public static void testComponentStateChangeThreadingViolation(Component comp) {
558
if (!SwingUtilities.isEventDispatchThread()) {
559
UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
560
"Component state change must be done on Event Dispatch Thread");
561
uiThreadingViolationError.printStackTrace(System.err);
562
throw uiThreadingViolationError;
567
* Fires the matching property change event on the specific component.
571
* @param propertyName
574
* Old property value.
576
* New property value.
578
public static void firePropertyChangeEvent(JComponent component,
579
String propertyName, Object oldValue, Object newValue) {
580
PropertyChangeEvent pce = new PropertyChangeEvent(component,
581
propertyName, oldValue, newValue);
582
for (PropertyChangeListener general : component
583
.getPropertyChangeListeners()) {
584
general.propertyChange(pce);
586
for (PropertyChangeListener specific : component
587
.getPropertyChangeListeners(propertyName)) {
588
specific.propertyChange(pce);
593
* Returns the composite to use for painting the specified component. The
594
* result should be set on the {@link Graphics2D} before any custom
595
* rendering is done. This method can be used by application painting code
596
* and by look-and-feel delegates.
600
* @param translucency
601
* The translucency of the original painting.
603
* The original graphics context.
604
* @return The composite to use for painting the specified component.
606
public static Composite getAlphaComposite(Component c, float translucency,
608
float xFactor = 1.0f;
609
if (g instanceof Graphics2D) {
610
Graphics2D g2d = (Graphics2D) g;
611
Composite existingComposite = g2d.getComposite();
612
if (existingComposite instanceof AlphaComposite) {
613
AlphaComposite ac = (AlphaComposite) existingComposite;
614
if (ac.getRule() == AlphaComposite.SRC_OVER)
615
xFactor = ac.getAlpha();
618
float finalAlpha = translucency * xFactor;
619
if (finalAlpha < 0.0f) {
622
if (finalAlpha > 1.0f) {
625
if (finalAlpha == 1.0f) {
626
return AlphaComposite.SrcOver;
628
return AlphaComposite.SrcOver.derive(finalAlpha);
631
public static Composite getAlphaComposite(Component c, float translucency) {
632
return getAlphaComposite(c, translucency, null);
636
* Returns the composite to use for painting the specified component. The
637
* result should be set on the {@link Graphics2D} before any custom
638
* rendering is done. This method can be used by application painting code
639
* and by look-and-feel delegates.
643
* @return The composite to use for painting the specified component.
645
public static Composite getAlphaComposite(Component c, Graphics g) {
646
return getAlphaComposite(c, 1.0f, g);
650
* Returns the composite to use for painting the specified component. The
651
* result should be set on the {@link Graphics2D} before any custom
652
* rendering is done. This method can be used by application painting code
653
* and by look-and-feel delegates.
657
* @return The composite to use for painting the specified component.
659
public static Composite getAlphaComposite(Component c) {
660
return getAlphaComposite(c, 1.0f, null);