JBuilder content manager concepts

General description

The ContentManager class provides the user a way to select from among all the open nodes, which are usually files, in the IDE. It appears in JBuilder's UI as the set of tabs located at the top of the content pane. Each tab contains an open node's display name. It's possible using OpenTools to append your own menu options to the context popup that appears when right-clicking on a tab.

There is only one ContentManager instance for each Browser. You can access its functionality indirectly only through Browser methods. You can't replace the ContentManager with the OpenTools API.

The ContentView provides the user a way to select from all the possible views of the node currently selected through the ContentManager. It appears in JBuilder's UI as the set of tabs at the bottom of the content pane and the pane itself. There is one tab for each NodeViewer that accepts the node opened in the content pane. Each NodeViewer is created by a NodeViewerFactory that was registered with the Browser in the initOpenTool() method.

There is one instance of ContentView for each open node. You can access its functionality indirectly only through Browser methods. You can't replace the ContentView with the OpenTools API.

Each view of a node is provided by a separate instance of NodeViewer (usually implemented with the AbstractNodeViewer class). Each NodeViewer is created by its NodeViewerFactory, which must be registered with the Browser during the OpenTools discovery process.

Each NodeViewer must supply a JComponent that becomes a child of the JTabbedPane provided by ContentView. The entire JBuilder IDE is based on the Swing component architecture, so all ContentManager UI components must be Swing-based.

Each NodeViewerFactory must be associated with at least one particular FileNode class. There's no limit on the number of factories that can be registered for the same FileNode. JBuilder provides many different FileNode classes. If a FileNode isn't already defined for your file type, you must register a new one you create in your OpenTool's initOpenTool() method. If a file with an extension hasn't been registered, it's treated as if it were a TextFileNode.

Here is an example of how it all works together. The TextNodeViewerFactory is a registered NodeViewerFactory that can display, or it accepts, all nodes of type TextFileNode and its subclasses. When a TextFileNode is added to the content pane (the ContentView) in the browser, the TextNodeViewerFactory creates an instance of TextNodeViewer to display the contents of the node on the Source page in the content pane when the Source tab is selected.

Browser methods provide access to the ContentManager and its component classes. For convenience, some of those methods are also available in the ProjectView class. The most commonly used Browser methods are getActiveNode() and setActiveNode(). The active node is the file that is currently selected in the ContentManager. If the given node isn't currently open, calling setActiveNode() opens it. The ConentManager class contains other methods that find open nodes, close nodes, navigate between nodes, find NodeViewers, and navigate between NodeViewers.

The Browser also provides some BrowserListener events that are tied to NodeViewer activation and deactivation. If you are writing a NodeViewer, however, you should override the similar NodeViewer methods instead of using these events. (For instance browserActivated only goes to the active NodeViewer which may change when the Browser is "deactivated" because a wizard or other dialog has the focus. To ensure a NodeViewer is notified of the Browser, always invoke browserActivated() prior to viewerActivated() so it may fire multiple times.)


Detailed description of feature/subsystem

Registering a new FileNode

You can register a new file type with JBuilder in two ways. The easiest way is to find an existing registered file type and make an association between it and your file extension so that they act identically. You can do this in JBuilder using the Tools|IDE Options dialog box and using the File Types panel.

You can accomplish the same thing with code in the initOpenTool() method that looks like this:

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      FileType ft = FileType.getFileType("mytxt");
      if (ft == null) {
        ft.registerFileType("mytxt", FileType.getFileType("txt"));
      }
    }
  }

If can't associate your new file type with an existing one, you must register your own FileNode in the initOpenTool() method. Here's an example:

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      Icon ICON = BrowserIcons.ICON_FILEIMAGE;
      registerFileNodeClass("mine", "My source file", MyFileNode.class, ICON);
    }
  }

NodeViewerFactory OpenTools registration

Your NodeViewerFactory must provide the standard interface looked for by the OpenTools discovery process and use it to register with the Browser with the registerNodeViewerFactory() method. This method has an asFirst boolean parameter that, if true, means that the NodeViewerFactory is requesting that it appear as the first NodeViewer for the file types it supports in the ContentView's JTabbedPane. If more than one NodeViewerFactory for the same file type makes the same request as all the OpenTools are discovered, only one can be honored, of course, so making the request doesn't guarantee your NodeViewerFactory will have priority.

This code example registers a new NodeViewerFactory with the Browser and requests that it be the first NodeViewerFactory:

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      Browser.registerNodeViewerFactory(new MyNodeViewerFactory(), true);
    }
  }

If you specify the -verbose command-line option as you start up JBuilder, the console display reports the successful registration of all NodeViewerFactories.

Implementing FileNode and TextFileNode

If your file isn't text or you choose to provide your own editor, you must extend FileNode. Your implementation should look like the following:
public class MyFileNode extends FileNode {
  public static final Icon ICON = BrowserIcons.ICON_FILEIMAGE;

  public MyFileNode(Project project, Node parent, Url url) throws DuplicateNodeException {
    super(project, parent, url);
  }

  public javax.swing.Icon getDisplayIcon() {
    return ICON;
  }
}

If the file contains text, you could base your implementation on TextFileNode. In that case, your implementation might be something like this:

public class MyFileNode extends TextFileNode {

  public MyFileNode(Project project, Node parent, Url url) throws DuplicateNodeException {
    super(project, parent, url);
  }

  public Class getEditorKitClass() {
    return MyEditorKit.class;
  }

  public Class getTextStructureClass() {
    return MyTextStructure.class;
  }
}

In this example, you would have to register the MyEditorKit class using the Open Tools API. See the Editor documentation for more details.

Implementing NodeViewerFactory

NodeViewerFactory is a simple interface with only two methods to implement: canDisplayNode() and createNodeViewer(). When a user opens a file in JBuilder, each registered factory is checked to see if it can create a NodeViewer for that file type. Here's an implementation of a NodeViewerFactory:
public class MyNodeViewerFactory implements NodeViewerFactory {

  public boolean canDisplayNode(Node node) {
    return (node instanceof MyFileNode);
  }

  public NodeViewer createNodeViewer(Context context) {
    if (canDisplayNode(context.getNode())) {
      return new MyNodeViewer(context);
    }
    return null;
  }
}

Implementing NodeViewer

There are basically two different types of NodeViewer: those that view a FileNode and those that view a TextFileNode. The TextFileNode viewer can use existing logic of the JBuilder editor.

FileNode-based NodeViewer

Because of the AbstractNodeViewer class, implementing a FileNode-based NodeViewer is easy. All you must do is provide a JComponent for the file content and an optional JComponent for the file structure to appear in the structure pane. You supply the name that appears on the ContentView tab, and the text that becomes the tab's tooltip. Here's an example:
public class MyNodeViewer extends AbstractNodeViewer {

  public MyNodeViewer(Context context) {
    super(context);
  }

  public String getViewerTitle() {
    return "MyView";
  }

  public String getViewerDescription() {
    return "My cool viewer";
  }

  public JComponent createViewerComponent() {
    if (context.getNode() instanceof MyFileNode) {
       return new MyViewerComponent(context);
    }
    return null;
  }

  public JComponent createStructureComponent() {
    return null;
  }
}

Another way to implement such a viewer is based on AbstractBufferNodeViewer. If your viewer needs to read and write its content through the Virtual File System and co-exist with other NodeViewers registered for your file type, then this class greatly simplifies your work. It does this by notifying you that the file buffer has changed only if your viewer is active.

TextFileNode-based NodeViewer

For TextFileNodes, you don't need to implement the NodeViewer. Instead you provide your customized implementations of the supporting classes used by the JBuilder editor NodeViewer. These supporting classes include TextEditorKit, and AbstractScanner. If you don't override any of these, you end up with the same NodeViewer as provided by default for a TextFileNode.

See the Editor documentation for more details.

Adding an action to the context menu

You can append your own UpdateAction or ActionGroup to the ContentManager pop-up menu that appears when right-click on a file tab. The following code demonstrates adding a WizardAction. Note that such wizards must be registered using the OpenTools API.

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      ContentManager.registerContextActionProvider(new ContextActionProvider() {
        public Action getContextAction(Browser browser, Node[] nodes) {
          if (browser.getActiveProject() != null) {
            return WIZARD_MyWizard;
          }
          return null;
        }
      });
    }
  }

  public static final WizardAction WIZARD_MyWizard = new WizardAction (
    "My wizard...", 'w', "My ContentManager wizard",
    BrowserIcons.ICON_BLANK,
    BrowserIcons.ICON_BLANK,
      false) {
    protected Wizard createWizard() {
      return new MyWizard();
    }
  };

For more information about writing and registering wizards with the OpenTools API, see JBuilder wizard concepts.