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 }