001: package org.zilonis.tool.ui;
002:
003: import java.awt.AlphaComposite;
004: import java.awt.Color;
005: import java.awt.Composite;
006: import java.awt.Dimension;
007: import java.awt.FlowLayout;
008: import java.awt.Font;
009: import java.awt.FontMetrics;
010: import java.awt.Graphics;
011: import java.awt.Graphics2D;
012: import java.awt.Insets;
013: import java.awt.Rectangle;
014: import java.awt.RenderingHints;
015: import java.awt.event.MouseAdapter;
016: import java.awt.event.MouseEvent;
017: import java.awt.font.FontRenderContext;
018: import java.awt.font.TextLayout;
019: import java.awt.image.BufferedImage;
020: import java.util.Stack;
021: import javax.swing.Box;
022: import javax.swing.JButton;
023: import javax.swing.JComponent;
024: import javax.swing.SwingUtilities;
025:
026: import org.jdesktop.animation.timing.Animator;
027: import org.jdesktop.animation.timing.TimingTarget;
028: import org.jdesktop.animation.timing.Animator.RepeatBehavior;
029: import org.jdesktop.fuse.InjectedResource;
030: import org.jdesktop.fuse.ResourceInjector;
031:
032: class NavigationHeader extends JComponent {
033: private final PathButtonHandler eventHandler;
034: private final Stack<PathButton> buttonStack;
035:
036: ////////////////////////////////////////////////////////////////////////////
037: // THEME SPECIFIC FIELDS
038: ////////////////////////////////////////////////////////////////////////////
039: @InjectedResource
040: private Color lightColor;
041: @InjectedResource
042: private Color shadowColor;
043: @InjectedResource
044: private int preferredHeight;
045: @InjectedResource
046: private BufferedImage backgroundGradient;
047: @InjectedResource
048: private float titleAlpha;
049: @InjectedResource
050: private BufferedImage title;
051: @InjectedResource
052: private Font pathFont;
053: @InjectedResource
054: private Color pathColor;
055: @InjectedResource
056: private float pathShadowOpacity;
057: @InjectedResource
058: private Color pathShadowColor;
059: @InjectedResource
060: private int pathShadowDistance;
061: @InjectedResource
062: private int pathShadowDirection;
063: @InjectedResource
064: private BufferedImage pathSeparatorLeft;
065: @InjectedResource
066: private BufferedImage pathSeparatorRight;
067: @InjectedResource
068: private BufferedImage haloPicture;
069:
070: NavigationHeader() {
071: ResourceInjector.get().inject(this );
072:
073: setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
074: add(Box.createRigidArea(new Dimension(2, 2)));
075:
076: this .eventHandler = new PathButtonHandler();
077: this .buttonStack = new Stack<PathButton>();
078: }
079:
080: void addLink(final String title) {
081: if (buttonStack.size() > 0) {
082: PathButton button = buttonStack.get(buttonStack.size() - 1);
083: button.setHyperlinkCursor(true);
084: }
085:
086: // REMIND: should only be called from EDT?
087: PathButton pathButton = new PathButton(title);
088: add(buttonStack.push(pathButton));
089:
090: revalidate();
091: repaint();
092: }
093:
094: void clearLinks() {
095: while (buttonStack.size() > 0) {
096: remove(buttonStack.pop());
097: }
098: revalidate();
099: repaint();
100: }
101:
102: private void removeLinksAbove(final PathButton pathButton) {
103: while (!buttonStack.peek().equals(pathButton)) {
104: remove(buttonStack.pop());
105: }
106:
107: if (buttonStack.size() > 0) {
108: PathButton button = buttonStack.get(buttonStack.size() - 1);
109: button.setHyperlinkCursor(false);
110: }
111:
112: revalidate();
113: repaint();
114: }
115:
116: @Override
117: public Dimension getPreferredSize() {
118: Dimension size = super .getPreferredSize();
119: size.height = preferredHeight;
120: return size;
121: }
122:
123: @Override
124: public Dimension getMaximumSize() {
125: Dimension size = super .getMaximumSize();
126: size.height = preferredHeight;
127: return size;
128: }
129:
130: @Override
131: protected void paintComponent(Graphics g) {
132: if (!isVisible()) {
133: return;
134: }
135:
136: Graphics2D g2 = (Graphics2D) g;
137: setupGraphics(g2);
138:
139: paintBackground(g2);
140: paintLogo(g2);
141: }
142:
143: private static void setupGraphics(final Graphics2D g2) {
144: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
145: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
146: }
147:
148: private void paintLogo(final Graphics2D g2) {
149: Composite composite = g2.getComposite();
150: g2.setComposite(AlphaComposite.getInstance(
151: AlphaComposite.SRC_OVER, titleAlpha));
152: g2.drawImage(title, getWidth() - title.getWidth(), 0, null);
153: g2.setComposite(composite);
154: }
155:
156: private void paintBackground(final Graphics2D g2) {
157: int height = backgroundGradient.getHeight();
158:
159: Rectangle bounds = g2.getClipBounds();
160: g2.drawImage(backgroundGradient, (int) bounds.getX(), 0,
161: (int) bounds.getWidth(), height, null);
162:
163: g2.setColor(lightColor);
164: g2.drawLine(0, height, getWidth(), height);
165:
166: g2.setColor(shadowColor);
167: g2.drawLine(0, height + 1, getWidth(), height + 1);
168: }
169:
170: private class PathButton extends JButton {
171: private final float shadowOffsetX;
172: private final float shadowOffsetY;
173: private int textWidth;
174: private Rectangle clickable;
175: private float ghostValue = 0.0f;
176:
177: private PathButton(final String item) {
178: super (item);
179:
180: setFont(pathFont);
181: setFocusable(false);
182:
183: setBorderPainted(false);
184: setContentAreaFilled(false);
185: setFocusPainted(false);
186:
187: setMargin(new Insets(0, 0, 0, 0));
188:
189: FontMetrics metrics = getFontMetrics(pathFont);
190: textWidth = SwingUtilities.computeStringWidth(metrics,
191: getText());
192:
193: double rads = Math.toRadians(pathShadowDirection);
194: shadowOffsetX = (float) Math.cos(rads) * pathShadowDistance;
195: shadowOffsetY = (float) Math.sin(rads) * pathShadowDistance;
196:
197: addMouseListener(eventHandler);
198: addMouseListener(new HiglightHandler());
199: }
200:
201: private void setHyperlinkCursor(boolean hyperlink) {
202: if (hyperlink) {
203: FontMetrics metrics = getFontMetrics(pathFont);
204: int textHeight = metrics.getHeight();
205:
206: int x = 10;
207: if (this != buttonStack.get(0)) {
208: x += pathSeparatorRight.getWidth();
209: }
210: clickable = new Rectangle(x, metrics.getDescent(),
211: textWidth, textHeight);
212: HyperlinkHandler.add(this , clickable);
213: } else {
214: HyperlinkHandler.remove(this );
215: }
216: }
217:
218: @Override
219: public Dimension getSize() {
220: return getPreferredSize();
221: }
222:
223: @Override
224: public Dimension getPreferredSize() {
225: int width = 20 + textWidth;
226: if (this != buttonStack.peek()) {
227: width += pathSeparatorLeft.getWidth();
228: }
229: if (this != buttonStack.get(0)) {
230: width += pathSeparatorRight.getWidth();
231: }
232:
233: return new Dimension(width, preferredHeight);
234: }
235:
236: @Override
237: protected void paintComponent(Graphics g) {
238: Graphics2D g2 = (Graphics2D) g;
239:
240: Composite composite = g2.getComposite();
241:
242: if (ghostValue > 0.0f && this != buttonStack.peek()) {
243: int x = -5;
244: if (this != buttonStack.peek()) {
245: x += pathSeparatorLeft.getWidth();
246: }
247: g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
248: RenderingHints.VALUE_INTERPOLATION_BILINEAR);
249:
250: g2.setComposite(AlphaComposite.getInstance(
251: AlphaComposite.SRC_OVER, ghostValue));
252: g2.drawImage(haloPicture, x, 0, textWidth + 20,
253: getHeight(), null);
254: }
255:
256: float offset = 10.0f;
257: if (this != buttonStack.get(0)) {
258: offset += pathSeparatorRight.getWidth();
259: }
260:
261: FontRenderContext context = g2.getFontRenderContext();
262: TextLayout layout = new TextLayout(getText(), pathFont,
263: context);
264:
265: g2.setComposite(AlphaComposite.getInstance(
266: AlphaComposite.SRC_OVER, pathShadowOpacity));
267: g2.setColor(pathShadowColor);
268: layout.draw(g2, shadowOffsetX + offset, layout.getAscent()
269: + layout.getDescent() + shadowOffsetY);
270: g2.setComposite(composite);
271:
272: g2.setColor(pathColor);
273: layout.draw(g2, offset, layout.getAscent()
274: + layout.getDescent());
275:
276: if (this != buttonStack.peek()) {
277: g2.drawImage(pathSeparatorLeft, getWidth()
278: - pathSeparatorLeft.getWidth(), 0, null);
279: }
280: if (this != buttonStack.get(0)) {
281: g2.drawImage(pathSeparatorRight, 0, 0, null);
282: }
283: }
284:
285: private final class HiglightHandler extends MouseAdapter {
286: private Animator timer;
287:
288: @Override
289: public void mouseEntered(MouseEvent e) {
290: if (timer != null && timer.isRunning()) {
291: timer.stop();
292: }
293: timer = new Animator(300, new AnimateGhost(true));
294: timer.start();
295: }
296:
297: @Override
298: public void mouseExited(MouseEvent e) {
299: if (timer != null && timer.isRunning()) {
300: timer.stop();
301: }
302: timer = new Animator(300, new AnimateGhost(false));
303: timer.start();
304: }
305: }
306:
307: private final class AnimateGhost implements TimingTarget {
308: private boolean forward;
309: private float oldValue;
310:
311: AnimateGhost(boolean forward) {
312: this .forward = forward;
313: oldValue = ghostValue;
314: }
315:
316: public void timingEvent(float fraction) {
317: ghostValue = oldValue + fraction
318: * (forward ? 1.0f : -1.0f);
319:
320: if (ghostValue > 1.0f) {
321: ghostValue = 1.0f;
322: } else if (ghostValue < 0.0f) {
323: ghostValue = 0.0f;
324: }
325:
326: repaint();
327: }
328:
329: public void repeat() {
330:
331: }
332:
333: public void begin() {
334: }
335:
336: public void end() {
337: }
338: }
339: }
340:
341: private class PathButtonHandler extends MouseAdapter {
342: @Override
343: public void mouseClicked(MouseEvent e) {
344: PathButton pathButton = (PathButton) e.getSource();
345:
346: int index = buttonStack.indexOf(pathButton);
347: if (index == buttonStack.size() - 1
348: || !pathButton.clickable.contains(e.getPoint())) {
349: return;
350: }
351:
352: switch (index) {
353: case 0:
354: TransitionManager.showMainScreen();
355: removeLinksAbove(pathButton);
356: break;
357: default:
358: break;
359: }
360: }
361: }
362: }
|