/*
* 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);
}
}
}