001package org.jdesktop.swingx.plaf; 002 003import static javax.swing.BorderFactory.createEmptyBorder; 004 005import java.awt.Dimension; 006import java.awt.Graphics; 007import java.awt.Graphics2D; 008import java.awt.Insets; 009import java.awt.Point; 010import java.awt.Rectangle; 011import java.awt.TextComponent; 012import java.awt.Component.BaselineResizeBehavior; 013import java.awt.event.FocusAdapter; 014import java.awt.event.FocusEvent; 015import java.lang.reflect.Method; 016 017import javax.accessibility.Accessible; 018import javax.swing.JComponent; 019import javax.swing.SwingUtilities; 020import javax.swing.border.Border; 021import javax.swing.plaf.ComponentUI; 022import javax.swing.plaf.TextUI; 023import javax.swing.text.BadLocationException; 024import javax.swing.text.Caret; 025import javax.swing.text.EditorKit; 026import javax.swing.text.Highlighter; 027import javax.swing.text.JTextComponent; 028import javax.swing.text.Position; 029import javax.swing.text.View; 030import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter; 031import javax.swing.text.Position.Bias; 032 033import org.jdesktop.swingx.painter.Painter; 034import org.jdesktop.swingx.prompt.PromptSupport; 035import org.jdesktop.swingx.prompt.PromptSupport.FocusBehavior; 036 037/** 038 * <p> 039 * Abstract {@link TextUI} class that delegates most work to another 040 * {@link TextUI} and additionally renders a prompt text as specified in the 041 * {@link JTextComponent}s client properties by {@link PromptSupport}. 042 * <p> 043 * Subclasses of this class must provide a prompt component used for rendering 044 * the prompt text. 045 * </p> 046 * 047 * @author Peter Weishapl <petw@gmx.net> 048 * 049 */ 050public abstract class PromptTextUI extends TextUI { 051 protected class PainterHighlighter implements Highlighter { 052 private final Painter painter; 053 054 private JTextComponent c; 055 056 public PainterHighlighter(Painter painter) { 057 this.painter = painter; 058 } 059 060 /** 061 * {@inheritDoc} 062 */ 063 @Override 064 public Object addHighlight(int p0, int p1, HighlightPainter p) 065 throws BadLocationException { 066 return new Object(); 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 @Override 073 public void changeHighlight(Object tag, int p0, int p1) 074 throws BadLocationException { 075 076 } 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 public void deinstall(JTextComponent c) { 083 c = null; 084 } 085 086 /** 087 * {@inheritDoc} 088 */ 089 @Override 090 public Highlight[] getHighlights() { 091 return null; 092 } 093 094 /** 095 * {@inheritDoc} 096 */ 097 @Override 098 public void install(JTextComponent c) { 099 this.c = c; 100 } 101 102 /** 103 * {@inheritDoc} 104 */ 105 @Override 106 public void paint(Graphics g) { 107 Graphics2D g2d = (Graphics2D) g.create(); 108 109 try { 110 painter.paint(g2d, c, c.getWidth(), c.getHeight()); 111 } finally { 112 g2d.dispose(); 113 } 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override 120 public void removeAllHighlights() { 121 // TODO Auto-generated method stub 122 123 } 124 125 /** 126 * {@inheritDoc} 127 */ 128 @Override 129 public void removeHighlight(Object tag) { 130 // TODO Auto-generated method stub 131 132 } 133 } 134 135 static final FocusHandler focusHandler = new FocusHandler(); 136 137 /** 138 * Delegate the hard work to this object. 139 */ 140 protected final TextUI delegate; 141 142 /** 143 * This component ist painted when rendering the prompt text. 144 */ 145 protected JTextComponent promptComponent; 146 147 /** 148 * Creates a new {@link PromptTextUI} which delegates most work to another 149 * {@link TextUI}. 150 * 151 * @param delegate 152 */ 153 public PromptTextUI(TextUI delegate) { 154 this.delegate = delegate; 155 } 156 157 /** 158 * Creates a component which should be used to render the prompt text. 159 * 160 * @return 161 */ 162 protected abstract JTextComponent createPromptComponent(); 163 164 /** 165 * Calls TextUI#installUI(JComponent) on the delegate and installs a focus 166 * listener on <code>c</code> which repaints the component when it gains or 167 * loses the focus. 168 */ 169 @Override 170 public void installUI(JComponent c) { 171 delegate.installUI(c); 172 173 JTextComponent txt = (JTextComponent) c; 174 175 // repaint to correctly highlight text if FocusBehavior is 176 // HIGHLIGHT_LABEL in Metal and Windows LnF 177 txt.addFocusListener(focusHandler); 178 } 179 180 /** 181 * Delegates, then uninstalls the focus listener. 182 */ 183 @Override 184 public void uninstallUI(JComponent c) { 185 delegate.uninstallUI(c); 186 c.removeFocusListener(focusHandler); 187 promptComponent = null; 188 } 189 190 /** 191 * Creates a label component, if none has already been created. Sets the 192 * prompt components properties to reflect the given {@link JTextComponent}s 193 * properties and returns it. 194 * 195 * @param txt 196 * @return the adjusted prompt component 197 */ 198 public JTextComponent getPromptComponent(JTextComponent txt) { 199 if (promptComponent == null) { 200 promptComponent = createPromptComponent(); 201 } 202 if (txt.isFocusOwner() 203 && PromptSupport.getFocusBehavior(txt) == FocusBehavior.HIDE_PROMPT) { 204 promptComponent.setText(null); 205 } else { 206 promptComponent.setText(PromptSupport.getPrompt(txt)); 207 } 208 209 promptComponent.getHighlighter().removeAllHighlights(); 210 if (txt.isFocusOwner() 211 && PromptSupport.getFocusBehavior(txt) == FocusBehavior.HIGHLIGHT_PROMPT) { 212 promptComponent.setForeground(txt.getSelectedTextColor()); 213 try { 214 promptComponent.getHighlighter().addHighlight(0, 215 promptComponent.getText().length(), 216 new DefaultHighlightPainter(txt.getSelectionColor())); 217 } catch (BadLocationException e) { 218 e.printStackTrace(); 219 } 220 } else { 221 promptComponent.setForeground(PromptSupport.getForeground(txt)); 222 } 223 224 if (PromptSupport.getFontStyle(txt) == null) { 225 promptComponent.setFont(txt.getFont()); 226 } else { 227 promptComponent.setFont(txt.getFont().deriveFont( 228 PromptSupport.getFontStyle(txt))); 229 } 230 231 promptComponent.setBackground(PromptSupport.getBackground(txt)); 232 promptComponent.setHighlighter(new PainterHighlighter(PromptSupport 233 .getBackgroundPainter(txt))); 234 promptComponent.setEnabled(txt.isEnabled()); 235 promptComponent.setOpaque(txt.isOpaque()); 236 promptComponent.setBounds(txt.getBounds()); 237 Border b = txt.getBorder(); 238 239 if (b == null) { 240 promptComponent.setBorder(txt.getBorder()); 241 } else { 242 Insets insets = b.getBorderInsets(txt); 243 promptComponent.setBorder( 244 createEmptyBorder(insets.top, insets.left, insets.bottom, insets.right)); 245 } 246 247 promptComponent.setSelectedTextColor(txt.getSelectedTextColor()); 248 promptComponent.setSelectionColor(txt.getSelectionColor()); 249 promptComponent.setEditable(txt.isEditable()); 250 promptComponent.setMargin(txt.getMargin()); 251 252 return promptComponent; 253 } 254 255 /** 256 * When {@link #shouldPaintPrompt(JTextComponent)} returns true, the prompt 257 * component is retrieved by calling 258 * {@link #getPromptComponent(JTextComponent)} and it's preferred size is 259 * returned. Otherwise super{@link #getPreferredSize(JComponent)} is called. 260 */ 261 @Override 262 public Dimension getPreferredSize(JComponent c) { 263 JTextComponent txt = (JTextComponent) c; 264 if (shouldPaintPrompt(txt)) { 265 return getPromptComponent(txt).getPreferredSize(); 266 } 267 return delegate.getPreferredSize(c); 268 } 269 270 /** 271 * Delegates painting when {@link #shouldPaintPrompt(JTextComponent)} 272 * returns false. Otherwise the prompt component is retrieved by calling 273 * {@link #getPromptComponent(JTextComponent)} and painted. Then the caret 274 * of the given text component is painted. 275 */ 276 @Override 277 public void paint(Graphics g, final JComponent c) { 278 JTextComponent txt = (JTextComponent) c; 279 280 if (shouldPaintPrompt(txt)) { 281 paintPromptComponent(g, txt); 282 } else { 283 delegate.paint(g, c); 284 } 285 } 286 287 protected void paintPromptComponent(Graphics g, JTextComponent txt) { 288 JTextComponent lbl = getPromptComponent(txt); 289 SwingUtilities.paintComponent(g, lbl, txt, 0, 0, txt.getWidth(), txt.getHeight()); 290 291 if (txt.getCaret() != null) { 292 txt.getCaret().paint(g); 293 } 294 } 295 296 /** 297 * Returns if the prompt or the text field should be painted, depending on 298 * the state of <code>txt</code>. 299 * 300 * @param txt 301 * @return true when <code>txt</code> contains not text, otherwise false 302 */ 303 public boolean shouldPaintPrompt(JTextComponent txt) { 304 return txt.getText() == null || txt.getText().length() == 0; 305 } 306 307 /** 308 * Calls super.{@link #update(Graphics, JComponent)}, which in turn calls 309 * the paint method of this object. 310 */ 311 @Override 312 public void update(Graphics g, JComponent c) { 313 super.update(g, c); 314 } 315 316 /** 317 * Delegate when {@link #shouldPaintPrompt(JTextComponent)} returns false. 318 * Otherwise get the prompt component's UI and delegate to it. This ensures 319 * that the {@link Caret} is painted on the correct position (this is 320 * important when the text is centered, so that the caret will not be 321 * painted inside the label text) 322 */ 323 @Override 324 public Rectangle modelToView(JTextComponent t, int pos, Bias bias) 325 throws BadLocationException { 326 if (shouldPaintPrompt(t)) { 327 return getPromptComponent(t).getUI().modelToView(t, pos, bias); 328 } else { 329 return delegate.modelToView(t, pos, bias); 330 } 331 } 332 333 /** 334 * Calls {@link #modelToView(JTextComponent, int, Bias)} with 335 * {@link Bias#Forward}. 336 */ 337 @Override 338 public Rectangle modelToView(JTextComponent t, int pos) 339 throws BadLocationException { 340 return modelToView(t, pos, Position.Bias.Forward); 341 } 342 343 // ********************* Delegate methods *************************/// 344 // ****************************************************************/// 345 346 @Override 347 public boolean contains(JComponent c, int x, int y) { 348 return delegate.contains(c, x, y); 349 } 350 351 @Override 352 public void damageRange(JTextComponent t, int p0, int p1, Bias firstBias, 353 Bias secondBias) { 354 delegate.damageRange(t, p0, p1, firstBias, secondBias); 355 } 356 357 @Override 358 public void damageRange(JTextComponent t, int p0, int p1) { 359 delegate.damageRange(t, p0, p1); 360 } 361 362 @Override 363 public boolean equals(Object obj) { 364 return delegate.equals(obj); 365 } 366 367 @Override 368 public Accessible getAccessibleChild(JComponent c, int i) { 369 return delegate.getAccessibleChild(c, i); 370 } 371 372 @Override 373 public int getAccessibleChildrenCount(JComponent c) { 374 return delegate.getAccessibleChildrenCount(c); 375 } 376 377 @Override 378 public EditorKit getEditorKit(JTextComponent t) { 379 return delegate.getEditorKit(t); 380 } 381 382 @Override 383 public Dimension getMaximumSize(JComponent c) { 384 return delegate.getMaximumSize(c); 385 } 386 387 @Override 388 public Dimension getMinimumSize(JComponent c) { 389 return delegate.getMinimumSize(c); 390 } 391 392 @Override 393 public int getNextVisualPositionFrom(JTextComponent t, int pos, Bias b, 394 int direction, Bias[] biasRet) throws BadLocationException { 395 return delegate 396 .getNextVisualPositionFrom(t, pos, b, direction, biasRet); 397 } 398 399 @Override 400 public View getRootView(JTextComponent t) { 401 return delegate.getRootView(t); 402 } 403 404 @Override 405 public String getToolTipText(JTextComponent t, Point pt) { 406 return delegate.getToolTipText(t, pt); 407 } 408 409 @Override 410 public int hashCode() { 411 return delegate.hashCode(); 412 } 413 414 @Override 415 public String toString() { 416 return String.format("%s (%s)", getClass().getName(), delegate 417 .toString()); 418 } 419 420 @Override 421 public int viewToModel(JTextComponent t, Point pt, Bias[] biasReturn) { 422 return delegate.viewToModel(t, pt, biasReturn); 423 } 424 425 @Override 426 public int viewToModel(JTextComponent t, Point pt) { 427 return delegate.viewToModel(t, pt); 428 } 429 430 /** 431 * Tries to call {@link ComponentUI#getBaseline(int, int)} on the delegate 432 * via Reflection. Workaround to maintain compatibility with Java 5. Ideally 433 * we should also override {@link #getBaselineResizeBehavior(JComponent)}, 434 * but that's impossible since the {@link BaselineResizeBehavior} class, 435 * which does not exist in Java 5, is involved. 436 * 437 * @return the baseline, or -2 if <code>getBaseline</code> could not be 438 * invoked on the delegate. 439 */ 440 @Override 441 public int getBaseline(JComponent c, int width, int height) { 442 try { 443 Method m = delegate.getClass().getMethod("getBaseline", 444 JComponent.class, int.class, int.class); 445 Object o = m.invoke(delegate, new Object[] { c, width, height }); 446 return (Integer) o; 447 } catch (Exception ex) { 448 // ignore 449 return -2; 450 } 451 } 452 453 /** 454 * Repaint the {@link TextComponent} when it loses or gains the focus. 455 */ 456 private static final class FocusHandler extends FocusAdapter { 457 @Override 458 public void focusGained(FocusEvent e) { 459 e.getComponent().repaint(); 460 } 461 462 @Override 463 public void focusLost(FocusEvent e) { 464 e.getComponent().repaint(); 465 } 466 } 467}