Previous Page
Next Page

8.3. Editing

When the Properties page displays the content in a tree, it is important to edit the content without having to switch to the Source page (see Section 14.2.4, Marker resolutionquick fix, on page 520 for an example of manipulating the content in an existing text editor).

8.3.1. Cell editors

Cell editors use identifiers rather than a column index to identify what aspect of a particular object is being edited. Before creating the cell editors, first create some constants used as identifiers.

public static final String VALUE_COLUMN_ID = "Value";
public static final String KEY_COLUMN_ID = "Key";

Then, assign property identifiers to each column by adding a new initTreeEditors() method, which is called from createPages().

treeViewer.setColumnProperties(
   new String[] { KEY_COLUMN_ID, VALUE_COLUMN_ID });

Cell editors are created by adding code to the initTreeEditors() method:

final TextCellEditor keyEditor =
   new TextCellEditor(treeViewer.getTree());
final TextCellEditor valueEditor =
   new TextCellEditor(treeViewer.getTree());
treeViewer.setCellEditors(
   new CellEditor[] { keyEditor, valueEditor });

8.3.2. Cell modifiers

Cell editors know nothing about the model being edited, which is where cell modifiers come in. Cell modifiers adapt the underlying model to an interface understood by cell editors so that the cell editors can present the correct value to the user and update the model with new values when a user has made modifications. Add this new code to the initTreeEditors() method:

treeViewer.setCellModifier(
   new PropertiesEditorCellModifier(this, treeViewer));

Then, create a new class to handle this editor/model interaction.

package com.qualityeclipse.favorites.editors;

import ...

public class PropertiesEditorCellModifier
   implements ICellModifier
{
   private PropertiesEditor editor;

   public PropertiesEditorCellModifier(
      PropertiesEditor editor,
      TreeViewer viewer
   ) {
      this.editor = editor;
   }

   public boolean canModify(Object element, String property) {
      if (property == PropertiesEditor.KEY_COLUMN_ID) {
         if (element instanceof PropertyCategory)
            return true;
         if (element instanceof PropertyEntry)
            return true;
      }
      if (property == PropertiesEditor.VALUE_COLUMN_ID){
         if (element instanceof PropertyEntry)
            return true;
      }
      return false;
   }

   public Object getValue(Object element, String property) {
      if (property == PropertiesEditor.KEY_COLUMN_ID) {
         if (element instanceof PropertyCategory)
           return ((PropertyCategory) element).getName();
         if (element instanceof PropertyEntry)
           return ((PropertyEntry) element).getKey();
      }
      if (property == PropertiesEditor.VALUE_COLUMN_ID){
         if (element instanceof PropertyEntry)
            return ((PropertyEntry) element).getValue();
      }
      return null;
   }

   public void modify(Object item, String property, Object value) {
      // Null indicates that the validator rejected the value.
      if (value == null)
         return;

     Object element = item;
     if (element instanceof TreeItem)
        element = ((TreeItem) element).getData();

     String text = ((String) value).trim();
     if (property == PropertiesEditor.KEY_COLUMN_ID) {
        if (element instanceof PropertyCategory)
           ((PropertyCategory) element).setName(text);
        if (element instanceof PropertyEntry)
           ((PropertyEntry) element).setKey(text);
     }
     if (property == PropertiesEditor.VALUE_COLUMN_ID){
        if (element instanceof PropertyEntry)
           ((PropertyEntry) element).setValue(text);
     }
   }
}

The modify(Object, String, Object) method changes the editor model (see Figure 8-6), which then calls a new TReeModified() method in the PropertiesEditor class to notify any interested members that the editor's content has been modified. This happens via a new PropertyFileListener listener created in the next section.

public void treeModified() {
   if (!isDirty())
      firePropertyChange(IEditorPart.PROP_DIRTY);
}

Figure 8-6. Properties editor with modified cell value.


8.3.3. Change listeners

When a user edits a value, the model generates a change event to notify registered listeners. The next step is to hook up a change listener in the PropertiesEditor class so that you can be notified of events and update the tree appropriately. First, add a new PropertyFileListener.

private final PropertyFileListener propertyFileListener =
   new PropertyFileListener()
{
  public void keyChanged(
     PropertyCategory category, PropertyEntry entry
  ) {
     treeViewer.update(entry, new String[] { KEY_COLUMN_ID });
     treeModified();
  }

  public void valueChanged(
     PropertyCategory category, PropertyEntry entry
  ) {
     treeViewer.update(entry, new String[] { VALUE_COLUMN_ID });
     treeModified();
  }

  public void nameChanged(PropertyCategory category) {
     treeViewer.update(category, new String[] { KEY_COLUMN_ID });
     treeModified();
  }

  public void entryAdded(
     PropertyCategory category, PropertyEntry entry
  ) {
     treeViewer.refresh();
     treeModified();
  }

  public void entryRemoved(
     PropertyCategory category, PropertyEntry entry
  ) {
     treeViewer.refresh();
     treeModified();
  }

  public void categoryAdded(PropertyCategory category) {
     treeViewer.refresh();
     treeModified();
  }

  public void categoryRemoved(PropertyCategory category) {
     treeViewer.refresh();
     treeModified();
  }
};

Next, modify the updateTreeFromTextEditor() method, as folows so that the listener is removed from the old editor model before it is discarded and added to the new editor model.

void updateTreeFromTextEditor() {
   PropertyFile propertyFile = (PropertyFile) treeViewer.getInput();
   propertyFile.removePropertyFileListener(propertyFileListener);
   propertyFile = new PropertyFile(
      textEditor
         .getDocumentProvider()
         .getDocument(textEditor.getEditorInput())
         .get());
   treeViewer.setInput(propertyFile);
   propertyFile.addPropertyFileListener(propertyFileListener);
}

8.3.4. Cell validators

Cell editors have validators to prevent invalid input from reaching model objects. Whenever a user modifies a cell editor's content, the isValid(Object) method returns an error message if the object represents an invalid value, or null if the value is valid. Assign each cell editor a validator in the initTreeEditors() method as follows:

keyEditor.setValidator(new ICellEditorValidator() {
   public String isValid(Object value) {
      if (((String) value).trim().length() == 0)
         return "Key must not be empty string";
      return null;
  }
});
valueEditor.setValidator(new ICellEditorValidator() {
   public String isValid(Object value) {
      return null;
  }
});

Whenever a user enters an invalid value, you have to decide how the user will be notified that the value is invalid. In this case, add an ICellEditorListener in the initTreeEditors() method so that the error message will appear in the window's status line (see Figure 8-7). For a more prominent error message, the editor's header area could be redesigned to allow an error image and message to be displayed just above the tree rather than in the workbench's status line.

keyEditor.addListener(new ICellEditorListener() {
   public void applyEditorValue() {
      setErrorMessage(null);
  }
   public void cancelEditor() {
      setErrorMessage(null);
  }
  public void editorValueChanged(
     boolean oldValidState,
     boolean newValidState
  ) {
     setErrorMessage(keyEditor.getErrorMessage());
  }
  void setErrorMessage(String errorMessage) {
     getEditorSite().getActionBars().getStatusLineManager()
        .setErrorMessage(errorMessage);
  }
});

Figure 8-7. Error message in status line indicating invalid input.


8.3.5. Editing versus selecting

Before editing is added in the tree, a user could easily select one or more rows, but now the cell editor is always open. One possible solution is to only open the editor when the Alt key is held down, but select one or more rows when it is not. To accomplish this, some additional PropertiesEditor code needs to be added to capture the state of the Alt key by adding a listener in the initTreeEditors() method. This same approach could be used to capture the state of any modifier key.

private boolean isAltPressed;

private void initTreeEditors() {
   ... existing code here ...
   treeViewer.getTree().addKeyListener(new KeyListener() {
      public void keyPressed(KeyEvent e) {
         if (e.keyCode == SWT.ALT)
            isAltPressed = true;
      }
      public void keyReleased(KeyEvent e) {
         if (e.keyCode == SWT.ALT)
            isAltPressed = false;
      }
   });
}

public boolean shouldEdit() {
   if (!isAltPressed)
      return false;
   // Must reset this value here because if an editor
   // is opened, we don't get the Alt key up event.
   isAltPressed = false;
   return true;
}

Next, the following method in PropertiesEditorCellModifier needs to be modifed so that the cell editor will only be opened when the Alt key is held down.

public boolean canModify(Object element,String property) {
   if (property == PropertiesEditor.KEY_COLUMN_ID) {
      if (element instanceof PropertyCategory)
         return editor.shouldEdit() ;
     if  (element instanceof PropertyEntry)
         return editor.shouldEdit() ;
  }
  if (property == PropertiesEditor.VALUE_COLUMN_ID){
     if (element instanceof PropertyEntry)
        return editor.shouldEdit() ;
  }
  return false;
}


Previous Page
Next Page