Note that the a giant button replaces the structure pane. When the user clicks the button, a comment line is added to both the top and bottom of the batch file and the background color changes to cyan.
This tutorial also adds a menu item to context menus. A View in Notepad menu option appears in the project pane's context menu and in the content pane's context menu when the Source tab is selected.
This tutorial teaches these primary skills:
You can find the source code for this OpenTool sample in the samples/OpenToolsAPI/NodeDemo
directory.
NodeDemo.jpx
.
If you need help performing these tasks, see JBuilder OpenTools basics.
This tutorial requires you to create four classes:
BatchFileNode
BatchViewerFactory
BatchViewer
BatchMenu
BatchFileNode
.
com.borland.primetime.node.TextFileNode
.
Your code in the editor should look like this:
package NodeDemo; import com.borland.primetime.node.TextFileNode; public class BatchFileNode extends TextFileNode { public BatchFileNode() { } }
Because the batch file node displays text, it extends the com.borland.node.TextFileNode
, which in turn extends com.borland.node.FileNode
.
import javax.swing.*; import com.borland.primetime.node.*; import com.borland.primetime.vfs.*;
BatchFileNode
class, but all subclasses of FileNode
must have a constructor that takes three parameters: the name of the project, the parent node, and the storage available to this node. Modify BatchFileNode()
so it looks like this:
public BatchFileNode(Project project, Node parent, Url url) throws DuplicateNodeException { super(project, parent, url); }
The method calls the constructor of the parent class (TextFileNode
) and throws an exception if a batch file node of the same type is already registered.
public static final Icon ICON = new ImageIcon(BatchFileNode.class.getResource("Batch.gif"));
To provide a way to display the icon, add this method:
public Icon getDisplayIcon() { return ICON; }
Add a Batch.gif
file to your project. You can find one in the samples/OpentoolsAPI/NodeDemo
directory.
initOpenTool()
method that is called as JBuilder is loading. The initOpenTool()
method of BatchFileNode
registers the new file node type with JBuilder. Add this method to the class:
public static void initOpenTool(byte major, byte minor) { FileNode.registerFileNodeClass("bat", "Batch file", BatchFileNode.class, ICON); }
After JBuilder loads this new OpenTool and the user adds a file with a .bat
extension to a project, the new file node is represented with the icon in the project pane. At this point, there is no viewer associated with the new node type.
For more information about creating a new node type, see Registering a new file node type and other topics in JBuilder content manager concepts.
Use the Class wizard to begin the new class:
Use the Implement Interface wizard to have the class implement the com.borland.primetime.ide.NodeViewerFactory
interface:
com.borland.primetime.ide.NodeViewerFactory
interface.
Your code in the editor should look like this:
package NodeDemo; import com.borland.primetime.ide.NodeViewer; import com.borland.primetime.ide.Context; import com.borland.primetime.node.Node; import com.borland.primetime.ide.NodeViewerFactory; public class BatchViewerFactory implements NodeViewerFactory { public NodeViewer createNodeViewer(Context parm1) { /**@todo: Implement this com.borland.primetime.ide.NodeViewerFactory method*/ throw new java.lang.UnsupportedOperationException("Method createNodeViewer() not yet implemented."); } public boolean canDisplayNode(Node parm1) { /**@todo: Implement this com.borland.primetime.ide.NodeViewerFactory method*/ throw new java.lang.UnsupportedOperationException("Method canDisplayNode() not yet implemented."); } }
import com.borland.primetime.ide.*; import com.borland.primetime.node.*;
NodeViewerFactory
interface has just two methods you must implement: canDisplayNode()
and createNodeViewer()
. Start with canDisplayNode()
first.
The JBuilder IDE must quickly poll the registered node viewer factories to determine whether a factory exists that can create the right type of viewer for the selected file node. It does this by calling the canDisplayNode()
method of the registered node viewer factories.
The canDisplayNode()
method simply determines whether the file node passed to it is the right type of file node; in this case, whether it is an instance of a BatchFileNode
. Modify the canDisplayNode()
method in your code so that it looks like this:
public boolean canDisplayNode(Node node) { return node instanceof BatchFileNode; }
canDisplayNode()
returns true if the node in question is an instance of BatchFileNode
.
createNodeViewer()
method attempts to create a viewer to display the node. In this case, it tries to create a BatchViewer
instance, which is a viewer you'll create later for displaying batch files in the content pane. Modify the createNodeViewer()
method in your code so that it looks like this:
public NodeViewer createNodeViewer(Context context) { if (context.getNode() instanceof BatchFileNode) return new BatchViewer(context); return null; }
The context
parameter specifies a particular browser/node pair.
initOpenTool()
method that is called as JBuilder is loading. The initOpenTool()
method of BatchViewerFactory
registers the new file node type with JBuilder. Add this method to the class:
public static void initOpenTool(byte major, byte minor) { Browser.registerNodeViewerFactory(new BatchViewerFactory()); }
BatchViewerFactory
can create. The BatchViewer
class you are going to create displays the contents of the a batch file in the content pane. It has its own Batch tab the user can use to switch to this view if it's not the one currently displayed. BatchViewer
also replaces the usual hierarchy in the structure pane with a button the user can use to modify the contents of the buffer the viewer is presenting.
Use the Class wizard to begin the new class:
BatchViewer
.
com.borland.primetime.viewer.AbstractBufferNodeViewer
.
Use the Implement Interface wizard to have the class implement the java.awt.event.ActionListener
interface:
java.awt.event.ActionListener
interface.
Your code should look like this:
package NodeDemo; import com.borland.primetime.viewer.*; import com.borland.primetime.vfs.Buffer; import javax.swing.JComponent; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class BatchViewer extends AbstractBufferNodeViewer implements ActionListener { public BatchViewer() { } public String getViewerTitle() { /**@todo: implement this com.borland.primetime.viewer.AbstractNodeViewer abstract method*/ } public byte[] getBufferContent(Buffer parm1) { /**@todo: implement this com.borland.primetime.viewer.AbstractBufferNodeViewer abstract method*/ } public JComponent createStructureComponent() { /**@todo: implement this com.borland.primetime.viewer.AbstractNodeViewer abstract method*/ } protected void setBufferContent(byte[] parm1) { /**@todo: implement this com.borland.primetime.viewer.AbstractBufferNodeViewer abstract method*/ } public JComponent createViewerComponent() { /**@todo: implement this com.borland.primetime.viewer.AbstractNodeViewer abstract method*/ } public void actionPerformed(ActionEvent e) { /**@todo: Implement this java.awt.event.ActionListener method*/ throw new java.lang.UnsupportedOperationException("Method actionPerformed() not yet implemented."); } }Most node viewers refresh each time a buffer change takes place, even if the viewer isn't visible or it doesn't have the focus.
BatchViewer
, however, extends AbstractBufferNodeViewer
, which is a node viewer that can cache buffer changes and update the view only when necessary.
import javax.swing.*; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.UnsupportedEncodingException; import com.borland.primetime.vfs.*; import com.borland.primetime.ide.Context; import com.borland.primetime.viewer.AbstractBufferNodeViewer;
context
parameter that identifies a unique browser/node pair is sent to it and the parent class is informed that the viewer buffer should update only when it is visible:
public BatchViewer(Context context) { super(context, UPDATE_WHEN_VISIBLE); }
UPDATE_WHEN_VISIBLE
is a class constant defined in AbstractBufferNodeViewer
.
createViewerComponent()
method in your class so that it creates an instance of JTextArea
, makes the text area read only, and sets its background color to yellow. First define a JTextArea
field in the class:
private JTextArea area;
Then implement the createViewerComponent()
method:
public JComponent createViewerComponent() area = new JTextArea(); area.setEditable(false); area.setBackground(Color.yellow); return area; }
Also implement the getViewerTitle()
method so that it returns the string "Batch". This string is used as the label of the tab for the viewer:
public String getViewerTitle() { return "Batch"; }
First add a new field to the class for the button:
private JButton button;Then implement the
createStructureComponent()
method with this code:
public JComponent createStructureComponent() { button = new JButton("Press me to modify the Batch File"); button.addActionListener(this); return button; }
As the new button instance is created, it's passed the "Press me to modify the Batch File" string. When the button is "pressed," an actionPerformed()
method is called. You must implement that method in the BatchViewer
class. Modify actionPerformed()
so that it looks like this:
public void actionPerformed(ActionEvent e) { area.setText("REM -- Comment at top --\n" + area.getText() + "REM -- Comment at bottom\n"); try { setBufferModified(); } catch (ReadOnlyException ex) { setBufferContent(); } }
The actionPerformed()
method adds a comment to the top and bottom of the text in the viewer component. Then setBufferModified()
is called, which the Virtual File System (VFS) takes care of. setBufferModified()
throws a ReadOnlyException
, however, so you must handle it. Remember you specified the viewer as read only. In this case, the catch clause calls setBufferContent()
, a method of the AbstractBufferNodeViewer
class.
Modify the setBufferContent()
method with this code, making sure you change the parm1
parmeter name to content
:
protected void setBufferContent(byte[] content) { try { area.setText(new String(content, getEncoding())); } catch (UnsupportedEncodingException ex) { } }The
AbstractBufferNodeViewer
, the parent class of your BatchViewer
class, decides when to call the setBufferContent()
method. This allows AbstractBufferNodeViewer
to save up changes to the buffer when the viewer isn't active or visible. When setBufferContent()
is called, the viewer is refreshed if the buffer has changed. So the setBufferContent()
you implemented attempts to modify the viewer contents by setting the new text. It also calls the getEncoding()
method to discover the appropriate encoding to use when converting any binary data.
AbstractBufferNodeViewer
implements the BufferListener
interface, which contains a bufferStateChanged()
method that is called by the Buffer
object when the buffer changes. You must implement bufferStateChanged()
. Add a bufferStateChanged()
method to your class that changes the background color to cyan, and, if the buffer state is ready only, changes the state so it can edited. Here is the code to do that:
public void bufferStateChanged(Buffer buffer, int oldState, int newState) { area.setBackground( (newState & Buffer.STATE_MODIFIED) == Buffer.STATE_MODIFIED ? Color.cyan : Color.yellow); button.setEnabled( (newState & Buffer.STATE_READONLY) != Buffer.STATE_READONLY); }
AbstractBufferNodeViewer
also implements the BufferUpdater
interface, which defines a getBufferContent()
method. Your derived class must supply an implementation of this method. Here getBufferContent()
returns the current content of the buffer as an array of bytes:
public byte[] getBufferContent(Buffer buffer) { return area.getText().getBytes(); }
BatchMenu
class shows you how to use both methods.
To begin the BatchMenu
class,
BatchMenu
.
java.lang.Object
.
BatchMenu
implements the ContextActionProvider
interface that defines just one method, getContextAction()
.
Use the Implement Interface wizard to have the class implement the com.borland.primetime.ide.ContextActionProvider
interface:
com.borland.primetime.ide.ContextActionProvider
interface.
The wizard adds the getContextAction()
method to your code. Your code should look like this:
package NodeDemo; import javax.swing.Action; import com.borland.primetime.ide.Browser; import com.borland.primetime.node.Node; import com.borland.primetime.ide.ContextActionProvider; public class BatchMenu implements ContextActionProvider { public Action getContextAction(Browser parm1, Node[] parm2) { /**@todo: Implement this com.borland.primetime.ide.ContextActionProvider method*/ throw new java.lang.UnsupportedOperationException("Method getContextAction() not yet implemented."); } }
BatchMenu
class requires several import statements. Modify the import section of your class so that it looks like this:
import javax.swing.*; import java.awt.event.*; import com.borland.primetime.ide.*; import com.borland.primetime.node.Node; import com.borland.primetime.editor.*; import com.borland.primetime.viewer.*; import com.borland.primetime.actions.*;
getContextAction()
method specifies an action that occurs within a particular context; in this case, when the user right-clicks a batch file node in the project pane.
Modify getContextAction()
so that if the currently selected node is a single batch file node, an action that opens the file in the notepad.exe utility occurs. (Be sure you change the name of the parameters passed to getContextAction()
to browser
and nodes
.) Here is the code:
public Action getContextAction(Browser browser, Node[] nodes) { if (nodes.length == 1 && nodes[0] instanceof BatchFileNode) return ACTION_VIEW_NOTEPAD; return null; }
You must now define the ACTION_VIEW_NOTEPAD
action that getContextAction()
returns. Here is the action code in its entirety:
public static final Action ACTION_VIEW_NOTEPAD = new BrowserAction( "View in Notepad") { public void actionPerformed(Browser browser) { Node node = browser.getProjectView().getSelectedNode(); if (node instanceof BatchFileNode) { BatchFileNode batchNode = (BatchFileNode)node; try { String path = batchNode.getUrl().getFileObject().getAbsolutePath(); Runtime.getRuntime().exec("notepad " + path); } catch (Exception ex) { } } } };
The action created is a BrowserAction
given the text string "View in Notepad" that appears on the context menu. The current Browser
instance is passed to the actionPerformed()
method, which begins by obtaining the selected node in the project pane and then determing whether that node is a batch file node type. If it is, the method calls notepad.exe
, giving it to open the fully-qualified name of the batch file the node represents.
initOpenTool()
method you must create to register the new action with JBuilder, you must declare a menu
field of type BatchMenu
, and then register it as a context action provider for the project pane. Here is the code to do that:
public static void initOpenTool(byte major, byte minor) { BatchMenu menu = new BatchMenu(); ProjectView.registerContextActionProvider(menu); }
Your BatchMenu
class now adds a menu item to the context menu of the project pane when the user selects a batch file node in the project pane.
EditorContextActionProvider
interface in the BatchMenu
class and then registering the whole class as the provider, you could choose instead to create and register a local class as the provider.
Begin by starting the definition of a ViewNotepad
class, which implements the EditorContextActionProvider
interface:
static EditorContextActionProvider ViewNotepad = new EditorContextActionProvider() { };
Classes that implement EditorContextActionProvider
must also implement a getContextAction()
method, but this one is passed an instance of an EditorPane
that appears in the Source pane when the batch file is opened in the code editor. Below is the code for getContextAction()
; place it in the ViewNotepad
class definition.
public Action getContextAction(EditorPane editor) { Node node = Browser.getActiveBrowser().getActiveNode(); if (node instanceof BatchFileNode) return GROUP_ViewNotePad; return null; }
getContextAction()
obtains the selected node, and if the node is an instance of a batch file node, it returns the ActionGroup
GROUP_ViewNotePad
. By specifying a separate ActionGroup
for the menu command, it will appear in its own group, separate from the other menu items on the editor context menu.
An implementation of EditorContextActionProvider
must also include a getPriority()
method:
public int getPriority() { return 4; }
The priority of a menu item determines where the item appears on a menu. Possible priorities range from 1 - 100. A priority less than 5 usually results in the menu item appearing at the bottom of the menu, while a priority of 99 usually makes it appear at the top. Priorities are shared with other menu entries, so no priority value guarantees a specific position on the menu.
For clarity, the entire ViewNotepad
implementation is presented here:
static EditorContextActionProvider ViewNotepad = new EditorContextActionProvider() { public Action getContextAction(EditorPane editor) { Node node = Browser.getActiveBrowser().getActiveNode(); if (node instanceof BatchFileNode) return GROUP_ViewNotePad; return null; } public int getPriority() { return 4; } };
You have yet to define the GROUP_ViewNotePad
that getContextAction()
returns. The group contains just one action. Define the ActionGroup
like this in your class:
protected static final ActionGroup GROUP_ViewNotePad = new ActionGroup(); static { GROUP_ViewNotePad.add(ACTION_EDITOR_VIEW_NOTEPAD); }
Finally, define the action that is called when the user-right clicks the batch file in the editor; place it above the GROUP_ViewNotePad
definition you just added in the class:
public static final AbstractAction ACTION_EDITOR_VIEW_NOTEPAD = new UpdateAction("View in Notepad", 'v', "View in Notepad") { public void update(Object source) { Node node = Browser.getActiveBrowser().getActiveNode(); setEnabled(node instanceof BatchFileNode); } public void actionPerformed(ActionEvent e) { Node node = Browser.getActiveBrowser().getActiveNode(); if (node instanceof BatchFileNode) { // Show the batch file in Notepad. BatchFileNode batchNode = (BatchFileNode)node; try { String path = batchNode.getUrl().getFileObject().getAbsolutePath(); Runtime.getRuntime().exec("notepad " + path); } catch (Exception ex) { } } } };
The ACTION_EDITOR_VIEW_NOTEPAD
action is defined as an UpdateAction
that contains two methods: update()
and actionPerformed()
.
The update()
method determines whether the menu item text "View in Notepad" appears on the menu. The method obtains the active node and enables the menu item if the node is a BatchFileNode
instance.
The actionPerformed()
method also gets the currently selected file node and determines whether it's a BatchFileNode
instance. If it is, actionPerformed()
passes the fully-qualified name of the file to the notepad.exe
utility to open.
ViewNotepad
class as a ContextActionProvider
with the EditorManager
. Just as before, you do this in the initOpenTool()
method. Once you've added the necessary code to initOpenTool()
, the initOpenTool()
code should look like this, with the new code shown in bold:
public static void initOpenTool(byte major, byte minor) { BatchMenu menu = new BatchMenu(); ProjectView.registerContextActionProvider(menu); EditorManager.registerContextActionProvider(ViewNotepad); }
OpenTools-Core: NodeDemo.BatchFileNode OpenTools-UI: NodeDemo.BatchViewerFactory NodeDemo.BatchMenu
NodeDemo\classes.opentools
.
bin
directory. Add NodeDemo\classes to the Java classpath.
For more detailed information on these final steps, see OpenTools basics.