We'll not discuss what is a word in this lesson. Let's suppose a word is simply a contiguous sequence of non-space characters.
We'll not describe how words are actually counted.
There are many ways to implement word counting in XMLmind XML Editor, from simplest to hardest:
Write a CommandBase
which, when invoked, counts the words found in the selection and then, prints this word count using DocumentView.showStatus
. After that:
declare the command in the .xxe_gui
file;
reference this command in the hidden
child of the layout
element of the .xxe_gui
file;
declare the action making use of this command;
reference this action somewhere in the .xxe_gui
file (e.g. in a menu
or toolBar
element itself referenced in the layout
element of the .xxe_gui
file).
Write a AppTool
, a very simple extension of interface AppPart
, which will be added to the status bar. The word count will be automatically updated each time the active document is saved. If the user wants to count words without saving the document, she will have to click on the AppTool
.
Write a AppTool
which will be added to the status bar. The word count will be automatically updated each time the editing context is changed in the active document (i.e. caret moved to another text node, nodes are explicitly selected, etc). This AppTool would need to return true
in AppPart.isEditingContextSensitive
and would need to do most of its job in AppPart.editingContextChanged
.
We will not implement [a] because we have already explained how to write a custom Command
.
We will not implement [c] because this would be too hard. Any context-sensitive part needs to be fast. This would mean implementing an incremental method for counting of words.
Excerpts from CountWordsTool.java
:
public class CountWordsTool extends JPanel implements AppTool { private App app; private String id; private String helpId; ... public CountWordsTool() { ... } public void initApp(App app, String id) { this.app = app; this.id = id; } public App getApp() { return app; } public String getId() { return id; } public void setHelpId(String helpId) { this.helpId = helpId; } public String getHelpId() { return helpId; } ...
These methods belows can always be implemented mechanically as shown above:
private boolean activated; private int minCharCount; ... public void applyPreferences() { Preferences prefs = app.getPreferences(); activated = prefs.getBoolean("countWords", false); minCharCount = prefs.getInt("countedWordMinChars", 1, 1000, 1); toggle.setSelected(activated); activationChanged(); } public void flushPreferences() { Preferences prefs = app.getPreferences(); prefs.putBoolean("countWords", toggle.isSelected()); prefs.putInt("countedWordMinChars", minCharCount); }
The word counter supports two user preferences:
| |
|
private static final class WordCount { public int wordCount; public boolean needUpdate; public WordCount() { wordCount = 0; needUpdate = true; } } private static final String WORD_COUNT_PROPERTY = "CountWordsTool.WordCount"; ... public void activeEditorChanged() { if (!activated) { return; } WordCount wc = null; OpenedDocument openedDoc = app.getActiveOpenedDocument(); if (openedDoc != null) { wc = (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY); if (wc == null) { wc = new WordCount(); openedDoc.putProperty(WORD_COUNT_PROPERTY, wc); } } updateField(wc); } public void saveStateChanged() { if (!activated) { return; } OpenedDocument openedDoc = app.getActiveOpenedDocument(); if (openedDoc.isSaveNeeded()) { WordCount wc = (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY); wc.needUpdate = true; updateField(wc); } else { updateWordCount(openedDoc); } } ... private void updateWordCount(OpenedDocument openedDoc) { WordCount wc = (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY); wc.wordCount = countWords(openedDoc.getDocument()); wc.needUpdate = false; updateField(wc); } private int countWords(Document doc) { ... }
Each time the active In order to do that, the counter adds a client property to each | |
The active Note that | |
The counter needs to recompute the word count of a document each time this document is saved. Therefore, the counter needs to implement the | |
Here | |
Here |
private JToggleButton toggle; private JTextField field; private Color needUpdateColor; private Color upToDateColor; ... public CountWordsTool() { setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0)); toggle = new JToggleButton( new ImageIcon(CountWordsTool.class.getResource( "icons/wordCountTool.png"))); DialogUtil.setIconic(toggle); toggle.setToolTipText("Turns word counting on and off"); toggle.setFocusable(false); add(toggle); toggle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { activated = toggle.isSelected(); activationChanged(); } }); field = new JTextField(10); field.setFont(new Font("SansSerif", Font.PLAIN, Math.max(10, font.getSize()-2))); field.setToolTipText("Word count (click on it to update it)"); InfoBorder.configureField(field); add(field); needUpdateColor = field.getBackground().darker(); upToDateColor = field.getForeground(); field.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent event) { if (!activated) { return; } OpenedDocument openedDoc = app.getActiveOpenedDocument(); if (openedDoc != null) { updateWordCount(openedDoc); } } }); } private void activationChanged() { WordCount wc = null; OpenedDocument[] openedDocs = app.getOpenedDocuments(); for (int i = 0; i < openedDocs.length; ++i) { if (!activated) { openedDocs[i].removeProperty(WORD_COUNT_PROPERTY); } else { wc = new WordCount(); openedDocs[i].putProperty(WORD_COUNT_PROPERTY, wc); } } updateField(wc); } private void updateField(WordCount wc) { if (!activated || wc == null) { field.setText(""); } else { field.setText( NumberFormat.getIntegerInstance().format(wc.wordCount)); field.setForeground(wc.needUpdate? needUpdateColor : upToDateColor); } }
The rest of the implementation has nothing specific to an AppPart
/AppTool
and therefore, will not be described. Just notice how: