Source code: org/gjt/sp/jedit/gui/OptionsDialog.java
1 /*
2 * OptionsDialog.java - Tree options dialog
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1998, 2003 Slava Pestov
7 * Portions copyright (C) 1999 mike dillon
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24 package org.gjt.sp.jedit.gui;
25
26 //{{{ Imports
27 import javax.swing.*;
28 import javax.swing.border.*;
29 import javax.swing.event.*;
30 import javax.swing.tree.*;
31 import java.awt.*;
32 import java.awt.event.*;
33 import java.util.*;
34 import org.gjt.sp.jedit.*;
35 import org.gjt.sp.util.Log;
36 //}}}
37
38 /**
39 * An abstract tabbed options dialog box.
40 * @author Slava Pestov
41 * @version $Id: OptionsDialog.java,v 1.36 2003/10/10 23:46:24 spestov Exp $
42 */
43 public abstract class OptionsDialog extends EnhancedDialog
44 implements ActionListener, TreeSelectionListener
45 {
46 //{{{ OptionsDialog constructor
47 public OptionsDialog(Frame frame, String name, String pane)
48 {
49 super(frame, jEdit.getProperty(name + ".title"), true);
50 init(name,pane);
51 } //}}}
52
53 //{{{ OptionsDialog constructor
54 public OptionsDialog(Dialog dialog, String name, String pane)
55 {
56 super(dialog, jEdit.getProperty(name + ".title"), true);
57 init(name,pane);
58 } //}}}
59
60 //{{{ addOptionGroup() method
61 public void addOptionGroup(OptionGroup group)
62 {
63 getDefaultGroup().addOptionGroup(group);
64 } //}}}
65
66 //{{{ addOptionPane() method
67 public void addOptionPane(OptionPane pane)
68 {
69 getDefaultGroup().addOptionPane(pane);
70 } //}}}
71
72 //{{{ ok() method
73 public void ok()
74 {
75 if(currentPane != null)
76 jEdit.setProperty(name + ".last",currentPane.getName());
77 ok(true);
78 } //}}}
79
80 //{{{ cancel() method
81 public void cancel()
82 {
83 if(currentPane != null)
84 jEdit.setProperty(name + ".last",currentPane.getName());
85 dispose();
86 } //}}}
87
88 //{{{ ok() method
89 public void ok(boolean dispose)
90 {
91 OptionTreeModel m = (OptionTreeModel) paneTree
92 .getModel();
93 save(m.getRoot());
94
95 /* This will fire the PROPERTIES_CHANGED event */
96 jEdit.propertiesChanged();
97
98 // Save settings to disk
99 jEdit.saveSettings();
100
101 // get rid of this dialog if necessary
102 if(dispose)
103 dispose();
104 } //}}}
105
106 //{{{ dispose() method
107 public void dispose()
108 {
109 GUIUtilities.saveGeometry(this,name);
110 jEdit.setIntegerProperty(name + ".splitter",splitter.getDividerLocation());
111 super.dispose();
112 } //}}}
113
114 //{{{ actionPerformed() method
115 public void actionPerformed(ActionEvent evt)
116 {
117 Object source = evt.getSource();
118
119 if(source == ok)
120 {
121 ok();
122 }
123 else if(source == cancel)
124 {
125 cancel();
126 }
127 else if(source == apply)
128 {
129 ok(false);
130 }
131 } //}}}
132
133 //{{{ valueChanged() method
134 public void valueChanged(TreeSelectionEvent evt)
135 {
136 TreePath path = evt.getPath();
137
138 if(path == null)
139 return;
140
141 Object lastPathComponent = path.getLastPathComponent();
142 if(!(lastPathComponent instanceof String
143 || lastPathComponent instanceof OptionPane))
144 {
145 return;
146 }
147
148 Object[] nodes = path.getPath();
149
150 StringBuffer buf = new StringBuffer();
151
152 OptionPane optionPane = null;
153
154 int lastIdx = nodes.length - 1;
155
156 for (int i = paneTree.isRootVisible() ? 0 : 1;
157 i <= lastIdx; i++)
158 {
159 String label;
160 Object node = nodes[i];
161 if (node instanceof OptionPane)
162 {
163 optionPane = (OptionPane)node;
164 label = jEdit.getProperty("options."
165 + optionPane.getName()
166 + ".label");
167 }
168 else if (node instanceof OptionGroup)
169 {
170 label = ((OptionGroup)node).getLabel();
171 }
172 else if (node instanceof String)
173 {
174 label = jEdit.getProperty("options."
175 + node + ".label");
176 optionPane = (OptionPane)deferredOptionPanes
177 .get((String)node);
178 if(optionPane == null)
179 {
180 String propName = "options." + node + ".code";
181 String code = jEdit.getProperty(propName);
182 if(code != null)
183 {
184 optionPane = (OptionPane)
185 BeanShell.eval(
186 jEdit.getActiveView(),
187 BeanShell.getNameSpace(),
188 code
189 );
190
191 if(optionPane != null)
192 {
193 deferredOptionPanes.put(
194 node,optionPane);
195 }
196 else
197 continue;
198 }
199 else
200 {
201 Log.log(Log.ERROR,this,propName
202 + " not defined");
203 continue;
204 }
205 }
206 }
207 else
208 {
209 continue;
210 }
211
212 buf.append(label);
213
214 if (i != lastIdx)
215 buf.append(": ");
216 }
217
218 if(optionPane == null)
219 return;
220
221 setTitle(jEdit.getProperty("options.title-template",
222 new Object[] { jEdit.getProperty(this.name + ".title"),
223 buf.toString() }));
224
225 try
226 {
227 optionPane.init();
228 }
229 catch(Throwable t)
230 {
231 Log.log(Log.ERROR,this,"Error initializing options:");
232 Log.log(Log.ERROR,this,t);
233 }
234
235 if(currentPane != null)
236 stage.remove(currentPane.getComponent());
237 currentPane = optionPane;
238 stage.add(BorderLayout.CENTER,currentPane.getComponent());
239 stage.revalidate();
240 stage.repaint();
241
242 if(!isShowing())
243 addNotify();
244
245 updateSize();
246
247 currentPane = optionPane;
248 } //}}}
249
250 //{{{ Protected members
251 protected abstract OptionTreeModel createOptionTreeModel();
252 protected abstract OptionGroup getDefaultGroup();
253 //}}}
254
255 //{{{ Private members
256
257 //{{{ Instance variables
258 private String name;
259 private JSplitPane splitter;
260 private JTree paneTree;
261 private JPanel stage;
262 private JButton ok;
263 private JButton cancel;
264 private JButton apply;
265 private OptionPane currentPane;
266 private Map deferredOptionPanes;
267 //}}}
268
269 //{{{ init() method
270 private void init(String name, String pane)
271 {
272 this.name = name;
273
274 deferredOptionPanes = new HashMap();
275
276 JPanel content = new JPanel(new BorderLayout(12,12));
277 content.setBorder(new EmptyBorder(12,12,12,12));
278 setContentPane(content);
279
280 stage = new JPanel(new BorderLayout());
281
282 paneTree = new JTree(createOptionTreeModel());
283 paneTree.setVisibleRowCount(1);
284 paneTree.setCellRenderer(new PaneNameRenderer());
285
286 // looks bad with the OS X L&F, apparently...
287 if(!OperatingSystem.isMacOSLF())
288 paneTree.putClientProperty("JTree.lineStyle", "Angled");
289
290 paneTree.setShowsRootHandles(true);
291 paneTree.setRootVisible(false);
292
293 JScrollPane scroller = new JScrollPane(paneTree,
294 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
295 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
296 splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
297 scroller,stage);
298 content.add(splitter, BorderLayout.CENTER);
299
300 Box buttons = new Box(BoxLayout.X_AXIS);
301 buttons.add(Box.createGlue());
302
303 ok = new JButton(jEdit.getProperty("common.ok"));
304 ok.addActionListener(this);
305 buttons.add(ok);
306 buttons.add(Box.createHorizontalStrut(6));
307 getRootPane().setDefaultButton(ok);
308 cancel = new JButton(jEdit.getProperty("common.cancel"));
309 cancel.addActionListener(this);
310 buttons.add(cancel);
311 buttons.add(Box.createHorizontalStrut(6));
312 apply = new JButton(jEdit.getProperty("common.apply"));
313 apply.addActionListener(this);
314 buttons.add(apply);
315
316 buttons.add(Box.createGlue());
317
318 content.add(buttons, BorderLayout.SOUTH);
319
320 // register the Options dialog as a TreeSelectionListener.
321 // this is done before the initial selection to ensure that the
322 // first selected OptionPane is displayed on startup.
323 paneTree.getSelectionModel().addTreeSelectionListener(this);
324
325 OptionGroup rootNode = (OptionGroup)paneTree.getModel().getRoot();
326 for(int i = 0; i < rootNode.getMemberCount(); i++)
327 {
328 paneTree.expandPath(new TreePath(
329 new Object[] { rootNode, rootNode.getMember(i) }));
330 }
331
332 // returns false if no such pane exists; calling with null
333 // param selects first option pane found
334 if(!selectPane(rootNode,pane))
335 selectPane(rootNode,null);
336
337 splitter.setDividerLocation(paneTree.getPreferredSize().width
338 + scroller.getVerticalScrollBar().getPreferredSize()
339 .width);
340
341 GUIUtilities.loadGeometry(this,name);
342 int dividerLocation = jEdit.getIntegerProperty(name + ".splitter",-1);
343 if(dividerLocation != -1)
344 splitter.setDividerLocation(dividerLocation);
345
346 // in case saved geometry is too small
347 updateSize();
348
349 show();
350 } //}}}
351
352 //{{{ selectPane() method
353 private boolean selectPane(OptionGroup node, String name)
354 {
355 return selectPane(node,name,new ArrayList());
356 } //}}}
357
358 //{{{ selectPane() method
359 private boolean selectPane(OptionGroup node, String name, ArrayList path)
360 {
361 path.add(node);
362
363 Enumeration enum = node.getMembers();
364 while(enum.hasMoreElements())
365 {
366 Object obj = enum.nextElement();
367 if(obj instanceof OptionGroup)
368 {
369 OptionGroup grp = (OptionGroup)obj;
370 if(grp.getName().equals(name))
371 {
372 path.add(grp);
373 path.add(grp.getMember(0));
374 TreePath treePath = new TreePath(
375 path.toArray());
376 paneTree.scrollPathToVisible(treePath);
377 paneTree.setSelectionPath(treePath);
378 return true;
379 }
380 else if(selectPane((OptionGroup)obj,name,path))
381 return true;
382 }
383 else if(obj instanceof OptionPane)
384 {
385 OptionPane pane = (OptionPane)obj;
386 if(pane.getName().equals(name)
387 || name == null)
388 {
389 path.add(pane);
390 TreePath treePath = new TreePath(
391 path.toArray());
392 paneTree.scrollPathToVisible(treePath);
393 paneTree.setSelectionPath(treePath);
394 return true;
395 }
396 }
397 else if(obj instanceof String)
398 {
399 String pane = (String)obj;
400 if(pane.equals(name)
401 || name == null)
402 {
403 path.add(pane);
404 TreePath treePath = new TreePath(
405 path.toArray());
406 paneTree.scrollPathToVisible(treePath);
407 paneTree.setSelectionPath(treePath);
408 return true;
409 }
410 }
411 }
412
413 path.remove(node);
414
415 return false;
416 } //}}}
417
418 //{{{ save() method
419 private void save(Object obj)
420 {
421 if(obj instanceof OptionGroup)
422 {
423 OptionGroup grp = (OptionGroup)obj;
424 Enumeration members = grp.getMembers();
425 while(members.hasMoreElements())
426 {
427 save(members.nextElement());
428 }
429 }
430 else if(obj instanceof OptionPane)
431 {
432 try
433 {
434 ((OptionPane)obj).save();
435 }
436 catch(Throwable t)
437 {
438 Log.log(Log.ERROR,this,"Error saving options:");
439 Log.log(Log.ERROR,this,t);
440 }
441 }
442 else if(obj instanceof String)
443 {
444 save(deferredOptionPanes.get(obj));
445 }
446 } //}}}
447
448 //{{{ updateSize() method
449 private void updateSize()
450 {
451 Dimension currentSize = getSize();
452 Dimension requestedSize = getPreferredSize();
453 Dimension newSize = new Dimension(
454 Math.max(currentSize.width,requestedSize.width),
455 Math.max(currentSize.height,requestedSize.height)
456 );
457 if(newSize.width < 300)
458 newSize.width = 300;
459 if(newSize.height < 200)
460 newSize.height = 200;
461 setSize(newSize);
462 validate();
463 } //}}}
464
465 //}}}
466
467 //{{{ PaneNameRenderer class
468 class PaneNameRenderer extends DefaultTreeCellRenderer
469 {
470 public PaneNameRenderer()
471 {
472 paneFont = UIManager.getFont("Tree.font");
473 if(paneFont == null)
474 paneFont = jEdit.getFontProperty("metal.secondary.font");
475 groupFont = paneFont.deriveFont(Font.BOLD);
476 }
477
478 public Component getTreeCellRendererComponent(JTree tree,
479 Object value, boolean selected, boolean expanded,
480 boolean leaf, int row, boolean hasFocus)
481 {
482 super.getTreeCellRendererComponent(tree,value,
483 selected,expanded,leaf,row,hasFocus);
484
485 String name = null;
486
487 if (value instanceof OptionGroup)
488 {
489 setText(((OptionGroup)value).getLabel());
490 this.setFont(groupFont);
491 }
492 else if (value instanceof OptionPane)
493 {
494 name = ((OptionPane)value).getName();
495 this.setFont(paneFont);
496 }
497 else if (value instanceof String)
498 {
499 name = ((String)value);
500 this.setFont(paneFont);
501 }
502
503 if (name != null)
504 {
505 String label = jEdit.getProperty("options." +
506 name + ".label");
507
508 if (label == null)
509 {
510 setText("NO LABEL PROPERTY: " + name);
511 }
512 else
513 {
514 setText(label);
515 }
516 }
517
518 setIcon(null);
519
520 return this;
521 }
522
523 private Font paneFont;
524 private Font groupFont;
525 } //}}}
526
527 //{{{ OptionTreeModel class
528 public class OptionTreeModel implements TreeModel
529 {
530 public void addTreeModelListener(TreeModelListener l)
531 {
532 listenerList.add(TreeModelListener.class, l);
533 }
534
535 public void removeTreeModelListener(TreeModelListener l)
536 {
537 listenerList.remove(TreeModelListener.class, l);
538 }
539
540 public Object getChild(Object parent, int index)
541 {
542 if (parent instanceof OptionGroup)
543 {
544 return ((OptionGroup)parent).getMember(index);
545 }
546 else
547 {
548 return null;
549 }
550 }
551
552 public int getChildCount(Object parent)
553 {
554 if (parent instanceof OptionGroup)
555 {
556 return ((OptionGroup)parent).getMemberCount();
557 }
558 else
559 {
560 return 0;
561 }
562 }
563
564 public int getIndexOfChild(Object parent, Object child)
565 {
566 if (parent instanceof OptionGroup)
567 {
568 return ((OptionGroup)parent)
569 .getMemberIndex(child);
570 }
571 else
572 {
573 return -1;
574 }
575 }
576
577 public Object getRoot()
578 {
579 return root;
580 }
581
582 public boolean isLeaf(Object node)
583 {
584 return !(node instanceof OptionGroup);
585 }
586
587 public void valueForPathChanged(TreePath path, Object newValue)
588 {
589 // this model may not be changed by the TableCellEditor
590 }
591
592 protected void fireNodesChanged(Object source, Object[] path,
593 int[] childIndices, Object[] children)
594 {
595 Object[] listeners = listenerList.getListenerList();
596
597 TreeModelEvent modelEvent = null;
598 for (int i = listeners.length - 2; i >= 0; i -= 2)
599 {
600 if (listeners[i] != TreeModelListener.class)
601 continue;
602
603 if (modelEvent == null)
604 {
605 modelEvent = new TreeModelEvent(source,
606 path, childIndices, children);
607 }
608
609 ((TreeModelListener)listeners[i + 1])
610 .treeNodesChanged(modelEvent);
611 }
612 }
613
614 protected void fireNodesInserted(Object source, Object[] path,
615 int[] childIndices, Object[] children)
616 {
617 Object[] listeners = listenerList.getListenerList();
618
619 TreeModelEvent modelEvent = null;
620 for (int i = listeners.length - 2; i >= 0; i -= 2)
621 {
622 if (listeners[i] != TreeModelListener.class)
623 continue;
624
625 if (modelEvent == null)
626 {
627 modelEvent = new TreeModelEvent(source,
628 path, childIndices, children);
629 }
630
631 ((TreeModelListener)listeners[i + 1])
632 .treeNodesInserted(modelEvent);
633 }
634 }
635
636 protected void fireNodesRemoved(Object source, Object[] path,
637 int[] childIndices, Object[] children)
638 {
639 Object[] listeners = listenerList.getListenerList();
640
641 TreeModelEvent modelEvent = null;
642 for (int i = listeners.length - 2; i >= 0; i -= 2)
643 {
644 if (listeners[i] != TreeModelListener.class)
645 continue;
646
647 if (modelEvent == null)
648 {
649 modelEvent = new TreeModelEvent(source,
650 path, childIndices, children);
651 }
652
653 ((TreeModelListener)listeners[i + 1])
654 .treeNodesRemoved(modelEvent);
655 }
656 }
657
658 protected void fireTreeStructureChanged(Object source,
659 Object[] path, int[] childIndices, Object[] children)
660 {
661 Object[] listeners = listenerList.getListenerList();
662
663 TreeModelEvent modelEvent = null;
664 for (int i = listeners.length - 2; i >= 0; i -= 2)
665 {
666 if (listeners[i] != TreeModelListener.class)
667 continue;
668
669 if (modelEvent == null)
670 {
671 modelEvent = new TreeModelEvent(source,
672 path, childIndices, children);
673 }
674
675 ((TreeModelListener)listeners[i + 1])
676 .treeStructureChanged(modelEvent);
677 }
678 }
679
680 private OptionGroup root = new OptionGroup(null);
681 private EventListenerList listenerList = new EventListenerList();
682 } //}}}
683 }