Table of Contents
What to do when cascading style sheets (CSS) are not powerful enough to style XML elements exactly like you want? Answer: write custom style sheet extensions in Java™ using the APIs described in this chapter.
In this chapter, we will use a custom XML schema: email.xsd. This schema is used to model a simple email message:
Root element message contains:
Required from, to elements,
Optional replyTo, cc, bcc elements,
Required subject, body elements,
Optional signature, attachments elements.
A body contains at least one para, literallayout, itemizedlist or orderedlist element (similar to their DocBook counterparts).
A para contains text interspersed with any number of email, ulink, emphasis, inlinegraphic or smiley elements.
An emphasis element has a role attribute with 2 values properly styled by the CSS style sheet: bold and highlight.
A smiley is an empty element having an emotion attribute with many possible values: happy, wink, vicious, etc.
Using a CSS without custom extensions to style an email message gives good results, but here we want excellent results. And CSS alone cannot solve the following problems:
A message has From:, To:, Subject:, etc, headers. We would like to see the name of these headers displayed in French (De:, À:, Objet:, etc) if the user adds the attribute xml:lang=fr to the root message element.
An emphasis element can contain another emphasis element and this, at any nesting level. We would like emphasis elements having an even number of emphasis ancestors to be displayed using an italic font. We would like emphasis elements having an odd number of emphasis ancestors to be displayed using a plain (non-italic) font.
Figure 8.2. In the three following paragraphs, nested emphasis elements (containing words "nested emphasis text") are displayed using a non-italic font

Like in DocBook, orderedlist elements have a continuation attribute. This attribute has two possible values:
This is the default value of the continuation attribute.
If a message body contains an orderedlist having 2 listitems (therefore numbered 1 and 2), followed by another orderedlist having 2 listitems and if the second orderedlist has continuation=restarts, its listitems are numbered 1 and 2.
If a message body contains an orderedlist having 2 listitems (therefore numbered 1 and 2), followed by another orderedlist having 2 listitems and if the second orderedlist has continuation=continues, its listitems are numbered 3 and 4.
The text of a message can be interspersed with smileys expressing emotions: happy, sad, tired, etc. Not only we would like these smiley elements to be represented graphically (
,
,
, etc) but we also would like to use a combobox embedded in the document view to directly edit the emotion attribute of a smiley element.
The from header, for example, is normally styled using these CSS rules:
from {
display: block;
margin-left: 15ex;
}
from:before {
display: marker;
color: #004080;
font-weight: bold;
content: "From:";
}To solve our problem, we need to replace:
content: "From:";
by:
content: invoke("localize", "from") ":";where localize is a custom method, which, given a key ("from" in the above example), returns a localized string.
Localize is a method defined in a class named StyleSheetExtension (see samples/email/StyleSheetExtension.java). The following at-rule, added at the top of email.css, registers this class with XXE style engine (StyledViewFactory) as being a style sheet extension.
@extension "StyleSheetExtension navy white";
More precisely:
When a CSS style sheet is loaded, the style engine searches @extension " in it.arg0 arg1 ... argN"
If such at-rule is found, the style engine creates an instance of the class with fully qualified name arg0 and keeps this instance as long as the style sheet is in use.
This class must have a public constructor with the following signature:
class_name(String[]args, StyledViewFactoryviewFactory)
When a property value contains a pseudo-function call like invoke(, the style engine attempts to find a public method named arg0, arg1, ..., argN)arg0 in the class described above.
This method must have the following signature:
StyleValuemethod_name(StyleValue[]args, NodecontextNode, StyledViewFactoryviewFactory)
If such method is found, it is invoked and the returned result is used to specify the property value.
The constructor of StyleSheetExtension is:
public StyleSheetExtension(String[] args, StyledViewFactory viewFactory) { viewFactory.addDependency(EMAIL_NAMESPACE
, "message", Namespace.XML, "lang");
. . . }
The | |
| |
When a CSS style sheet contains a rule such as: p[align=left] {
text-align: left;
}the style engine knows that the view of a In email.css, there is no rule which explicitly instructs the style engine that the view of a |
The localize method is implemented as follows:
public StyleValue localize(StyleValue[] args, Node contextNode,
StyledViewFactory viewFactory) {
String text;
if (args.length != 1 || (text = args[0].stringValue()) == null) {
System.err.println("usage: content: invoke('localize', text);");
return null;
}
Element root = contextNode.getDocument().getRootElement();
String lang = root.getTokenAttribute(Name.XML_LANG, "en");
ResourceBundle messages = getMessages(lang);
if (messages == null && !"en".equals(lang))
messages = getMessages("en");
String localizedText = null;
if (messages != null) {
try {
localizedText = messages.getString(text);
} catch (Exception ignored) {}
}
if (localizedText == null)
return args[0];
else
return StyleValue.createString(localizedText);
}
private static HashMap<String, ResourceBundle> langToMessages =
new HashMap<String, ResourceBundle>(5);
private static ResourceBundle getMessages(String lang) {
ResourceBundle messages = langToMessages.get(lang);
if (messages == null) {
try {
messages = ResourceBundle.getBundle("messages/Messages",
new Locale(lang));
} catch (MissingResourceException ignored) {}
if (messages != null)
langToMessages.put(lang, messages);
}
return messages;
}The implementation of the localize method is straightforward:
Get the xml:lang attribute of the root message element, if any. The value of this attribute specifies the language to use.
Try to load a ResourceBundle containing messages localized to this language.
Use the key passed as an argument to read the localized text from the ResourceBundle.
About the implementation:
| |
getTokenAttribute is one of the many convenience methods which return an attribute value. Unlike getAttribute, getTokenAttribute properly strips whitespaces from the attribute value. | |
StyleValue represents a parsed CSS property value. It is a simple data structure which contains a bunch of public fields. Examples: public Type type;
public Keyword keyword;
public double number;
public String string;
public Name name;
public StyleValue[] list;
public Color color;
public StringExpr xpath;The |
Style sheet email.css could have contained:
emphasis {
display: inline;
font-style: italic;
}
emphasis[role=bold] {
font-style: normal;
font-weight: bold;
}
emphasis[role=hightlight] {
font-style: normal;
background-color: navy;
color: white;
}But email.css does not contain any rule to style emphasis elements. Instead, class StyleSheetExtension extends class StyleSpecsBase (which is an adapter class for interface StyleSpecs) and its constructor registers the StyleSheetExtension instance with the StyledViewFactory as being a set of intrinsic style specifications.
Excerpt of the constructor:
switch (args.length) {
case 2:
highlightForeground = StyleValue.parseColor(args[1]);
/*FALLTHROUGH*/
case 1:
highlightBackground = StyleValue.parseColor(args[0]);
break;
}
if (highlightBackground == null)
highlightBackground = Color.yellow;
if (highlightForeground == null)
highlightForeground = Color.red;
viewFactory.addIntrinsicStyleSpecs(this);
viewFactory.addDependency(EMAIL_NAMESPACE, "emphasis",
Namespace.NONE, "role");
The constructor is passed two strings " | |
The newly constructed | |
In |
Interface StyleSpecs specifies the services expected by XXE style engine from a style sheet.
An actual StyleSheet of course implements StyleSpecs. Custom code could also implement this interface to style a few, otherwise hard to style, elements. For example, such custom code is used to style XHTML (HTML 4) tables[7] and another custom code is used to style DocBook (CALS) tables.
If an implementation of StyleSpecs has been registered, the style engine first uses it to find styles for the target of current CSS rule, then it uses the regular StyleSheet to find more styles. Therefore the styles returned by the StyleSheet may override those returned by the StyleSpecs.
An implementation of StyleSpecs generally just defines the following method:
int findStyleSpec(Element element, StyleSpec[] specs);
The style engine passes to the StyleSpecs the element which is the target of current CSS rule and an array of 3 pre-created StyleSpec data structures.
specs[StyleSpecs.ELEMENT] must be filled with the styles of the element itself.
specs[StyleSpecs.BEFORE_ELEMENT] must be filled with the styles of the content generated before the element.
specs[StyleSpecs.AFTER_ELEMENT] must be filled with the styles of the content generated after the element.
The returned value is a mask specifying which one, of the 3 StyleSpec data structures, has been filled with styles.
public int findStyleSpec(Element element, StyleSpec[] specs) {
if (element.getName() == EMPHASIS) {
styleEmphasis(element, specs[StyleSpecsBase.ELEMENT]);
return StyleSpecsBase.ELEMENT_MASK;
}
return 0x0;
}
private void styleEmphasis(Element element, StyleSpec styleSpec) {
int role = getRole(element);
int nesting = 0;
Element ancestor = element.getParentElement();
while (ancestor != null) {
if (ancestor.getName() != EMPHASIS ||
getRole(ancestor) != role)
break;
++nesting;
ancestor = ancestor.getParentElement();
}
switch (role) {
case BOLD:
if ((nesting % 2) == 0) {
setFontWeight(styleSpec, StyleValue.Keyword.BOLD);
} else {
setFontWeight(styleSpec, StyleValue.Keyword.NORMAL);
}
break;
case HIGHLIGHT:
if ((nesting % 2) == 0) {
setBackgroundColor(styleSpec, highlightBackground);
setColor(styleSpec, highlightForeground);
} else {
setBackgroundColor(styleSpec, Color.white);
setColor(styleSpec, Color.black);
}
break;
default:
if ((nesting % 2) == 0) {
setFontStyle(styleSpec, StyleValue.Keyword.ITALIC);
} else {
setFontStyle(styleSpec, StyleValue.Keyword.NORMAL);
}
}
}
private static final int ITALIC = 0;
private static final int BOLD = 1;
private static final int HIGHLIGHT = 2;
private static int getRole(Element element) {
String role = element.getTokenAttribute(ROLE, null);
if ("bold".equals(role))
return BOLD;
else if ("highlight".equals(role))
return HIGHLIGHT;
else
return ITALIC;
}
private StyleValue fontStyleValue =
StyleValue.createIdentifier(StyleValue.Keyword.NORMAL);
private StyleValue fontWeightValue =
StyleValue.createIdentifier(StyleValue.Keyword.NORMAL);
private StyleValue backgroundColorValue =
StyleValue.createColor(Color.white);
private StyleValue colorValue =
StyleValue.createColor(Color.black);
private void setFontStyle(StyleSpec styleSpec,
StyleValue.Keyword fontStyle) {
fontStyleValue.initIdentifier(fontStyle);
styleSpec.fontStyle = fontStyleValue;
}
private void setFontWeight(StyleSpec styleSpec,
StyleValue.Keyword fontWeight) {
fontWeightValue.initIdentifier(fontWeight);
styleSpec.fontWeight = fontWeightValue;
}
private void setBackgroundColor(StyleSpec styleSpec, Color color) {
backgroundColorValue.color = color;
styleSpec.backgroundColor = backgroundColorValue;
}
private void setColor(StyleSpec styleSpec, Color color) {
colorValue.color = color;
styleSpec.color = colorValue;
}findStyleSpecs is called very often. An implementation of such method must decide very quickly whether it can return styles for the target element or not. | |
The logic of | |
If the number of | |
The way XXE style engine is written allows to reuse pre-created StyleValues. | |
A StyleSpec is a simple data structure which contains one StyleValue field per CSS property supported by XXE. Examples: public StyleValue marginTop = null;
public StyleValue marginRight = null;
public StyleValue marginBottom = null;
public StyleValue marginLeft = null;
public StyleValue paddingTop = null;
public StyleValue paddingRight = null;
public StyleValue paddingBottom = null;
public StyleValue paddingLeft = null;
public StyleValue borderStyle = null;
public StyleValue borderWidth = null;
public StyleValue borderTopColor = null;
.
.
. |
A listitem contained in an orderedlist could be styled using the following rules:
listitem {
display: block;
}
orderedlist > listitem {
margin-left: 6ex;
}
orderedlist > listitem:before {
display: marker;
content: counter(n, decimal) ".";
font-weight: bold;
color: #004080;
}With the above rules, orderedlists with continuation=continues are not properly styled. Therefore, in email.css, last rule has been replaced by:
orderedlist > listitem:before {
display: marker;
content: invoke("listItemCounter");
font-weight: bold;
color: #004080;
}Custom method listItemCounter is implemented as follows:
public StyleValue listItemCounter(StyleValue[] args, Node contextNode,
StyledViewFactory viewFactory) {
int index = indexOfListItem((Element) contextNode);
return StyleValue.createString(Integer.toString(1 + index) + '.');
}
private static int indexOfListItem(Element listItem) {
Element orderedList = listItem.getParentElement();
if (orderedList == null || orderedList.getName() != ORDEREDLIST)
return -1;
int index = orderedList.indexOfChildElement(listItem);
int offset = 0;
String continuation = orderedList.getNmtokenAttribute(CONTINUATION,
"restarts");
if ("continues".equals(continuation)) {
Element prevOrderedList = null;
if (orderedList.getParentElement() != null) {
Node node = orderedList.getPreviousSibling();
while (node != null) {
if ((node instanceof Element) &&
((Element) node).getName() == ORDEREDLIST) {
prevOrderedList = (Element) node;
break;
}
node = node.getPreviousSibling();
}
} // Otherwise, orderedList is the root element.
if (prevOrderedList != null) {
Element last = prevOrderedList.getLastChildElement();
if (last != null) {
offset = indexOfListItem(last) + 1;
} // Otherwise, prevOrderedList is invalid.
}
}
return (offset + index);
}In the solution of problem #1, we have already explained all the concepts behind a custom method such as StyleSheetExtension.listItemCounter. So what is new in problem #3?
With the above code, listitems contained in orderedlists with continuation=continues are properly styled. But if you insert or delete orderedlists in an email message, other orderedlists with continuation=continues are not properly updated.
Just invoking
viewFactory.addDependency(EMAIL_NAMESPACE, "orderedlist",
Namespace.NONE, "continuation");in the constructor of StyleSheetExtension to declare a dependency between orderedlist and its continuation attribute is obviously not sufficient.
Here the idea is to write some custom code which observes modifications made to the email message and which rebuilds the views of orderedlists with continuation=continues when needed to.
This custom code is an implementation of interface BasicElementObserver. A BasicElementObserver must implement:
void elementChanged(DocumentEvent[] events);
This method is invoked by the CustomViewManager of the StyledViewFactory each time the structure or the attributes (but not the text contained in mixed elements) of elements of interest have been modified. (More about CustomViewManagers in next section.)
The implementation of BasicElementObserver is created and registered with XXE style engine in the constructor of StyleSheetExtension:
CustomViewManager.NamePattern[] observed = {
new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "body"),
new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "listitem"),
new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "orderedlist")
};
viewFactory.getCustomViewManager().add(
new OrderedListObserver(viewFactory), observed);
A NamePattern specifies a set of qualified names. Unlike Name, it supports wildcards like "any qualified name with a given local part" or like "any qualified name in a given namespace". | |
This statement means: invoke |
Class OrderedListObserver is implemented as follows:
private static class OrderedListObserver
implements CustomViewManager.BasicElementObserver {
private DocumentView docView;
private ArrayList<Element> orderedLists = new ArrayList<Element>();
public OrderedListObserver(StyledViewFactory viewFactory) {
docView = viewFactory.getDocumentView();
}
public void customViewAdded() {}
public void customViewRemoved() {}
public void elementChanged(DocumentEvent[] events) {
orderedLists.clear();
for (int i = 0; i < events.length; ++i) {
DocumentEvent event = events[i];
switch (event.getType()) {
case CHILD_ADDED:
case CHILD_REPLACED:
case CHILD_REMOVED:
{
TreeEvent e = (TreeEvent) event;
Element element = e.getElementSource();
if (element == null)
break;
if (element.getName() == ORDEREDLIST) {
add(orderedLists, element);
} else {
if (isOrderedList(e.getOldChild()) ||
isOrderedList(e.getNewChild())) {
// Add first child orderedlist (if any).
Node node = element.getFirstChild();
while (node != null) {
if ((node instanceof Element) &&
((Element) node).getName() ==
ORDEREDLIST) {
add(orderedLists, (Element) node);
break;
}
node = node.getNextSibling();
}
}
}
}
break;
// ------------------------------------------------------
// Left as an exercise, treat INCLUSION_UPDATED similarly
// to CHILD_REPLACED
// ------------------------------------------------------
case ATTRIBUTE_ADDED:
case ATTRIBUTE_CHANGED:
case ATTRIBUTE_REMOVED:
{
Element element =
((AttributeEvent) event).getElementSource();
if (element.getName() == ORDEREDLIST) {
add(orderedLists, element);
}
}
break;
}
}
int count = orderedLists.size();
if (count > 0) {
for (int i = 0; i < count; ++i) {
Element orderedList = (Element) orderedLists.get(i);
if (orderedList.getParentElement() != null) {
// Add all following orderedlist siblings (if any).
Node node = orderedList.getNextSibling();
while (node != null) {
if ((node instanceof Element) &&
((Element)node).getName() == ORDEREDLIST) {
add(orderedLists, (Element) node);
}
node = node.getNextSibling();
}
} // Otherwise, orderedList is the root element.
}
count = orderedLists.size();
for (int i = 0; i < count; ++i) {
docView.rebuildView((Element) orderedLists.get(i));
}
orderedLists.clear();
}
}
private static final void add(ArrayList<Element> orderedLists,
Element orderedList) {
if (!orderedLists.contains(orderedList))
orderedLists.add(orderedList);
}
private static final boolean isOrderedList(Node node) {
if (node == null || !(node instanceof Element))
return false;
else
return (((Element) node).getName() == ORDEREDLIST);
}
}The above implementation is simple but not very efficient:
First pass: add to set Document modifications are reported as DocumentEvents. A | |
Add to the set the | |
Add to the set all the | |
Add to the set the | |
Second pass: For each | |
Third pass: rebuild the views of all the |
A smiley element is styled as follows:
smiley {
content: component("Smiley");
font: normal normal small sans-serif;
/* Needed to display the red border of the selection */
display: inline-block;
padding: 1px;
}Here, normal content has been replaced by custom content: component("Smiley"). When XXE style engine finds the component pseudo-function in a style sheet, it creates an instance of the class whose fully qualified name has been passed as the argument of component.
This class must implement interface ComponentFactory.
Component createComponent(Element element,
Style style, StyleValue[] parameters,
StyledViewFactory viewFactory,
boolean[] stretch)The factory is used to create a custom view of Element element.
This custom view may use the Style of this element. (Our Smiley example will use the font specified in the CSS rule: sans-serif, small.)
Passing extra parameters to component after the fully qualified name of the factory class is possible. Theses parameters, which are StyleValues, that is parsed CSS property values, are passed in the parameters array.
The factory can set stretch[0] to true if it wants the custom view to be enlarged or shrunken when the document view is itself enlarged or shrunken. stretch[0] specifies this option for the width of the custom view. stretch[1], which specifies this option for the height of the custom view, is currently ignored.
The returned custom view is simply a newly created AWT component [9]which has been properly configured to render graphically its model: part or all of Element element.
Before really solving problem #4, we will explain here how to write the simplest custom views.
Alternate style sheet email_passive_smiley.css contains:
@import url(email.css);
smiley {
content: component("PassiveSmiley");
}Class PassiveSmiley is a very simplified version of Smiley:
| PassiveSmiley | Smiley |
|---|---|
Represents smiley elements a JLabels. | Represents smiley elements as JComboBoxes. |
The value of the emotion attribute must be changed using the Attribute tool. | The JComboBox can be used to change the value of the emotion attribute, directly from the document view. |
public class PassiveSmiley implements ComponentFactory {
private static final Name EMOTION = Name.get("emotion");
public Component createComponent(Element element,
Style style, StyleValue[] parameters,
StyledViewFactory viewFactory,
boolean[] stretch) {
SmileyLabel smileyLabel = new SmileyLabel(element);
smileyLabel.setFont(style.font);
return smileyLabel;
}
private static class SmileyLabel extends JLabel implements TextLines
{
private Element element;
public SmileyLabel(Element element) {
String emotion = element.getNmtokenAttribute(EMOTION, "happy");
SmileyInfo smiley = null;
SmileyInfo[] smileys = SmileyInfo.getKnownSmileys();
for (int i = 0; i < smileys.length; ++i) {
if (smileys[i].getEmotion().equals(emotion)) {
smiley = smileys[i];
break;
}
}
if (smiley == null)
// Invalid emotion. Show it anyway.
smiley = new SmileyInfo(emotion, null, "???");
if (smiley.getIcon() != null)
setIcon(smiley.getIcon());
setText(smiley.toString());
}
public int getFirstBaseLine() {
return ComponentUtil.getFirstBaseLine(this);
}
public int getLastBaseLine() {
return getFirstBaseLine();
}
}
}The implementation of the | |
Implementing interface TextLines is a refinement which allows to specify the baseline of a custom view. The implementation uses utility ComponentUtil.getFirstBaseLine. | |
Static method public class SmileyInfo {
private String emotion;
private ImageIcon icon;
private String asciiArt;
public SmileyInfo(String emotion, ImageIcon icon, String asciiArt) {
this.emotion = emotion;
this.icon = icon;
this.asciiArt = asciiArt;
}
.
.
.
public static SmileyInfo[] getKnownSmileys() {
.
.
.
}
}Known smileys are listed in the samples/email/smileys/smileys.properties property file. This property file and all the smiley icons are resources contained in |
PassiveSmiley would not work very well without explicitly declaring a dependency between the smiley element and its emotion attribute in the constructor of StyleSheetExtension:
viewFactory.addDependency(EMAIL_NAMESPACE, "smiley",
Namespace.NONE, "emotion");Like class PassiveSmiley, class Smiley also implements interface ComponentFactory.
public class Smiley implements ComponentFactory {
private static final Name EMOTION = Name.get("emotion");
public Component createComponent(Element element,
Style style, StyleValue[] parameters,
StyledViewFactory viewFactory,
boolean[] stretch) {
SmileyComboBox smileyCombo = new SmileyComboBox(element);
smileyCombo.setFont(style.font);
viewFactory.getCustomViewManager().add(smileyCombo, element, EMOTION);
ComponentUtil.addFocusGainedListener(smileyCombo, element,
viewFactory);
return smileyCombo;
}
.
.
.
}
| |
Unlike the void attributeValueChanged(DocumentEvent[] events); The above add statement registers the It means: invoke the | |
ComponentUtil.addFocusGainedListener is a refinement which automatically selects the element (and therefore draws a red box around the |
A CustomViewManager is a helper object owned by the StyledViewFactory. This object is a registry for custom views created by applying certain CSS rules to elements. This registry is cleared each time the StyleSheet is changed using setStyleSheet.
A rule like the following creates a custom view.
smiley {
content: component("Smiley");
font: normal normal small sans-serif;
/* Needed to display the red border of the selection */
display: inline-block;
padding: 1px;
}Immediately after the creation of an active custom view, the factory that created it registers it with the CustomViewManager.
The CustomViewManager, which is a DocumentEventListener, mainly forwards DocumentEvents to its registered custom views.
When and which DocumentEvents are sent to a custom view depend on the interface implemented by the custom view. We have already studied BasicElementObserver and AttributeValueEditor, but there are many other kinds of custom views: SimpleCounter, BasicElementEditor, SimpleElementEditor, ElementEditor, ElementValueEditor.
An alternative to CustomViewManager would be to have custom views directly implement interface DocumentEventListener. In such case, all custom views (possibly thousands) would receive all document events. Not only this would make custom views tedious to write (because they would have to filter themselves uninteresting events) but this would also be extremely inefficient.
CustomViewManager also notifies its registered custom views:
when a custom view is actually registered, by invoking method customViewAdded;
when a custom view is no longer in use (because its model has been removed from the document), by invoking method customViewRemoved;
when a custom view needs to update its model, by invoking method commitChanges.
Imagine a JTextField used to implement an attribute editor. User has typed a value in the JTextField and then has typed Ctrl+S to save its document. Before the document is saved, commitChanges instructs the JTextField that the new value needs to be assigned to the attribute.
private static class SmileyComboBox extends JComboBox
implements CustomViewManager.AttributeValueEditor,
TextLines {
private Element element;
private boolean performingAction = false;
private boolean selectingItem = false;
public SmileyComboBox(Element element) {
super(SmileyInfo.getKnownSmileys());
setEditable(false);
setRenderer(new SmileyRenderer());
this.element = element;
updateSelectedItem();
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
if (selectingItem)
return;
SmileyInfo smiley = (SmileyInfo) getSelectedItem();
if (smiley != null) {
performingAction = true;
putAttribute(SmileyComboBox.this.element,
EMOTION, smiley.getEmotion());
performingAction = false;
}
}
});
}
public void customViewAdded() {}
public void customViewRemoved() {}
public void commitChanges() {}
public void attributeValueChanged(DocumentEvent[] events) {
if (performingAction)
return;
updateSelectedItem();
}
private void updateSelectedItem() {
String emotion = element.getNmtokenAttribute(EMOTION, "happy");
SmileyInfo smiley = null;
SmileyInfo[] smileys = SmileyInfo.getKnownSmileys();
for (int i = 0; i < smileys.length; ++i) {
if (smileys[i].getEmotion().equals(emotion)) {
smiley = smileys[i];
break;
}
}
if (smiley == null)
// Invalid emotion. Show it anyway.
smiley = new SmileyInfo(emotion, null, "???");
selectingItem = true;
// setSelectedItem triggers actionPerformed.
setSelectedItem(smiley);
selectingItem = false;
}
public int getFirstBaseLine() {
return ComponentUtil.getFirstBaseLine(this);
}
public int getLastBaseLine() {
return getFirstBaseLine();
}
}Interactively selecting an item using the | |
The private static final String putAttribute(Element element,
Name name, String value) {
if (element.isEditable())
return element.putAttribute(name, value);
else
return null;
} | |
When the | |
The |
Compile StyleSheetExtension.java, SmileyInfo.java, PassiveSmiley.java and Smiley.java by running ant in the samples/email/ directory.
This command will also archive the compiled code in samples/email/email_config/email.jar.
Directory samples/email/email_config/ contains an XXE configuration for the email document class.
This directory contains:
The XXE configuration.
The XML schemas used to model an email message.
The CSS style sheet (and its resources) used to style an email message.
An alternate CSS style sheet.
A template for creating new email messages.
Custom code. In order to tell XXE to load this jar file, simply copy it to one of the directories scanned by XXE at startup time (e.g. )XXE_user_preferences_dir/addon/
Copy the whole directory to to make XXE load the configuration next time it is started. XXE user preferences directory is:XXE_user_preferences_dir/addon/
on Linux, Mac, and more generally, on Unix.$HOME/.xxe4/
on Windows 2000, XP, Vista.%APPDATA%\XMLmind\XMLEditor4\
Example: C:\Documents and Settings\john\Application Data\XMLmind\XMLEditor4\ on Windows 2000 and XP. C:\Users\john\AppData\Roaming\XMLmind\XMLEditor4\ on Windows Vista.
Restart XXE.
Use → and select Email Message/From me to you to create a new email message.
Or open the sample email message contained in samples/email/tests/in/sample.email.
[7] Even if this code has been written by XMLmind staff, it is technically custom code. That is,
this code could have been written by third-party programmers using documented APIs;
this code is not contained in xxe.jar;
instead this code is dynamically discovered by XXE at startup time.
[8] Like body, listitems can contain orderedlists.
[9] Note that a Swing JComponent is also an AWT Component.