001 /* 002 * $Id: AbstractPatternPanel.java,v 1.9 2005/12/05 15:00:55 kizune Exp $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx; 022 023 import java.awt.Dimension; 024 import java.beans.PropertyChangeEvent; 025 import java.beans.PropertyChangeListener; 026 027 import javax.swing.Action; 028 import javax.swing.ActionMap; 029 import javax.swing.JCheckBox; 030 import javax.swing.JLabel; 031 import javax.swing.JTextField; 032 import javax.swing.SwingUtilities; 033 import javax.swing.UIManager; 034 import javax.swing.event.DocumentEvent; 035 import javax.swing.event.DocumentListener; 036 037 import org.jdesktop.swingx.action.AbstractActionExt; 038 import org.jdesktop.swingx.action.ActionContainerFactory; 039 import org.jdesktop.swingx.action.BoundAction; 040 import org.jdesktop.swingx.plaf.LookAndFeelAddons; 041 042 /** 043 * Common base class of ui clients. 044 * 045 * Implements basic synchronization between PatternModel state and 046 * actions bound to it. 047 * 048 * 049 * 050 * PENDING: extending JXPanel is a convenience measure, should be extracted 051 * into a dedicated controller. 052 * PENDING: should be re-visited when swingx goes binding-aware 053 * 054 * @author Jeanette Winzenburg 055 */ 056 public abstract class AbstractPatternPanel extends JXPanel { 057 058 public static final String SEARCH_FIELD_LABEL = "searchFieldLabel"; 059 public static final String SEARCH_FIELD_MNEMONIC = SEARCH_FIELD_LABEL + ".mnemonic"; 060 public static final String SEARCH_TITLE = "searchTitle"; 061 public static final String MATCH_ACTION_COMMAND = "match"; 062 063 static { 064 // Hack to enforce loading of SwingX framework ResourceBundle 065 LookAndFeelAddons.getAddon(); 066 } 067 068 protected JLabel searchLabel; 069 protected JTextField searchField; 070 protected JCheckBox matchCheck; 071 072 protected PatternModel patternModel; 073 private ActionContainerFactory actionFactory; 074 075 076 //------------------------ actions 077 078 /** 079 * Callback action bound to MATCH_ACTION_COMMAND. 080 */ 081 public abstract void match(); 082 083 /** 084 * convenience method for type-cast to AbstractActionExt. 085 * 086 * @param key Key to retrieve action 087 * @return Action bound to this key 088 * @see AbstractActionExt 089 */ 090 protected AbstractActionExt getAction(String key) { 091 // PENDING: outside clients might add different types? 092 return (AbstractActionExt) getActionMap().get(key); 093 } 094 095 /** 096 * creates and registers all actions for the default the actionMap. 097 */ 098 protected void initActions() { 099 initPatternActions(); 100 initExecutables(); 101 } 102 103 /** 104 * creates and registers all "executable" actions. 105 * Meaning: the actions bound to a callback method on this. 106 * 107 * PENDING: not quite correctly factored? Name? 108 * 109 */ 110 protected void initExecutables() { 111 Action execute = createBoundAction(MATCH_ACTION_COMMAND, "match"); 112 getActionMap().put(JXDialog.EXECUTE_ACTION_COMMAND, 113 execute); 114 getActionMap().put(MATCH_ACTION_COMMAND, execute); 115 refreshEmptyFromModel(); 116 } 117 118 /** 119 * creates actions bound to PatternModel's state. 120 */ 121 protected void initPatternActions() { 122 ActionMap map = getActionMap(); 123 map.put(PatternModel.MATCH_CASE_ACTION_COMMAND, 124 createModelStateAction(PatternModel.MATCH_CASE_ACTION_COMMAND, 125 "setCaseSensitive", getPatternModel().isCaseSensitive())); 126 map.put(PatternModel.MATCH_WRAP_ACTION_COMMAND, 127 createModelStateAction(PatternModel.MATCH_WRAP_ACTION_COMMAND, 128 "setWrapping", getPatternModel().isWrapping())); 129 map.put(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND, 130 createModelStateAction(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND, 131 "setBackwards", getPatternModel().isBackwards())); 132 map.put(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND, 133 createModelStateAction(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND, 134 "setIncremental", getPatternModel().isIncremental())); 135 } 136 137 /** 138 * tries to find a String value from the UIManager, prefixing the 139 * given key with the UIPREFIX. 140 * 141 * TODO: move to utilities? 142 * 143 * @param key <code>String</code> that specifyes the value in UIManager 144 * @return the <code>String</code> as returned by the UIManager or key itself 145 * if no value bound to this key in UIManager 146 */ 147 protected String getUIString(String key) { 148 String text = UIManager.getString(PatternModel.SEARCH_PREFIX + key); 149 return text != null ? text : key; 150 } 151 152 153 /** 154 * creates, configures and returns a bound state action on a boolean property 155 * of the PatternModel. 156 * 157 * @param command the actionCommand - same as key to find localizable resources 158 * @param methodName the method on the PatternModel to call on item state changed 159 * @param initial the initial value of the property 160 * @return newly created action 161 */ 162 protected AbstractActionExt createModelStateAction(String command, String methodName, boolean initial) { 163 String actionName = getUIString(command); 164 BoundAction action = new BoundAction(actionName, 165 command); 166 action.setStateAction(); 167 action.registerCallback(getPatternModel(), methodName); 168 action.setSelected(initial); 169 return action; 170 } 171 172 /** 173 * creates, configures and returns a bound action to the given method of 174 * this. 175 * 176 * @param actionCommand the actionCommand, same as key to find localizable resources 177 * @param methodName the method to call an actionPerformed. 178 * @return newly created action 179 */ 180 protected AbstractActionExt createBoundAction(String actionCommand, String methodName) { 181 String actionName = getUIString(actionCommand); 182 BoundAction action = new BoundAction(actionName, 183 actionCommand); 184 action.registerCallback(this, methodName); 185 return action; 186 } 187 188 189 //---------------------- synch patternModel <--> components 190 191 /** 192 * called from listening to pattern property of PatternModel. 193 * 194 * This implementation calls match() if the model is in 195 * incremental state. 196 * 197 */ 198 protected void refreshPatternFromModel() { 199 if (getPatternModel().isIncremental()) { 200 match(); 201 } 202 } 203 204 205 /** 206 * returns the patternModel. Lazyly creates and registers a 207 * propertyChangeListener if null. 208 * 209 * @return current <code>PatternModel</code> if it exists or newly created 210 * one if it was not initialized before this call 211 */ 212 protected PatternModel getPatternModel() { 213 if (patternModel == null) { 214 patternModel = createPatternModel(); 215 patternModel.addPropertyChangeListener(getPatternModelListener()); 216 } 217 return patternModel; 218 } 219 220 221 /** 222 * factory method to create the PatternModel. 223 * Hook for subclasses to install custom models. 224 * 225 * @return newly created <code>PatternModel</code> 226 */ 227 protected PatternModel createPatternModel() { 228 PatternModel l = new PatternModel(); 229 return l; 230 } 231 232 /** 233 * creates and returns a PropertyChangeListener to the PatternModel. 234 * 235 * NOTE: the patternModel is totally under control of this class - currently 236 * there's no need to keep a reference to the listener. 237 * 238 * @return created and bound to appropriate callback methods 239 * <code>PropertyChangeListener</code> 240 */ 241 protected PropertyChangeListener getPatternModelListener() { 242 PropertyChangeListener l = new PropertyChangeListener() { 243 244 public void propertyChange(PropertyChangeEvent evt) { 245 String property = evt.getPropertyName(); 246 if ("pattern".equals(property)) { 247 refreshPatternFromModel(); 248 } else if ("rawText".equals(property)) { 249 refreshDocumentFromModel(); 250 } else if ("caseSensitive".equals(property)){ 251 getAction(PatternModel.MATCH_CASE_ACTION_COMMAND). 252 setSelected(((Boolean) evt.getNewValue()).booleanValue()); 253 } else if ("wrapping".equals(property)) { 254 getAction(PatternModel.MATCH_WRAP_ACTION_COMMAND). 255 setSelected(((Boolean) evt.getNewValue()).booleanValue()); 256 } else if ("backwards".equals(property)) { 257 getAction(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND). 258 setSelected(((Boolean) evt.getNewValue()).booleanValue()); 259 } else if ("incremental".equals(property)) { 260 getAction(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND). 261 setSelected(((Boolean) evt.getNewValue()).booleanValue()); 262 263 } else if ("empty".equals(property)) { 264 refreshEmptyFromModel(); 265 } 266 267 } 268 269 }; 270 return l; 271 } 272 273 /** 274 * called from listening to empty property of PatternModel. 275 * 276 * this implementation synch's the enabled state of the action with 277 * MATCH_ACTION_COMMAND to !empty. 278 * 279 */ 280 protected void refreshEmptyFromModel() { 281 boolean enabled = !getPatternModel().isEmpty(); 282 getAction(MATCH_ACTION_COMMAND).setEnabled(enabled); 283 284 } 285 286 /** 287 * callback method from listening to searchField. 288 * 289 */ 290 protected void refreshModelFromDocument() { 291 getPatternModel().setRawText(searchField.getText()); 292 } 293 294 /** 295 * callback method that updates document from the search field 296 * 297 */ 298 protected void refreshDocumentFromModel() { 299 if (searchField.getText().equals(getPatternModel().getRawText())) return; 300 SwingUtilities.invokeLater(new Runnable() { 301 public void run() { 302 searchField.setText(getPatternModel().getRawText()); 303 } 304 }); 305 } 306 307 /** 308 * Create <code>DocumentListener</code> for the search field that calls 309 * corresponding callback method whenever the search field contents is being changed 310 * 311 * @return newly created <code>DocumentListener</code> 312 */ 313 protected DocumentListener getSearchFieldListener() { 314 DocumentListener l = new DocumentListener() { 315 public void changedUpdate(DocumentEvent ev) { 316 // JW - really?? we've a PlainDoc without Attributes 317 refreshModelFromDocument(); 318 } 319 320 public void insertUpdate(DocumentEvent ev) { 321 refreshModelFromDocument(); 322 } 323 324 public void removeUpdate(DocumentEvent ev) { 325 refreshModelFromDocument(); 326 } 327 328 }; 329 return l; 330 } 331 332 //-------------------------- config helpers 333 334 /** 335 * configure and bind components to/from PatternModel 336 */ 337 protected void bind() { 338 bindSearchLabel(); 339 searchField.getDocument().addDocumentListener(getSearchFieldListener()); 340 getActionContainerFactory().configureButton(matchCheck, 341 (AbstractActionExt) getActionMap().get(PatternModel.MATCH_CASE_ACTION_COMMAND), 342 null); 343 344 } 345 346 /** 347 * Configures the searchLabel. 348 * Here: sets text and mnenomic properties form ui values, 349 * configures as label for searchField. 350 */ 351 protected void bindSearchLabel() { 352 searchLabel.setText(getUIString(SEARCH_FIELD_LABEL)); 353 String mnemonic = getUIString(SEARCH_FIELD_MNEMONIC); 354 if (mnemonic != SEARCH_FIELD_MNEMONIC) { 355 searchLabel.setDisplayedMnemonic(mnemonic.charAt(0)); 356 } 357 searchLabel.setLabelFor(searchField); 358 } 359 360 /** 361 * @return current <code>ActionContainerFactory</code>. 362 * Will lazily create new factory if it does not exist 363 */ 364 protected ActionContainerFactory getActionContainerFactory() { 365 if (actionFactory == null) { 366 actionFactory = new ActionContainerFactory(null); 367 } 368 return actionFactory; 369 } 370 371 /** 372 * Initialize all the incorporated components and models 373 */ 374 protected void initComponents() { 375 searchLabel = new JLabel(); 376 searchField = new JTextField(getSearchFieldWidth()) { 377 public Dimension getMaximumSize() { 378 Dimension superMax = super.getMaximumSize(); 379 superMax.height = getPreferredSize().height; 380 return superMax; 381 } 382 }; 383 matchCheck = new JCheckBox(); 384 } 385 386 /** 387 * @return width in characters of the search field 388 */ 389 protected int getSearchFieldWidth() { 390 return 15; 391 } 392 }