001: /*
002: * OptionsDialog.java - Tree options dialog
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1998, 2003 Slava Pestov
007: * Portions copyright (C) 1999 mike dillon
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit.gui;
025:
026: //{{{ Imports
027: import javax.swing.*;
028: import javax.swing.border.*;
029: import javax.swing.event.*;
030: import javax.swing.tree.*;
031: import java.awt.*;
032: import java.awt.event.*;
033: import java.util.*;
034: import java.util.List;
035:
036: import org.gjt.sp.jedit.*;
037: import org.gjt.sp.util.Log;
038:
039: //}}}
040:
041: /**
042: * An abstract options dialog box.
043: * @author Slava Pestov
044: * @version $Id: OptionsDialog.java 8676 2007-01-19 20:14:58Z kpouer $
045: */
046: public abstract class OptionsDialog extends EnhancedDialog implements
047: ActionListener, TreeSelectionListener {
048: //{{{ Instance variables
049: private String name;
050: private JSplitPane splitter;
051: protected JTree paneTree;
052: private JScrollPane stage;
053: private JButton ok;
054: private JButton cancel;
055: private JButton apply;
056: protected OptionPane currentPane;
057: private Map<Object, OptionPane> deferredOptionPanes;
058:
059: //}}}
060:
061: //{{{ OptionsDialog constructor
062: /**
063: * @param frame - the parent frame for dialogs created
064: * @param name the name of an option pane - it must have a .title and .code
065: * property defined in order to instantiate.
066: * @param pane the initial pane to show when this is created.
067: */
068: protected OptionsDialog(Frame frame, String name, String pane) {
069: super (frame, jEdit.getProperty(name + ".title"), true);
070: init(name, pane);
071: } //}}}
072:
073: //{{{ OptionsDialog constructor
074: protected OptionsDialog(Dialog dialog, String name, String pane) {
075: super (dialog, jEdit.getProperty(name + ".title"), true);
076: init(name, pane);
077: } //}}}
078:
079: //{{{ addOptionGroup() method
080: public void addOptionGroup(OptionGroup group) {
081: getDefaultGroup().addOptionGroup(group);
082: } //}}}
083:
084: //{{{ addOptionPane() method
085: public void addOptionPane(OptionPane pane) {
086: getDefaultGroup().addOptionPane(pane);
087: } //}}}
088:
089: //{{{ ok() method
090: public void ok() {
091: if (currentPane != null)
092: jEdit.setProperty(name + ".last", currentPane.getName());
093: ok(true);
094: } //}}}
095:
096: //{{{ cancel() method
097: public void cancel() {
098: if (currentPane != null)
099: jEdit.setProperty(name + ".last", currentPane.getName());
100: dispose();
101: } //}}}
102:
103: //{{{ ok() method
104: public void ok(boolean dispose) {
105: OptionTreeModel m = (OptionTreeModel) paneTree.getModel();
106: save(m.getRoot());
107:
108: /* This will fire the PROPERTIES_CHANGED event */
109: jEdit.propertiesChanged();
110:
111: // Save settings to disk
112: jEdit.saveSettings();
113:
114: // get rid of this dialog if necessary
115: if (dispose)
116: dispose();
117: } //}}}
118:
119: //{{{ dispose() method
120: public void dispose() {
121: GUIUtilities.saveGeometry(this , name);
122: jEdit.setIntegerProperty(name + ".splitter", splitter
123: .getDividerLocation());
124: super .dispose();
125: } //}}}
126:
127: //{{{ actionPerformed() method
128: public void actionPerformed(ActionEvent evt) {
129: Object source = evt.getSource();
130:
131: if (source == ok) {
132: ok();
133: } else if (source == cancel) {
134: cancel();
135: } else if (source == apply) {
136: ok(false);
137: }
138: } //}}}
139:
140: //{{{ valueChanged() method
141: public void valueChanged(TreeSelectionEvent evt) {
142: TreePath path = evt.getPath();
143:
144: if (path == null)
145: return;
146:
147: Object lastPathComponent = path.getLastPathComponent();
148: if (!(lastPathComponent instanceof String || lastPathComponent instanceof OptionPane)) {
149: return;
150: }
151:
152: Object[] nodes = path.getPath();
153:
154: StringBuilder buf = new StringBuilder();
155:
156: OptionPane optionPane = null;
157:
158: int lastIdx = nodes.length - 1;
159:
160: for (int i = paneTree.isRootVisible() ? 0 : 1; i <= lastIdx; i++) {
161: String label;
162: Object node = nodes[i];
163: if (node instanceof OptionPane) {
164: optionPane = (OptionPane) node;
165: label = jEdit.getProperty("options."
166: + optionPane.getName() + ".label");
167: } else if (node instanceof OptionGroup) {
168: label = ((OptionGroup) node).getLabel();
169: } else if (node instanceof String) {
170: label = jEdit.getProperty("options." + node + ".label");
171: optionPane = deferredOptionPanes.get(node);
172: if (optionPane == null) {
173: String propName = "options." + node + ".code";
174: String code = jEdit.getProperty(propName);
175: if (code != null) {
176: optionPane = (OptionPane) BeanShell.eval(jEdit
177: .getActiveView(), BeanShell
178: .getNameSpace(), code);
179:
180: if (optionPane != null) {
181: deferredOptionPanes.put(node, optionPane);
182: } else
183: continue;
184: } else {
185: Log.log(Log.ERROR, this , propName
186: + " not defined");
187: continue;
188: }
189: }
190: } else {
191: continue;
192: }
193:
194: buf.append(label);
195:
196: if (i != lastIdx)
197: buf.append(": ");
198: }
199:
200: if (optionPane == null)
201: return;
202:
203: setTitle(jEdit.getProperty("options.title-template",
204: new Object[] { jEdit.getProperty(name + ".title"),
205: buf.toString() }));
206:
207: try {
208: optionPane.init();
209: } catch (Throwable t) {
210: Log.log(Log.ERROR, this , "Error initializing options:");
211: Log.log(Log.ERROR, this , t);
212: }
213:
214: currentPane = optionPane;
215: stage.setViewportView(currentPane.getComponent());
216: stage.revalidate();
217: stage.repaint();
218:
219: if (!isShowing())
220: addNotify();
221:
222: updateSize();
223:
224: currentPane = optionPane;
225: } //}}}
226:
227: //{{{ Protected members
228: // {{{ createOptionTreeModel
229: /**
230: * Creates the tree model that goes on the left of the option pane,
231: * loading all the items that are needed.
232: */
233: protected abstract OptionTreeModel createOptionTreeModel();
234:
235: // }}}
236:
237: protected abstract OptionGroup getDefaultGroup();
238:
239: //}}}
240:
241: //{{{ init() method
242: /**
243: * @param name the name of this pane
244: * @param pane - a sub-pane name to select (?)
245: * Could someone please write better docs for this function?
246: * Creates buttons, adds listeners, and makes the pane visible.
247: * This method is called automatically from the constructor,
248: *
249: * and also calls init on each of the optionPanes?
250: *
251: * @since jEdit 4.3pre9 (was private before)
252: */
253: protected void init(String name, String pane) {
254: this .name = name;
255:
256: deferredOptionPanes = new HashMap<Object, OptionPane>();
257:
258: JPanel content = new JPanel(new BorderLayout(12, 12));
259: content.setBorder(new EmptyBorder(12, 12, 12, 12));
260: setContentPane(content);
261: stage = new JScrollPane();
262:
263: paneTree = new JTree(createOptionTreeModel());
264: paneTree.setVisibleRowCount(1);
265: paneTree.setCellRenderer(new PaneNameRenderer());
266:
267: // looks bad with the OS X L&F, apparently...
268: if (!OperatingSystem.isMacOSLF())
269: paneTree.putClientProperty("JTree.lineStyle", "Angled");
270:
271: paneTree.setShowsRootHandles(true);
272: paneTree.setRootVisible(false);
273:
274: JScrollPane scroller = new JScrollPane(paneTree,
275: ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
276: ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
277: scroller.setMinimumSize(new Dimension(100, 0));
278: splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jEdit
279: .getBooleanProperty("appearance.continuousLayout"),
280: scroller, stage);
281: content.add(splitter, BorderLayout.CENTER);
282:
283: Box buttons = new Box(BoxLayout.X_AXIS);
284: buttons.add(Box.createGlue());
285:
286: ok = new JButton(jEdit.getProperty("common.ok"));
287: ok.addActionListener(this );
288: buttons.add(ok);
289: buttons.add(Box.createHorizontalStrut(6));
290: getRootPane().setDefaultButton(ok);
291: cancel = new JButton(jEdit.getProperty("common.cancel"));
292: cancel.addActionListener(this );
293: buttons.add(cancel);
294: buttons.add(Box.createHorizontalStrut(6));
295: apply = new JButton(jEdit.getProperty("common.apply"));
296: apply.addActionListener(this );
297: buttons.add(apply);
298:
299: buttons.add(Box.createGlue());
300:
301: content.add(buttons, BorderLayout.SOUTH);
302:
303: // register the Options dialog as a TreeSelectionListener.
304: // this is done before the initial selection to ensure that the
305: // first selected OptionPane is displayed on startup.
306: paneTree.getSelectionModel().addTreeSelectionListener(this );
307:
308: OptionGroup rootNode = (OptionGroup) paneTree.getModel()
309: .getRoot();
310: for (int i = 0; i < rootNode.getMemberCount(); i++) {
311: paneTree.expandPath(new TreePath(new Object[] { rootNode,
312: rootNode.getMember(i) }));
313: }
314:
315: // returns false if no such pane exists; calling with null
316: // param selects first option pane found
317: if (!selectPane(rootNode, pane))
318: selectPane(rootNode, null);
319:
320: splitter
321: .setDividerLocation(paneTree.getPreferredSize().width
322: + scroller.getVerticalScrollBar()
323: .getPreferredSize().width);
324:
325: GUIUtilities.loadGeometry(this , name);
326: int dividerLocation = jEdit.getIntegerProperty(name
327: + ".splitter", -1);
328: if (dividerLocation != -1)
329: splitter.setDividerLocation(dividerLocation);
330:
331: // in case saved geometry is too small
332: updateSize();
333:
334: setVisible(true);
335: } //}}}
336:
337: //{{{ Private members
338:
339: //{{{ selectPane() method
340: private boolean selectPane(OptionGroup node, String name) {
341: return selectPane(node, name, new ArrayList());
342: } //}}}
343:
344: //{{{ selectPane() method
345: private boolean selectPane(OptionGroup node, String name, List path) {
346: path.add(node);
347:
348: Enumeration e = node.getMembers();
349: while (e.hasMoreElements()) {
350: Object obj = e.nextElement();
351: if (obj instanceof OptionGroup) {
352: OptionGroup grp = (OptionGroup) obj;
353: if (grp.getName().equals(name)) {
354: path.add(grp);
355: path.add(grp.getMember(0));
356: TreePath treePath = new TreePath(path.toArray());
357: paneTree.scrollPathToVisible(treePath);
358: paneTree.setSelectionPath(treePath);
359: return true;
360: } else if (selectPane((OptionGroup) obj, name, path))
361: return true;
362: } else if (obj instanceof OptionPane) {
363: OptionPane pane = (OptionPane) obj;
364: if (pane.getName().equals(name) || name == null) {
365: path.add(pane);
366: TreePath treePath = new TreePath(path.toArray());
367: paneTree.scrollPathToVisible(treePath);
368: paneTree.setSelectionPath(treePath);
369: return true;
370: }
371: } else if (obj instanceof String) {
372: String pane = (String) obj;
373: if (pane.equals(name) || name == null) {
374: path.add(pane);
375: TreePath treePath = new TreePath(path.toArray());
376: paneTree.scrollPathToVisible(treePath);
377: paneTree.setSelectionPath(treePath);
378: return true;
379: }
380: }
381: }
382:
383: path.remove(node);
384:
385: return false;
386: } //}}}
387:
388: //{{{ save() method
389: private void save(Object obj) {
390: if (obj instanceof OptionGroup) {
391: OptionGroup grp = (OptionGroup) obj;
392: Enumeration members = grp.getMembers();
393: while (members.hasMoreElements()) {
394: save(members.nextElement());
395: }
396: } else if (obj instanceof OptionPane) {
397: try {
398: ((OptionPane) obj).save();
399: } catch (Throwable t) {
400: Log.log(Log.ERROR, this , "Error saving options:");
401: Log.log(Log.ERROR, this , t);
402: }
403: } else if (obj instanceof String) {
404: save(deferredOptionPanes.get(obj));
405: }
406: } //}}}
407:
408: //{{{ updateSize() method
409: private void updateSize() {
410: Dimension currentSize = getSize();
411: Dimension requestedSize = getPreferredSize();
412: Dimension newSize = new Dimension(Math.max(currentSize.width,
413: requestedSize.width), Math.max(currentSize.height,
414: requestedSize.height));
415: if (newSize.width < 300)
416: newSize.width = 300;
417: if (newSize.height < 200)
418: newSize.height = 200;
419: setSize(newSize);
420: validate();
421: } //}}}
422:
423: //}}}
424:
425: //{{{ PaneNameRenderer class
426: public static class PaneNameRenderer extends
427: DefaultTreeCellRenderer {
428: public PaneNameRenderer() {
429: paneFont = UIManager.getFont("Tree.font");
430: if (paneFont == null)
431: paneFont = jEdit
432: .getFontProperty("metal.secondary.font");
433: groupFont = paneFont.deriveFont(Font.BOLD);
434: }
435:
436: public Component getTreeCellRendererComponent(JTree tree,
437: Object value, boolean selected, boolean expanded,
438: boolean leaf, int row, boolean hasFocus) {
439: super .getTreeCellRendererComponent(tree, value, selected,
440: expanded, leaf, row, hasFocus);
441:
442: String name = null;
443:
444: if (value instanceof OptionGroup) {
445: setText(((OptionGroup) value).getLabel());
446: setFont(groupFont);
447: } else if (value instanceof OptionPane) {
448: name = ((OptionPane) value).getName();
449: setFont(paneFont);
450: } else if (value instanceof String) {
451: name = (String) value;
452: setFont(paneFont);
453: }
454:
455: if (name != null) {
456: String label = jEdit.getProperty("options." + name
457: + ".label");
458:
459: if (label == null) {
460: setText("NO LABEL PROPERTY: " + name);
461: } else {
462: setText(label);
463: }
464: }
465:
466: setIcon(null);
467:
468: return this ;
469: }
470:
471: private Font paneFont;
472: private final Font groupFont;
473: } //}}}
474:
475: //{{{ OptionTreeModel class
476: public class OptionTreeModel implements TreeModel {
477: public void addTreeModelListener(TreeModelListener l) {
478: listenerList.add(TreeModelListener.class, l);
479: }
480:
481: public void removeTreeModelListener(TreeModelListener l) {
482: listenerList.remove(TreeModelListener.class, l);
483: }
484:
485: public Object getChild(Object parent, int index) {
486: if (parent instanceof OptionGroup) {
487: return ((OptionGroup) parent).getMember(index);
488: } else {
489: return null;
490: }
491: }
492:
493: public int getChildCount(Object parent) {
494: if (parent instanceof OptionGroup) {
495: return ((OptionGroup) parent).getMemberCount();
496: } else {
497: return 0;
498: }
499: }
500:
501: public int getIndexOfChild(Object parent, Object child) {
502: if (parent instanceof OptionGroup) {
503: return ((OptionGroup) parent).getMemberIndex(child);
504: } else {
505: return -1;
506: }
507: }
508:
509: public Object getRoot() {
510: return root;
511: }
512:
513: public boolean isLeaf(Object node) {
514: return !(node instanceof OptionGroup);
515: }
516:
517: public void valueForPathChanged(TreePath path, Object newValue) {
518: // this model may not be changed by the TableCellEditor
519: }
520:
521: protected void fireNodesChanged(Object source, Object[] path,
522: int[] childIndices, Object[] children) {
523: Object[] listeners = listenerList.getListenerList();
524:
525: TreeModelEvent modelEvent = null;
526: for (int i = listeners.length - 2; i >= 0; i -= 2) {
527: if (listeners[i] != TreeModelListener.class)
528: continue;
529:
530: if (modelEvent == null) {
531: modelEvent = new TreeModelEvent(source, path,
532: childIndices, children);
533: }
534:
535: ((TreeModelListener) listeners[i + 1])
536: .treeNodesChanged(modelEvent);
537: }
538: }
539:
540: protected void fireNodesInserted(Object source, Object[] path,
541: int[] childIndices, Object[] children) {
542: Object[] listeners = listenerList.getListenerList();
543:
544: TreeModelEvent modelEvent = null;
545: for (int i = listeners.length - 2; i >= 0; i -= 2) {
546: if (listeners[i] != TreeModelListener.class)
547: continue;
548:
549: if (modelEvent == null) {
550: modelEvent = new TreeModelEvent(source, path,
551: childIndices, children);
552: }
553:
554: ((TreeModelListener) listeners[i + 1])
555: .treeNodesInserted(modelEvent);
556: }
557: }
558:
559: protected void fireNodesRemoved(Object source, Object[] path,
560: int[] childIndices, Object[] children) {
561: Object[] listeners = listenerList.getListenerList();
562:
563: TreeModelEvent modelEvent = null;
564: for (int i = listeners.length - 2; i >= 0; i -= 2) {
565: if (listeners[i] != TreeModelListener.class)
566: continue;
567:
568: if (modelEvent == null) {
569: modelEvent = new TreeModelEvent(source, path,
570: childIndices, children);
571: }
572:
573: ((TreeModelListener) listeners[i + 1])
574: .treeNodesRemoved(modelEvent);
575: }
576: }
577:
578: protected void fireTreeStructureChanged(Object source,
579: Object[] path, int[] childIndices, Object[] children) {
580: Object[] listeners = listenerList.getListenerList();
581:
582: TreeModelEvent modelEvent = null;
583: for (int i = listeners.length - 2; i >= 0; i -= 2) {
584: if (listeners[i] != TreeModelListener.class)
585: continue;
586:
587: if (modelEvent == null) {
588: modelEvent = new TreeModelEvent(source, path,
589: childIndices, children);
590: }
591:
592: ((TreeModelListener) listeners[i + 1])
593: .treeStructureChanged(modelEvent);
594: }
595: }
596:
597: private final OptionGroup root = new OptionGroup(null);
598: private final EventListenerList listenerList = new EventListenerList();
599: } //}}}
600: }
|