openesb-components/jbi-cdk/jbisupport/jbicomponentsupport/src/org/netbeans/modules/jbi/apisupport/xmlmv/ui/SectionInnerPanel.java

706 lines
26 KiB
Java
Executable File

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.jbi.apisupport.xmlmv.ui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.netbeans.modules.jbi.apisupport.xmlmv.Error;
import org.netbeans.modules.jbi.apisupport.xmlmv.Refreshable;
import org.netbeans.modules.jbi.apisupport.xmlmv.Utils;
import org.netbeans.modules.jbi.apisupport.xmlmv.cookies.ErrorLocator;
import org.netbeans.modules.jbi.apisupport.xmlmv.cookies.LinkCookie;
import org.openide.util.RequestProcessor;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Iterator;
import java.util.LinkedList;
/**
* This class represents a panel that is contained in <code>SectionPanel</code>,
* i.e. an inner panel as the name implies. This class provides support for registering
* UI components that modify the model by means of various <code>addModifier</code> and
* <code>addImmediateModifier</code> methods. See javadocs of those for
* more details.
*
* @author mkuchtiak
*/
public abstract class SectionInnerPanel extends javax.swing.JPanel implements LinkCookie, ErrorLocator {
private SectionView sectionView;
private java.util.List refreshableList = new LinkedList();
private boolean localFocusListenerInitialized = false;
private FocusListener localFocusListener = new FocusListener() {
public void focusGained(FocusEvent evt) {
final FocusListener[] focusListeners = getFocusListeners();
for (int i = 0; i < focusListeners.length; i++) {
focusListeners[i].focusGained(evt);
}
}
public void focusLost(FocusEvent evt) {
processFocusEvent(evt);
}
};
private RequestProcessor.Task refreshTask = RequestProcessor.getDefault().create(new Runnable() {
public void run() {
refreshView();
}
});
private static final int REFRESH_DELAY = 50;
private FlushFocusListener activeListener = null;
private boolean closing = false;
/** Constructor that takes the enclosing SectionView object as its argument
* @param sectionView enclosing SectionView object
*/
public SectionInnerPanel(SectionView sectionView) {
this.sectionView = sectionView;
}
public synchronized void addFocusListener(FocusListener l) {
super.addFocusListener(l);
if (!localFocusListenerInitialized) {
localFocusListenerInitialized = true;
Container container = this;
FocusListener focusListener = localFocusListener;
addFocusListenerRecursively(container, focusListener);
}
}
private void addFocusListenerRecursively(Container container, FocusListener focusListener) {
final Component[] components = container.getComponents();
for (int i = 0; i < components.length; i++) {
Component component = components[i];
if (component.isFocusable() && !(component instanceof JLabel)) {
component.addFocusListener(focusListener);
}
if (component instanceof Container) {
if (!(component instanceof SectionNodePanel)) {
addFocusListenerRecursively((Container) component, focusListener);
}
}
}
}
/** Getter for section view
* @return sectionView enclosing SectionView object
*/
public SectionView getSectionView() {
return sectionView;
}
/**
* Callback method called on focus lost event after the value was checked for correctness
* (in case <code>source</code> was registered using one of the <code>addModifier</code> methods
* of this class, if <code>addImmediateModifier</code> was used instead, this method
* gets called immediately when the state of <code>source</code> changes).
* @param source the last focused JComponent or whose state was changed.
* @param value the value that has been set (typed) in the component
*/
public abstract void setValue(JComponent source, Object value);
/** Callback method called on document change event. This is called for components that
* require just-in-time validation.
* @param source JTextComponent being actually edited
* @param value the actual value of the component
*/
public void documentChanged(javax.swing.text.JTextComponent source, String value) {
}
/** Callback method called on focus lost event after the value was checked for correctness.
* and the result is that the value is wrong. The value should be rollbacked from the model.
* @param source last focused JComponent
*/
public void rollbackValue(javax.swing.text.JTextComponent source) {
}
/** Adds text component to the set of JTextComponents that modifies the model.
* After the value in this component is changed the setValue() method is called.
* @param tc JTextComponent whose content is related to data model
*/
public final void addModifier(final JTextComponent tc) {
tc.addFocusListener(new ModifyFocusListener(tc));
}
/** Adds text component to the set of JTextComponents that modifies the model.
* After the value in this component is changed the setValue() method is called.
*
* @param tc JTextComponent whose content is related to data model
* @param test indicates whether the original value should be tested against the current value when focus is lost
*/
public final void addModifier(final JTextComponent tc, boolean test) {
tc.addFocusListener(new ModifyFocusListener(tc, test));
}
/**
* Adds combo box component to the set of JComboBoxes that modifies the model.
* After the value in this component is changed the setValue() method is called.
* @param comboBox JComboBox whose content is related to data model
*/
public final void addModifier(final JComboBox comboBox) {
comboBox.addFocusListener(new ComboBoxModifyFocusListener(comboBox));
}
/**
* Adds combo box component to the set of JComboBoxes that modifies the model.
* After the value in this component is changed the setValue() method is called.
*
* @param comboBox JComboBox whose content is related to data model
* @param test indicates whether the original value should be tested against the current value when focus is lost
*/
public final void addModifier(final JComboBox comboBox, boolean test) {
comboBox.addFocusListener(new ComboBoxModifyFocusListener(comboBox, test));
}
/**
* Adds radio button component to the set of JRadioButtons that modifies the model.
* After the value in this component is changed the setValue() method is called.
* @param radioButton JRadioButton whose content is related to data model
*/
public final void addModifier(final JRadioButton radioButton){
radioButton.addFocusListener(new RadioButtonModifyFocusListener(radioButton));
}
/**
* Adds check box component to the set of JCheckBox that modifies the model.
* After the value in this component is changed the setValue() method is called.
* @param checkBox JCheckBox whose content is related to data model
*/
public final void addModifier(final JCheckBox checkBox){
checkBox.addFocusListener(new CheckBoxModifyFocusListener(checkBox));
}
/**
* Adds check box component to the set of JCheckBoxes that modifies the model.
* After the value in this component is changed the setValue() method is called.
* Unlike <code>addModifier</code>, this method will cause <code>setValue()</code>
* to be called on each action event related to the given <code>checkBox</code>. Note
* that the same component should not be added both as a regular modifier and
* as an immediate modifier.
* @param checkBox JCheckBox whose content is related to data model
*/
public final void addImmediateModifier(final JCheckBox checkBox){
checkBox.addActionListener(new CheckBoxActionListener(checkBox));
}
/**
* Adds the given <code>radioButton</code> to the set of JRadioButtons that modify the model.
* After the value in this component is changed the setValue() method is called.
* Unlike <code>addModifier</code>, this method will cause <code>setValue()</code>
* to be called on each action event related to the given <code>checkBox</code>. Note
* that the same component should not be added both as a regular modifier and
* as an immediate modifier.
* @param radioButton the radio button whose content is related to the data model
*/
public final void addImmediateModifier(final JRadioButton radioButton){
radioButton.addActionListener(new RadioButtonActionListener(radioButton));
}
/**
* Adds combo box component to the set of JComboBoxes that modifies the model.
* After the value in this component is changed the setValue() method is called.
* Unlike <code>addModifier</code>, this method will cause <code>setValue()</code>
* to be called on each action event related to the given <code>comboBox</code>.Note
* that the same component should not be added both as a regular modifier and
* as an immediate modifier.
* @param comboBox JComboBox whose content is related to data model
*/
public final void addImmediateModifier(final JComboBox comboBox) {
comboBox.addActionListener(new ComboBoxActionListener(comboBox));
}
/** Adds text component to the set of JTextComponents that modifies the model.
* After the value in this component is changed the setValue() method is called.
* Unlike <code>addModifier</code>, this method will cause <code>setValue()</code>
* to be called on every change related to the given <code>tc</code>.Note
* that the same component should not be added both as a regular modifier and
* as an immediate modifier.
* @param tc JTextComponent whose content is related to data model
*/
public final void addImmediateModifier(final JTextComponent tc) {
tc.getDocument().addDocumentListener(new TextListener(tc, true));
}
/** Adds text component to the set of JTextComponents that should be validated for correctness.
* After the value in this component is changed either setValue() method is called(value is correct)
* or rollbackValue() method is called(value is incorrect). Also the documentChanged() method is called during editing.
* @param tc JTextComponent whose content is related to data model and should be validated before saving to data model.
*/
public final void addValidatee(final JTextComponent tc) {
tc.getDocument().addDocumentListener(new TextListener(tc));
tc.addFocusListener(new ValidateFocusListener(tc));
}
protected void scheduleRefreshView() {
refreshTask.schedule(REFRESH_DELAY);
}
/**
* Reloads data from data model
*/
public void refreshView() {
for (Iterator it = refreshableList.iterator(); it.hasNext();) {
((Refreshable) it.next()).refresh();
}
}
protected void addRefreshable(Refreshable refreshable) {
refreshableList.add(refreshable);
}
public void dataModelPropertyChange(Object source, String propertyName, Object oldValue, Object newValue) {
scheduleRefreshView();
}
private class TextListener implements javax.swing.event.DocumentListener {
private JTextComponent tc;
private boolean setValue = false;
TextListener(JTextComponent tc) {
this(tc, false);
}
TextListener(JTextComponent tc, boolean setValue) {
this.tc = tc;
this.setValue = setValue;
}
/**
* Method from DocumentListener
*/
public void changedUpdate(javax.swing.event.DocumentEvent evt) {
update(evt);
}
/**
* Method from DocumentListener
*/
public void insertUpdate(javax.swing.event.DocumentEvent evt) {
update(evt);
}
/**
* Method from DocumentListener
*/
public void removeUpdate(javax.swing.event.DocumentEvent evt) {
update(evt);
}
private void update(javax.swing.event.DocumentEvent evt) {
if (setValue){
startUIChange();
setValue(tc, tc.getText());
signalUIChange();
endUIChange();
} else {
documentChanged(tc, tc.getText());
}
}
}
private abstract class FlushFocusListener extends java.awt.event.FocusAdapter {
public abstract boolean flushData();
}
private class ValidateFocusListener extends FlushFocusListener {
private String orgValue;
private boolean viewIsBuggy;
private final JTextComponent tc;
/**
* Prevents 'fix now' dialog from popping up multiple times.
*/
private boolean disable;
public ValidateFocusListener(JTextComponent tc) {
this.tc = tc;
}
public void focusGained(FocusEvent evt) {
activeListener = this;
orgValue = tc.getText();
if (sectionView.getErrorPanel().getError() != null) {
viewIsBuggy = true;
} else {
viewIsBuggy = false;
}
}
public void focusLost(FocusEvent evt) {
if (!closing) {
if (!flushData()) {
Utils.runInAwtDispatchThread(new Runnable() {
public void run() {
//todo: make sure the panel is visible
tc.requestFocus();
}
});
} else {
disable = false;
activeListener = null;
}
}
}
public boolean flushData() {
Error error = sectionView.getErrorPanel().getError();
if (error != null && error.isEditError() && tc == error.getFocusableComponent()) {
if (Error.TYPE_WARNING == error.getSeverityLevel() && !disable) {
org.openide.DialogDescriptor desc = new RefreshSaveDialog(sectionView.getErrorPanel());
Dialog dialog = org.openide.DialogDisplayer.getDefault().createDialog(desc);
dialog.show();
Integer opt = (Integer) desc.getValue();
if (opt.equals(RefreshSaveDialog.OPTION_FIX)) {
disable = true;
return false;
} else if (opt.equals(RefreshSaveDialog.OPTION_REFRESH)) {
rollbackValue(tc);
sectionView.checkValidity();
} else {
startUIChange();
setValue(tc, tc.getText());
signalUIChange();
endUIChange();
sectionView.checkValidity();
}
} else if (!disable){
org.openide.DialogDescriptor desc = new RefreshDialog(sectionView.getErrorPanel());
Dialog dialog = org.openide.DialogDisplayer.getDefault().createDialog(desc);
dialog.show();
Integer opt = (Integer) desc.getValue();
if (opt.equals(RefreshDialog.OPTION_FIX)) {
disable = true;
return false;
} else if (opt.equals(RefreshDialog.OPTION_REFRESH)) {
rollbackValue(tc);
sectionView.checkValidity();
}
}
} else {
if (!tc.getText().equals(orgValue)) {
startUIChange();
setValue(tc, tc.getText());
signalUIChange();
endUIChange();
sectionView.checkValidity();
} else {
if (viewIsBuggy) {
sectionView.checkValidity();
}
}
}
disable = false;
return true;
}
}
private class ModifyFocusListener extends FlushFocusListener {
private String orgValue;
private final JTextComponent tc;
// indicates whether the original value (orgValue) should be tested against the current value when focus is lost
private boolean test;
public ModifyFocusListener(JTextComponent tc) {
this(tc, true);
}
public ModifyFocusListener(JTextComponent tc, boolean test) {
this.tc = tc;
this.test = test;
}
public void focusGained(FocusEvent evt) {
orgValue = tc.getText();
activeListener = this;
}
public void focusLost(FocusEvent evt) {
if (!closing) {
flushData();
activeListener = null;
}
}
public boolean flushData() {
if (!test || !tc.getText().equals(orgValue)) {
startUIChange();
setValue(tc, tc.getText());
signalUIChange();
endUIChange();
}
return true;
}
}
/**
* Listener attached to combo boxes that modify the model.
*/
private class ComboBoxModifyFocusListener extends FlushFocusListener {
private Object orgValue;
private final JComboBox comboBox;
// indicates whether the original value (orgValue) should be tested against the current value when focus is lost
private boolean test;
public ComboBoxModifyFocusListener(JComboBox comboBox) {
this(comboBox, true);
}
public ComboBoxModifyFocusListener(JComboBox comboBox, boolean test) {
this.comboBox = comboBox;
this.test = test;
}
public void focusGained(FocusEvent evt) {
orgValue = comboBox.getSelectedItem();
activeListener = this;
}
public void focusLost(FocusEvent evt) {
if (!closing) {
flushData();
activeListener = null;
}
}
public boolean flushData() {
Object newValue = comboBox.getSelectedItem();
boolean newEqualsOld = (newValue==null ? orgValue==null : newValue.equals(orgValue));
if (!test || !newEqualsOld) {
startUIChange();
setValue(comboBox, comboBox.getSelectedItem());
signalUIChange();
endUIChange();
}
return true;
}
}
/**
* Listener attached to radio buttons that modify the model.
*/
private class RadioButtonModifyFocusListener extends FlushFocusListener {
private boolean orgValue;
private final JRadioButton radioButton;
public RadioButtonModifyFocusListener(JRadioButton radioButton) {
this.radioButton = radioButton;
}
public void focusGained(FocusEvent evt) {
orgValue = radioButton.isSelected();
activeListener = this;
}
public void focusLost(FocusEvent evt) {
if (!closing) {
flushData();
activeListener = null;
}
}
public boolean flushData() {
if (!(radioButton.isSelected() == orgValue)) {
startUIChange();
setValue(radioButton, Boolean.valueOf(radioButton.isSelected()));
signalUIChange();
endUIChange();
}
return true;
}
}
public boolean canClose() {
closing = true;
try {
if (activeListener != null) {
return activeListener.flushData();
}
return true;
} finally {
closing = false;
}
}
/**
* Base class for action listeners for components that require setValue to be called
* immediately on action event (instead of when focus is gained/lost).
*/
private abstract class FlushActionListener implements ActionListener {
public final void actionPerformed(ActionEvent e) {
startUIChange();
doSetValue(e);
signalUIChange();
endUIChange();
}
/**
* Does the actual setting of the value to the model. Subclasses
* should usually invoke <code>setValue</code> method here.
*/
public abstract void doSetValue(ActionEvent e);
}
/**
* Action listener for combo boxes that require setValue to be called
* immediately on action event (instead of when focus is gained/lost).
*/
private class ComboBoxActionListener extends FlushActionListener{
private final JComboBox comboBox;
public ComboBoxActionListener(JComboBox comboBox){
this.comboBox = comboBox;
}
public void doSetValue(ActionEvent e) {
setValue(comboBox, comboBox.getSelectedItem());
}
}
/**
* Action listener for checkboxes that require setValue to be called
* immediately on action event (instead of when focus is gained/lost).
*/
private class CheckBoxActionListener extends FlushActionListener{
private final JCheckBox checkBox;
public CheckBoxActionListener(JCheckBox checkBox){
this.checkBox = checkBox;
}
public void doSetValue(ActionEvent e) {
setValue(checkBox, Boolean.valueOf(checkBox.isSelected()));
}
}
/**
* Action listener for radio buttons that require setValue to be called
* immediately on action event (instead of when focus is gained/lost).
*/
private class RadioButtonActionListener extends FlushActionListener{
private final JRadioButton radioButton;
public RadioButtonActionListener(JRadioButton radioButton){
this.radioButton = radioButton;
}
public void doSetValue(ActionEvent e) {
setValue(radioButton, Boolean.valueOf(radioButton.isSelected()));
}
}
/**
* Listener attached to check boxes that modify the model.
*/
private class CheckBoxModifyFocusListener extends FlushFocusListener {
private boolean orgValue;
private final JCheckBox checkBox;
public CheckBoxModifyFocusListener(JCheckBox checkBox) {
this.checkBox = checkBox;
}
public void focusGained(FocusEvent evt) {
orgValue = checkBox.isSelected();
activeListener = this;
}
public void focusLost(FocusEvent evt) {
if (!closing) {
flushData();
activeListener = null;
}
}
public boolean flushData() {
if (!(checkBox.isSelected() == orgValue)) {
startUIChange();
setValue(checkBox, Boolean.valueOf(checkBox.isSelected()));
signalUIChange();
endUIChange();
}
return true;
}
public boolean canClose() {
closing = true;
try {
if (activeListener != null) {
return activeListener.flushData();
}
return true;
} finally {
closing = false;
}
}
}
/** This will be called after model is changed from this panel
* @deprecated use {@link SectionInnerPanel#endUIChange} instead
*/
protected void signalUIChange() {
}
/** This will be called before model is changed from this panel
*/
protected void startUIChange() {
}
/** This will be called after model is changed from this panel
*/
protected void endUIChange() {
}
}