
com.globalmentor.swing.ModifiablePanel Maven / Gradle / Ivy
Show all versions of globalmentor-swing Show documentation
/*
* Copyright © 1996-2009 GlobalMentor, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.globalmentor.swing;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.event.*;
import com.globalmentor.awt.*;
import com.globalmentor.java.*;
import com.globalmentor.model.Modifiable;
import com.globalmentor.swing.event.*;
/**
* A panel that can be modified, and recognized when its contained components have been modified.
*
* The panel can keep track of whether its contents have been modified.
*
*
* If the {@link Modifiable#MODIFIED_PROPERTY}is set to false
, all all descendant components that implement {@link Modifiable} have their modified
* properties set to false
as well.
*
*
* Whenever a component is added that is {@link Modifiable} or has {@link Modifiable} children, listeners are installed that will change this panel's modified
* status to true
when one of those children are modified. (Setting the modification status of those children to false
will have no
* effect.) Similarly, recognized child components such as text components, radio buttons, and checkboxes, along with children of added non-{@link Modifiable}
* components, will have listeners installed that change this panel's modification status to true
in response to modifications in those components.
*
*
* Bound properties:
*
*
* - {@link Modifiable#MODIFIED_PROPERTY} (
Boolean
)
* - Indicates that the boolean modified property has been changed.
*
* @author Garret Wilson
* @see PropertyChangeListener
*/
public class ModifiablePanel extends BasicPanel implements Modifiable {
/** Whether the object has been modified; the default is not modified. */
private boolean modified = false;
/** @return Whether the object been modified. */
public boolean isModified() {
return modified;
}
/**
* Sets whether the object has been modified. This is a bound property.
* @param newModified The new modification status.
*/
public void setModified(final boolean newModified) {
final boolean oldModified = modified; //get the old modified value
if(oldModified != newModified) { //if the value is really changing
modified = newModified; //update the value
//show that the modified property has changed
firePropertyChange(MODIFIED_PROPERTY, Boolean.valueOf(oldModified), Boolean.valueOf(newModified));
if(!modified) { //if we're now not modified
Containers.setModifiableDescendants(this, false); //tell all of our child components that they are not modified, either
}
}
}
/** The lazily-created listener that modifies this panel when an action occurs. */
private ActionListener modifyActionListener;
/** @return The lazily-created listener that modifies this panel when an action occurs. */
protected ActionListener getModifyActionListener() {
if(modifyActionListener == null) { //if we haven't created the listener, yet
modifyActionListener = createModifyActionListener(); //create the listener
}
return modifyActionListener; //return the listener
}
/** The lazily-created listener that modifies this panel when a document is modified. */
private DocumentListener modifyDocumentListener;
/** @return The lazily-created listener that modifies this panel when a document is modified. */
protected DocumentListener getModifyDocumentListener() {
if(modifyDocumentListener == null) { //if we haven't created the listener, yet
modifyDocumentListener = createModifyDocumentListener(); //create the listener
}
return modifyDocumentListener; //return the listener
}
/** The lazily-created listener that modifies this panel when an item state changes. */
private ItemListener modifyItemListener;
/** @return The lazily-created listener that modifies this panel when an item state changes. */
protected ItemListener getModifyItemListener() {
if(modifyItemListener == null) { //if we haven't created the listener, yet
modifyItemListener = createModifyItemListener(); //create the listener
}
return modifyItemListener; //return the listener
}
/** The lazily-created listener that modifies this panel when a list selection changes. */
private ListSelectionListener modifyListSelectionListener;
/** @return The lazily-created listener that modifies this panel when a list selection. */
protected ListSelectionListener getModifyListSelectionListener() {
if(modifyListSelectionListener == null) { //if we haven't created the listener, yet
modifyListSelectionListener = createModifyListSelectionListener(); //create the listener
}
return modifyListSelectionListener; //return the listener
}
/**
* The lazily-created listener that modifies this panel when a Modifiable
is modified.
*/
private PropertyChangeListener modifyModifiedPropertyChangeListener;
/**
* @return The lazily-created listener that modifies this panel when a Modifiable
is modified.
*/
protected PropertyChangeListener getModifyModifiedPropertyChangeListener() {
if(modifyModifiedPropertyChangeListener == null) { //if we haven't created the listener, yet
modifyModifiedPropertyChangeListener = createModifyModifiedChangeListener(); //create the listener
}
return modifyModifiedPropertyChangeListener; //return the listener
}
/**
* Default constructor that uses a FlowLayout
.
* @see #FlowLayout
*/
public ModifiablePanel() {
this(true); //initialize the panel
}
/**
* Constructor with optional initialization that uses a FlowLayout
.
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
* @see #FlowLayout
*/
public ModifiablePanel(final boolean initialize) {
this(new FlowLayout(), initialize); //construct the panel with a flow layout by default
}
/**
* Layout constructor.
* @param layout The layout manager to use.
*/
public ModifiablePanel(final LayoutManager layout) {
this(layout, true); //construct the class with the layout, initializing the panel
}
/**
* Layout constructor with optional initialization.
* @param layout The layout manager to use.
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
*/
public ModifiablePanel(final LayoutManager layout, final boolean initialize) {
super(layout, false); //construct the parent class but don't initialize
modifyDocumentListener = null;
modifyItemListener = null;
modifyListSelectionListener = null;
modifyModifiedPropertyChangeListener = null;
if(initialize) //if we should initialize
initialize(); //initialize the panel
}
/** Initializes the panel. Should only be called once per instance. */
public void initialize() { //TODO set a flag that will only allow initialization once per instance
super.initialize(); //do the default initialization
setModified(false); //show that the information has not been modified
updateStatus(); //update the status again, because it might have been updated while we were modified TODO this means updateStatus() is called twice---try to get around that, or maybe even place updateStatus() in setModified()
}
/**
* Adds the specified component to this container at the specified index.
*
* This version adds appropriate listeners to added components so that this panel's modified status will be appropriately update when descendant components
* are modified.
*
* @param component The component to be added.
* @param constraints An object expressing layout constraints for this component.
* @param index The position in the container's list at which to insert the component, where -1
means append to the end.
* @throws IllegalArgumentException Thrown if index
is invalid.
* @throws IllegalArgumentException Thrown if adding the container's parent to itself.
* @throws IllegalArgumentException Thrown if adding a window to a container.
* @see #addModifyListeners(Component)
*/
protected void addImpl(final Component component, final Object constraints, final int index) {
super.addImpl(component, constraints, index); //do the default adding
addModifyListeners(component); //add listeners to modify this panel in response to changes in this component
}
/**
* Removes the component, specified by index
, from this container.
*
* Any listeners that were added are removed.
*
* @param index The index of the component to be removed.
* @see #removeModifyListeners(Component)
*/
public void remove(final int index) {
final Component component = getComponent(index); //get a reference to the component to remove
super.remove(index); //remove the component
removeModifyListeners(component); //remove listeners from the component if everything succeeded
}
/**
* Removes the specified component from this container.
*
* Any listeners that were added are removed.
*
* @param component The component to be removed.
* @see #removeModifyListeners(Component)
*/
public void remove(final Component component) {
super.remove(component); //remove the component
removeModifyListeners(component); //remove our listeners
}
/**
* Removes all the components from this container.
*/
public void removeAll() {
final Component[] components = getComponents(); //get all child components
super.removeAll(); //remove all the components from the panel
for(int i = components.length - 1; i >= 0; --i) { //look at each child component
removeModifyListeners(components[i]); //remove our listeners from this child component that is now removed
}
}
/**
* Adds appropriate listeners to this component or its descendants that will update this panel's modified status to true
when descendant
* components are modified.
*
* If the component or any of its descendants is Modifiable
, a listener is added that changes this panel's modified status to true
* if the Modifiable
's modified status is changed to true
.
*
*
* If the component is not Modifiable
, any descendants of certain recognized types, such as text components and checkboxes, will have listeners
* registered that will change the modified status of this panel to true
when those components are modified.
*
* @param component The component to which listeners will be added, along with appropriate descendants.
* @see #removeModifyListeners(Component)
*/
protected void addModifyListeners(final Component component) {
if(component instanceof Modifiable) { //if the component is modifiable
component.addPropertyChangeListener(getModifyModifiedPropertyChangeListener()); //listen for modifications and modify this panel in response
}
/*TODO fix
else if(component instanceof JTextComponent) { //if the component is a text component
((JTextComponent)component).getD
}
*/
else if(component instanceof Container) { //if this component is a container
final Component[] components = ((Container)component).getComponents(); //get all child components
for(int i = components.length - 1; i >= 0; --i) { //look at each child component
addModifyListeners(components[i]); //see if we need to add listeners to this child component
}
}
}
/**
* Removes all modify listeners that were added within addModifyListeners()
.
* @param component The component from which listeners will be removed, along with appropriate descendants.
* @see #addModifyListeners(Component)
*/
private void removeModifyListeners(final Component component) {
if(component instanceof Modifiable) { //if the component is modifiable
component.removePropertyChangeListener(getModifyModifiedPropertyChangeListener()); //stop listening for modifications
} else if(component instanceof Container) { //if this component is a container
final Component[] components = ((Container)component).getComponents(); //get all child components
for(int i = components.length - 1; i >= 0; --i) { //look at each child component
removeModifyListeners(components[i]); //see if we need to remove listeners from this child component
}
}
}
/**
* Creates an action listener that, when an action occurs, updates the modified status to true
.
* @see #setModified
*/
private ActionListener createModifyActionListener() {
return new ActionListener() { //create a new action listener that will do nothing but set modified to true
public void actionPerformed(final ActionEvent actionEvent) {
setModified(true);
} //if the action occurs, show that we've been modified
};
}
/**
* Creates a document listener that, when a document is modified, updates the modified status to true
.
* @see #setModified
*/
private DocumentListener createModifyDocumentListener() {
return new DocumentModifyAdapter() { //create a new document listener that will do nothing but set modified to true
public void modifyUpdate(final DocumentEvent documentEvent) {
setModified(true);
} //if the document is modified, show that we've been modfied
};
}
/**
* Creates an item listener that, when an item state changes, updates the modified status to true
.
* @see #setModified
*/
public ItemListener createModifyItemListener() {
return new ItemListener() { //create a new item listener that will do nothing but set modified to true
public void itemStateChanged(final ItemEvent itemEvent) {
setModified(true);
} //if an item state changes, show that we've been modifiefd
};
}
/**
* Creates a list selection listener that, when the list selection changes, updates the modified status to true
.
* @see #setModified
*/
private ListSelectionListener createModifyListSelectionListener() {
return new ListSelectionListener() { //create a new list selection listener that will do nothing but set modified to true
public void valueChanged(final ListSelectionEvent listSelectionEvent) {
setModified(true);
} //if the list selection changes, show that we've been modified
};
}
/**
* Creates a property change listener that, when any property changes, updates the modified status to true
.
* @see #setModified
*/
/*TODO bring back
public PropertyChangeListener createModifyPropertyChangeListener()
{
return new PropertyChangeListener() { //create a new property change listener that will do nothing but set modified to true
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) {setModified(true);} //if a property is modified, show that we've been modified
};
}
*/
/**
* Creates a property change listener that, when the given property changes, updates the modified status to true
.
* @param propertyName The name of the property that, when changed, will set the modified status to true
.
* @see #setModified
*/
protected PropertyChangeListener createModifyPropertyChangeListener(final String propertyName) {
return new PropertyChangeListener() { //create a new property change listener that will do nothing but set modified to true
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) { //if a property is modified
if(propertyName.equals(propertyChangeEvent.getPropertyName())) { //if the property we're concerned about changed
setModified(true); //show that we've been modified
}
}
};
}
/**
* Creates a property change listener that, when the given property changes to the given value, updates the modified status to true
.
* @param propertyName The name of the property that, when changed, will set the modified status to true
.
* @param propertyValue The value of the property that, when equal to the new value, will set the modified status to true
.
* @see #setModified
*/
private PropertyChangeListener createModifyPropertyChangeListener(final String propertyName, final Object propertyValue) {
return new PropertyChangeListener() { //create a new property change listener that will do nothing but set modified to true
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) { //if a property is modified
if(propertyName.equals(propertyChangeEvent.getPropertyName()) //if the property we're concerned about changed
&& Objects.equals(propertyValue, propertyChangeEvent.getNewValue())) {
setModified(true); //show that we've been modified
}
}
};
}
/**
* Creates a property change listener that, when the the "modified" property changes to true
, updates the modified status to true
.
* Convenience method.
* @see Modifiable#MODIFIED_PROPERTY
* @see Boolean#TRUE
* @see #setModified
*/
private PropertyChangeListener createModifyModifiedChangeListener() {
return createModifyPropertyChangeListener(MODIFIED_PROPERTY, Boolean.TRUE); //create a property change listener that will set modified to true if the modified property changes to true
}
/**
* Creates an item that, when an item is modified, updates the modified status to true
.
* @see #setModified
*/
/*TODO fix or del
protected DocumentListener createModifyDocumentListener()
{
return new DocumentModifyAdapter() { //create a new document listener that will do nothing but update the status
public void modifyUpdate(final DocumentEvent documentEvent) {setModified(true);} //if the document is modified, show that we've been modfied
};
}
*/
}