NbDocument
.
JFrame
) from template--in either case, there is a
new .java
file in the user's
repository containing Java source, together with a .form
file and possibly some .class
files.
.form
file associated with a
.java
file and perhaps some .class
files;
when it finds them, it:
.form
) file in the Form Editor.
.java
file in an editor.
.java
file was already open in some editor
window or pane, then this window/pane is reused and just given
focus (i.e. a new
JEditorPane
is created, but using the same
EditorKit
and
StyledDocument
).
If not, a new JEditorPane
is
asked
to create an editor for the Java source content type (typically
text/x-java
). Now, the Form Editor's data loader is an
extension of the Java Source data loader; the Java loader specially treats the request to load its content
into an editor: it inserts most of the text straight into the
StyledDocument
just as you would expect, but also checks
for special comments looking something like //GEN-BEGIN:
in the source code, which are the markers used to indicate guarded
areas in saved source code. When it finds one, rather than inserting
the marker into the document, it uses
NbDocument.insertGuarded(...)
to insert the enclosed text area, making it read-only to the user.
JEditorPane
now has a StyledDocument
suited to Java source code loaded into it. This document may be
implemented by the standard NetBeans Editor, or it may be a custom editor. The user may edit non-guarded
parts of the document; attempted actions (like typing) that would
affect guarded areas are prevented by the editor in use.
Note that the Form Editor directly or indirectly calls
Document.insertString(...)
(or
NbDocument.insertGuarded(...)
)
and
Document.remove(...)
to do this work, so it is itself unaffected by guard blocks.
NbDocument.WriteLockable.runAtomicAsUser(...)
to make sure that the guard blocks are properly honored.
//GEN-BEGIN:
before the text is written to file.
FileObject fo=TopManager.getDefault().getRepository().find("my.pkg", "MyFile", "java"); DataObject do=TopManager.getDefault().getLoaderPool().findDataObject(fo); EditorCookie ec=(EditorCookie)(do.getCookie(EditorCookie.class)); StyledDocument doc=ec.openDocument();
if (ec.isModified()) ...Then see what the current contents of line 25 are:
int start=NbDocument.findLineOffset(doc, 25); int end=NbDocument.findLineOffset(doc, 26); String contents=doc.getText(start, end-start);And display this line in the Editor window:
ec.getLineSet().getCurrent(25).show(Line.SHOW_TRY_SHOW);Now insert a new line here after it:
final BadLocationException[] exc=new BadLocationException[] {null}; NbDocument.runAtomicAsUser(doc, new Runnable() { public void run() { try { doc.insertString(NbDocument.findLineOffset(26), "New text.\n", SimpleAttributeSet.EMPTY); } catch (BadLocationException e) { exc[0]=e; } } }); if (exc[0] != null) throw exc[0];All done! Prompt to save the file, and close the editor window:
ec.close();
NbDocument.markGuarded(...)
;
the guard block may subsequently be removed using
NbDocument.unmarkGuarded(...)
.
Typically you will want to remember the positions of the guard
blocks you added using a position, so that user edits in the vicinity
of the guard block will be taken into consideration. You may create
such a position using
NbDocument.createPosition(...)
,
and retrieve its current offset when needed using
Position.getOffset()
.
NbDocument.insertGuarded(...)
.
Document.insertString(...)
and
Document.remove(...)
.
You probably want to use
NbDocument.runAtomic(...)
to prevent errors in threaded code.
To do the same sort of thing while preventing yourself from
accidentally touching a guard block, i.e. if your module was not the
creator of the guard block (or you are not even sure if there any in
the document), please use
NbDocument.runAtomicAsUser(...)
instead.
NbDocument.markError(doc, NbDocument.findLineOffset(doc, 25));If the user corrects the problem (you could use a document change listener to detect this), it is just as easy to remove the marking:
NbDocument.markNormal(doc, NbDocument.findLineOffset(doc, 25));In the current API, there are only three special attributes, as specified in
NbDocument.ERROR_STYLE_NAME
(for example).
You are of course free to create your own attributes and mark lines
with them on the document using the standard Swing methods, but it is
not guaranteed that every editor will pay attention to your custom
markings: it is possible that an editor for which arbitrary style
support is difficult, may choose to provide special display of only
these attributes. Also, the IDE presents a control panel to the user
in which it is possible to customize the colors associated with these
markings, so they are treated somewhat specially. If there is enough
interest, a way could be added to create new custom
attributes using the same model, that would have customizable colors
(and perhaps other attributes), a display name used in the control
panel, etc.
If you would like to add something akin to guard blocks to a custom
file type handled by your module, you will need to insert the
appropriate hooks. The best thing to do is to subclass
EditorSupport
,
and use this subclass to implement cookies such as
EditorCookie
.
Now just provide appropriate implementations of
CloneableEditorSupport.loadFromStreamToKit(...)
(to interpret special markings in the saved file and convert them into document attributes); and
CloneableEditorSupport.saveFromKitToStream(...)
(to translate these attributes back into ASCII markings).
Implementing a Custom Editor
It is possible to integrate a custom editor (presumably for textual
content types) into the IDE, in place of the default
editor. The basic requirement is that it conform to the Swing
EditorKit conventions. The Swing Text system is rather complex, so if
you are not familiar with it you should look at Using
the Swing Text Package.
Beyond being a generic EditorKit, there are two significant pieces of functionality used by the IDE which the editor should support if at all possible:
If you wish, you may add context help for the editor; see the
Modules API
for details.
Creating styled document
You will need to create an implementation of
javax.swing.text.StyledDocument
to hold the content of the buffer being edited.
Actually, you could get away with a plain
There is very little to the way the IDE indicates
guarding. Essentially, it is just a character attribute, Note that currently guarded areas always consist of entire lines or
sets of lines, so if you make this assumption your editor will be
acceptable--but please handle sub-line granularity of guard blocks if
you can do so easily, in case this situation changes. Along the same
lines, you should make sure that users may only insert a newline
immediately before a guarded block, and not any other character, for
otherwise a half-guarded line would be created.
It is important to understand that the guarded attribute applies
only to user modifications, i.e. those undertaken in the
context of a user-initiated Swing action, or by
It is preferable, though optional, for the editor to visually mark
guarded areas, say with a special background color. It is your
responsibility to choose a color or another appearance for this
purpose, however. Possibly the IDE will specify a
Document
if you really wanted, but you would be unable to support guard blocks
or background colors for IDE markings; while this would probably
suffice for an editor that was not to be used on Java source code, in
general it is very much recommended that StyledDocument
be used instead--you only need to implement a few aspects of styling
pertaining to these two issues, so full support for changing font and
so on is completely optional.
Handling guards
This is really the central problem in creating a compliant editor. If
you want to completely skip this step, it is possible to return a
plain Document
from the registered editor kit; however
this will not support guards (or IDE attributes either), and so if the
user edits what should have been a guarded block, they may cause
errors in the code, and their edits may be overwritten by the IDE. If
you implement StyledDocument
, it is assumed you are
handling guard blocks, but again it is up to you to actually do so
correctly--the IDE cannot determine this.
NbDocument.GUARDED
,
which will be set to Boolean.TRUE
for guarded characters,
and Boolean.FALSE
(or unset) for others; it is typically
placed on the document by means of the
NbDocument.markGuarded(...)
and
NbDocument.unmarkGuarded(...)
methods, which just call
StyledDocument.setCharacterAttributes(...)
.
Very likely you will want to override this method, calling its
superclass method but first checking to see if the attribute setting
mentions GUARDED
, whether setting it to TRUE
or FALSE
, and if so keeping track in another data
structure of which ranges of characters are currently guarded or not;
or you might already have a good way of keeping track of attributes in
general and would rather just query this attribute when it is
needed. If overriding setCharacterAttributes
for this
purpose, also remember to check the attributes on
insertString
,
etc.
NbDocument.runAtomicAsUser(...)
.
Internal IDE code may freely insert or remove text (typically using
NbDocument.insertGuarded(...)
and Document.remove(...)
). For example, this would be
done by the Form Editor component while adding a new event
handler. The editor implementation should permit such programmatic
inserts and removes, while corresponding user actions must be
forbidden.
StyleConstants.ColorConstants.Background
attribute describing which color will actually be used, and make this
color settable in a control panel, but assume that you need to
colorize based on this attribute yourself.
The recommended way to handle guards
It is highly recommended that all of the Swing actions (such as those
returned by
EditorKit.getActions()
)
applicable to your editor kit be reviewed for the possibility that
they might attempt to change the content of the document (so, not only
character inserts but search-and-replace, block paste, etc.). Any that
do perform some kind of mutation should be reimplemented (or
a wrapper written for them) so that they check to make sure that a
guarded block is not being violated.
Even better, whenever possible an action should be disabled when it can easily be determined in advance that it would be in violation if performed at that time. As an example, if the user moves the caret into the middle of a guarded block, it would be much preferable for the editor to disable "Paste" (say, on a right-click context menu) for the time being, rather than permitting a Paste action to be invoked but then displaying an error dialog or beeping!
StyledDocument
, such as Swing's
DefaultStyledDocument
,
which already has a full implementation of important things but no
recognition of guard blocks.
The idea is to first get a list of all
actions
which might involve buffer modification--or, to be conservative, just
all actions. For each of these, create a new action which calls the
original
actionPerformed
,
but "dynamically binds" some special flag stored with the document to
true--dynamic binding here means that it should be restored (turned
back off) in a finally
block, and also that its value
should be specific to the executing thread (and its children,
perhaps). This flag indicates that operations are currently being
performed from a user action.
Override the insertString(...)
and remove
methods. They should check the flag just mentioned. If it is turned
off, just call the superclass method normally. If it is turned on,
check whether the requested insertion or removal is affecting a
guarded area (insertion within, or removal within or overlapping). If
not, again call the superclass method. If there is an attempted
violation, you might do one or more of the following:
javax.swing.text.BadLocationException
and see what happens. Since the original action cannot ignore this
exception (since actionPerformed
does not have any
throws), it must do something--currently, it seems the Swing actions
simply beep.
Toolkit.beep()
).
This would probably need to be done with
SwingUtilities.invokeLater(...)
.
Now bind all invocation objects (such as keymaps) to the new "wrapper" actions. Note that just returning them from the editor kit may not suffice.
If you go to the trouble of identifying specifically which actions could cause a violation, and under what circumstances they would or would not do so, then you could make specific wrappers that could disable actions, or safely invoke them only when possible, calling the original action if all is well--this is just one way to do the recommended implementation.
SimpleAttributeSet sas=new SimpleAttributeSet();
sas.addAttribute(NbDocument.GUARDED, Boolean.TRUE);
doc.setCharacterAttributes(0, 100, sas, false);
Now verify that modification attempts in this block are disabled.
Handling IDE special attributes
The interface to IDE special attributes does not really extend Swing
at all--we just use
StyledDocument.setLogicalStyle(...)
.
The style will only have one attribute, which is
StyleConstants.ColorConstants.Background
, so you need
only pay attention to this attribute if you have no other use for
styles. Note that you do not need to know anything about the
particular type of attribute (debugger breakpoint vs. compiler error
line) being used--the IDE handles keeping track of (and letting the
user modify) the background colors for these, anyway.
setLogicalStyle
applies to paragraphs according to the
Swing architecture, but we really want to mark things like breakpoints
and so on by lines, since this is how debuggers and compilers
typically index Java source code. So, if you are to handle special IDE
attributes you should make Swing "paragraph" elements represent lines
of text--see
StyledDocument.getParagraphElement(...)
.
Important: whether or not you handle special attributes, you should make sure that lines in the editor are separated by exactly one text character--a newline. We currently rely on this to count character positions during parsing, so that this functionality may be efficient.
Note that you must listen to changes in the attributes present in document styles (unless you are simply using the names of these styles, and ignoring the background color completely). This is necessary to even get the colors right the first time, as the style is added to the document first and then its background color is set. But you should add a listener for each new style anyway, just to be sure. This is why styles are used instead of character attributes--we want to be able to change the display characteristics globally, and styles are more appropriate for this purpose.
Feel free to understand other style attributes besides
Background
, and listen on changes in them--currently only
Background
is used for these purposes by the rest of the
IDE, however.
Alternatively, if supporting arbitrary colors in your editor does
not make sense, you may prefer to just specially annotate lines with
the special styles. In this case it may make more sense to just
explicitly test for the styles
NbDocument.BREAKPOINT_STYLE_NAME
,
CURRENT_STYLE_NAME
,
and
ERROR_STYLE_NAME
.
Style st=doc.addStyle("test", null);
st.addAttribute(StyleConstants.ColorConstants.Background, Color.RED);
doc.setLogicalStyle(100, st);
Verify that the line at position 100 turns red. Now check that this also makes a live change:
If you want your editor to support printing of its contents from
the IDE, you might just provide no special support. In this case,
printing should be able to work with plain text. If you have a styled
document, it is possible that the IDE will be able to run through the
entire contents of the document; for each paragraph, retrieve its
However, this process could be rather slow; is not terribly exact;
and may not be implemented. To better support printing, it is
desirable for the Also, if your document supports either
If you are extending
You must also implement
Note that while both methods in this interface ought to provide a
transactional interface if at all possible--i.e., either succeed at
executing the entire There is currently no default convenience implementation of
If this is not done, the IDE creates its own replacement.
The IDE sometimes asks the editor to scroll the display so that a
given line will be displayed. You should not need to do anything
special to support this;
While it might be possible to add some of these things manually
into the IDE's action containers (like the "Main Window" entry in the
Environment, visible in the Explorer), this is discouraged as that
makes it more difficult for the IDE to manage actions added by various
modules.
Instead, you should use the Actions API to add
You do not need to do much to support this editor toolbar: in the
st.addAttribute(StyleConstants.ColorConstants.Background, Color.GREEN);
Printing
Style
, and for each character, its attribute set; and
then attempt to convert these (Swing) formatting specifications into
(AWT) attributed character descriptions, for purposes of printing.
StyledDocument
to implement
org.openide.text.NbDocument.Printable
,
which will allow it to specify exactly how it wants to be printed. The
method
NbDocument.Printable.createPrintIterators(...)
should return a list of
java.text.AttributedCharacterIterator
s,
providing attributes taken from
java.awt.font.TextAttribute
.
java.awt.print.Printable
or
java.awt.print.Pageable
,
then these interfaces will be used to perform the printing
directly. You should only need to do this if attributed characters do
not satisfactorily capture everything that you are interested in
printing--for example, if your editor is working on HTML and it is
desired to print embedded images.
Locking
It is desirable for your StyledDocument
implementation to
be able to lock the document against write access, as this will make
certain operations (performed by the IDE, not in response to user
events) more reliable and safer. In order to do this, please
implement
org.openide.text.NbDocument.WriteLockable
and its
runAtomic(...)
method. The Runnable
passed in this way should be
executed with all other write actions (and reads) disabled,
i.e. blocked.
javax.swing.text.AbstractDocument
,
please note that just enclosing the Runnable
block in
calls to
writeLock()
and
writeUnlock()
will not do the trick--these calls do take out exclusive locks,
however they specifically do not nest. This means that you cannot
lock and then enter a runnable this way, because any modifications
attempted within the runnable will throw an illegal state
exception. Also, the locking methods are final and cannot be
advised to nest. So you must find some other way to implement
this, e.g. your own locks.
NbDocument.WriteLockable.runAtomicAsUser(...)
,
which is very similar but is invoked on behalf of user actions
unrelated to whatever component created the guard blocks--e.g., this
would be used to rename a method from the Explorer, in which case the
rename ought to check that the renamed method is not guarded due to
being used by the Form Editor. Thus, it should attempt to make
modifications requested by the Runnable
, locking out
other modifications, but ensure that guard blocks are not violated.
Any attempted violation should be prevented (or rolled back at the end
of the runnable), and after the end of the runnable an appropriate
exception should be thrown.
Runnable
, or fail and leave the
document untouched--the transactional aspect is much more important to
implement properly for runAtomicAsUser
. This is because
it is quite possible for an innocent user action (e.g. attempting to
rename a JavaBean property) to interfere with a guard block, and the
document should not be left half-modified after such a
mistake. On the other hand, a failure in the block of
runAtomic
is more likely to be an bug in some IDE module,
and not the user's fault; so perfect recovery is of course less
important, as the bug should be fixed anyway.
runAtomicAsUser
, as any implementation may well be
closely tied to how guard blocks are implemented in that particular
editor--e.g. in Emacs there will be a quite idiosyncratic
implementation.
Biases
Please consider implementing
org.openide.text.NbDocument.PositionBiasable
and its
createPosition(...)
method, to create a position marker which not only moves freely with
insertions and deletions in nearby text, but also specifies the
direction the marker will move to when text is inserted at that
position.
Scrolling to display
Caret.setDot()
will be called and ought to perform the scrolling appropriately.
Presentable actions
It is desirable for the editor to present actions to the user that may
be invoked on the document and may be accessed in various ways. For
example, if your editor provides an
action
for reindenting a section of text, it is best if this action can be
integrated into the rest of the IDE consistently: a "Reindent" item
under the "Edit" menu on the main toolbar, active when your editor has
focus; a button with an icon representing reindentation placed onto
the IDE's toolbar; a context menu item within the editor pane named
"Reindent"; a keyboard shortcut Ctrl-R
; etc.
CallbackSystemAction
s
(e.g.) to your module.
Default toolbar
Although it is best to use the Actions API to specify in your module's
manifest file which actions to install where in the IDE, the IDE may
also create a toolbar attached to the editor window which will reflect
all displayable actions supported by your editor, automatically. This
may be better for less-commonly used actions which it would be
inappropriate to install into the main control window.
getActions()
method of your editor kit, if there are any actions which are also
SystemAction
s
implementing
Presenter.Toolbar
,
then this interface will be used to get a
java.awt.Component
which will be displayed in the editor toolbar. You are free to make
this component be whatever you like, e.g. a button to run some
routine, or a checkbox to toggle an option; just add the appropriate
event listeners to it.
Standard actions
A few basic editing commands you do not need to explicitly
present to the IDE; this will be done for you automatically. The IDE
tests whether an editor kit provides (from its
EditorKit.getActions()
method) any actions with certain
names. Default instances of these are also available in the
DefaultEditorKit
class, but your actions will be
recognized based on a call to
getValue(Action.NAME)
:
Name | Static field in
DefaultEditorKit |
---|---|
copy-to-clipboard | copyAction |
cut-to-clipboard | cutAction |
paste-from-clipboard | pasteAction |
There are a few more which unfortunately do not exist in the
DefaultEditorKit
, and therefore have no standard
names. The following may be used:
Name | Description |
---|---|
find | Pop up dialog to find a string in the text. |
replace | Pop up dialog to find & replace a string in the text. |
goto | Pop up dialog to go to some place in the text, e.g. a specific line number. |
delete | Delete the selection, without affecting clipboard. |
undo | Undo action. See package
javax.swing.undo for help. Currently not specially
supported by the IDE, except to call your action. |
redo | Redo action. Ditto. |
The corresponding items in the IDE's "Edit" menu, and other
standard invocations such as from the system toolbar, will
automatically invoke these actions on your document, if you supply
them. You should use
Action.isEnabled()
to indicate whether your action is ready, and thus whether or not the
corresponding UI elements in the IDE should be grayed out.
Undo/redo support
It is desirable for your document's user-level edit actions to implement
UndoableEdit
,
so that the IDE can smoothly provide Undo/Redo support for the editor
from the standard places (e.g. in the Edit menu). Note that
AbstractDocument
,
for example, already provides this support.
Customization
This API does not provide any special mechanism for allowing the user
to customize aspects of the editor's operation from the IDE; if you
want to do this, please use the Options API.
Installing
It should be straightforward to install the new editor into the
IDE--you will make a JAR file containing a manifest with attributes
recognized by the IDE as constituting an IDE module, and just call
javax.swing.JEditorPane.registerEditorKitForContentType(...)
,
and the IDE should subsequently use it.
It is possible that there will be special support in the Modules
API for registering editors. Although doing it manually in the
module's
installed()
method is not difficult, properly the module should also restore the
original editor kit for each content type upon an uninstall, and
ideally there would be a user-visible control panel permitting
selection of the editor for each content type from among those
claiming to support it.
This is not likely to be done unless it turns out to be popular to have multiple editors installed, and conflicts become a problem.
In the meantime, please refer to the Modules API for instructions on creating a module to contain your editor.
org.openide.filesystems.FileUtil.setMIMEType(String ext, String mimeType)
in the
module install method
to register it with the IDE (be sure to check for
IllegalArgumentException
in case of a conflict). This
does not really have anything to do with the editor, but may be
useful.