001package org.jdesktop.swingx.plaf; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005 006import javax.swing.JComponent; 007import javax.swing.JTextArea; 008import javax.swing.JTextField; 009import javax.swing.UIDefaults; 010import javax.swing.plaf.TextUI; 011import javax.swing.plaf.basic.BasicTextUI; 012import javax.swing.text.JTextComponent; 013 014import org.jdesktop.swingx.JXSearchField; 015import org.jdesktop.swingx.prompt.BuddySupport; 016 017/** 018 * TODO: 019 * 020 * @author Peter Weishapl <petw@gmx.net> 021 * 022 * @param <UI> 023 */ 024public abstract class TextUIWrapper<UI extends TextUI> { 025 private static final DefaultWrapper defaultWrapper = new DefaultWrapper(); 026 027 public static final TextUIWrapper<? extends PromptTextUI> getDefaultWrapper() { 028 return defaultWrapper; 029 } 030 031 private Class<UI> wrapperClass; 032 033 protected TextUIWrapper(Class<UI> wrapperClass) { 034 this.wrapperClass = wrapperClass; 035 } 036 037 /** 038 * <p> 039 * Wraps and replaces the current UI of the given <code>textComponent</code>, by calling 040 * {@link #wrapUI(JTextComponent)} if necessary. 041 * </p> 042 * 043 * @param textComponent 044 * @param stayOnUIChange 045 * if <code>true</code>, a {@link PropertyChangeListener} is registered, which 046 * listens for UI changes and wraps any new UI object. 047 */ 048 public final void install(final JTextComponent textComponent, boolean stayOnUIChange) { 049 replaceUIIfNeeded(textComponent); 050 if (stayOnUIChange) { 051 uiChangeHandler.install(textComponent); 052 } 053 } 054 055 /** 056 * Wraps and replaces the text components current UI by calling {@link #wrapUI(TextUI)}, if the 057 * text components current UI is not an instance of the given wrapper class. 058 * 059 * @param textComponent 060 * @return <code>true</code> if the UI has been replaced 061 */ 062 protected boolean replaceUIIfNeeded(JTextComponent textComponent) { 063 if (wrapperClass.isAssignableFrom(textComponent.getUI().getClass())) { 064 return false; 065 } 066 067 textComponent.setUI(wrapUI(textComponent)); 068 069 return true; 070 } 071 072 /** 073 * Override to return the appropriate UI wrapper object for the given {@link TextUI}. 074 * 075 * @param textUI 076 * @return the wrapping UI 077 */ 078 public abstract UI wrapUI(JTextComponent textComponent); 079 080 /** 081 * Returns the wrapper class. 082 * 083 * @return the wrapper class 084 */ 085 public Class<UI> getWrapperClass() { 086 return wrapperClass; 087 } 088 089 /** 090 * <p> 091 * Removes the {@link PropertyChangeListener}, which listens for "UI" property changes (if 092 * installed) and then calls {@link JComponent#updateUI()} on the <code>textComponent</code> to 093 * set the UI object provided by the current {@link UIDefaults}. 094 * </p> 095 * 096 * @param textComponent 097 */ 098 public final void uninstall(final JTextComponent textComponent) { 099 uiChangeHandler.uninstall(textComponent); 100 textComponent.updateUI(); 101 } 102 103 private final TextUIChangeHandler uiChangeHandler = new TextUIChangeHandler(); 104 105 private final class TextUIChangeHandler extends AbstractUIChangeHandler { 106 @Override 107 public void propertyChange(PropertyChangeEvent evt) { 108 JTextComponent txt = (JTextComponent) evt.getSource(); 109 110 replaceUIIfNeeded(txt); 111 } 112 } 113 114 public static final class DefaultWrapper extends TextUIWrapper<PromptTextUI> { 115 private DefaultWrapper() { 116 super(PromptTextUI.class); 117 } 118 119 /** 120 * <p> 121 * Creates a new {@link PromptTextUI}, which wraps the given <code>textComponent</code>s UI. 122 * </p> 123 * <p> 124 * If the UI is already of type {@link PromptTextUI}, it will be returned. If 125 * <code>textComponent</code> is of type {@link JXSearchField} a new {@link SearchFieldUI} 126 * object will be returned. If <code>textComponent</code> is of type {@link JTextField} or 127 * {@link JTextArea} a {@link BuddyTextFieldUI} or {@link PromptTextAreaUI} will be 128 * returned, respectively. If the UI is of any other type, a 129 * {@link IllegalArgumentException} will be thrown. 130 * </p> 131 * 132 * @param textComponent 133 * wrap this components UI 134 * @return a {@link PromptTextUI} which wraps the <code>textComponent</code>s UI. 135 */ 136 @Override 137 public PromptTextUI wrapUI(JTextComponent textComponent) { 138 TextUI textUI = textComponent.getUI(); 139 140 if (textUI instanceof PromptTextUI) { 141 return (PromptTextUI) textUI; 142 } else if (textComponent instanceof JXSearchField) { 143 return new SearchFieldUI(textUI); 144 } else if (textComponent instanceof JTextField) { 145 return new BuddyTextFieldUI(textUI); 146 } else if (textComponent instanceof JTextArea) { 147 return new PromptTextAreaUI(textUI); 148 } 149 throw new IllegalArgumentException("ui implementation not supported: " 150 + textUI.getClass()); 151 } 152 153 /** 154 * Every time the UI needs to be replaced we also need to make sure, that all buddy 155 * components are also in the component hierarchy. (That's because {@link BasicTextUI} 156 * removes all our buddies upon UI changes). 157 */ 158 @Override 159 protected boolean replaceUIIfNeeded(JTextComponent textComponent) { 160 boolean replaced = super.replaceUIIfNeeded(textComponent); 161 162 if (replaced && textComponent instanceof JTextField) { 163 BuddySupport.ensureBuddiesAreInComponentHierarchy((JTextField) textComponent); 164 } 165 return replaced; 166 } 167 } 168}