/* 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;
}
}