/* * OptionGroupPane.java - A Pane (view) for displaying/selecting OptionGroups. * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit: * * Copyright (C) 2005 Slava Pestov * Copyright (C) 2011 Alan Ezust * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.jedit.options; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.TextListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTree; import javax.swing.border.EmptyBorder; import javax.swing.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import org.gjt.sp.util.StringModel; import org.gjt.sp.jedit.AbstractOptionPane; import org.gjt.sp.jedit.BeanShell; import org.gjt.sp.jedit.OperatingSystem; import org.gjt.sp.jedit.OptionGroup; import org.gjt.sp.jedit.OptionPane; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.jedit.gui.OptionsDialog.PaneNameRenderer; import org.gjt.sp.util.Log; /** * An option pane for displaying groups of options. There is code here * which was taken from OptionsDialog, but this class is a component which can * be embedded in other Dialogs. * * Shows a JTree on the left, and an option pane on the right, with a splitter * between. * * @see org.gjt.sp.jedit.gui.OptionsDialog OptionsDialog * * @author ezust * */ public class OptionGroupPane extends AbstractOptionPane implements TreeSelectionListener { // {{{ Members OptionGroup optionGroup; JSplitPane splitter; JTree paneTree; OptionPane currentPane; OptionTreeModel optionTreeModel; HashMap<Object, OptionPane> deferredOptionPanes; JPanel stage; StringModel title = new StringModel(); // }}} public OptionGroupPane(OptionGroup group) { super(group.getName()); optionGroup = group; init(); } void addTextListener(TextListener l) { title.addTextListener(l); } public String getTitle() { return title.toString(); } void setTitle(String newTitle) { title.setText(newTitle); } // {{{ valueChanged() method public void valueChanged(TreeSelectionEvent evt) { TreePath path = evt.getPath(); if (path == null) return; Object lastPathComponent = path.getLastPathComponent(); if (!(lastPathComponent instanceof String || lastPathComponent instanceof OptionPane)) { return; } Object[] nodes = path.getPath(); StringBuilder sb = new StringBuilder(); OptionPane optionPane = null; int lastIdx = nodes.length - 1; for (int i = paneTree.isRootVisible() ? 0 : 1; i <= lastIdx; i++) { String label; Object node = nodes[i]; if (node instanceof OptionPane) { optionPane = (OptionPane) node; label = jEdit.getProperty("options." + optionPane.getName() + ".label"); } else if (node instanceof OptionGroup) { label = ((OptionGroup) node).getLabel(); } else if (node instanceof String) { label = jEdit.getProperty("options." + node + ".label"); optionPane = (OptionPane) deferredOptionPanes.get((String) node); if (optionPane == null) { String propName = "options." + node + ".code"; String code = jEdit.getProperty(propName); if (code != null) { optionPane = (OptionPane) BeanShell.eval(jEdit .getActiveView(), BeanShell.getNameSpace(), code); if (optionPane != null) { deferredOptionPanes.put(node, optionPane); } else continue; } else { Log.log(Log.ERROR, this, propName + " not defined"); continue; } } } else { continue; } if (label != null) sb.append(label); if (i > 0 && i < lastIdx) sb.append(": "); } if (optionPane == null) return; String ttext = jEdit.getProperty("optional.title-template", new Object[] { optionGroup.getName(), sb.toString() }); setTitle(ttext); try { optionPane.init(); } catch (Throwable t) { Log.log(Log.ERROR, this, "Error initializing option pane:"); Log.log(Log.ERROR, this, t); } if (currentPane != null) stage.remove(currentPane.getComponent()); currentPane = optionPane; stage.add(BorderLayout.CENTER, currentPane.getComponent()); stage.revalidate(); stage.repaint(); currentPane = optionPane; } // }}} // {{{ selectPane() methods private boolean selectPane(OptionGroup node, String name) { return selectPane(node, name, new ArrayList<Object>()); } private boolean selectPane(OptionGroup node, String name, ArrayList<Object> path) { path.add(node); Enumeration<Object> e = node.getMembers(); while (e.hasMoreElements()) { Object obj = e.nextElement(); if (obj instanceof OptionGroup) { OptionGroup grp = (OptionGroup) obj; if (grp.getName().equals(name)) { path.add(grp); path.add(grp.getMember(0)); TreePath treePath = new TreePath(path.toArray()); if (treePath != null) { paneTree.scrollPathToVisible(treePath); paneTree.setSelectionPath(treePath); return true; } } else if (selectPane((OptionGroup) obj, name, path)) { return true; } } else if (obj instanceof OptionPane) { OptionPane pane = (OptionPane) obj; if (pane.getName().equals(name) || name == null) { path.add(pane); TreePath treePath = new TreePath(path.toArray()); paneTree.scrollPathToVisible(treePath); paneTree.setSelectionPath(treePath); return true; } } else if (obj instanceof String) { String pane = (String) obj; if (pane.equals(name) || name == null) { path.add(pane); TreePath treePath = new TreePath(path.toArray()); paneTree.scrollPathToVisible(treePath); try { paneTree.setSelectionPath(treePath); } catch (NullPointerException npe) {} return true; } } } path.remove(node); return false; } // }}} // {{{ init() method protected void _init() { setLayout(new BorderLayout()); deferredOptionPanes = new HashMap<Object, OptionPane>(); optionTreeModel = new OptionTreeModel(); OptionGroup rootGroup = (OptionGroup) optionTreeModel.getRoot(); // #3608324: ignore the root node of the option group as it does not provide // a label and only add its children for (Enumeration<Object> members = optionGroup.getMembers(); members.hasMoreElements();) { Object member = members.nextElement(); if (member instanceof OptionGroup) rootGroup.addOptionGroup((OptionGroup)member); else if (member instanceof String) rootGroup.addOptionPane((String)member); // TODO are there any other cases that must handled? } paneTree = new JTree(optionTreeModel); paneTree.setRootVisible(false); paneTree.setCellRenderer(new PaneNameRenderer()); JPanel content = new JPanel(new BorderLayout(12, 12)); content.setBorder(new EmptyBorder(12, 12, 12, 12)); add(content, BorderLayout.CENTER); stage = new JPanel(new BorderLayout()); // looks bad with the OS X L&F, apparently... if (!OperatingSystem.isMacOSLF()) paneTree.putClientProperty("JTree.lineStyle", "Angled"); paneTree.setShowsRootHandles(true); paneTree.setRootVisible(false); JScrollPane scroller = new JScrollPane(paneTree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scroller.setMinimumSize(new Dimension(120, 0)); JScrollPane scroll = new JScrollPane(stage); scroll.getVerticalScrollBar().setUnitIncrement(10); splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, scroller, scroll); content.add(splitter, BorderLayout.CENTER); // register the Options dialog as a TreeSelectionListener. // this is done before the initial selection to ensure that the // first selected OptionPane is displayed on startup. paneTree.getSelectionModel().addTreeSelectionListener(this); OptionGroup rootNode = (OptionGroup) paneTree.getModel().getRoot(); String name = optionGroup.getName(); String pane = jEdit.getProperty(name + ".last"); selectPane(rootNode, pane); paneTree.setVisibleRowCount(1); int dividerLocation = jEdit.getIntegerProperty(name + ".splitter", -1); if (dividerLocation != -1) splitter.setDividerLocation(dividerLocation); else splitter.setDividerLocation(paneTree.getPreferredSize().width + scroller.getVerticalScrollBar().getPreferredSize().width); } //}}} //{{{ save() methods protected void _save() { if (currentPane != null) jEdit.setProperty(getName() + ".last", currentPane.getName()); int dividerPosition = splitter.getDividerLocation(); jEdit.setIntegerProperty(optionGroup.getName() + ".splitter", dividerPosition); save(optionGroup); } private void save(Object obj) { if (obj instanceof OptionGroup) { OptionGroup grp = (OptionGroup) obj; Enumeration<Object> members = grp.getMembers(); while (members.hasMoreElements()) { save(members.nextElement()); } } else if (obj instanceof OptionPane) { try { ((OptionPane) obj).save(); } catch (Throwable t) { Log.log(Log.ERROR, this, "Error saving options:"); Log.log(Log.ERROR, this, t); } } else if (obj instanceof String) { save(deferredOptionPanes.get(obj)); } } // }}} // {{{ class OptionTreeModel public class OptionTreeModel implements TreeModel { private OptionGroup root = new OptionGroup(null); private EventListenerList listenerList = new EventListenerList(); public void addTreeModelListener(TreeModelListener l) { listenerList.add(TreeModelListener.class, l); } public void removeTreeModelListener(TreeModelListener l) { listenerList.remove(TreeModelListener.class, l); } public Object getChild(Object parent, int index) { if (parent instanceof OptionGroup) { return ((OptionGroup)parent).getMember(index); } else { return null; } } public int getChildCount(Object parent) { if (parent instanceof OptionGroup) { return ((OptionGroup)parent).getMemberCount(); } else { return 0; } } public int getIndexOfChild(Object parent, Object child) { if (parent instanceof OptionGroup) { return ((OptionGroup)parent) .getMemberIndex(child); } else { return -1; } } public Object getRoot() { return root; } public boolean isLeaf(Object node) { return !(node instanceof OptionGroup); } public void valueForPathChanged(TreePath path, Object newValue) { // this model may not be changed by the TableCellEditor } protected void fireNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent modelEvent = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != TreeModelListener.class) continue; if (modelEvent == null) { modelEvent = new TreeModelEvent(source, path, childIndices, children); } ((TreeModelListener)listeners[i + 1]) .treeNodesChanged(modelEvent); } } protected void fireNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent modelEvent = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != TreeModelListener.class) continue; if (modelEvent == null) { modelEvent = new TreeModelEvent(source, path, childIndices, children); } ((TreeModelListener)listeners[i + 1]) .treeNodesInserted(modelEvent); } } protected void fireNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent modelEvent = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != TreeModelListener.class) continue; if (modelEvent == null) { modelEvent = new TreeModelEvent(source, path, childIndices, children); } ((TreeModelListener)listeners[i + 1]) .treeNodesRemoved(modelEvent); } } protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent modelEvent = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != TreeModelListener.class) continue; if (modelEvent == null) { modelEvent = new TreeModelEvent(source, path, childIndices, children); } ((TreeModelListener)listeners[i + 1]) .treeStructureChanged(modelEvent); } } } //}}} }