Table of Contents
This section explains how a build a custom XML editor displaying a styled view of the document.
This section uses samples/stylededitor/StyledEditor.java as an example. Class StyledEditor is derived from class SimpleEditor, described in previous section. StyledEditor.java is about 250 lines of Java™ language longer than SimpleEditor.java.
Compile StyledEditor by executing ant (see build.xml) in the samples/stylededitor/ directory.
Run StyledEditor by executing ant run in the samples/stylededitor/ directory. This will start StyledEditor and load sample XHTML document tests/in/sample1.html.
StyledEditor creates a StyledDocumentPane when SimpleEditor creates a DocumentPane.
protected DocumentPane createDocumentPane() {
return new StyledDocumentPane();
}A StyledDocumentPane uses a StyledDocumentView as its RootGadget when a DocumentPane uses a DocumentView as its RootGadget.
A StyledDocumentView uses a StyledViewFactory as its ViewFactory when a DocumentView uses a TreeViewFactory as its ViewFactory.
A ViewFactory is the abstract base class of all view factories.
A DocumentView delegates to a view factory the responsibility to create the views which are used to graphically represent document nodes.
Here the term view must be understood like in the MVC (Model View Controller) framework. A NodeView is a graphical view of its model: the Node.
A NodeView is sometimes a Gadget but not necessarily. It may be also a special purpose object which renders itself on screen using one or several hierarchies of Gadgets.
A TreeViewFactory cannot be much configured. You can mainly specify the level of details of what is being displayed: do you want to see all the characters of a Text node or just an outline? Do you want to see all the attributes of an Element or just an outline?
protected void configureDocumentView(DocumentView docView) {
TreeViewFactory viewFactory =
(TreeViewFactory) docView.getViewFactory();
TreeViewFactory.Settings settings = viewFactory.getSettings();
settings.showAttribute = showAttribute;
settings.showText = showText;
settings.showComment = showComment;
settings.showPI = showPI;
}SimpleEditor has an menu which allows the user to specify this level of details at any time.
This menu is implemented as follows:
public void actionPerformed(ActionEvent event) {
String command = event.getActionCommand();
.
.
.
} else if (command.equals("toggleShowAttribute")) {
toggleShowAttribute();
} else if (command.equals("toggleShowText")) {
toggleShowText();
} else if (command.equals("toggleShowComment")) {
toggleShowComment();
} else if (command.equals("toggleShowPI")) {
toggleShowPI();
.
.
.
}
protected void toggleShowAttribute() {
TreeViewFactory viewFactory =
(TreeViewFactory) docView.getViewFactory();
TreeViewFactory.Settings settings = viewFactory.getSettings();
settings.showAttribute = !settings.showAttribute;
viewFactory.applySettings();
}Note that the StyledViewFactory used in the StyledEditor derives from TreeViewFactory. This explains why the menu also works in StyledEditor.
StyledEditor adds a menu to SimpleEditor.
This menu is dynamically populated with items at document load time:
protected void updateStyleMenu() {
JMenu menu = menuBar.getMenu(STYLE_MENU);
// [0] No Style, [1] -
for (int i = menu.getItemCount() - 1; i >= 1; --i)
menu.remove(i);
if (styleSheets != null) {
menu.addSeparator();
for (int i = 0; i < styleSheets.length; ++i) {
StyleSheetInfo info = styleSheets[i];
JMenuItem item = new JMenuItem(info.getLabel());
item.setActionCommand("setStyle" + i);
item.addActionListener(this);
menu.add(item);
}
}
}It contains at least one entry to display document without a style sheet (tree view).
If the document being displayed has CSS style sheets associated to it (see below), this menu also has one entry per style sheet. These entries allows the user to switch from one style sheet to another at any time.
If the XML document being loaded contains <?xml-stylesheet?> processing instructions, we need to add to it a property that represents these processing instructions. Such property is called STYLE_SHEET_INFO_PROPERTY. Adding this property is done as follows:
protected Document loadDocument(File file) {
Document doc = super.loadDocument(file);
if (doc != null) {
StyleSheetInfo[] prop = StyleSheetInfo.loadStyleSheetPI(doc);
if (prop != null)
doc.putProperty(STYLE_SHEET_INFO_PROPERTY, prop);
}
return doc;
}The value of this property is an array of StyleSheetInfo objects. A StyleSheetInfo is, to make it simple, the parsed form of the <?xml-stylesheet?> PI.
StyledEditor overrides installDocument() in order to sort this array of StyleSheetInfo (if any):
protected void installDocument(Document doc) {
super.installDocument(doc);
if (installStyleSheets(doc))
setStyle(0);
}
protected boolean installStyleSheets(Document doc) {
StyleSheetInfo[] prop =
(StyleSheetInfo[]) doc.getProperty(STYLE_SHEET_INFO_PROPERTY);
StyleSheetInfo[] list = null;
int count = 0;
int firstNonAlternate = -1;
if (prop != null) {
list = new StyleSheetInfo[prop.length];
for (int i = 0; i < prop.length; ++i) {
StyleSheetInfo info = prop[i];
if (info.type.equals("text/css") &&
(info.media == null || info.media.equals("screen"))) {
if (!info.alternate && firstNonAlternate < 0)
firstNonAlternate = count;
list[count++] = info;
}
}
}
if (count == 0)
return false;
if (count > 10)
count = 10;
if (count == list.length) {
styleSheets = list;
} else {
styleSheets = new StyleSheetInfo[count];
System.arraycopy(list, 0, styleSheets, 0, count);
}
if (firstNonAlternate < 0)
firstNonAlternate = 0;
if (firstNonAlternate != 0) {
StyleSheetInfo swap = list[firstNonAlternate];
if (firstNonAlternate < styleSheets.length)
styleSheets[firstNonAlternate] = styleSheets[0];
styleSheets[0] = swap;
}
updateStyleMenu();
getMenuItem(STYLE_MENU, NO_STYLE_ITEM).setEnabled(true);
return true;
}Changing the style sheet of a StyledDocumentView is done by invoking StyledViewFactory.setStyleSheet, passing a StyleSheet object to the StyledViewFactory. Passing null is allowed in order to display a tree view.
protected void setStyle(int index) {
StyleSheet styleSheet = null;
if (index >= 0) {
StyleSheetInfo info = styleSheets[index];
styleSheet = loadStyleSheet(info.url);
if (styleSheet == null) {
JMenuItem item = getMenuItem(STYLE_MENU, 2+index);
item.setEnabled(false);
}
}
StyledViewFactory viewFactory =
(StyledViewFactory) docView.getViewFactory();
if (styleSheet != null || viewFactory.getStyleSheet() != null)
setStyleSheet(viewFactory, styleSheet);
}Note that the StyleSheet object is rebuilt from its CSS source each time the user switches from a style sheet to another. Creating a StyleSheet object from a CSS source is the job of the StyleSheetLoader.
protected StyleSheet loadStyleSheet(URL url) {
StyleSheetLoader loader = new StyleSheetLoader();
StyleSheet styleSheet = null;
String error = null;
try {
styleSheet = loader.load(url, /*media*/ null);
} catch (Exception e) {
error = ThrowableUtil.reason(e);
}
if (styleSheet == null) {
Alert.showError(frame,
"Cannot load CSS style sheet '" + url + "':\n" +
error);
} else {
StyleSheetLoader.Warning[] warnings = loader.getWarnings();
if (warnings.length > 0) {
StringBuffer messages = new StringBuffer();
for (int j = 0; j < warnings.length; ++j) {
StyleSheetLoader.Warning warn = warnings[j];
messages.append(warn.url);
messages.append(':');
messages.append(warn.lineNumber);
messages.append(':');
messages.append(warn.columnNumber);
messages.append(": ");
messages.append(warn.message);
messages.append("\n\n");
}
Alert.showWarning(frame,
"CSS style sheet '" + url + "' has errors:",
messages.toString(), true, 10, 40);
}
}
return styleSheet;
}setStyleSheet() is equivalent to StyledViewFactory.setStyleSheet.