org.jdesktop.swingx.AbstractPatternPanel Maven / Gradle / Ivy
/*
* $Id: AbstractPatternPanel.java 3927 2011-02-22 16:34:11Z 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.jdesktop.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.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.action.ActionContainerFactory;
import org.jdesktop.swingx.action.BoundAction;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.UIManagerExt;
import org.jdesktop.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 UIPREFIX
before doing the
* lookup. The lookup respects this table's current locale
* 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 UIPREFIX
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}
* 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.
*
*
* 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 PatternModel
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 PatternModel
*/
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
* PropertyChangeListener
*/
protected PropertyChangeListener getPatternModelListener() {
return new PropertyChangeListener() {
@Override
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() {
@Override
public void run() {
searchField.setText(getPatternModel().getRawText());
}
});
}
/**
* Create DocumentListener
for the search field that calls
* corresponding callback method whenever the search field contents is being changed
*
* @return newly created DocumentListener
*/
protected DocumentListener getSearchFieldListener() {
return new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent ev) {
// JW - really?? we've a PlainDoc without Attributes
refreshModelFromDocument();
}
@Override
public void insertUpdate(DocumentEvent ev) {
refreshModelFromDocument();
}
@Override
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 ActionContainerFactory
.
* 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;
}
}