/* Copyright (C) 2003-2011 JabRef contributors. 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package net.sf.jabref.groups; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.border.Border; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CompoundEdit; import net.sf.jabref.AbstractWorker; import net.sf.jabref.BasePanel; import net.sf.jabref.BibtexEntry; import net.sf.jabref.ErrorMessageDisplay; import net.sf.jabref.GUIGlobals; import net.sf.jabref.Globals; import net.sf.jabref.JabRefFrame; import net.sf.jabref.JabRefPreferences; import net.sf.jabref.MetaData; import net.sf.jabref.SearchRule; import net.sf.jabref.SearchRuleSet; import net.sf.jabref.SidePaneComponent; import net.sf.jabref.SidePaneManager; import net.sf.jabref.help.HelpAction; import net.sf.jabref.undo.NamedCompound; public class GroupSelector extends SidePaneComponent implements TreeSelectionListener, ActionListener, ErrorMessageDisplay { private static Logger logger = Logger.getLogger(GroupSelector.class.getName()); JButton newButton = new JButton(GUIGlobals.getImage("new")), helpButton = new JButton( GUIGlobals.getImage("help")), refresh = new JButton( GUIGlobals.getImage("refresh")), autoGroup = new JButton(GUIGlobals.getImage("autoGroup")), openset = new JButton(Globals.lang("Settings")); Color bgColor = Color.white; GroupsTree groupsTree; DefaultTreeModel groupsTreeModel; GroupTreeNode groupsRoot; JScrollPane sp; GridBagLayout gbl = new GridBagLayout(); GridBagConstraints con = new GridBagConstraints(); JabRefFrame frame; String searchField; JPopupMenu groupsContextMenu = new JPopupMenu(); JPopupMenu settings = new JPopupMenu(); private JRadioButtonMenuItem hideNonHits, grayOut; JRadioButtonMenuItem andCb = new JRadioButtonMenuItem(Globals.lang("Intersection"), true); JRadioButtonMenuItem orCb = new JRadioButtonMenuItem(Globals.lang("Union"), false); JRadioButtonMenuItem floatCb = new JRadioButtonMenuItem(Globals.lang("Float"), true); JRadioButtonMenuItem highlCb = new JRadioButtonMenuItem(Globals.lang("Highlight"), false); JCheckBoxMenuItem invCb = new JCheckBoxMenuItem(Globals.lang("Inverted"), false), select = new JCheckBoxMenuItem(Globals.lang("Select matches"), false); JCheckBoxMenuItem showOverlappingGroups = new JCheckBoxMenuItem( Globals.lang("Highlight overlapping groups")); // JZTODO lyrics JCheckBoxMenuItem autoAssignGroup = new JCheckBoxMenuItem( Globals.lang("Automatically assign new entry to selected groups")); ButtonGroup bgr = new ButtonGroup(); ButtonGroup visMode = new ButtonGroup(); ButtonGroup nonHits = new ButtonGroup(); JButton expand = new JButton(GUIGlobals.getImage("down")), reduce = new JButton(GUIGlobals.getImage("up")); JCheckBoxMenuItem editModeCb = new JCheckBoxMenuItem(Globals.lang("Edit Group Membership"), false); Border editModeBorder = BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.RED), "Edit mode", TitledBorder.RIGHT, TitledBorder.TOP, Font.getFont("Default"), Color.RED); boolean editModeIndicator; SidePaneManager manager; /** * The first element for each group defines which field to use for the * quicksearch. The next two define the name and regexp for the group. * * */ public GroupSelector(JabRefFrame frame, SidePaneManager manager) { super(manager, GUIGlobals.getIconUrl("toggleGroups"), Globals.lang("Groups")); this.groupsRoot = new GroupTreeNode(new AllEntriesGroup()); this.manager = manager; this.frame = frame; hideNonHits = new JRadioButtonMenuItem(Globals.lang("Hide non-hits"), !Globals.prefs.getBoolean("grayOutNonHits")); grayOut = new JRadioButtonMenuItem(Globals.lang("Gray out non-hits"), Globals.prefs.getBoolean("grayOutNonHits")); nonHits.add(hideNonHits); nonHits.add(grayOut); floatCb.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("groupFloatSelections", floatCb.isSelected()); } }); andCb.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("groupIntersectSelections", andCb.isSelected()); } }); invCb.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("groupInvertSelections", invCb.isSelected()); } }); showOverlappingGroups.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("groupShowOverlapping", showOverlappingGroups.isSelected()); if (!showOverlappingGroups.isSelected()) { groupsTree.setHighlight2Cells(null); } } }); select.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("groupSelectMatches", select.isSelected()); } }); grayOut.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("grayOutNonHits", grayOut.isSelected()); } }); if (Globals.prefs.getBoolean("groupFloatSelections")) { floatCb.setSelected(true); highlCb.setSelected(false); } else { highlCb.setSelected(true); floatCb.setSelected(false); } if (Globals.prefs.getBoolean("groupIntersectSelections")) { andCb.setSelected(true); orCb.setSelected(false); } else { orCb.setSelected(true); andCb.setSelected(false); } autoAssignGroup.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { Globals.prefs.putBoolean("autoAssignGroup", autoAssignGroup.isSelected()); } }); invCb.setSelected(Globals.prefs.getBoolean("groupInvertSelections")); showOverlappingGroups.setSelected(Globals.prefs.getBoolean("groupShowOverlapping")); select.setSelected(Globals.prefs.getBoolean("groupSelectMatches")); editModeIndicator = Globals.prefs.getBoolean(JabRefPreferences.EDIT_GROUP_MEMBERSHIP_MODE); editModeCb.setSelected(editModeIndicator); autoAssignGroup.setSelected(Globals.prefs.getBoolean("autoAssignGroup")); openset.setMargin(new Insets(0, 0, 0, 0)); settings.add(andCb); settings.add(orCb); settings.addSeparator(); settings.add(invCb); settings.addSeparator(); settings.add(select); settings.addSeparator(); settings.add(editModeCb); settings.addSeparator(); settings.add(grayOut); settings.add(hideNonHits); settings.addSeparator(); settings.add(showOverlappingGroups); settings.addSeparator(); settings.add(autoAssignGroup); // settings.add(moreRow); // settings.add(lessRow); openset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (settings.isVisible()) { // System.out.println("oee"); // settings.setVisible(false); } else { JButton src = (JButton) e.getSource(); autoAssignGroup.setSelected(Globals.prefs.getBoolean("autoAssignGroup")); settings.show(src, 0, openset.getHeight()); } } }); expand.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int i = Globals.prefs.getInt("groupsVisibleRows") + 1; groupsTree.setVisibleRowCount(i); groupsTree.revalidate(); groupsTree.repaint(); GroupSelector.this.revalidate(); GroupSelector.this.repaint(); Globals.prefs.putInt("groupsVisibleRows", i); logger.fine(Double.toString(GroupSelector.this.getHeight())); logger.fine(Double.toString(GroupSelector.this.getPreferredSize().getHeight())); } }); reduce.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int i = Globals.prefs.getInt("groupsVisibleRows") - 1; if (i < 1) { i = 1; } groupsTree.setVisibleRowCount(i); groupsTree.revalidate(); groupsTree.repaint(); GroupSelector.this.revalidate(); // _panel.sidePaneManager.revalidate(); GroupSelector.this.repaint(); Globals.prefs.putInt("groupsVisibleRows", i); } }); editModeCb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { editModeIndicator = editModeCb.getState(); updateBorder(editModeIndicator); Globals.prefs.putBoolean(JabRefPreferences.EDIT_GROUP_MEMBERSHIP_MODE, editModeIndicator); } }); int butSize = newButton.getIcon().getIconHeight() + 5; Dimension butDim = new Dimension(butSize, butSize); //Dimension butDimSmall = new Dimension(20, 20); newButton.setPreferredSize(butDim); newButton.setMinimumSize(butDim); refresh.setPreferredSize(butDim); refresh.setMinimumSize(butDim); helpButton.setPreferredSize(butDim); helpButton.setMinimumSize(butDim); autoGroup.setPreferredSize(butDim); autoGroup.setMinimumSize(butDim); openset.setPreferredSize(butDim); openset.setMinimumSize(butDim); expand.setPreferredSize(butDim); expand.setMinimumSize(butDim); reduce.setPreferredSize(butDim); reduce.setMinimumSize(butDim); Insets butIns = new Insets(0, 0, 0, 0); helpButton.setMargin(butIns); reduce.setMargin(butIns); expand.setMargin(butIns); openset.setMargin(butIns); newButton.addActionListener(this); refresh.addActionListener(this); andCb.addActionListener(this); orCb.addActionListener(this); invCb.addActionListener(this); showOverlappingGroups.addActionListener(this); autoGroup.addActionListener(this); floatCb.addActionListener(this); highlCb.addActionListener(this); select.addActionListener(this); hideNonHits.addActionListener(this); grayOut.addActionListener(this); newButton.setToolTipText(Globals.lang("New group")); refresh.setToolTipText(Globals.lang("Refresh view")); andCb.setToolTipText(Globals.lang("Display only entries belonging to all selected" + " groups.")); orCb.setToolTipText(Globals.lang("Display all entries belonging to one or more " + "of the selected groups.")); autoGroup.setToolTipText(Globals.lang("Automatically create groups for database.")); invCb.setToolTipText(Globals.lang("Show entries *not* in group selection")); showOverlappingGroups.setToolTipText( // JZTODO lyrics "Highlight groups that contain entries contained in any currently selected group"); floatCb.setToolTipText(Globals.lang("Move entries in group selection to the top")); highlCb.setToolTipText(Globals.lang("Gray out entries not in group selection")); select.setToolTipText(Globals.lang("Select entries in group selection")); expand.setToolTipText(Globals.lang("Show one more row")); reduce.setToolTipText(Globals.lang("Show one less rows")); editModeCb.setToolTipText(Globals.lang("Click group to toggle membership of selected entries")); bgr.add(andCb); bgr.add(orCb); visMode.add(floatCb); visMode.add(highlCb); JPanel main = new JPanel(); main.setLayout(gbl); con.fill = GridBagConstraints.BOTH; //con.insets = new Insets(0, 0, 2, 0); con.weightx = 1; con.gridwidth = 1; con.gridx = 0; con.gridy = 0; //con.insets = new Insets(1, 1, 1, 1); gbl.setConstraints(newButton, con); main.add(newButton); con.gridx = 1; gbl.setConstraints(refresh, con); main.add(refresh); con.gridx = 2; gbl.setConstraints(autoGroup, con); main.add(autoGroup); con.gridx = 3; con.gridwidth = GridBagConstraints.REMAINDER; HelpAction helpAction = new HelpAction(frame.helpDiag, GUIGlobals.groupsHelp, "Help on groups"); helpButton.addActionListener(helpAction); helpButton.setToolTipText(Globals.lang("Help on groups")); gbl.setConstraints(helpButton, con); main.add(helpButton); // header.setBorder(BorderFactory.createMatteBorder(1,1,1,1,Color.red)); // helpButton.setBorder(BorderFactory.createMatteBorder(1,1,1,1,Color.red)); groupsTree = new GroupsTree(this); groupsTree.addTreeSelectionListener(this); groupsTree.setModel(groupsTreeModel = new DefaultTreeModel(groupsRoot)); sp = new JScrollPane(groupsTree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); revalidateGroups(); con.gridwidth = GridBagConstraints.REMAINDER; con.weighty = 1; con.gridx = 0; con.gridwidth = 4; con.gridy = 1; gbl.setConstraints(sp, con); main.add(sp); JPanel pan = new JPanel(); GridBagLayout gb = new GridBagLayout(); con.weighty = 0; gbl.setConstraints(pan, con); pan.setLayout(gb); con.insets = new Insets(0, 0, 0, 0); con.gridx = 0; con.gridy = 0; con.weightx = 1; con.gridwidth = 4; con.fill = GridBagConstraints.HORIZONTAL; gb.setConstraints(openset, con); pan.add(openset); con.gridwidth = 1; con.gridx = 4; con.gridy = 0; gb.setConstraints(expand, con); pan.add(expand); con.gridx = 5; gb.setConstraints(reduce, con); pan.add(reduce); con.gridwidth = 6; con.gridy = 1; con.gridx = 0; con.fill = GridBagConstraints.HORIZONTAL; con.gridy = 2; con.gridx = 0; con.gridwidth = 4; gbl.setConstraints(pan, con); main.add(pan); main.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); add(main, BorderLayout.CENTER); updateBorder(editModeIndicator); definePopup(); moveNodeUpAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_MASK)); moveNodeDownAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_MASK)); moveNodeLeftAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_MASK)); moveNodeRightAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_MASK)); } private void definePopup() { // These key bindings are just to have the shortcuts displayed // in the popup menu. The actual keystroke processing is in // BasePanel (entryTable.addKeyListener(...)). groupsContextMenu.add(editGroupPopupAction); groupsContextMenu.add(addGroupPopupAction); groupsContextMenu.add(addSubgroupPopupAction); groupsContextMenu.addSeparator(); groupsContextMenu.add(removeGroupAndSubgroupsPopupAction); groupsContextMenu.add(removeGroupKeepSubgroupsPopupAction); groupsContextMenu.add(removeSubgroupsPopupAction); groupsContextMenu.addSeparator(); groupsContextMenu.add(expandSubtreePopupAction); groupsContextMenu.add(collapseSubtreePopupAction); groupsContextMenu.addSeparator(); groupsContextMenu.add(moveSubmenu); sortSubmenu.add(sortDirectSubgroupsPopupAction); sortSubmenu.add(sortAllSubgroupsPopupAction); groupsContextMenu.add(sortSubmenu); moveSubmenu.add(moveNodeUpPopupAction); moveSubmenu.add(moveNodeDownPopupAction); moveSubmenu.add(moveNodeLeftPopupAction); moveSubmenu.add(moveNodeRightPopupAction); groupsContextMenu.addSeparator(); groupsContextMenu.add(addToGroup); groupsContextMenu.add(moveToGroup); groupsContextMenu.add(removeFromGroup); groupsTree.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { showPopup(e); } } public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { showPopup(e); } } public void mouseClicked(MouseEvent e) { TreePath path = groupsTree.getPathForLocation(e.getPoint().x, e.getPoint().y); if (path == null) { return; } GroupTreeNode node = (GroupTreeNode) path.getLastPathComponent(); // the root node is "AllEntries" and cannot be edited if (node.isRoot()) { return; } if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { // edit editGroupAction.actionPerformed(null); // dummy event } else if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { annotationEvent(node); } } }); // be sure to remove a possible border highlight when the popup menu // disappears groupsContextMenu.addPopupMenuListener(new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // nothing to do } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { groupsTree.setHighlightBorderCell(null); } public void popupMenuCanceled(PopupMenuEvent e) { groupsTree.setHighlightBorderCell(null); } }); } private void showPopup(MouseEvent e) { final TreePath path = groupsTree.getPathForLocation(e.getPoint().x, e.getPoint().y); addGroupPopupAction.setEnabled(true); addSubgroupPopupAction.setEnabled(path != null); editGroupPopupAction.setEnabled(path != null); removeGroupAndSubgroupsPopupAction.setEnabled(path != null); removeGroupKeepSubgroupsPopupAction.setEnabled(path != null); moveSubmenu.setEnabled(path != null); expandSubtreePopupAction.setEnabled(path != null); collapseSubtreePopupAction.setEnabled(path != null); removeSubgroupsPopupAction.setEnabled(path != null); sortSubmenu.setEnabled(path != null); addToGroup.setEnabled(false); moveToGroup.setEnabled(false); removeFromGroup.setEnabled(false); if (path != null) { // some path dependent enabling/disabling GroupTreeNode node = (GroupTreeNode) path.getLastPathComponent(); editGroupPopupAction.setNode(node); addSubgroupPopupAction.setNode(node); removeGroupAndSubgroupsPopupAction.setNode(node); removeSubgroupsPopupAction.setNode(node); removeGroupKeepSubgroupsPopupAction.setNode(node); expandSubtreePopupAction.setNode(node); collapseSubtreePopupAction.setNode(node); sortDirectSubgroupsPopupAction.setNode(node); sortAllSubgroupsPopupAction.setNode(node); groupsTree.setHighlightBorderCell(node); AbstractGroup group = node.getGroup(); if (group instanceof AllEntriesGroup) { editGroupPopupAction.setEnabled(false); addGroupPopupAction.setEnabled(false); removeGroupAndSubgroupsPopupAction.setEnabled(false); removeGroupKeepSubgroupsPopupAction.setEnabled(false); } else { editGroupPopupAction.setEnabled(true); addGroupPopupAction.setEnabled(true); addGroupPopupAction.setNode(node); removeGroupAndSubgroupsPopupAction.setEnabled(true); removeGroupKeepSubgroupsPopupAction.setEnabled(true); } expandSubtreePopupAction.setEnabled(groupsTree.isCollapsed(path) || groupsTree.hasCollapsedDescendant(path)); collapseSubtreePopupAction.setEnabled(groupsTree.isExpanded(path) || groupsTree.hasExpandedDescendant(path)); sortSubmenu.setEnabled(!node.isLeaf()); removeSubgroupsPopupAction.setEnabled(!node.isLeaf()); moveNodeUpPopupAction.setEnabled(node.canMoveUp()); moveNodeDownPopupAction.setEnabled(node.canMoveDown()); moveNodeLeftPopupAction.setEnabled(node.canMoveLeft()); moveNodeRightPopupAction.setEnabled(node.canMoveRight()); moveSubmenu.setEnabled(moveNodeUpPopupAction.isEnabled() || moveNodeDownPopupAction.isEnabled() || moveNodeLeftPopupAction.isEnabled() || moveNodeRightPopupAction.isEnabled()); moveNodeUpPopupAction.setNode(node); moveNodeDownPopupAction.setNode(node); moveNodeLeftPopupAction.setNode(node); moveNodeRightPopupAction.setNode(node); // add/remove entries to/from group BibtexEntry[] selection = frame.basePanel().getSelectedEntries(); if (selection.length > 0) { if (node.getGroup().supportsAdd() && !node.getGroup(). containsAll(selection)) { addToGroup.setNode(node); addToGroup.setBasePanel(panel); addToGroup.setEnabled(true); moveToGroup.setNode(node); moveToGroup.setBasePanel(panel); moveToGroup.setEnabled(true); } if (node.getGroup().supportsRemove() && node.getGroup(). containsAny(selection)) { removeFromGroup.setNode(node); removeFromGroup.setBasePanel(panel); removeFromGroup.setEnabled(true); } } } else { editGroupPopupAction.setNode(null); addGroupPopupAction.setNode(null); addSubgroupPopupAction.setNode(null); removeGroupAndSubgroupsPopupAction.setNode(null); removeSubgroupsPopupAction.setNode(null); removeGroupKeepSubgroupsPopupAction.setNode(null); moveNodeUpPopupAction.setNode(null); moveNodeDownPopupAction.setNode(null); moveNodeLeftPopupAction.setNode(null); moveNodeRightPopupAction.setNode(null); expandSubtreePopupAction.setNode(null); collapseSubtreePopupAction.setNode(null); sortDirectSubgroupsPopupAction.setNode(null); sortAllSubgroupsPopupAction.setNode(null); } groupsContextMenu.show(groupsTree, e.getPoint().x, e.getPoint().y); } private void updateBorder(boolean editMode) { if (editMode) { groupsTree.setBorder(editModeBorder); this.setTitle("<html><font color='red'>Groups Edit mode</font></html>"); } else { groupsTree.setBorder(null); this.setTitle(Globals.lang("Groups")); } groupsTree.revalidate(); groupsTree.repaint(); } /** * * @param deletion != addition */ private void updateGroupContent(GroupTreeNode node) { BibtexEntry[] entries = panel.getSelectedEntries(); AbstractGroup group = node.getGroup(); AbstractUndoableEdit undoRemove = null; AbstractUndoableEdit undoAdd = null; // Sort entries into current members and non-members of the group // Current members will be removed // Current non-members will be added ArrayList<BibtexEntry> toRemove = new ArrayList<BibtexEntry>(entries.length); ArrayList<BibtexEntry> toAdd = new ArrayList<BibtexEntry>(entries.length); for (BibtexEntry entry : entries) { // Sort according to current state of the entries if (group.contains(entry)) { logger.fine("remove " + entry.toString()); toRemove.add(entry); } else { logger.fine("add " + entry.toString()); toAdd.add(entry); } } // If there are entries to remove if (!toRemove.isEmpty()) undoRemove = node.removeFromGroup(toRemove.toArray(new BibtexEntry[0])); // If there are entries to add if (!toAdd.isEmpty()) undoAdd = node.addToGroup(toAdd.toArray(new BibtexEntry[0])); // Remember undo information if (undoRemove != null) { if (undoAdd != null) { // we removed and added entries undoRemove.addEdit(undoAdd); } panel.undoManager.addEdit(undoRemove); } else if (undoAdd != null) { panel.undoManager.addEdit(undoAdd); } } /** * * @param deletion != addition */ public void updateGroupContentIfEnabled(boolean deletion) { if ((groupsTree == null) || (groupsTree.getSelectionCount() == 0)) { return; } if (!this.editModeIndicator) { // add button selected return; } GroupTreeNode curNode = (GroupTreeNode) ((groupsTree.getSelectionPaths())[0].getLastPathComponent()); updateGroupContent(curNode); } private void annotationEvent(GroupTreeNode node) { logger.fine("annotationEvent"); logger.fine(node.toString()); if (editModeIndicator) { updateGroupContent(node); panel.markBaseChanged(); panel.updateEntryEditorIfShowing(); updateSelections(); } } // private void annotationEvent() { // this.annotationEvent((GroupTreeNode) ((groupsTree.getSelectionPaths())[0].getLastPathComponent())); // } public void valueChanged(TreeSelectionEvent e) { if (panel == null) // sorry, we're closed! { return; // ignore this event } final TreePath[] selection = groupsTree.getSelectionPaths(); if (selection == null || selection.length == 0 || (selection.length == 1 && ((GroupTreeNode) selection[0].getLastPathComponent()).getGroup() instanceof AllEntriesGroup)) { panel.stopShowingGroup(); panel.mainTable.stopShowingFloatGrouping(); if (showOverlappingGroups.isSelected()) { groupsTree.setHighlight2Cells(null); } frame.output(Globals.lang("Displaying no groups") + "."); return; } if (!editModeIndicator) { // annotationEvent(); // } else { updateSelections(); } } private void updateSelections() { final AndOrSearchRuleSet searchRules = new AndOrSearchRuleSet(andCb.isSelected(), invCb.isSelected()); TreePath[] selection = groupsTree.getSelectionPaths(); for (int i = 0; i < selection.length; ++i) { searchRules.addRule(((GroupTreeNode) selection[i].getLastPathComponent()).getSearchRule()); } Hashtable<String, String> searchOptions = new Hashtable<String, String>(); searchOptions.put("option", "dummy"); GroupingWorker worker = new GroupingWorker(searchRules, searchOptions); worker.getWorker().run(); worker.getCallBack().update(); /*panel.setGroupMatcher(new SearchMatcher(searchRules, searchOptions)); DatabaseSearch search = new DatabaseSearch(this, searchOptions, searchRules, panel, Globals.GROUPSEARCH, floatCb.isSelected(), Globals.prefs .getBoolean("grayOutNonHits"), //true, select.isSelected()); search.start();*/ } class GroupingWorker extends AbstractWorker { private SearchRuleSet rules; private Hashtable<String, String> searchTerm; private ArrayList<BibtexEntry> matches = new ArrayList<BibtexEntry>(); private boolean showOverlappingGroupsP; int hits = 0; public GroupingWorker(SearchRuleSet rules, Hashtable<String, String> searchTerm) { this.rules = rules; this.searchTerm = searchTerm; showOverlappingGroupsP = showOverlappingGroups.isSelected(); } public void run() { for (BibtexEntry entry : panel.getDatabase().getEntries()) { boolean hit = rules.applyRule(searchTerm, entry) > 0; entry.setGroupHit(hit); if (hit) { hits++; if (showOverlappingGroupsP) { matches.add(entry); } } } } public void update() { // Show the result in the chosen way: if (hideNonHits.isSelected()) { panel.mainTable.stopShowingFloatGrouping(); // Turn off shading, if active. panel.setGroupMatcher(GroupMatcher.INSTANCE); // Turn on filtering. } else if (grayOut.isSelected()) { panel.stopShowingGroup(); // Turn off filtering, if active. panel.mainTable.showFloatGrouping(GroupMatcher.INSTANCE); // Turn on shading. } if (showOverlappingGroupsP) { showOverlappingGroups(matches); } frame.output(Globals.lang("Updated group selection") + "."); } } /** * Revalidate the groups tree (e.g. after the data stored in the model has * been changed) and set the specified selection and expansion state. * @param node If this is non-null, the view is scrolled to make it visible. */ public void revalidateGroups(TreePath[] selectionPaths, Enumeration<TreePath> expandedNodes) { revalidateGroups(selectionPaths, expandedNodes, null); } /** * Revalidate the groups tree (e.g. after the data stored in the model has * been changed) and set the specified selection and expansion state. * @param node If this is non-null, the view is scrolled to make it visible. */ public void revalidateGroups(TreePath[] selectionPaths, Enumeration<TreePath> expandedNodes, GroupTreeNode node) { groupsTreeModel.reload(); groupsTree.clearSelection(); if (selectionPaths != null) { groupsTree.setSelectionPaths(selectionPaths); } // tree is completely collapsed here if (expandedNodes != null) { while (expandedNodes.hasMoreElements()) { groupsTree.expandPath(expandedNodes.nextElement()); } } groupsTree.revalidate(); if (node != null) { groupsTree.scrollPathToVisible(new TreePath(node.getPath())); } } /** * Revalidate the groups tree (e.g. after the data stored in the model has * been changed) and maintain the current selection and expansion state. */ public void revalidateGroups() { revalidateGroups(null); } /** * Revalidate the groups tree (e.g. after the data stored in the model has * been changed) and maintain the current selection and expansion state. * @param node If this is non-null, the view is scrolled to make it visible. */ public void revalidateGroups(GroupTreeNode node) { revalidateGroups(groupsTree.getSelectionPaths(), getExpandedPaths(), node); } public void actionPerformed(ActionEvent e) { if (e.getSource() == refresh) { valueChanged(null); } else if (e.getSource() == newButton) { GroupDialog gd = new GroupDialog(frame, panel, null); gd.setVisible(true); // gd.show(); -> deprecated since 1.5 if (gd.okPressed()) { AbstractGroup newGroup = gd.getResultingGroup(); GroupTreeNode newNode = new GroupTreeNode(newGroup); groupsRoot.add(newNode); revalidateGroups(); UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( GroupSelector.this, groupsRoot, newNode, UndoableAddOrRemoveGroup.ADD_NODE); panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Created_group_\"%0\".", newGroup.getName())); } } else if (e.getSource() == autoGroup) { AutoGroupDialog gd = new AutoGroupDialog(frame, panel, GroupSelector.this, groupsRoot, Globals.prefs.get("groupsDefaultField"), " .,", ","); gd.setVisible(true); // gd.show(); -> deprecated since 1.5 // gd does the operation itself } else if (e.getSource() instanceof JCheckBox) { valueChanged(null); } else if (e.getSource() instanceof JCheckBoxMenuItem) { valueChanged(null); } else if (e.getSource() instanceof JRadioButtonMenuItem) { valueChanged(null); } } @Override public void componentOpening() { valueChanged(null); } @Override public void componentClosing() { if (panel != null) {// panel may be null if no file is open any more panel.stopShowingGroup(); panel.mainTable.stopShowingFloatGrouping(); } frame.groupToggle.setSelected(false); } public void setGroups(GroupTreeNode groupsRoot) { groupsTree.setModel(groupsTreeModel = new DefaultTreeModel(groupsRoot)); this.groupsRoot = groupsRoot; if (Globals.prefs.getBoolean("groupExpandTree")) { groupsTree.expandSubtree(groupsRoot); } } /** * Adds the specified node as a child of the current root. The group * contained in <b>newGroups </b> must not be of type AllEntriesGroup, since * every tree has exactly one AllEntriesGroup (its root). The <b>newGroups * </b> are inserted directly, i.e. they are not deepCopy()'d. */ public void addGroups(GroupTreeNode newGroups, CompoundEdit ce) { // paranoia: ensure that there are never two instances of AllEntriesGroup if (newGroups.getGroup() instanceof AllEntriesGroup) { return; // this should be impossible anyway } groupsRoot.add(newGroups); UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(this, groupsRoot, newGroups, UndoableAddOrRemoveGroup.ADD_NODE); ce.addEdit(undo); } private abstract class NodeAction extends AbstractAction { protected GroupTreeNode m_node = null; public NodeAction(String s) { super(s); } public GroupTreeNode getNode() { return m_node; } public void setNode(GroupTreeNode node) { this.m_node = node; } /** Returns the node to use in this action. If a node has been * set explicitly (via setNode), it is returned. Otherwise, the first * node in the current selection is returned. If all this fails, null * is returned. */ public GroupTreeNode getNodeToUse() { if (m_node != null) { return m_node; } TreePath path = groupsTree.getSelectionPath(); if (path != null) { return (GroupTreeNode) path.getLastPathComponent(); } return null; } } final AbstractAction editGroupAction = new EditGroupAction(); final NodeAction editGroupPopupAction = new EditGroupAction(); final NodeAction addGroupPopupAction = new AddGroupAction(); final NodeAction addSubgroupPopupAction = new AddSubgroupAction(); final NodeAction removeGroupAndSubgroupsPopupAction = new RemoveGroupAndSubgroupsAction(); final NodeAction removeSubgroupsPopupAction = new RemoveSubgroupsAction(); final NodeAction removeGroupKeepSubgroupsPopupAction = new RemoveGroupKeepSubgroupsAction(); final NodeAction moveNodeUpPopupAction = new MoveNodeUpAction(); final NodeAction moveNodeDownPopupAction = new MoveNodeDownAction(); final NodeAction moveNodeLeftPopupAction = new MoveNodeLeftAction(); final NodeAction moveNodeRightPopupAction = new MoveNodeRightAction(); final NodeAction moveNodeUpAction = new MoveNodeUpAction(); final NodeAction moveNodeDownAction = new MoveNodeDownAction(); final NodeAction moveNodeLeftAction = new MoveNodeLeftAction(); final NodeAction moveNodeRightAction = new MoveNodeRightAction(); final NodeAction expandSubtreePopupAction = new ExpandSubtreeAction(); final NodeAction collapseSubtreePopupAction = new CollapseSubtreeAction(); final NodeAction sortDirectSubgroupsPopupAction = new SortDirectSubgroupsAction(); final NodeAction sortAllSubgroupsPopupAction = new SortAllSubgroupsAction(); final AddToGroupAction addToGroup = new AddToGroupAction(false); final AddToGroupAction moveToGroup = new AddToGroupAction(true); final RemoveFromGroupAction removeFromGroup = new RemoveFromGroupAction(); private class EditGroupAction extends NodeAction { public EditGroupAction() { super(Globals.lang("Edit group")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final AbstractGroup oldGroup = node.getGroup(); final GroupDialog gd = new GroupDialog(frame, panel, oldGroup); gd.setVisible(true); if (gd.okPressed()) { AbstractGroup newGroup = gd.getResultingGroup(); AbstractUndoableEdit undoAddPreviousEntries = gd.getUndoForAddPreviousEntries(); UndoableModifyGroup undo = new UndoableModifyGroup( GroupSelector.this, groupsRoot, node, newGroup); node.setGroup(newGroup); revalidateGroups(node); // Store undo information. if (undoAddPreviousEntries == null) { panel.undoManager.addEdit(undo); } else { NamedCompound nc = new NamedCompound("Modify Group"); // JZTODO lyrics nc.addEdit(undo); nc.addEdit(undoAddPreviousEntries); nc.end(); panel.undoManager.addEdit(nc); } panel.markBaseChanged(); frame.output(Globals.lang("Modified group \"%0\".", newGroup.getName())); } } } private class AddGroupAction extends NodeAction { public AddGroupAction() { super(Globals.lang("Add Group")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final GroupDialog gd = new GroupDialog(frame, panel, null); gd.setVisible(true); if (!gd.okPressed()) { return; // ignore } final AbstractGroup newGroup = gd.getResultingGroup(); final GroupTreeNode newNode = new GroupTreeNode(newGroup); if (node == null) { groupsRoot.add(newNode); } else { ((GroupTreeNode) node.getParent()).insert(newNode, node.getParent().getIndex(node) + 1); } UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( GroupSelector.this, groupsRoot, newNode, UndoableAddOrRemoveGroup.ADD_NODE); revalidateGroups(); groupsTree.expandPath(new TreePath( (node != null ? node : groupsRoot).getPath())); // Store undo information. panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Added group \"%0\".", newGroup.getName())); } } private class AddSubgroupAction extends NodeAction { public AddSubgroupAction() { super(Globals.lang("Add Subgroup")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final GroupDialog gd = new GroupDialog(frame, panel, null); gd.setVisible(true); if (!gd.okPressed()) { return; // ignore } final AbstractGroup newGroup = gd.getResultingGroup(); final GroupTreeNode newNode = new GroupTreeNode(newGroup); node.add(newNode); UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( GroupSelector.this, groupsRoot, newNode, UndoableAddOrRemoveGroup.ADD_NODE); revalidateGroups(); groupsTree.expandPath(new TreePath(node.getPath())); // Store undo information. panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Added group \"%0\".", newGroup.getName())); } } private class RemoveGroupAndSubgroupsAction extends NodeAction { public RemoveGroupAndSubgroupsAction() { super(Globals.lang("Remove group and subgroups")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final AbstractGroup group = node.getGroup(); int conf = JOptionPane.showConfirmDialog(frame, Globals.lang("Remove group \"%0\" and its subgroups?", group.getName()), Globals.lang("Remove group and subgroups"), JOptionPane.YES_NO_OPTION); if (conf == JOptionPane.YES_OPTION) { final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( GroupSelector.this, groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_AND_CHILDREN); node.removeFromParent(); revalidateGroups(); // Store undo information. panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Removed group \"%0\" and its subgroups.", group.getName())); } } } private class RemoveSubgroupsAction extends NodeAction { public RemoveSubgroupsAction() { super(Globals.lang("Remove all subgroups")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final AbstractGroup group = node.getGroup(); int conf = JOptionPane.showConfirmDialog(frame, Globals.lang("Remove all subgroups of \"%0\"?", group.getName()), Globals.lang("Remove all subgroups"), JOptionPane.YES_NO_OPTION); if (conf == JOptionPane.YES_OPTION) { final UndoableModifySubtree undo = new UndoableModifySubtree( GroupSelector.this, getGroupTreeRoot(), node, "Remove all subgroups"); node.removeAllChildren(); revalidateGroups(); // Store undo information. panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Removed all subgroups of group \"%0\".", group.getName())); } } } private class RemoveGroupKeepSubgroupsAction extends NodeAction { public RemoveGroupKeepSubgroupsAction() { super(Globals.lang("Remove group, keep subgroups")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); final AbstractGroup group = node.getGroup(); int conf = JOptionPane.showConfirmDialog(frame, Globals.lang("Remove group \"%0\"?", group.getName()), Globals.lang("Remove group"), JOptionPane.YES_NO_OPTION); if (conf == JOptionPane.YES_OPTION) { final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( GroupSelector.this, groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN); final GroupTreeNode parent = (GroupTreeNode) node.getParent(); final int childIndex = parent.getIndex(node); node.removeFromParent(); while (node.getChildCount() > 0) { parent.insert((GroupTreeNode) node.getFirstChild(), childIndex); } revalidateGroups(); // Store undo information. panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Removed group \"%0\".", group.getName())); } } } public TreePath getSelectionPath() { return groupsTree.getSelectionPath(); } private class SortDirectSubgroupsAction extends NodeAction { public SortDirectSubgroupsAction() { super(Globals.lang("Immediate subgroups")); } public void actionPerformed(ActionEvent ae) { final GroupTreeNode node = getNodeToUse(); final UndoableModifySubtree undo = new UndoableModifySubtree( GroupSelector.this, getGroupTreeRoot(), node, Globals.lang("sort subgroups")); groupsTree.sort(node, false); panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Sorted immediate subgroups.")); } } private class SortAllSubgroupsAction extends NodeAction { public SortAllSubgroupsAction() { super(Globals.lang("All subgroups (recursively)")); } public void actionPerformed(ActionEvent ae) { final GroupTreeNode node = getNodeToUse(); final UndoableModifySubtree undo = new UndoableModifySubtree( GroupSelector.this, getGroupTreeRoot(), node, Globals.lang("sort subgroups")); groupsTree.sort(node, true); panel.undoManager.addEdit(undo); panel.markBaseChanged(); // JZTODO lyrics frame.output(Globals.lang("Sorted all subgroups recursively.")); } } public final AbstractAction clearHighlightAction = new AbstractAction(Globals.lang("Clear highlight")) { public void actionPerformed(ActionEvent ae) { groupsTree.setHighlight3Cells(null); } }; private class ExpandSubtreeAction extends NodeAction { public ExpandSubtreeAction() { super(Globals.lang("Expand subtree")); } public void actionPerformed(ActionEvent ae) { final GroupTreeNode node = getNodeToUse(); TreePath path = new TreePath(node.getPath()); groupsTree.expandSubtree((GroupTreeNode) path.getLastPathComponent()); revalidateGroups(); } } private class CollapseSubtreeAction extends NodeAction { public CollapseSubtreeAction() { super(Globals.lang("Collapse subtree")); } public void actionPerformed(ActionEvent ae) { final GroupTreeNode node = getNodeToUse(); TreePath path = new TreePath(node.getPath()); groupsTree.collapseSubtree((GroupTreeNode) path.getLastPathComponent()); revalidateGroups(); } } private class MoveNodeUpAction extends NodeAction { public MoveNodeUpAction() { super(Globals.lang("Up")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); moveNodeUp(node, false); } } private class MoveNodeDownAction extends NodeAction { public MoveNodeDownAction() { super(Globals.lang("Down")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); moveNodeDown(node, false); } } private class MoveNodeLeftAction extends NodeAction { public MoveNodeLeftAction() { super(Globals.lang("Left")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); moveNodeLeft(node, false); } } private class MoveNodeRightAction extends NodeAction { public MoveNodeRightAction() { super(Globals.lang("Right")); } public void actionPerformed(ActionEvent e) { final GroupTreeNode node = getNodeToUse(); moveNodeRight(node, false); } } /** * @param node The node to move * @return true if move was successful, false if not. */ public boolean moveNodeUp(GroupTreeNode node, boolean checkSingleSelection) { if (checkSingleSelection) { if (groupsTree.getSelectionCount() != 1) { frame.output(Globals.lang("Please select exactly one group to move.")); return false; // not possible } } AbstractUndoableEdit undo = null; if (!node.canMoveUp() || (undo = node.moveUp(GroupSelector.this)) == null) { frame.output(Globals.lang( "Cannot move group \"%0\" up.", node.getGroup().getName())); return false; // not possible } // update selection/expansion state (not really needed when // moving among siblings, but I'm paranoid) revalidateGroups(groupsTree.refreshPaths(groupsTree.getSelectionPaths()), groupsTree.refreshPaths(getExpandedPaths())); concludeMoveGroup(undo, node); return true; } /** * @param node The node to move * @return true if move was successful, false if not. */ public boolean moveNodeDown(GroupTreeNode node, boolean checkSingleSelection) { if (checkSingleSelection) { if (groupsTree.getSelectionCount() != 1) { frame.output(Globals.lang("Please select exactly one group to move.")); return false; // not possible } } AbstractUndoableEdit undo = null; if (!node.canMoveDown() || (undo = node.moveDown(GroupSelector.this)) == null) { frame.output(Globals.lang( "Cannot move group \"%0\" down.", node.getGroup().getName())); return false; // not possible } // update selection/expansion state (not really needed when // moving among siblings, but I'm paranoid) revalidateGroups(groupsTree.refreshPaths(groupsTree.getSelectionPaths()), groupsTree.refreshPaths(getExpandedPaths())); concludeMoveGroup(undo, node); return true; } /** * @param node The node to move * @return true if move was successful, false if not. */ public boolean moveNodeLeft(GroupTreeNode node, boolean checkSingleSelection) { if (checkSingleSelection) { if (groupsTree.getSelectionCount() != 1) { frame.output(Globals.lang("Please select exactly one group to move.")); return false; // not possible } } AbstractUndoableEdit undo = null; if (!node.canMoveLeft() || (undo = node.moveLeft(GroupSelector.this)) == null) { frame.output(Globals.lang( "Cannot move group \"%0\" left.", node.getGroup().getName())); return false; // not possible } // update selection/expansion state revalidateGroups(groupsTree.refreshPaths(groupsTree.getSelectionPaths()), groupsTree.refreshPaths(getExpandedPaths())); concludeMoveGroup(undo, node); return true; } /** * @param node The node to move * @return true if move was successful, false if not. */ public boolean moveNodeRight(GroupTreeNode node, boolean checkSingleSelection) { if (checkSingleSelection) { if (groupsTree.getSelectionCount() != 1) { frame.output(Globals.lang("Please select exactly one group to move.")); return false; // not possible } } AbstractUndoableEdit undo = null; if (!node.canMoveRight() || (undo = node.moveRight(GroupSelector.this)) == null) { frame.output(Globals.lang( "Cannot move group \"%0\" right.", node.getGroup().getName())); return false; // not possible } // update selection/expansion state revalidateGroups(groupsTree.refreshPaths(groupsTree.getSelectionPaths()), groupsTree.refreshPaths(getExpandedPaths())); concludeMoveGroup(undo, node); return true; } /** * Concludes the moving of a group tree node by storing the specified * undo information, marking the change, and setting the status line. * @param undo Undo information for the move operation. * @param node The node that has been moved. */ public void concludeMoveGroup(AbstractUndoableEdit undo, GroupTreeNode node) { panel.undoManager.addEdit(undo); panel.markBaseChanged(); frame.output(Globals.lang("Moved group \"%0\".", node.getGroup().getName())); } public void concludeAssignment(AbstractUndoableEdit undo, GroupTreeNode node, int assignedEntries) { if (undo == null) { frame.output(Globals.lang("The group \"%0\" already contains the selection.", new String[]{node.getGroup().getName()})); return; } panel.undoManager.addEdit(undo); panel.markBaseChanged(); panel.updateEntryEditorIfShowing(); final String groupName = node.getGroup().getName(); if (assignedEntries == 1) { frame.output(Globals.lang("Assigned 1 entry to group \"%0\".", groupName)); } else { frame.output(Globals.lang("Assigned %0 entries to group \"%1\".", String.valueOf(assignedEntries), groupName)); } } JMenu moveSubmenu = new JMenu(Globals.lang("Move")); JMenu sortSubmenu = new JMenu(Globals.lang("Sort alphabetically")); // JZTODO lyrics public GroupTreeNode getGroupTreeRoot() { return groupsRoot; } public Enumeration<TreePath> getExpandedPaths() { return groupsTree.getExpandedDescendants( new TreePath(groupsRoot.getPath())); } /** panel may be null to indicate that no file is currently open. */ public void setActiveBasePanel(BasePanel panel) { super.setActiveBasePanel(panel); if (panel == null) { // hide groups frame.sidePaneManager.hide("groups"); return; } MetaData metaData = panel.metaData(); if (metaData.getGroups() != null) { setGroups(metaData.getGroups()); } else { GroupTreeNode newGroupsRoot = new GroupTreeNode(new AllEntriesGroup()); metaData.setGroups(newGroupsRoot); setGroups(newGroupsRoot); } // auto show/hide groups interface if (Globals.prefs.getBoolean("groupAutoShow") && !groupsRoot.isLeaf()) { // groups were defined frame.sidePaneManager.show("groups"); frame.groupToggle.setSelected(true); } else if (Globals.prefs.getBoolean("groupAutoHide") && groupsRoot.isLeaf()) { // groups were not defined frame.sidePaneManager.hide("groups"); frame.groupToggle.setSelected(false); } synchronized (getTreeLock()) { validateTree(); } } /** * This method is required by the ErrorMessageDisplay interface, and lets this class * serve as a callback for regular expression exceptions happening in DatabaseSearch. * @param errorMessage */ public void reportError(String errorMessage) { // this should never happen, since regular expressions are checked for // correctness by the edit group dialog, and no other errors should // occur in a search System.err.println("Error in group search: " + errorMessage + ". Please report this on www.sf.net/projects/jabref"); } /** * This method is required by the ErrorMessageDisplay interface, and lets this class * serve as a callback for regular expression exceptions happening in DatabaseSearch. * @param errorMessage */ public void reportError(String errorMessage, Exception exception) { reportError(errorMessage); } /** * Highlight all groups that contain any/all of the specified entries. * If entries is null or has zero length, highlight is cleared. */ public void showMatchingGroups(BibtexEntry[] entries, boolean requireAll) { if (entries == null || entries.length == 0) { // nothing selected groupsTree.setHighlight3Cells(null); groupsTree.revalidate(); return; } GroupTreeNode node; AbstractGroup group; Vector<GroupTreeNode> vec = new Vector<GroupTreeNode>(); for (Enumeration<GroupTreeNode> e = groupsRoot.preorderEnumeration(); e.hasMoreElements();) { node = e.nextElement(); group = node.getGroup(); int i; for (i = 0; i < entries.length; ++i) { if (requireAll) { if (!group.contains(entries[i])) { break; } } else { if (group.contains(entries[i])) { vec.add(node); } } } if (requireAll && i >= entries.length) // did not break from loop { vec.add(node); } } groupsTree.setHighlight3Cells(vec.toArray()); // ensure that all highlighted nodes are visible for (int i = 0; i < vec.size(); ++i) { node = (GroupTreeNode) vec.elementAt(i).getParent(); if (node != null) { groupsTree.expandPath(new TreePath(node.getPath())); } } groupsTree.revalidate(); } /** Show groups that, if selected, would show at least one * of the entries found in the specified search. */ protected void showOverlappingGroups(List<BibtexEntry> matches) { //DatabaseSearch search) { GroupTreeNode node; SearchRule rule; BibtexEntry entry; Vector<GroupTreeNode> vec = new Vector<GroupTreeNode>(); Map<String, String> dummyMap = new HashMap<String, String>(); // just because I don't want to use null... for (Enumeration<GroupTreeNode> e = groupsRoot.depthFirstEnumeration(); e.hasMoreElements();) { node = e.nextElement(); rule = node.getSearchRule(); for (Iterator<BibtexEntry> it = matches.iterator(); it.hasNext();) { entry = it.next(); if (rule.applyRule(dummyMap, entry) == 0) { continue; } vec.add(node); break; } } groupsTree.setHighlight2Cells(vec.toArray()); } public GroupsTree getGroupsTree() { return this.groupsTree; } }