001: /*
002: * Copyright (c) 2005-2008 Substance Kirill Grouchnikov. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of Substance Kirill Grouchnikov nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030: package org.jvnet.substance;
031:
032: import java.awt.*;
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035:
036: import javax.swing.*;
037: import javax.swing.border.Border;
038: import javax.swing.plaf.*;
039: import javax.swing.plaf.basic.BasicBorders;
040: import javax.swing.plaf.basic.BasicPasswordFieldUI;
041: import javax.swing.text.*;
042:
043: import org.jvnet.lafwidget.animation.FadeStateListener;
044: import org.jvnet.substance.painter.text.SubstanceTextPainter;
045: import org.jvnet.substance.text.SubstanceFieldView;
046: import org.jvnet.substance.text.SubstanceHighlighter;
047: import org.jvnet.substance.theme.SubstanceTheme;
048: import org.jvnet.substance.utils.*;
049:
050: /**
051: * UI for password fields in <b>Substance</b> look and feel.
052: *
053: * @author Kirill Grouchnikov
054: */
055: public class SubstancePasswordFieldUI extends BasicPasswordFieldUI {
056: /**
057: * Listener for fade animations.
058: */
059: protected FadeStateListener substanceFadeStateListener;
060:
061: /**
062: * The associated password field.
063: */
064: protected JPasswordField passwordField;
065:
066: /**
067: * Property change listener.
068: */
069: protected PropertyChangeListener substancePropertyChangeListener;
070:
071: /**
072: * Custom password view.
073: *
074: * @author Kirill Grouchnikov
075: */
076: private static class SubstancePasswordView extends
077: SubstanceFieldView {
078: /**
079: * The associated password field.
080: */
081: private JPasswordField field;
082:
083: /**
084: * Simple constructor.
085: *
086: * @param field
087: * The associated password field.
088: * @param element
089: * The element
090: */
091: public SubstancePasswordView(JPasswordField field,
092: Element element) {
093: super (element);
094: this .field = field;
095: }
096:
097: protected int drawEchoCharacter(Graphics g, int x, int y,
098: char c, boolean isSelected) {
099: Container container = this .getContainer();
100:
101: Graphics2D graphics = (Graphics2D) g;
102: graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
103: RenderingHints.VALUE_ANTIALIAS_ON);
104:
105: JPasswordField field = (JPasswordField) container;
106:
107: int fontSize = SubstanceSizeUtils
108: .getComponentFontSize(this .field);
109: int dotDiameter = SubstanceSizeUtils
110: .getPasswordDotDiameter(fontSize);
111: int dotGap = SubstanceSizeUtils.getPasswordDotGap(fontSize);
112: ComponentState state = // isSelected ? ComponentState.SELECTED
113: (field.isEnabled() ? ComponentState.DEFAULT
114: : ComponentState.DISABLED_UNSELECTED);
115: SubstanceTheme theme = SubstanceThemeUtilities.getTheme(
116: field, state);
117: Color topColor = isSelected ? theme
118: .getSelectionForegroundColor() : theme
119: .getForegroundColor();
120: Color bottomColor = topColor.brighter();
121: graphics.setPaint(new GradientPaint(x, y - dotDiameter,
122: topColor, x, y, bottomColor));
123: int echoPerChar = SubstanceCoreUtilities
124: .getEchoPerChar(field);
125: for (int i = 0; i < echoPerChar; i++) {
126: graphics.fillOval(x + dotGap / 2, y - dotDiameter,
127: dotDiameter, dotDiameter);
128: x += (dotDiameter + dotGap);
129: }
130: return x;
131: }
132:
133: protected int getEchoCharAdvance() {
134: int fontSize = SubstanceSizeUtils
135: .getComponentFontSize(this .field);
136: int dotDiameter = SubstanceSizeUtils
137: .getPasswordDotDiameter(fontSize);
138: int dotGap = SubstanceSizeUtils.getPasswordDotGap(fontSize);
139: int echoPerChar = SubstanceCoreUtilities
140: .getEchoPerChar(field);
141: return echoPerChar * (dotDiameter + dotGap);
142: }
143:
144: /*
145: * (non-Javadoc)
146: *
147: * @see javax.swing.text.PlainView#drawSelectedText(java.awt.Graphics,
148: * int, int, int, int)
149: */
150: @Override
151: protected int drawSelectedText(Graphics g, final int x,
152: final int y, int p0, int p1)
153: throws BadLocationException {
154: Container c = getContainer();
155: if (c instanceof JPasswordField) {
156: JPasswordField f = (JPasswordField) c;
157: if (!f.echoCharIsSet()) {
158: return super .drawSelectedText(g, x, y, p0, p1);
159: }
160: final int n = p1 - p0;
161: final char echoChar = f.getEchoChar();
162: SubstanceTextPainter.BackgroundPaintingCallback echoCharsCallback = new SubstanceTextPainter.BackgroundPaintingCallback() {
163: public void paintBackground(Graphics g) {
164: int currPos = x;
165: for (int i = 0; i < n; i++) {
166: currPos = drawEchoCharacter(g, currPos, y,
167: echoChar, true);
168: }
169: }
170: };
171: SubstanceTextPainter textPainter = SubstanceLookAndFeel
172: .getCurrentTextPainter();
173: if (textPainter.needsBackgroundImage()) {
174: textPainter.attachCallback(echoCharsCallback);
175: } else {
176: echoCharsCallback.paintBackground(g);
177: }
178: return x + n * getEchoCharAdvance();
179: }
180: return x;
181: }
182:
183: /*
184: * (non-Javadoc)
185: *
186: * @see javax.swing.text.PlainView#drawUnselectedText(java.awt.Graphics,
187: * int, int, int, int)
188: */
189: @Override
190: protected int drawUnselectedText(Graphics g, final int x,
191: final int y, int p0, int p1)
192: throws BadLocationException {
193: Container c = getContainer();
194: if (c instanceof JPasswordField) {
195: JPasswordField f = (JPasswordField) c;
196: if (!f.echoCharIsSet()) {
197: return super .drawUnselectedText(g, x, y, p0, p1);
198: }
199: final int n = p1 - p0;
200: final char echoChar = f.getEchoChar();
201: SubstanceTextPainter.BackgroundPaintingCallback echoCharsCallback = new SubstanceTextPainter.BackgroundPaintingCallback() {
202: public void paintBackground(Graphics g) {
203: int currPos = x;
204: for (int i = 0; i < n; i++) {
205: currPos = drawEchoCharacter(g, currPos, y,
206: echoChar, false);
207: }
208: }
209: };
210: SubstanceTextPainter textPainter = SubstanceLookAndFeel
211: .getCurrentTextPainter();
212: if (textPainter.needsBackgroundImage()) {
213: textPainter.attachCallback(echoCharsCallback);
214: } else {
215: echoCharsCallback.paintBackground(g);
216: }
217: return x + n * getEchoCharAdvance();
218: }
219: return x;
220: }
221:
222: /*
223: * (non-Javadoc)
224: *
225: * @see javax.swing.text.View#modelToView(int, java.awt.Shape,
226: * javax.swing.text.Position.Bias)
227: */
228: @Override
229: public Shape modelToView(int pos, Shape a, Position.Bias b)
230: throws BadLocationException {
231: Container c = this .getContainer();
232: if (c instanceof JPasswordField) {
233: JPasswordField f = (JPasswordField) c;
234: if (!f.echoCharIsSet()) {
235: return super .modelToView(pos, a, b);
236: }
237:
238: Rectangle alloc = this .adjustAllocation(a).getBounds();
239: int echoPerChar = SubstanceCoreUtilities
240: .getEchoPerChar(f);
241: int fontSize = SubstanceSizeUtils
242: .getComponentFontSize(this .field);
243: int dotWidth = SubstanceSizeUtils
244: .getPasswordDotDiameter(fontSize)
245: + SubstanceSizeUtils
246: .getPasswordDotGap(fontSize);
247:
248: int dx = (pos - this .getStartOffset()) * echoPerChar
249: * dotWidth;
250: alloc.x += dx;
251: alloc.width = 1;
252: return alloc;
253: }
254: return null;
255: }
256:
257: /*
258: * (non-Javadoc)
259: *
260: * @see javax.swing.text.View#viewToModel(float, float, java.awt.Shape,
261: * javax.swing.text.Position.Bias[])
262: */
263: @Override
264: public int viewToModel(float fx, float fy, Shape a,
265: Position.Bias[] bias) {
266: bias[0] = Position.Bias.Forward;
267: int n = 0;
268: Container c = this .getContainer();
269: if (c instanceof JPasswordField) {
270: JPasswordField f = (JPasswordField) c;
271: if (!f.echoCharIsSet()) {
272: return super .viewToModel(fx, fy, a, bias);
273: }
274: a = this .adjustAllocation(a);
275: Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a
276: : a.getBounds();
277: int echoPerChar = SubstanceCoreUtilities
278: .getEchoPerChar(f);
279: int fontSize = SubstanceSizeUtils
280: .getComponentFontSize(this .field);
281: int dotWidth = SubstanceSizeUtils
282: .getPasswordDotDiameter(fontSize)
283: + SubstanceSizeUtils
284: .getPasswordDotGap(fontSize);
285: n = ((int) fx - alloc.x) / (echoPerChar * dotWidth);
286: if (n < 0) {
287: n = 0;
288: } else {
289: if (n > (this .getStartOffset() + this .getDocument()
290: .getLength())) {
291: n = this .getDocument().getLength()
292: - this .getStartOffset();
293: }
294: }
295: }
296: return this .getStartOffset() + n;
297: }
298:
299: /*
300: * (non-Javadoc)
301: *
302: * @see javax.swing.text.View#getPreferredSpan(int)
303: */
304: @Override
305: public float getPreferredSpan(int axis) {
306: switch (axis) {
307: case View.X_AXIS:
308: Container c = this .getContainer();
309: if (c instanceof JPasswordField) {
310: JPasswordField f = (JPasswordField) c;
311: if (f.echoCharIsSet()) {
312: int echoPerChar = SubstanceCoreUtilities
313: .getEchoPerChar(f);
314: int fontSize = SubstanceSizeUtils
315: .getComponentFontSize(this .field);
316: int dotWidth = SubstanceSizeUtils
317: .getPasswordDotDiameter(fontSize)
318: + SubstanceSizeUtils
319: .getPasswordDotGap(fontSize);
320: return echoPerChar * dotWidth
321: * this .getDocument().getLength();
322: }
323: }
324: }
325: return super .getPreferredSpan(axis);
326: }
327:
328: }
329:
330: /*
331: * (non-Javadoc)
332: *
333: * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
334: */
335: public static ComponentUI createUI(JComponent c) {
336: return new SubstancePasswordFieldUI(c);
337: }
338:
339: /**
340: * Creates the UI delegate for the specified component (password field).
341: *
342: * @param c
343: * Component.
344: */
345: public SubstancePasswordFieldUI(JComponent c) {
346: super ();
347: this .passwordField = (JPasswordField) c;
348: }
349:
350: /*
351: * (non-Javadoc)
352: *
353: * @see javax.swing.text.ViewFactory#create(javax.swing.text.Element)
354: */
355: @Override
356: public View create(Element elem) {
357: return new SubstancePasswordView(this .passwordField, elem);
358: }
359:
360: /*
361: * (non-Javadoc)
362: *
363: * @see javax.swing.plaf.basic.BasicTextUI#installListeners()
364: */
365: @Override
366: protected void installListeners() {
367: super .installListeners();
368:
369: this .substanceFadeStateListener = new FadeStateListener(
370: this .passwordField, null, null);
371: this .substanceFadeStateListener.registerListeners(false);
372:
373: this .substancePropertyChangeListener = new PropertyChangeListener() {
374: public void propertyChange(PropertyChangeEvent evt) {
375: if ("font".equals(evt.getPropertyName())) {
376: SwingUtilities.invokeLater(new Runnable() {
377: public void run() {
378: passwordField.updateUI();
379: }
380: });
381: }
382: }
383: };
384: this .passwordField
385: .addPropertyChangeListener(this .substancePropertyChangeListener);
386: }
387:
388: /*
389: * (non-Javadoc)
390: *
391: * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
392: */
393: @Override
394: protected void uninstallListeners() {
395: this .substanceFadeStateListener.unregisterListeners();
396: this .substanceFadeStateListener = null;
397:
398: this .passwordField
399: .removePropertyChangeListener(this .substancePropertyChangeListener);
400: this .substancePropertyChangeListener = null;
401:
402: super .uninstallListeners();
403: }
404:
405: /*
406: * (non-Javadoc)
407: *
408: * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
409: */
410: @Override
411: protected void installDefaults() {
412: super .installDefaults();
413: Border b = this .passwordField.getBorder();
414: if (b == null || b instanceof UIResource) {
415: Border newB = new BorderUIResource.CompoundBorderUIResource(
416: new SubstanceBorder(
417: SubstanceSizeUtils
418: .getTextBorderInsets(SubstanceSizeUtils
419: .getComponentFontSize(this .passwordField))),
420: new BasicBorders.MarginBorder());
421: this .passwordField.setBorder(newB);
422: }
423: }
424:
425: // /*
426: // * (non-Javadoc)
427: // *
428: // * @see
429: // javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
430: // */
431: // @Override
432: // protected void paintBackground(Graphics g) {
433: // SubstanceCoreUtilities.paintCompBackground(g, this.passwordField);
434: // }
435:
436: /*
437: * (non-Javadoc)
438: *
439: * @see javax.swing.plaf.basic.BasicTextUI#createHighlighter()
440: */
441: @Override
442: protected Highlighter createHighlighter() {
443: return new SubstanceHighlighter();
444: }
445:
446: /*
447: * (non-Javadoc)
448: *
449: * @see javax.swing.plaf.basic.BasicTextUI#paintSafely(java.awt.Graphics)
450: */
451: @Override
452: protected void paintSafely(Graphics _g) {
453: // // Have to call the super implementation since it sets a
454: // // private "painted" flag which affects many other places.
455: // // Without this there will be many visual artifacts with
456: // // painting the caret and highlights
457: // super.paintSafely(_g);
458: //
459: // SubstanceTextPainter textPainter = SubstanceLookAndFeel
460: // .getCurrentTextPainter();
461: //
462: // SubstanceTextPainter.BackgroundPaintingCallback callback = new
463: // SubstanceTextPainter.BackgroundPaintingCallback() {
464: // public void paintBackground(Graphics g) {
465: // Highlighter highlighter = getComponent().getHighlighter();
466: //
467: // // paint the background
468: // if (getComponent().isOpaque()) {
469: // SubstancePasswordFieldUI.this.paintBackground(g);
470: // }
471: //
472: // // paint the highlights
473: // if (highlighter != null) {
474: // highlighter.paint(g);
475: // }
476: // }
477: // };
478: // textPainter.init(passwordField, null, true);
479: // if (textPainter.needsBackgroundImage()) {
480: // textPainter.setBackgroundFill(passwordField, passwordField
481: // .getBackground(), false, 0, 0);
482: // textPainter.attachCallback(callback);
483: // } else {
484: // callback.paintBackground(_g);
485: // }
486: //
487: // // paint the view hierarchy
488: // Rectangle alloc = getVisibleEditorRect();
489: // if (alloc != null) {
490: // getRootView(passwordField).paint(_g, alloc);
491: // }
492: // textPainter.renderSurface(_g);
493: //
494: // // paint the caret
495: // Caret caret = getComponent().getCaret();
496: // if (caret != null) {
497: // caret.paint(_g);
498: // }
499: // Have to call the super implementation since it sets a
500: // private "painted" flag which affects many other places.
501: // Without this there will be many visual artifacts with
502: // painting the caret and highlights
503: Graphics2D dummy = (Graphics2D) _g.create(0, 0, 0, 0);
504: super.paintSafely(dummy);
505: dummy.dispose();
506:
507: SubstanceCoreUtilities.paintTextComponent(_g,
508: this.passwordField, this
509: .getRootView(this.passwordField), this
510: .getVisibleEditorRect());
511: }
512: }
|