001: package Schmortopf.Utility.gui;
002:
003: /**
004: * Behaves completely like an ordinary V1.22 JButton,
005: * but it will signalize mouseovers by slightly
006: * changing the fore/background contrast,
007: * like one is used from the internet.
008: *
009: * Additionally it will try to play the sensebutton.wav
010: * sound on each mouseover.
011: *
012: * To support change of constructors in future swing versions
013: * just change the constructors below accordingly.
014: *
015: * If playing sound is not wished, use the constructor
016: * with passed doPlaySound = false.
017: *
018: */
019:
020: import java.util.EventListener;
021: import java.awt.*;
022: import java.awt.event.*;
023: import java.awt.image.*;
024: import javax.swing.*;
025:
026: import Schmortopf.Main.IDE_MainFrameProvider;
027:
028: public class JSenseButton extends JButton implements MouseListener,
029: MouseMotionListener {
030:
031: private Color saveBackGround = null; // cache for original background color,
032: // on mouseovers
033: private Color saveForeGround = null; // cache for original background color,
034: // on mouseovers
035:
036: // Attributes for caching colors so they can be reused for subsequent calls
037: // with the same basiscolor and array size:
038: private int numberOfGradientColors = 10;
039: private Color backgroundGradientBasisColor = null;
040: private Color[] backgroundGradientColors = new Color[numberOfGradientColors];
041:
042: private boolean isSelected = false;
043: private boolean doPlaySound = true;
044: private boolean doAnimate = true;
045:
046: private ColorChangeThread colorChangeThread = null;
047: public JSenseButton this Button;
048:
049: // Caution: This is allowed to be null. In this case, sound
050: // is not supported.
051: private IDE_MainFrameProvider mainFrameProvider;
052:
053: // default gradient strength
054: // By calling setSmoothColorGradient this will be set to 36.0
055: private double maxColorShift = 72.0;
056:
057: /**
058: * Creates a button with no set text or icon.
059: * If mainFrameProvider is null, sound is not supported.
060: */
061: public JSenseButton(boolean doAnimate,
062: final IDE_MainFrameProvider mainFrameProvider) {
063: super (null, null);
064: this .addMouseListener(this );
065: this .addMouseMotionListener(this );
066: this .this Button = this ;
067: this .doAnimate = doAnimate;
068: this .setFocusPainted(false);
069: this .setCursor(new Cursor(Cursor.HAND_CURSOR));
070: this .mainFrameProvider = mainFrameProvider;
071: }
072:
073: /**
074: * Creates a button with an icon.
075: * If mainFrameProvider is null, sound is not supported.
076: * @param icon the Icon image to display on the button
077: */
078: public JSenseButton(Icon icon, boolean doAnimate,
079: final IDE_MainFrameProvider mainFrameProvider) {
080: super (null, icon);
081: this .addMouseListener(this );
082: this .addMouseMotionListener(this );
083: this .this Button = this ;
084: this .doAnimate = doAnimate;
085: this .setFocusPainted(false);
086: this .setCursor(new Cursor(Cursor.HAND_CURSOR));
087: this .mainFrameProvider = mainFrameProvider;
088: }
089:
090: /**
091: * Creates a button with text.
092: * If mainFrameProvider is null, sound is not supported.
093: * @param text the text of the button
094: */
095: public JSenseButton(String text, boolean doAnimate,
096: final IDE_MainFrameProvider mainFrameProvider) {
097: super (text, null);
098: this .addMouseListener(this );
099: this .addMouseMotionListener(this );
100: this .this Button = this ;
101: this .doAnimate = doAnimate;
102: this .setFocusPainted(false);
103: this .setCursor(new Cursor(Cursor.HAND_CURSOR));
104: this .mainFrameProvider = mainFrameProvider;
105: }
106:
107: /**
108: * Creates a button with initial text and an icon.
109: * If mainFrameProvider is null, sound is not supported.
110: * @param text the text of the button.
111: * @param icon the Icon image to display on the button
112: */
113: public JSenseButton(String text, Icon icon, boolean doAnimate,
114: final IDE_MainFrameProvider mainFrameProvider) {
115: super (text, icon);
116: this .addMouseListener(this );
117: this .addMouseMotionListener(this );
118: this .this Button = this ;
119: this .doAnimate = doAnimate;
120: this .setFocusPainted(false);
121: this .setCursor(new Cursor(Cursor.HAND_CURSOR));
122: this .mainFrameProvider = mainFrameProvider;
123: }
124:
125: /**
126: * Additionally allows to disable playing sound.
127: * If mainFrameProvider is null, sound is not supported.
128: */
129: public JSenseButton(Icon icon, boolean doPlaySound,
130: boolean doAnimate,
131: final IDE_MainFrameProvider mainFrameProvider) {
132: this (icon, doAnimate, mainFrameProvider);
133: this .doPlaySound = doPlaySound;
134: }
135:
136: /**
137: * Additionally allows to disable playing sound.
138: * If mainFrameProvider is null, sound is not supported.
139: */
140: public JSenseButton(String text, boolean doPlaySound,
141: boolean doAnimate,
142: final IDE_MainFrameProvider mainFrameProvider) {
143: this (text, doAnimate, mainFrameProvider);
144: this .doPlaySound = doPlaySound;
145: }
146:
147: /**
148: * Additionally allows to disable playing sound.
149: * If mainFrameProvider is null, sound is not supported.
150: */
151: public JSenseButton(String text, Icon icon, boolean doPlaySound,
152: boolean doAnimate,
153: final IDE_MainFrameProvider mainFrameProvider) {
154: this (text, icon, doAnimate, mainFrameProvider);
155: this .doPlaySound = doPlaySound;
156: }
157:
158: /**
159: * Call this, if you want a smaller color gradient.
160: */
161: public void setSmoothColorGradient() {
162: this .maxColorShift = 24.0;
163: }
164:
165: public void mouseEntered(MouseEvent e) {
166: if ((!isSelected) && (this .isVisible()) && (this .isEnabled())
167: && (this .getIsParentFrameOnTop())) {
168: isSelected = true;
169: saveForeGround = this .getForeground();
170: saveBackGround = this .getBackground();
171: int foreGroundGrayScale = saveForeGround.getRed()
172: + saveForeGround.getGreen()
173: + saveForeGround.getBlue();
174: int backGroundGrayScale = saveBackGround.getRed()
175: + saveBackGround.getGreen()
176: + saveBackGround.getBlue();
177: final int ColorDelta = 40;
178: if (foreGroundGrayScale < backGroundGrayScale) {
179: // we have dark text on light bg. Enlarger contrast :
180: int newRed;
181: newRed = saveBackGround.getRed() + ColorDelta;
182: if (newRed > 255) {
183: newRed = 255;
184: }
185: int newGreen;
186: newGreen = saveBackGround.getGreen() + ColorDelta;
187: if (newGreen > 255) {
188: newGreen = 255;
189: }
190: int newBlue;
191: newBlue = saveBackGround.getBlue() + ColorDelta;
192: if (newBlue > 255) {
193: newBlue = 255;
194: }
195: if (this .doAnimate) {
196: if (this .colorChangeThread == null) {
197: colorChangeThread = new ColorChangeThread(
198: this Button);
199: colorChangeThread
200: .setStartColor(getBackground());
201: colorChangeThread.setTargetColor(new Color(
202: newRed, newGreen, newBlue));
203: colorChangeThread.start();
204: }
205: } else {
206: this .setSuperBackground(new Color(newRed, newGreen,
207: newBlue));
208: }
209: } else {
210: // we have light text on dark bg. Enlarger contrast :
211: int newRed;
212: newRed = saveBackGround.getRed() - ColorDelta;
213: if (newRed < 0) {
214: newRed = 0;
215: }
216: int newGreen;
217: newGreen = saveBackGround.getGreen() - ColorDelta;
218: if (newGreen < 0) {
219: newGreen = 0;
220: }
221: int newBlue;
222: newBlue = saveBackGround.getBlue() - ColorDelta;
223: if (newBlue < 0) {
224: newBlue = 0;
225: }
226: if (this .doAnimate) {
227: if (this .colorChangeThread == null) {
228: colorChangeThread = new ColorChangeThread(
229: this Button);
230: colorChangeThread
231: .setStartColor(getBackground());
232: colorChangeThread.setTargetColor(new Color(
233: newRed, newGreen, newBlue));
234: colorChangeThread.start();
235: }
236: } else {
237: this .setSuperBackground(new Color(newRed, newGreen,
238: newBlue));
239: }
240: }
241: if ((doPlaySound) && (this .mainFrameProvider != null)) {
242: SoundPlaybackThread t = new SoundPlaybackThread(
243: "sensebutton.wav", this .mainFrameProvider);
244: t.start();
245: }
246: } // if
247: } // mouseEntered
248:
249: public void mouseExited(MouseEvent e) {
250: this .normalize();
251: }
252:
253: public void normalize() {
254: if ((this .isVisible()) && (this .getIsParentFrameOnTop())) {
255: if (this .colorChangeThread != null) {
256: this .colorChangeThread.doTerminate();
257: this .colorChangeThread = null;
258: }
259: // reset original colors :
260: if (saveBackGround != null) {
261: super .setBackground(saveBackGround);
262: }
263: isSelected = false;
264: }
265: } // mouseExited
266:
267: /**
268: * Additional checks, which actually only are needed,
269: * if the button is inside an JInternalFrame.
270: */
271: public boolean getIsParentFrameOnTop() {
272: boolean onTop = true;
273: Container workParent = this ;
274: while (true) {
275: workParent = workParent.getParent(); // step back
276: if (workParent == null) {
277: break;
278: }
279: if (workParent instanceof JInternalFrame) {
280: JInternalFrame iframe = (JInternalFrame) workParent;
281: onTop = iframe.isSelected();
282: break;
283: }
284: } // while
285: return onTop;
286: } // getIsParentFrameActivated
287:
288: public void mouseReleased(MouseEvent e) {
289: }
290:
291: public void mousePressed(MouseEvent e) {
292: }
293:
294: public void mouseClicked(MouseEvent e) {
295: }
296:
297: public void mouseMoved(MouseEvent e) {
298: }
299:
300: public void mouseDragged(MouseEvent e) {
301: }
302:
303: /**
304: * adjust the cache color and call super
305: */
306: public void setBackground(Color bgColor) {
307: this .saveBackGround = bgColor;
308: super .setBackground(bgColor);
309: }
310:
311: /**
312: * Used from the ColorChangeThread for setting
313: * the background using the super method.
314: */
315: public void setSuperBackground(Color bgColor) {
316: super .setBackground(bgColor);
317: }
318:
319: /**
320: * adjust the cache color and call super
321: */
322: public void setForeground(Color fgColor) {
323: this .saveForeGround = fgColor;
324: super .setForeground(fgColor);
325: }
326:
327: /**
328: * Overwritten paintComponent method, which paints
329: * the background before it calls the ui delegate's paint
330: * method. Normally, the ui delegate's update method
331: * would paint the background pefore it would call the
332: * same paint method like done by this method.
333: */
334: protected void paintComponent(Graphics g) {
335: if (ui != null) {
336: // For this JSenseButton, we paint a gradient in the
337: // background and do not react on the isOpaque attribute.
338: this .paintBackgroundGradient(g);
339: // Like in the super method, we let the UI delegate do the rest:
340: ui.paint(g, this );
341: }
342: }
343:
344: private void paintBackgroundGradient(Graphics g) {
345: // Check, if we have to (re)create the background gradient:
346: if (this .backgroundGradientBasisColor == null) // create it the first time
347: {
348: updateBackgroundGradientColors();
349: } else if (this .backgroundGradientBasisColor != this
350: .getBackground()) // adjust
351: {
352: updateBackgroundGradientColors();
353: } else if (this .getHeight() != this .numberOfGradientColors) // adjust
354: {
355: updateBackgroundGradientColors();
356: }
357: // Now paint the gradient:
358: int y1, y2;
359: int w = this .getWidth();
360: int h = this .getHeight();
361: for (int i = 0; i < this .numberOfGradientColors; i++) {
362: g.setColor(this .backgroundGradientColors[i]);
363: y1 = (i * h) / this .numberOfGradientColors;
364: y2 = ((i + 1) * h) / this .numberOfGradientColors;
365: g.fillRect(0, y1, w, y2 - y1);
366: }
367: } // paintBackgroundGradient
368:
369: private void updateBackgroundGradientColors() {
370: // Recreate the array, if the size has to be changed:
371: if (this .numberOfGradientColors != this .getHeight()) {
372: // GC assistance:
373: if (this .backgroundGradientColors != null) {
374: for (int i = 0; i < this .backgroundGradientColors.length; i++) {
375: this .backgroundGradientColors[i] = null;
376: }
377: this .backgroundGradientColors = null;
378: }
379: // Recreate:
380: this .numberOfGradientColors = this .getHeight();
381: this .backgroundGradientColors = new Color[this .numberOfGradientColors];
382: } else {
383: // GC assistance:(all elements will be overwritten, so set them to null
384: // already here)
385: if (this .backgroundGradientColors != null) {
386: for (int i = 0; i < this .backgroundGradientColors.length; i++) {
387: this .backgroundGradientColors[i] = null;
388: }
389: }
390: }
391: // backgroundGradientBasisColor also is used for testing for adjustments above.
392: // If it (=the reference) changes, the gradient is updated here.
393: this .backgroundGradientBasisColor = this .getBackground();
394: int rBasis = this .backgroundGradientBasisColor.getRed();
395: int gBasis = this .backgroundGradientBasisColor.getGreen();
396: int bBasis = this .backgroundGradientBasisColor.getBlue();
397: // Define the color shifts:
398: int[] colorShift = new int[this .numberOfGradientColors];
399: double centerIndex = this .numberOfGradientColors / 2.0;
400: double shift = 0.0;
401: double normFactor = 4 * this .maxColorShift;
402: double offset = 0;
403: int sign = 1;
404: for (int i = 0; i < this .numberOfGradientColors; i++) {
405: offset = 0.5 - (1.0 * i) / this .numberOfGradientColors;
406: shift = offset * offset * normFactor;
407: if (i > centerIndex)
408: sign = -1;
409: colorShift[i] = sign * (int) shift;
410: }
411: // And set the colors:
412: int r, g, b;
413: for (int i = 0; i < this .numberOfGradientColors; i++) {
414: r = rBasis + colorShift[i];
415: if (r < 0)
416: r = 0;
417: if (r > 255)
418: r = 255;
419: g = gBasis + colorShift[i];
420: if (g < 0)
421: g = 0;
422: if (g > 255)
423: g = 255;
424: b = bBasis + colorShift[i];
425: if (b < 0)
426: b = 0;
427: if (b > 255)
428: b = 255;
429: this .backgroundGradientColors[i] = new Color(r, g, b);
430: } // for
431: colorShift = null;
432: } // updateBackgroundGradientColors
433:
434: /**
435: * Sets the foreground color so that it has
436: * a good contrast to the background color.
437: */
438: public void maximizeForeGroundContrastToBackground() {
439: // Set it white or black, depending from background
440: // grayscale average :
441: int grayScaleAverage = (this .getBackground().getRed()
442: + this .getBackground().getGreen() + this
443: .getBackground().getBlue()) / 3;
444: if (grayScaleAverage > 127) {
445: this .setForeground(new Color(0, 0, 0));
446: } else {
447: this .setForeground(new Color(255, 255, 255));
448: }
449: }
450:
451: /**
452: * This thread performs the animated color change.
453: */
454: private class ColorChangeThread extends Thread {
455: private final JSenseButton parentButton;
456: private Color startColor = new Color(160, 160, 160);
457: private Color targetColor = new Color(100, 100, 100);
458: private boolean terminateThread = false;
459:
460: public ColorChangeThread(JSenseButton theButton) {
461: this .parentButton = theButton;
462: this .setDaemon(true);
463: // Give other threads more attention:
464: this .setPriority(Thread.MIN_PRIORITY);
465: }
466:
467: public void run() {
468: try {
469: Thread.sleep(11);
470: } catch (Exception wurscht00) {
471: }
472: final int loops = 5;
473: final float deltaRed = 1f
474: * (this .targetColor.getRed() - this .startColor
475: .getRed()) / loops;
476: final float deltaGreen = 1f
477: * (this .targetColor.getGreen() - this .startColor
478: .getGreen()) / loops;
479: final float deltaBlue = 1f
480: * (this .targetColor.getBlue() - this .startColor
481: .getBlue()) / loops;
482: for (int i = 1; i <= loops; i++) {
483: try {
484: Thread.sleep(49);
485: } catch (Exception wurscht) {
486: }
487: final int iStep = i;
488: SwingUtilities.invokeLater(new Runnable() {
489: public void run() {
490: if (!terminateThread) {
491: int cRed = (int) (startColor.getRed() + iStep
492: * deltaRed);
493: int cGreen = (int) (startColor.getGreen() + iStep
494: * deltaGreen);
495: int cBlue = (int) (startColor.getBlue() + iStep
496: * deltaBlue);
497: setSuperBackground(new Color(cRed, cGreen,
498: cBlue));
499: }
500: }
501: });
502: try {
503: Thread.yield();
504: } catch (Exception wurscht) {
505: }
506: }
507: } // run
508:
509: public void setStartColor(final Color theStartColor) {
510: this .startColor = theStartColor;
511: }
512:
513: public void setTargetColor(final Color theTargetColor) {
514: this .targetColor = theTargetColor;
515: }
516:
517: public void doTerminate() {
518: this .terminateThread = true;
519: }
520:
521: } // class ColorChangeThread
522:
523: } // JSenseButton
|