/* * $Id: AbstractPatternPanel.java 2948 2008-06-16 15:02:14Z kleopatra $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.hdesktop.swingx; import java.awt.Dimension; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Locale; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.hdesktop.swingx.action.AbstractActionExt; import org.hdesktop.swingx.action.ActionContainerFactory; import org.hdesktop.swingx.action.BoundAction; import org.hdesktop.swingx.plaf.LookAndFeelAddons; import org.hdesktop.swingx.plaf.UIManagerExt; import org.hdesktop.swingx.search.PatternModel; /** * Common base class of ui clients. * * Implements basic synchronization between PatternModel state and * actions bound to it. * * * * PENDING: extending JXPanel is a convenience measure, should be extracted * into a dedicated controller. * PENDING: should be re-visited when swingx goes binding-aware * * @author Jeanette Winzenburg */ public abstract class AbstractPatternPanel extends JXPanel { public static final String SEARCH_FIELD_LABEL = "searchFieldLabel"; public static final String SEARCH_FIELD_MNEMONIC = SEARCH_FIELD_LABEL + ".mnemonic"; public static final String SEARCH_TITLE = "searchTitle"; public static final String MATCH_ACTION_COMMAND = "match"; static { // Hack to enforce loading of SwingX framework ResourceBundle LookAndFeelAddons.getAddon(); } protected JLabel searchLabel; protected JTextField searchField; protected JCheckBox matchCheck; protected PatternModel patternModel; private ActionContainerFactory actionFactory; //------------------------ actions /** * Callback action bound to MATCH_ACTION_COMMAND. */ public abstract void match(); /** * convenience method for type-cast to AbstractActionExt. * * @param key Key to retrieve action * @return Action bound to this key * @see AbstractActionExt */ protected AbstractActionExt getAction(String key) { // PENDING: outside clients might add different types? return (AbstractActionExt) getActionMap().get(key); } /** * creates and registers all actions for the default the actionMap. */ protected void initActions() { initPatternActions(); initExecutables(); } /** * creates and registers all "executable" actions. * Meaning: the actions bound to a callback method on this. * * PENDING: not quite correctly factored? Name? * */ protected void initExecutables() { Action execute = createBoundAction(MATCH_ACTION_COMMAND, "match"); getActionMap().put(JXDialog.EXECUTE_ACTION_COMMAND, execute); getActionMap().put(MATCH_ACTION_COMMAND, execute); refreshEmptyFromModel(); } /** * creates actions bound to PatternModel's state. */ protected void initPatternActions() { ActionMap map = getActionMap(); map.put(PatternModel.MATCH_CASE_ACTION_COMMAND, createModelStateAction(PatternModel.MATCH_CASE_ACTION_COMMAND, "setCaseSensitive", getPatternModel().isCaseSensitive())); map.put(PatternModel.MATCH_WRAP_ACTION_COMMAND, createModelStateAction(PatternModel.MATCH_WRAP_ACTION_COMMAND, "setWrapping", getPatternModel().isWrapping())); map.put(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND, createModelStateAction(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND, "setBackwards", getPatternModel().isBackwards())); map.put(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND, createModelStateAction(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND, "setIncremental", getPatternModel().isIncremental())); } /** * Returns a potentially localized value from the UIManager. The given key * is prefixed by this component|s <code>UIPREFIX</code> before doing the * lookup. The lookup respects this table's current <code>locale</code> * property. Returns the key, if no value is found. * * @param key the bare key to look up in the UIManager. * @return the value mapped to UIPREFIX + key or key if no value is found. */ protected String getUIString(String key) { return getUIString(key, getLocale()); } /** * Returns a potentially localized value from the UIManager for the * given locale. The given key * is prefixed by this component's <code>UIPREFIX</code> before doing the * lookup. Returns the key, if no value is found. * * @param key the bare key to look up in the UIManager. * @param locale the locale use for lookup * @return the value mapped to UIPREFIX + key in the given locale, * or key if no value is found. */ protected String getUIString(String key, Locale locale) { String text = UIManagerExt.getString(PatternModel.SEARCH_PREFIX + key, locale); return text != null ? text : key; } /** * creates, configures and returns a bound state action on a boolean property * of the PatternModel. * * @param command the actionCommand - same as key to find localizable resources * @param methodName the method on the PatternModel to call on item state changed * @param initial the initial value of the property * @return newly created action */ protected AbstractActionExt createModelStateAction(String command, String methodName, boolean initial) { String actionName = getUIString(command); BoundAction action = new BoundAction(actionName, command); action.setStateAction(); action.registerCallback(getPatternModel(), methodName); action.setSelected(initial); return action; } /** * creates, configures and returns a bound action to the given method of * this. * * @param actionCommand the actionCommand, same as key to find localizable resources * @param methodName the method to call an actionPerformed. * @return newly created action */ protected AbstractActionExt createBoundAction(String actionCommand, String methodName) { String actionName = getUIString(actionCommand); BoundAction action = new BoundAction(actionName, actionCommand); action.registerCallback(this, methodName); return action; } //------------------------ dynamic locale support /** * {@inheritDoc} <p> * Overridden to update locale-dependent properties. * * @see #updateLocaleState(Locale) */ @Override public void setLocale(Locale l) { updateLocaleState(l); super.setLocale(l); } /** * Updates locale-dependent state. * * Here: updates registered column actions' locale-dependent state. * <p> * * PENDING: Try better to find all column actions including custom * additions? Or move to columnControl? * * @see #setLocale(Locale) */ protected void updateLocaleState(Locale locale) { for (Object key : getActionMap().allKeys()) { if (key instanceof String) { String keyString = getUIString((String) key, locale); if (!key.equals(keyString)) { getActionMap().get(key).putValue(Action.NAME, keyString); } } } bindSearchLabel(locale); } //---------------------- synch patternModel <--> components /** * called from listening to pattern property of PatternModel. * * This implementation calls match() if the model is in * incremental state. * */ protected void refreshPatternFromModel() { if (getPatternModel().isIncremental()) { match(); } } /** * returns the patternModel. Lazyly creates and registers a * propertyChangeListener if null. * * @return current <code>PatternModel</code> if it exists or newly created * one if it was not initialized before this call */ protected PatternModel getPatternModel() { if (patternModel == null) { patternModel = createPatternModel(); patternModel.addPropertyChangeListener(getPatternModelListener()); } return patternModel; } /** * factory method to create the PatternModel. * Hook for subclasses to install custom models. * * @return newly created <code>PatternModel</code> */ protected PatternModel createPatternModel() { return new PatternModel(); } /** * creates and returns a PropertyChangeListener to the PatternModel. * * NOTE: the patternModel is totally under control of this class - currently * there's no need to keep a reference to the listener. * * @return created and bound to appropriate callback methods * <code>PropertyChangeListener</code> */ protected PropertyChangeListener getPatternModelListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { String property = evt.getPropertyName(); if ("pattern".equals(property)) { refreshPatternFromModel(); } else if ("rawText".equals(property)) { refreshDocumentFromModel(); } else if ("caseSensitive".equals(property)){ getAction(PatternModel.MATCH_CASE_ACTION_COMMAND). setSelected(((Boolean) evt.getNewValue()).booleanValue()); } else if ("wrapping".equals(property)) { getAction(PatternModel.MATCH_WRAP_ACTION_COMMAND). setSelected(((Boolean) evt.getNewValue()).booleanValue()); } else if ("backwards".equals(property)) { getAction(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND). setSelected(((Boolean) evt.getNewValue()).booleanValue()); } else if ("incremental".equals(property)) { getAction(PatternModel.MATCH_INCREMENTAL_ACTION_COMMAND). setSelected(((Boolean) evt.getNewValue()).booleanValue()); } else if ("empty".equals(property)) { refreshEmptyFromModel(); } } }; } /** * called from listening to empty property of PatternModel. * * this implementation synch's the enabled state of the action with * MATCH_ACTION_COMMAND to !empty. * */ protected void refreshEmptyFromModel() { boolean enabled = !getPatternModel().isEmpty(); getAction(MATCH_ACTION_COMMAND).setEnabled(enabled); } /** * callback method from listening to searchField. * */ protected void refreshModelFromDocument() { getPatternModel().setRawText(searchField.getText()); } /** * callback method that updates document from the search field * */ protected void refreshDocumentFromModel() { if (searchField.getText().equals(getPatternModel().getRawText())) return; SwingUtilities.invokeLater(new Runnable() { public void run() { searchField.setText(getPatternModel().getRawText()); } }); } /** * Create <code>DocumentListener</code> for the search field that calls * corresponding callback method whenever the search field contents is being changed * * @return newly created <code>DocumentListener</code> */ protected DocumentListener getSearchFieldListener() { return new DocumentListener() { public void changedUpdate(DocumentEvent ev) { // JW - really?? we've a PlainDoc without Attributes refreshModelFromDocument(); } public void insertUpdate(DocumentEvent ev) { refreshModelFromDocument(); } public void removeUpdate(DocumentEvent ev) { refreshModelFromDocument(); } }; } //-------------------------- config helpers /** * configure and bind components to/from PatternModel */ protected void bind() { bindSearchLabel(getLocale()); searchField.getDocument().addDocumentListener(getSearchFieldListener()); getActionContainerFactory().configureButton(matchCheck, (AbstractActionExt) getActionMap().get(PatternModel.MATCH_CASE_ACTION_COMMAND), null); } /** * Configures the searchLabel. * Here: sets text and mnenomic properties form ui values, * configures as label for searchField. */ protected void bindSearchLabel(Locale locale) { searchLabel.setText(getUIString(SEARCH_FIELD_LABEL, locale)); String mnemonic = getUIString(SEARCH_FIELD_MNEMONIC, locale); if (mnemonic != SEARCH_FIELD_MNEMONIC) { searchLabel.setDisplayedMnemonic(mnemonic.charAt(0)); } searchLabel.setLabelFor(searchField); } /** * @return current <code>ActionContainerFactory</code>. * Will lazily create new factory if it does not exist */ protected ActionContainerFactory getActionContainerFactory() { if (actionFactory == null) { actionFactory = new ActionContainerFactory(null); } return actionFactory; } /** * Initialize all the incorporated components and models */ protected void initComponents() { searchLabel = new JLabel(); searchField = new JTextField(getSearchFieldWidth()) { @Override public Dimension getMaximumSize() { Dimension superMax = super.getMaximumSize(); superMax.height = getPreferredSize().height; return superMax; } }; matchCheck = new JCheckBox(); } /** * @return width in characters of the search field */ protected int getSearchFieldWidth() { return 15; } }