/* * 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 SectionPanel, * 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 addModifier and * addImmediateModifier 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 source was registered using one of the addModifier methods * of this class, if addImmediateModifier was used instead, this method * gets called immediately when the state of source 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 addModifier, this method will cause setValue() * to be called on each action event related to the given checkBox. 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 radioButton to the set of JRadioButtons that modify the model. * After the value in this component is changed the setValue() method is called. * Unlike addModifier, this method will cause setValue() * to be called on each action event related to the given checkBox. 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 addModifier, this method will cause setValue() * to be called on each action event related to the given comboBox.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 addModifier, this method will cause setValue() * to be called on every change related to the given tc.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 setValue 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() { } }