| TreeConfigEditor.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: TreeConfigEditor.java 2091 2011-03-07 04:15:31Z dmsmith $
21 */
22 package org.crosswire.common.config.swing;
23
24 import java.awt.BorderLayout;
25 import java.awt.CardLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.Font;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.List;
32
33 import javax.swing.BorderFactory;
34 import javax.swing.JLabel;
35 import javax.swing.JPanel;
36 import javax.swing.JScrollPane;
37 import javax.swing.JSplitPane;
38 import javax.swing.JTree;
39 import javax.swing.SwingConstants;
40 import javax.swing.event.EventListenerList;
41 import javax.swing.event.TreeModelEvent;
42 import javax.swing.event.TreeModelListener;
43 import javax.swing.event.TreeSelectionEvent;
44 import javax.swing.event.TreeSelectionListener;
45 import javax.swing.tree.DefaultTreeCellRenderer;
46 import javax.swing.tree.TreeModel;
47 import javax.swing.tree.TreePath;
48
49 import org.crosswire.common.config.Choice;
50 import org.crosswire.common.config.Config;
51 import org.crosswire.common.swing.CWScrollPane;
52 import org.crosswire.common.swing.FixedSplitPane;
53 import org.crosswire.common.swing.FormPane;
54 import org.crosswire.common.swing.GuiUtil;
55 import org.crosswire.common.swing.CWMsg;
56
57 /**
58 * A Configuration Editor that provides a tree for navigating to options.
59 *
60 * @see gnu.lgpl.License for license details.<br>
61 * The copyright to this program is held by it's authors.
62 * @author Joe Walker [joe at eireneh dot com]
63 */
64 public class TreeConfigEditor extends AbstractConfigEditor {
65 /**
66 * <br />
67 * Danger - this method is not called by the TreeConfigEditor constructor,
68 * it is called by the AbstractConfigEditor constructor so any field
69 * initializers will be called AFTER THIS METHOD EXECUTES so don't use field
70 * initializers.
71 */
72 @Override
73 protected void initializeGUI() {
74 JPanel panel = new JPanel();
75 JPanel blank = new JPanel();
76 DefaultTreeCellRenderer dtcr = new DefaultTreeCellRenderer();
77 // prevent truncation (This is a hack!)
78 dtcr.setPreferredSize(new Dimension(1200, 100));
79 ctm = new ConfigureTreeModel();
80 tree = new JTree();
81 title = new JLabel();
82 deck = new JPanel();
83 layout = new CardLayout();
84
85 // TRANSLATOR: Label indicating that the user should select a preference category.
86 blank.add(new JLabel(CWMsg.gettext("Select a preference category")));
87
88 deck.setLayout(layout);
89 deck.add(blank, BLANK);
90
91 dtcr.setLeafIcon(TASK_ICON_SMALL);
92
93 // These settings will need to change if we have a true tree.
94 tree.setModel(ctm);
95 tree.setCellRenderer(dtcr);
96 tree.setShowsRootHandles(false);
97 tree.setRootVisible(false);
98 tree.putClientProperty("JTree.lineStyle", "None");
99 tree.setSelectionRow(0);
100 tree.addTreeSelectionListener(new TreeSelectionListener() {
101 public void valueChanged(TreeSelectionEvent ev) {
102 selectCard();
103 }
104 });
105
106 title.setIcon(TASK_ICON_LARGE);
107 title.setFont(getFont().deriveFont(Font.PLAIN, 16));
108 title.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
109 title.setBackground(Color.gray);
110 title.setForeground(Color.white);
111 title.setOpaque(true);
112 // TRANSLATOR: This is the label for the banner when one opens Options/Preferences.
113 title.setText(CWMsg.gettext("Preferences"));
114 title.setAlignmentX(SwingConstants.LEADING);
115
116 panel.setLayout(new BorderLayout());
117 panel.setBorder(BorderFactory.createEtchedBorder());
118 panel.add(BorderLayout.NORTH, title);
119 panel.add(BorderLayout.CENTER, deck);
120
121 setLayout(new BorderLayout());
122 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
123
124 JSplitPane sptMain = new FixedSplitPane();
125 sptMain.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
126 sptMain.setDividerLocation(0.25D);
127 sptMain.setResizeWeight(0.25D);
128 sptMain.setLeftComponent(new CWScrollPane(tree));
129 sptMain.setRightComponent(panel);
130
131 add(BorderLayout.CENTER, sptMain);
132 add(BorderLayout.SOUTH, new ButtonPane(this));
133 GuiUtil.applyDefaultOrientation(this);
134 }
135
136 /**
137 * Updates to the tree that we need to do on any change
138 */
139 @Override
140 protected void updateTree() {
141 // expand the tree
142 int row = 0;
143 while (row < tree.getRowCount()) {
144 tree.expandRow(row++);
145 }
146
147 ctm.fireTreeStructureChanged(this);
148 }
149
150 /**
151 * Add a Choice to our set of panels
152 */
153 @Override
154 protected void addChoice(Choice model) {
155 if (model.isHidden()) {
156 return;
157 }
158
159 super.addChoice(model);
160
161 // Sort the tree out
162 String path = Config.getPath(model.getFullPath());
163 FormPane card = decks.get(path);
164 if (card != null && card.getParent() == null) {
165 JScrollPane scroll = new CWScrollPane(card);
166 scroll.setBorder(BorderFactory.createEmptyBorder());
167 deck.add(scroll, path);
168 }
169 }
170
171 /**
172 * Add a Choice to our set of panels
173 */
174 @Override
175 protected void removeChoice(Choice model) {
176 super.removeChoice(model);
177
178 // Sort the tree out
179 String path = Config.getPath(model.getFullPath());
180 FormPane card = decks.get(path);
181 if (card != null && card.isEmpty()) {
182 deck.remove(card.getParent());
183 }
184 }
185
186 /**
187 * Used to update the configuration panel whenever someone selects a
188 * different item form the tree on the LHS of the configuration dialog.
189 */
190 public void selectCard() {
191 Object obj = tree.getLastSelectedPathComponent();
192 if (obj == null) {
193 return;
194 }
195
196 // TRANSLATOR: This is the label for the banner when one opens a type of Options/Preferences.
197 // {0} is the type of preference, e.g. Bible Display
198 title.setText(CWMsg.gettext("{0} Preferences", obj));
199
200 // Get the name of the current deck
201 Object[] list = tree.getSelectionPath().getPath();
202 StringBuilder path = new StringBuilder();
203
204 for (int i = 1; i < list.length; i++) {
205 if (i > 1) {
206 path.append('.');
207 }
208
209 path.append(list[i].toString());
210 }
211
212 String key = path.toString();
213 GuiUtil.applyDefaultOrientation(deck);
214 if (decks.containsKey(key)) {
215 layout.show(deck, key);
216 } else {
217 layout.show(deck, BLANK);
218 }
219
220 deck.repaint();
221 }
222
223 /**
224 * The name of the blank tab
225 */
226 protected static final String BLANK = "$$BLANK$$";
227
228 /**
229 * The tree containing the Field sets
230 */
231 protected JTree tree;
232
233 /**
234 * The custom tree model for the tree
235 */
236 protected transient ConfigureTreeModel ctm;
237
238 /**
239 * The title for the config panels
240 */
241 protected JLabel title;
242
243 /**
244 * Contains the configuration panels
245 */
246 protected JPanel deck;
247
248 /**
249 * Layout for the config panels
250 */
251 protected CardLayout layout;
252
253 /**
254 * Serialization ID
255 */
256 private static final long serialVersionUID = 3256720688860576049L;
257
258 /**
259 * A custom data model for the TreeConfig Tree
260 *
261 * @author Claude Duguay
262 * @author Joe Walker
263 */
264 protected class ConfigureTreeModel implements TreeModel {
265 /*
266 * (non-Javadoc)
267 *
268 * @see javax.swing.tree.TreeModel#getRoot()
269 */
270 public Object getRoot() {
271 return root;
272 }
273
274 /**
275 * Get a List of the children rooted at path
276 */
277 protected List<String> getChildren(String path) {
278 List<String> retcode = new ArrayList<String>();
279
280 Iterator<Choice> it = config.iterator();
281 while (it.hasNext()) {
282 Choice choice = it.next();
283 if (choice.isHidden()) {
284 continue;
285 }
286
287 String temp = choice.getFullPath();
288
289 if (temp.startsWith(path) && !temp.equals(path)) {
290 // Chop off the similar start
291 temp = temp.substring(path.length());
292 if (temp.charAt(0) == '.') {
293 temp = temp.substring(1);
294 }
295
296 // Chop off all after the first dot
297 int dot_pos = temp.indexOf('.');
298 if (dot_pos == -1) {
299 continue;
300 }
301 temp = temp.substring(0, dot_pos);
302
303 // Add it to the list if needed
304 if (temp.length() > 0 && !retcode.contains(temp)) {
305 retcode.add(temp);
306 }
307 }
308 }
309
310 return retcode;
311 }
312
313 /*
314 * (non-Javadoc)
315 *
316 * @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
317 */
318 public Object getChild(Object parent, int index) {
319 String path = ((Node) parent).getFullName();
320 String name = getChildren(path).get(index);
321 return new Node(path, name);
322 }
323
324 /*
325 * (non-Javadoc)
326 *
327 * @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
328 */
329 public int getChildCount(Object parent) {
330 String path = ((Node) parent).getFullName();
331 return getChildren(path).size();
332 }
333
334 /*
335 * (non-Javadoc)
336 *
337 * @see javax.swing.tree.TreeModel#isLeaf(java.lang.Object)
338 */
339 public boolean isLeaf(Object node) {
340 String path = ((Node) node).getFullName();
341 return getChildren(path).size() == 0;
342 }
343
344 /*
345 * (non-Javadoc)
346 *
347 * @see
348 * javax.swing.tree.TreeModel#valueForPathChanged(javax.swing.tree.TreePath
349 * , java.lang.Object)
350 */
351 public void valueForPathChanged(TreePath path, Object value) {
352 }
353
354 /*
355 * (non-Javadoc)
356 *
357 * @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object,
358 * java.lang.Object)
359 */
360 public int getIndexOfChild(Object parent, Object child) {
361 String path = ((Node) parent).getFullName();
362 List<String> children = getChildren(path);
363 return children.indexOf(child);
364 }
365
366 /*
367 * (non-Javadoc)
368 *
369 * @see
370 * javax.swing.tree.TreeModel#addTreeModelListener(javax.swing.event
371 * .TreeModelListener)
372 */
373 public void addTreeModelListener(TreeModelListener li) {
374 listeners.add(TreeModelListener.class, li);
375 }
376
377 /*
378 * (non-Javadoc)
379 *
380 * @see
381 * javax.swing.tree.TreeModel#removeTreeModelListener(javax.swing.event
382 * .TreeModelListener)
383 */
384 public void removeTreeModelListener(TreeModelListener li) {
385 listeners.remove(TreeModelListener.class, li);
386 }
387
388 /**
389 * Notify all listeners that have registered interest for notification
390 * on this event type. The event instance is lazily created using the
391 * parameters passed into the fire method.
392 *
393 * @see EventListenerList
394 */
395 protected void fireTreeStructureChanged(Object source) {
396 fireTreeStructureChanged(source, root);
397 }
398
399 /**
400 * Notify all listeners that have registered interest for notification
401 * on this event type. The event instance is lazily created using the
402 * parameters passed into the fire method.
403 *
404 * @see EventListenerList
405 */
406 protected void fireTreeStructureChanged(Object source, Object... path) {
407 // Guaranteed to return a non-null array
408 Object[] array = listeners.getListenerList();
409 TreeModelEvent ev = null;
410
411 // Process the listeners last to first, notifying
412 // those that are interested in this event
413 for (int i = array.length - 2; i >= 0; i -= 2) {
414 if (array[i] == TreeModelListener.class) {
415 // Lazily create the event:
416 if (ev == null) {
417 ev = new TreeModelEvent(source, path);
418 }
419
420 ((TreeModelListener) array[i + 1]).treeStructureChanged(ev);
421 }
422 }
423 }
424
425 /**
426 * The Listeners.
427 */
428 protected EventListenerList listeners = new EventListenerList();
429
430 /**
431 * The root node
432 */
433 private Node root = new Node("", "");
434 }
435
436 /**
437 * Simple Tree Node
438 */
439 protected static class Node {
440 /**
441 * Create a node with a name and path
442 */
443 protected Node(String path, String name) {
444 this.path = path;
445 this.name = name;
446 }
447
448 /*
449 * (non-Javadoc)
450 *
451 * @see java.lang.Object#toString()
452 */
453 @Override
454 public String toString() {
455 return name;
456 }
457
458 /**
459 * The path to us
460 */
461 public String getFullName() {
462 if (path.length() == 0 || name.length() == 0) {
463 return path + name;
464 }
465 return path + "." + name;
466 }
467
468 /**
469 * The displayed string
470 */
471 private String name;
472
473 /**
474 * The path to us
475 */
476 private String path;
477 }
478 }
479