/* * GlobalSettingView.java * * Copyright (c) 2003-2004 Digi International * This program and the information contained in it is confidential and * proprietary to Digi International and may not be used, copied, or re- * produced without the prior written permission of Digi International. * */ package com.digi.config.ui; import com.digi.config.core.*; import com.digi.config.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.*; import java.net.*; /** * This class is a view panel that will show all fields in the current settings cluster of the device. * It also lets the user select one of the fields and then edit its value. The selected field and its * editable value will be shown at the bottom of the panel. * * This view can be embedded in other panels as desired. */ public class GlobalSettingView extends ConfigViewImpl { /** Label identifying this view */ JLabel heading; /** Panel that holds the tree and the edit panels */ JPanel encompassingPanel; /** The JTree that shows the setting Cluster hierarchy */ JTree globalKvpFieldTree; /** The data model that maps the setting Cluster data into a JTree model */ KvpNodeTreeModel kvpFieldTreeModel; /** The subpanel that holds the editable Field components */ JPanel editKvpFieldPanel; /** The currently selected Field */ KvpField selectedKvpField; /** The JLabel component that holds the name of the Field being edited */ JLabel editKvpFieldName; /** The JTextField that holds the editable value of the Field being edited */ JTextField editKvpFieldValue; /** View's current setting cluster values */ KvpNode viewSettingTree; /** Listens to changes in the device fields */ DeviceChangeListener deviceChangedListener; /** * Basic constructor. Sets up the tree view and the edit panel. */ public GlobalSettingView() throws Exception { super(true); // Set up basic view constructs. The data will be plugged in on the activate call kvpFieldTreeModel = new KvpNodeTreeModel(); globalKvpFieldTree = new JTree(kvpFieldTreeModel); globalKvpFieldTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); globalKvpFieldTree.setCellRenderer(new KvpFieldRenderer()); globalKvpFieldTree.addTreeSelectionListener(new TreeSelectionListener() { /** * Called whenever the value of the selection changes. * @param e the event that characterizes the change. */ public void valueChanged(TreeSelectionEvent e) { Object node = e.getPath().getLastPathComponent(); if (node==null) { return; } if (node instanceof KvpNode) { KvpNode kvpNode = (KvpNode)node; if ((kvpNode.getName().length()>0) && (kvpNode.getChildCount()==0)) { doEditKvpField(kvpNode); } } } } ); encompassingPanel = new CustomPanel(getName()); encompassingPanel.setLayout(new BorderLayout()); encompassingPanel.add(globalKvpFieldTree, BorderLayout.CENTER); encompassingPanel.add(getEditKvpFieldPanel(), BorderLayout.SOUTH); } /** * Returns a one word name identifying this view. */ public String getName() { return "GlobalSettingView"; } /** * Return the Component that displays the primary content for this view */ public Component getViewContent() { return encompassingPanel; } /** * This method is called to instruct the view that it is about to be made * active. When a view is active it needs to make sure its content is correct. * An example of why a panel may be inactive is if it were on a tabbed pane * and was not currently visible or if the user selected some other view. * * Views should use this method to create their content on first touch or when * the device kvpGroup have been refreshed. */ public void activate() { SystemLog.debug("Activating GlobalSettingView"); // Get a copy of the device kvpGroup for this view to use this.viewSettingTree = device.getSettingTree(); this.viewSettingTree.resetChanged(); // build a tree model over the kvpGroup data kvpFieldTreeModel.setKvpNode(this.viewSettingTree); // Start listening to the device for changes to its kvpGroup deviceChangedListener = new DeviceChangeListener() { public void deviceChanged(DeviceChangeEvent e) { // get the latest kvpGroup data from the device viewSettingTree = device.getSettingTree(); viewSettingTree.resetChanged(); // point the tree model to the new kvpGroup data kvpFieldTreeModel.setKvpNode(viewSettingTree); } }; device.addSettingChangeListener(deviceChangedListener); // do normal activate processing super.activate(); } /** * This method is called to instruct the view that it is about to be made * inactive. When a view is inactive, it does not need to worry * about maintaining its content in a correct state. An example of why a panel * may be inactive is if it were on a tabbed pane and was not currently visible * or if the user selected some other view. */ public void deactivate() { // do normal deactivate processing super.deactivate(); // Stop listening to the device for changes to its kvpGroup device.removeSettingChangeListener(deviceChangedListener); } /** * Indicates if any changes have been made by the user that have not yet been * saved to the device(ie committed). */ public boolean isChanged() { return this.viewSettingTree.hasChanged(); } /** * This method instructs the view to place any changes the user has made within * the view into the provided clusters. Once the changes have been saved to * to the device, a subsequent call to commitChanges() will be made. */ public void getChanges(KvpNode settingCluster, KvpNode stateCluster) { settingCluster.merge(viewSettingTree); } /** * Instructs the view to consider any user changes within the view as * saved to the device (ie committed). */ public void commitChanges() { this.viewSettingTree.resetChanged(); } /** * Instructs the view to discard any changes the user has made within the * view and revert those fields to the original state. The view is also * free to refresh all its fields to the present cached state of the device at * this time. */ public void cancelChanges() { // Get a copy of the device kvpGroup for this view to use this.viewSettingTree = device.getSettingTree(); this.viewSettingTree.resetChanged(); // point the tree model to the new view kvpGroup data kvpFieldTreeModel.setKvpNode(this.viewSettingTree); } /** * Sets up a kvpField for editting. This method is called when a kvpField is selected * from the tree. */ public void doEditKvpField(KvpField aKvpField) { selectedKvpField = aKvpField; editKvpFieldName.setText(aKvpField.getName()+" = "); editKvpFieldValue.setText(aKvpField.getStringValue()); editKvpFieldValue.requestFocus(); } /** * Finishes up an edit of a kvpField and pushes any changes back into the kvpField object. */ public void doEditKvpFieldComplete() { TreePath path = this.globalKvpFieldTree.getSelectionPath(); if (path!=null) { kvpFieldTreeModel.valueForPathChanged(path, editKvpFieldValue.getText()); } } /** * Returns the edit kvpGroup panel - constructing it if necessary. */ public JPanel getEditKvpFieldPanel() { if (editKvpFieldPanel==null) { editKvpFieldPanel = new JPanel(new BorderLayout()); editKvpFieldName = new JLabel(); editKvpFieldValue = new JTextField(); editKvpFieldPanel.add(editKvpFieldName, BorderLayout.WEST); editKvpFieldPanel.add(editKvpFieldValue, BorderLayout.CENTER); editKvpFieldValue.addActionListener(new ActionListener() { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e) { doEditKvpFieldComplete(); } } ); editKvpFieldPanel.setMinimumSize(new Dimension(400, 100)); } return editKvpFieldPanel; } /** * A basic Renderer for the KvpNode tree. It shows nodes in the tree as follows: * KvpFieldMap - always labeled "Device KvpNode" * KvpNode - node labeled with name of the kvpGroup * KvpField - node labelled with kvpField_name = kvpField_value * * Also, if a kvpField is selected, it will be displayed in reverse highlighting */ public class KvpFieldRenderer extends JLabel implements TreeCellRenderer { /** * Sets the value of the current tree cell to value. * If selected is true, the cell will be drawn as if * selected. If expanded is true the node is currently * expanded and if leaf is true the node represets a * leaf and if hasFocus is true the node currently has * focus. tree is the JTree the receiver is being * configured for. Returns the Component that the renderer * uses to draw the value. * * @return the Component that the renderer uses to draw the value */ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { this.setBackground(Color.WHITE); this.setForeground(Color.BLACK); if (value instanceof KvpNode) { KvpNode node = (KvpNode)value; String text = "Device"; // default root value if (node.getName().length()>0) { text = node.getName(); String index = node.getAttribute(RciProtocol.INDEX_TAG); if (index!=null) { text += "["+index+"]"; } if (node.getStringValue().length()>0) { text += " = "+node.getStringValue(); } } this.setText(text); if (selected) { this.setOpaque(true); this.setForeground(Color.WHITE); this.setBackground(Color.BLACK); } } else { this.setText("Error. Invalid Node type of "+value.getClass().getName()); } // if (value instanceof KvpNode) { // this.setText("Device KvpNode"); // } else if (value instanceof KvpNode) { // this.setText(((KvpNode)value).getName()); // } else if (value instanceof KvpField) { // if (selected) { // this.setOpaque(true); // this.setForeground(Color.WHITE); // this.setBackground(Color.BLACK); // } else { // } // this.setText(((KvpField)value).getName()+" = "+((KvpField)value).getStringValue()); // } else { // this.setText("Error!"); // } return this; } } public class KvpNodeTreeModel implements TreeModel { private KvpNode rootNode; private Vector treeModelListeners = new Vector(); /** * Define which kvpGroup Map should be surfaced by this model */ public void setKvpNode(KvpNode rootNode) { System.out.println("GlobalSettingView.setKvpNode("+rootNode+")"); this.rootNode = rootNode; if (rootNode!=null) { this.fireTreeStructureChanged(rootNode); } } /** * The only event raised by this model is TreeStructureChanged with the old root * as the path. This event essentially means the entire kvpGroup tree has changed. */ protected void fireTreeStructureChanged(KvpNode rootNode) { int len = treeModelListeners.size(); TreeModelEvent e = new TreeModelEvent(this, new Object[] {rootNode}); for (int i=0; inull * only if the tree has no nodes. * * @return the root of the tree */ public Object getRoot() { return rootNode; } /** * Returns the index of child in parent. If parent * is null or child is null, * returns -1. * * @param parent a note in the tree, obtained from this data source * @param child the node we are interested in * @return the index of the child in the parent, or -1 if either * child or parent are null */ public int getIndexOfChild(Object parent, Object child) { // finish this... very poor performance implementation... add indexing support to // kvpGroup classes to improve. if ((parent==null) || (child==null)) { return -1; } if (parent instanceof KvpNode) { ArrayList temp = new ArrayList(((KvpNode)parent).getAllFields()); return temp.indexOf(child); } return -1; } /** * Returns the child of parent at index index * in the parent's * child array. parent must be a node previously obtained * from this data source. This should not return null * if index * is a valid index for parent (that is index >= 0 && * index < getChildCount(parent)). * * @param parent a node in the tree, obtained from this data source * @return the child of parent at index index */ public Object getChild(Object parent, int index) { // finish this... very poor performance implementation... add indexing support to // kvpGroup classes to improve. if (parent==null) { return null; } if (parent instanceof KvpNode) { ArrayList temp = new ArrayList(((KvpNode)parent).getAllFields()); return temp.get(index); } return null; } /** * Adds a listener for the TreeModelEvent * posted after the tree changes. * * @param l the listener to add * @see #removeTreeModelListener */ public void addTreeModelListener(TreeModelListener l) { treeModelListeners.addElement(l); } /** * Returns the number of children of parent. * Returns 0 if the node * is a leaf or if it has no children. parent must be a node * previously obtained from this data source. * * @param parent a node in the tree, obtained from this data source * @return the number of children of the node parent */ public int getChildCount(Object parent) { int count = 0; if (parent instanceof KvpNode) { count = ((KvpNode)parent).getAllFields().size(); } return count; } /** * Returns true if node is a leaf. * It is possible for this method to return false * even if node has no children. * A directory in a filesystem, for example, * may contain no files; the node representing * the directory is not a leaf, but it also has no children. * * @param node a node in the tree, obtained from this data source * @return true if node is a leaf */ public boolean isLeaf(Object node) { boolean result = true; if (node instanceof KvpNode) { result = ((KvpNode)node).getChildCount()==0; } return result; } /** * Messaged when the user has altered the value for the item identified * by path to newValue. * If newValue signifies a truly new value * the model should post a treeNodesChanged event. * * @param path path to the node that the user has altered * @param newValue the new value from the TreeCellEditor */ public void valueForPathChanged(TreePath path, Object newValue) { Object node = path.getLastPathComponent(); if (node instanceof KvpField) { KvpField kvpField = (KvpField)node; // The TreeCellEditor for a kvpField node returns a string kvpField.setStringValue((String)newValue); // Notify listeners of change this.fireTreeNodesChanged(path); } } /** * Removes a listener previously added with * addTreeModelListener. * * @see #addTreeModelListener * @param l the listener to remove */ public void removeTreeModelListener(TreeModelListener l) { this.treeModelListeners.remove(l); } } }