2
* Copyright (c) 2005-2010 Substance 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 Substance 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.substance.internal.utils;
33
import java.awt.geom.AffineTransform;
34
import java.awt.image.*;
37
import javax.swing.border.Border;
38
import javax.swing.border.CompoundBorder;
39
import javax.swing.plaf.ComponentUI;
40
import javax.swing.plaf.basic.BasicGraphicsUtils;
41
import javax.swing.text.JTextComponent;
43
import org.pushingpixels.lafwidget.LafWidgetUtilities;
44
import org.pushingpixels.lafwidget.text.LockBorder;
45
import org.pushingpixels.lafwidget.utils.RenderingUtils;
46
import org.pushingpixels.substance.api.*;
47
import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
48
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
49
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
50
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
51
import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
54
* Text-related utilities. This class if for internal use only.
56
* @author Kirill Grouchnikov
58
public class SubstanceTextUtilities {
59
public static final String ENFORCE_FG_COLOR = "substancelaf.internal.textUtilities.enforceFgColor";
62
* Paints text with drop shadow.
68
* @param foregroundColor
73
* Text rectangle width.
75
* Text rectangle height.
77
* Text rectangle X offset.
79
* Text rectangle Y offset.
81
public static void paintTextWithDropShadow(JComponent c, Graphics g,
82
Color foregroundColor, String text, int width, int height,
83
int xOffset, int yOffset) {
84
Graphics2D graphics = (Graphics2D) g.create();
85
RenderingUtils.installDesktopHints(graphics, c);
87
// blur the text shadow
88
BufferedImage blurred = SubstanceCoreUtilities.getBlankImage(width,
90
Graphics2D gBlurred = (Graphics2D) blurred.getGraphics();
91
gBlurred.setFont(graphics.getFont());
92
gBlurred.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
93
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
95
// SubstanceColorUtilities.getNegativeColor(foregroundColor);
96
float luminFactor = SubstanceColorUtilities
97
.getColorStrength(foregroundColor);
98
gBlurred.setColor(SubstanceColorUtilities
99
.getNegativeColor(foregroundColor));
100
ConvolveOp convolve = new ConvolveOp(new Kernel(3, 3, new float[] {
101
.02f, .05f, .02f, .05f, .02f, .05f, .02f, .05f, .02f }),
102
ConvolveOp.EDGE_NO_OP, null);
103
gBlurred.drawString(text, xOffset, yOffset - 1);
104
blurred = convolve.filter(blurred, null);
106
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c,
108
graphics.drawImage(blurred, 0, 0, null);
109
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));
111
FontMetrics fm = graphics.getFontMetrics();
112
SubstanceTextUtilities.paintText(graphics, c, new Rectangle(xOffset,
113
yOffset - fm.getAscent(), width - xOffset, fm.getHeight()),
114
text, -1, graphics.getFont(), foregroundColor, graphics
121
* Paints the specified text.
131
* @param mnemonicIndex
138
* Optional clip. Can be <code>null</code>.
140
* Optional transform to apply. Can be <code>null</code>.
142
private static void paintText(Graphics g, JComponent comp,
143
Rectangle textRect, String text, int mnemonicIndex,
144
java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
145
java.awt.geom.AffineTransform transform) {
146
if ((text == null) || (text.length() == 0))
149
Graphics2D g2d = (Graphics2D) g.create();
150
// workaroundBug6576507(g2d);
151
// RenderingUtils.installDesktopHints(g2d);
155
// fix for issue 420 - call clip() instead of setClip() to
156
// respect the currently set clip shape
159
if (transform != null)
160
g2d.transform(transform);
161
BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, text, mnemonicIndex,
162
textRect.x, textRect.y + g2d.getFontMetrics().getAscent());
167
* Paints the specified text.
177
* @param mnemonicIndex
184
* Optional clip. Can be <code>null</code>.
186
public static void paintText(Graphics g, JComponent comp,
187
Rectangle textRect, String text, int mnemonicIndex,
188
java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip) {
189
SubstanceTextUtilities.paintText(g, comp, textRect, text,
190
mnemonicIndex, font, color, clip, null);
194
* Paints the specified vertical text.
204
* @param mnemonicIndex
211
* Optional clip. Can be <code>null</code>.
212
* @param isFromBottomToTop
213
* If <code>true</code>, the text will be painted from bottom to
214
* top, otherwise the text will be painted from top to bottom.
216
public static void paintVerticalText(Graphics g, JComponent comp,
217
Rectangle textRect, String text, int mnemonicIndex,
218
java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
219
boolean isFromBottomToTop) {
220
if ((text == null) || (text.length() == 0))
225
if (!isFromBottomToTop) {
226
at = AffineTransform.getTranslateInstance(textRect.x
227
+ textRect.width, textRect.y);
228
at.rotate(Math.PI / 2);
230
at = AffineTransform.getTranslateInstance(textRect.x, textRect.y
232
at.rotate(-Math.PI / 2);
234
Rectangle newRect = new Rectangle(0, 0, textRect.width, textRect.height);
236
SubstanceTextUtilities.paintText(g, comp, newRect, text, mnemonicIndex,
237
font, color, clip, at);
241
* Paints the text of the specified button.
251
* @param mnemonicIndex
254
public static void paintText(Graphics g, AbstractButton button,
255
Rectangle textRect, String text, int mnemonicIndex) {
256
paintText(g, button, button.getModel(), textRect, text, mnemonicIndex);
260
* Paints the text of the specified button.
272
* @param mnemonicIndex
275
public static void paintText(Graphics g, AbstractButton button,
276
ButtonModel model, Rectangle textRect, String text,
278
TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
280
StateTransitionTracker stateTransitionTracker = transitionAwareUI
281
.getTransitionTracker();
283
float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
284
ComponentState.getState(button));
286
if (button instanceof JMenuItem) {
287
paintMenuItemText(g, (JMenuItem) button, textRect, text,
288
mnemonicIndex, stateTransitionTracker.getModelStateInfo(),
291
paintText(g, button, textRect, text, mnemonicIndex,
292
stateTransitionTracker.getModelStateInfo(), buttonAlpha);
297
* Paints the specified text.
307
* @param mnemonicIndex
312
* Alpha channel for painting the text.
314
public static void paintText(Graphics g, JComponent component,
315
Rectangle textRect, String text, int mnemonicIndex,
316
ComponentState state, float textAlpha) {
317
Color fgColor = getForegroundColor(component, text, state, textAlpha);
319
SubstanceTextUtilities.paintText(g, component, textRect, text,
320
mnemonicIndex, component.getFont(), fgColor, null);
323
public static void paintText(Graphics g, JComponent component,
324
Rectangle textRect, String text, int mnemonicIndex,
325
StateTransitionTracker.ModelStateInfo modelStateInfo,
327
Color fgColor = getForegroundColor(component, text, modelStateInfo,
330
SubstanceTextUtilities.paintText(g, component, textRect, text,
331
mnemonicIndex, component.getFont(), fgColor, null);
334
public static void paintMenuItemText(Graphics g, JMenuItem menuItem,
335
Rectangle textRect, String text, int mnemonicIndex,
336
StateTransitionTracker.ModelStateInfo modelStateInfo,
338
Color fgColor = getMenuComponentForegroundColor(menuItem, text,
339
modelStateInfo, textAlpha);
341
SubstanceTextUtilities.paintText(g, menuItem, textRect, text,
342
mnemonicIndex, menuItem.getFont(), fgColor, null);
346
* Returns the foreground color for the specified component.
351
* Text. If empty or <code>null</code>, the result is
356
* Alpha channel for painting the text. If value is less than
357
* 1.0, the result is an opaque color which is an interpolation
358
* between the "real" foreground color and the background color
359
* of the component. This is done to ensure that native text
360
* rasterization will be performed on 6u10+ on Windows.
361
* @return The foreground color for the specified component.
363
public static Color getForegroundColor(JComponent component, String text,
364
ComponentState state, float textAlpha) {
365
if ((text == null) || (text.length() == 0))
368
boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
369
CellRendererPane.class, component) != null)
370
|| Boolean.TRUE.equals(component
371
.getClientProperty(ENFORCE_FG_COLOR));
373
Color fgColor = toEnforceFgColor ? component.getForeground()
374
: SubstanceColorSchemeUtilities
375
.getColorScheme(component, state).getForegroundColor();
377
// System.out.println(text + ":" + prevState.name() + "->" +
378
// state.name() + ":" + fgColor);
379
if (textAlpha < 1.0f) {
380
Color bgFillColor = SubstanceColorUtilities
381
.getBackgroundFillColor(component);
382
if (bgFillColor != null) {
383
fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
384
bgFillColor, textAlpha);
391
* Returns the foreground color for the specified component.
396
* Text. If empty or <code>null</code>, the result is
399
* Alpha channel for painting the text. If value is less than
400
* 1.0, the result is an opaque color which is an interpolation
401
* between the "real" foreground color and the background color
402
* of the component. This is done to ensure that native text
403
* rasterization will be performed on 6u10 on Windows.
404
* @return The foreground color for the specified component.
406
public static Color getForegroundColor(JComponent component, String text,
407
StateTransitionTracker.ModelStateInfo modelStateInfo,
409
if ((text == null) || (text.length() == 0))
412
boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
413
CellRendererPane.class, component) != null)
414
|| Boolean.TRUE.equals(component
415
.getClientProperty(ENFORCE_FG_COLOR));
418
if (toEnforceFgColor) {
419
fgColor = component.getForeground();
421
fgColor = SubstanceColorUtilities.getForegroundColor(component,
425
// System.out.println(text + ":" + prevState.name() + "->" +
426
// state.name() + ":" + fgColor);
427
if (textAlpha < 1.0f) {
428
Color bgFillColor = SubstanceColorUtilities
429
.getBackgroundFillColor(component);
430
fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
431
bgFillColor, textAlpha);
437
* Returns the foreground color for the specified menu component.
439
* @param menuComponent
442
* Text. If empty or <code>null</code>, the result is
444
* @param modelStateInfo
445
* Model state info for the specified component.
447
* Alpha channel for painting the text. If value is less than
448
* 1.0, the result is an opaque color which is an interpolation
449
* between the "real" foreground color and the background color
450
* of the component. This is done to ensure that native text
451
* rasterization will be performed on 6u10 on Windows.
452
* @return The foreground color for the specified component.
454
public static Color getMenuComponentForegroundColor(Component menuComponent,
455
String text, StateTransitionTracker.ModelStateInfo modelStateInfo,
457
if ((text == null) || (text.length() == 0))
460
Color fgColor = SubstanceColorUtilities
461
.getMenuComponentForegroundColor(menuComponent, modelStateInfo);
463
if (textAlpha < 1.0f) {
464
Color bgFillColor = SubstanceColorUtilities
465
.getBackgroundFillColor(menuComponent);
466
fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
467
bgFillColor, textAlpha);
473
* Paints background of the specified text component.
480
public static void paintTextCompBackground(Graphics g, JComponent comp) {
481
Color backgroundFillColor = getTextBackgroundFillColor(comp);
483
boolean toPaintWatermark = (SubstanceLookAndFeel.getCurrentSkin(comp)
484
.getWatermark() != null)
485
&& (SubstanceCoreUtilities.toDrawWatermark(comp) || !comp
487
paintTextCompBackground(g, comp, backgroundFillColor, toPaintWatermark);
490
public static Color getTextBackgroundFillColor(JComponent comp) {
491
Color backgroundFillColor = SubstanceColorUtilities
492
.getBackgroundFillColor(comp);
493
JTextComponent componentForTransitions = SubstanceCoreUtilities
494
.getTextComponentForTransitions(comp);
496
if (componentForTransitions != null) {
497
ComponentUI ui = componentForTransitions.getUI();
498
if (ui instanceof TransitionAwareUI) {
499
TransitionAwareUI trackable = (TransitionAwareUI) ui;
500
StateTransitionTracker stateTransitionTracker = trackable
501
.getTransitionTracker();
503
Color outerTextComponentBorderColor = SubstanceColorUtilities
504
.getOuterTextComponentBorderColor(backgroundFillColor);
505
outerTextComponentBorderColor = SubstanceColorUtilities
506
.getInterpolatedColor(outerTextComponentBorderColor,
507
backgroundFillColor, 0.6);
509
float selectionStrength = stateTransitionTracker
510
.getFacetStrength(ComponentStateFacet.SELECTION);
511
float rolloverStrength = stateTransitionTracker
512
.getFacetStrength(ComponentStateFacet.ROLLOVER);
513
backgroundFillColor = SubstanceColorUtilities
514
.getInterpolatedColor(outerTextComponentBorderColor,
515
backgroundFillColor, Math.max(
516
selectionStrength, rolloverStrength));
519
return backgroundFillColor;
523
* Paints background of the specified text component.
531
* @param toOverlayWatermark
532
* If <code>true</code>, this method will paint the watermark
533
* overlay on top of the background fill.
535
private static void paintTextCompBackground(Graphics g, JComponent comp,
536
Color backgr, boolean toOverlayWatermark) {
537
Graphics2D g2d = (Graphics2D) g.create();
539
int componentFontSize = SubstanceSizeUtils.getComponentFontSize(comp);
540
int borderDelta = (int) Math.floor(SubstanceSizeUtils
541
.getBorderStrokeWidth(componentFontSize));
542
Border compBorder = comp.getBorder();
544
if (compBorder instanceof LockBorder) {
545
compBorder = ((LockBorder) compBorder).getOriginalBorder();
547
boolean isSubstanceBorder = compBorder instanceof SubstanceTextComponentBorder;
549
if (!isSubstanceBorder) {
550
Border border = compBorder;
551
while (border instanceof CompoundBorder) {
552
Border outer = ((CompoundBorder) border).getOutsideBorder();
553
if (outer instanceof SubstanceTextComponentBorder) {
554
isSubstanceBorder = true;
557
Border inner = ((CompoundBorder) border).getInsideBorder();
558
if (inner instanceof SubstanceTextComponentBorder) {
559
isSubstanceBorder = true;
566
Shape contour = isSubstanceBorder ? SubstanceOutlineUtilities
575
.getClassicButtonCornerRadius(componentFontSize)
576
- borderDelta), null,
578
: new Rectangle(0, 0, comp.getWidth(), comp.getHeight());
580
BackgroundPaintingUtils.update(g, comp, false);
581
SubstanceWatermark watermark = SubstanceCoreUtilities.getSkin(comp)
583
if (watermark != null) {
584
watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(), comp
587
g2d.setColor(backgr);
590
if (toOverlayWatermark) {
591
if (watermark != null) {
593
watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(),