站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Section 7.2.  Defining the Chat Editor - Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Previous Page
Next Page

7.2. Defining the Chat Editor

The first step in adding the chat editor is defining the editor extension. As we saw with the Contacts view in Chapter 5, first you define an extension, then you implement the class identified in the extension. Start by opening the Hyperbola plug-in editor and from the Extensions page, click Add... Select the org.eclipse.ui.editors extension point from the list and click OK. This adds the extension point to the All Extensions list so you can contribute extensions.

Next, right-click on the added org.eclipse.ui.editors item in the All Extensions list, and from the context menu, select New > editor. On the right side of the plug-in editor, the details of the new extension are shown, as in Figure 7-2. Fill in the id, name, and icon fields.

Figure 7-2. Adding the editor extension point to plugin.xml


The class field references a class that implements the chat editor. A trick for creating the class is to click on the class hyperlink in the plug-in editor. This launches the New Class wizard primed with the correct superclass (EditorPart) and various other settings. Enter the class name ChatEditor and click Finish to create the skeleton code for the editor. The generated skeleton will contain many TODOs. Browse the file to get a feel of the methods that need to be implemented.

Now let's look at each method individually and see how you should fill in the code. Keep in mind that any method we do not mention can be left as-is. As usual, it's good to define a constant to track the id for the editor; this is the same id as defined in the editor extension. The first thing the editor needs to do is create the UI elements, such as the transcript area where the chat text appears, and the text entry area. The remainder of the editor code handles key presses and transferring text to the transcript area from the entry area.

org.eclipsercp.hyperbola/ChatEditor
public class ChatEditor extends EditorPart {
  public static String ID = "org.eclipsercp.hyperbola.editors.chat";
  private Text transcript;
  private Text entry;

  // Always need a no-arg constructor
  public ChatEditor() {
  }

  public void init(IEditorSite site, IEditorInput input)
      throws PartInitException {
    setSite(site);
    setInput(input);
    setPartName(getUser());
  }

  public void createPartControl(Composite parent) {
    Composite top = new Composite(parent, SWT.NONE);
    GridLayout layout = new GridLayout();
    layout.marginWidth = 0;
    layout.marginHeight = 0;
    top.setLayout(layout);

    transcript = new Text(top, SWT.BORDER | SWT.MULTI | SWT.WRAP);
    transcript.setLayoutData(new GridData(
        GridData.FILL, GridData.FILL, true, true));
    transcript.setEditable(false);
    transcript.setBackground(transcript.getDisplay().getSystemColor(
        SWT.COLOR_INFO_BACKGROUND));
    transcript.setForeground(transcript.getDisplay().getSystemColor(
        SWT.COLOR_INFO_FOREGROUND));

    entry = new Text(top, SWT.BORDER | SWT.WRAP);
    GridData gridData = new GridData(
        GridData.FILL, GridData.FILL, true, false);
    gridData.heightHint = entry.getLineHeight() * 2;
    entry.setLayoutData(gridData);
    entry.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        if (event.character == SWT.CR) {
          sendMessage();
          // ignore the CR and don't add to text control
          event.doit = false;
        }
      }
    });
  }

  public void setFocus() {
    if (entry != null && !entry.isDisposed()) {
      entry.setFocus();
    }
  }

  private String getUser() {
    return ((ChatEditorInput) getEditorInput()).getName();
  }

  private String renderMessage(String from, String body) {
    if (from == null)
      return body;
    int j = from.indexOf('@');
    if (j > 0)
      from = from.substring(0, j);
    return "<" + from + "> " + body;
  }

  private void scrollToEnd() {
    int n = transcript.getCharCount();
    transcript.setSelection(n, n);
    transcript.showSelection();
  }

  private void sendMessage() {
    String body = entry.getText();
    if (body.length() == 0)
      return;
    transcript.append(renderMessage(getUser(), body));
    transcript.append("\n");
    scrollToEnd();
    entry.setText("");
  }
}

Editors need an IEditorInput before they can be opened. Editor inputs are a lightweight description of the initialization information the editor uses to decide what to show. The chat editor expects a ChatEditorInput that contains the user name of the person at the other end of the chat. For now, just assume that a ChatEditorInput exists while we continue reviewing the code for the editor. The next section shows how to create the ChatEditorInput.

Note

In the real code, there are several methods needed to support the Save and Save As workflows. These are not shown here. You can simply use the defaults because you won't need to save a chat when Hyperbola exits.


Editor implementations follow a common pattern that starts when EditorPart.createPartControl() is called and the editor can create its widgets. ChatEditor.createPartControl(Composite) creates two text fields: one that contains the chat transcript and another to allow the user to enter and send messages.

In addition to creating the editor's widgets, an editor can update its title using EditorPart.setPartName(). Even though a title was specified in the extension description, it can be changed at any time. The last detail in the ChatEditor is a constant called ID. This is the editor id as defined in the editor extension and is used to programmatically refer to this editor. This is going to be particularly useful when it comes time to write the action to show the editor.

The life of an editor

The general lifecycle of an editor is:

  • The Workbench instantiates the editor, creates an editor site, then calls EditorPart.init(IEditorSite, IEditorInput). The editor site allows the editor to access the Workbench's services. It is important that the editor class has a public default constructor. This is used by the Workbench to instantiate the editor.

  • When the editor is made visible, the method EditorPart.createControl(Composite) is called to create the editor's widgets.

  • Once the editor is created, the method EditorPart.setFocus() is called.

  • When the editor is closed, if the contents need to be saved, the method EditorPart.doSave(IProgressMonitor) is called.

  • At the end the editor lifecycle, the method EditorPart.dispose() is called.


In addition to the temporal relationship between editors and the Workbench, editors refer to and access parts of the Workbench. Figure 7-3 shows the key characteristic that an EditorPart relates to the WorkbenchWindow via its site (IWorkbenchPartSite). The site is the context that the editor uses to access the Workbench and various Workbench services such as key bindings, the selection service, and their action bars.

Figure 7-3. The ChatEditor in context


7.2.1. Editor Input

All editors are opened with an IEditorInput via the IWorkbenchPage, as shown below. We are going to add the action to open the editor in a bit, but for now, we need a specific implementation of IEditorInput, to be called ChatEditorInput, so that Hyperbola can tell which editor it's talking to.

IWorkbenchPage.openEditor(IEditorInput input, String editorId)

The editor input is the model for the editor. Editor inputs are both initialization data for the editor, telling it what to show, and identification information for the Workbench, telling it which inputs have open editors. The latter point allows the Workbench to decide if an editor is already open for an input and to show it instead of opening a new one.

Create the ChatEditorInput as shown below. Most of the code here is bookkeeping. This editor input maintains a participant, the other person in the chat, and returns that value to identify the chat to be edited.

org.eclipsercp.hyperbola/ChatEditorInput
public class ChatEditorInput implements IEditorInput {
  private String participant;

  public ChatEditorInput(String participant) {
    super();
    Assert.isNotNull(participant);
    this.participant = participant;
  }

  public boolean exists() {
    return false;
  }

  public String getToolTipText() {
    return participant;
  }

  public ImageDescriptor getImageDescriptor() {
    return null;
  }

  public String getName() {
    return participant;
  }

  public IPersistableElement getPersistable() {
    return null;
  }

  public boolean equals(Object obj) {
    if (super.equals(obj))
      return true;
    if (!(obj instanceof ChatEditorInput))
      return false;
    ChatEditorInput other = (ChatEditorInput) obj;
    return participant.equals(other.participant);
  }

  public int hashCode() {
    return participant.hashCode();
  }
}

7.2.2. The Chat Action

At this point, the editor has been defined, but if you run Hyperbola, the ChatEditor cannot be shown. Editors are not opened automatically in a perspective, but rather as the direct result of a user action. You need a ChatAction that initiates a chat based on the contact selected by the user. Use the New Class wizard to create a class named ChatAction, as shown in Figure 7-4. The action implements the ISelectionListener to track the selection to calculate enablement. It also implements IWorkbenchAction to make it disposable.

Figure 7-4. New class wizard for creating the ChatAction


Once the skeleton action has been generated, add a constructor and a couple of instance variables to track the selection and window. This is almost identical to the AddContactAction you created in the previous chapter.

org.eclipsercp.hyperbola/ChatAction
public class ChatAction extends Action implements ISelectionListener,
    IWorkbenchAction {

private final IWorkbenchWindow window;
public final static String ID = "org.eclipsercp.hyperbola.chat";
private IStructuredSelection selection;

public ChatAction(IWorkbenchWindow window) {
  this.window = window;
  setId(ID);
  setText("&Chat");
  setToolTipText("Chat with the selected contact.");
  setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
      Application.PLUGIN_ID, IImageKeys.CHAT));
  window.getSelectionService().addSelectionListener(this);
}

public void dispose() {
  window.getSelectionService().removeSelectionListener(this);
}

In Hyperbola, it only makes sense to use the ChatAction when a contact is selected. You should add logic to the ChatAction that enables the action only when a Contact is selected. This is the same logic you used for the Add Contact action in Chapter 6, "Adding Actions." As an exercise, go and get that code now and update it to work in this case.

org.eclipsercp.hyperbola/ChatAction
public void selectionChanged(IWorkbenchPart part, ISelection incoming) {
  if (incoming instanceof IStructuredSelection) {
    selection = (IStructuredSelection) incoming;
    setEnabled(selection.size() == 1 &&
        selection.getFirstElement() instanceof ContactsEntry);
  } else {
    // Other selections, for example containing text or of other kinds.
    setEnabled(false);
  }
}

To round out ChatAction, add a run() method that creates an input for the selected user and asks the Workbench to open a chat editor. The Workbench takes care of finding existing open editors. Notice that different kinds of editors, like different kinds of views, are referenced by their id. This id is the one you defined in the org.eclipse.ui.editors extension. It was also defined as a constant on the ChatEditor.

org.eclipsercp.hyperbola/ChatAction
public void run() {
  Object item = selection.getFirstElement();
  ContactsEntry entry = (ContactsEntry) item;
  IWorkbenchPage page = window.getActivePage();
  ChatEditorInput input = new ChatEditorInput(entry.getName());
  try {
    page.openEditor(input, ChatEditor.ID);
  } catch (PartInitException e) {
    // handle error
  }
}

The final step is to add the new action to the Hyperbola menu and toolbar. By now you have done this three or four times, so we leave it as an exercise for you. Hint: Update the methods makeActions(), fillMenuBar(), and fillCoolBar() in the class ApplicationActionBarAdvisor.


Previous Page
Next Page