/* 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;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.DefaultCellEditor;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.undo.CompoundEdit;
import net.sf.jabref.export.LatexFieldFormatter;
import net.sf.jabref.help.HelpAction;
import net.sf.jabref.undo.UndoableInsertString;
import net.sf.jabref.undo.UndoableRemoveString;
import net.sf.jabref.undo.UndoableStringChange;
public class StringDialog extends JDialog {
// A reference to the entry this object works on.
BibtexDatabase base;
JabRefFrame frame;
BasePanel panel;
JabRefPreferences prefs;
TreeSet<BibtexString> stringsSet; // Our locally sorted set of strings.
Object[] strings;
// Layout objects.
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints con = new GridBagConstraints();
JLabel lab;
Container conPane = getContentPane();
JToolBar tlb = new JToolBar();
JPanel pan = new JPanel();
StringTable table;
HelpAction helpAction;
public StringDialog(JabRefFrame frame, BasePanel panel,
BibtexDatabase base, JabRefPreferences prefs) {
super(frame);
this.frame = frame;
this.panel = panel;
this.base = base;
this.prefs = prefs;
sortStrings();
helpAction = new HelpAction
(frame.helpDiag, GUIGlobals.stringEditorHelp, "Help");
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
closeAction.actionPerformed(null);
}
});
// We replace the default FocusTraversalPolicy with a subclass
// that only allows the StringTable to gain keyboard focus.
setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
protected boolean accept(Component c) {
return (super.accept(c) && (c instanceof StringTable));
}
});
setLocation(prefs.getInt("stringsPosX"), prefs.getInt("stringsPosY"));
setSize(prefs.getInt("stringsSizeX"), prefs.getInt("stringsSizeY"));
pan.setLayout(gbl);
con.fill = GridBagConstraints.BOTH;
con.weighty = 1;
con.weightx = 1;
StringTableModel stm = new StringTableModel(this, base);
table = new StringTable(stm);
if (base.getStringCount() > 0)
table.setRowSelectionInterval(0,0);
gbl.setConstraints(table.getPane(), con);
pan.add(table.getPane());
InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = tlb.getActionMap();
im.put(prefs.getKey("String dialog, add string"), "add");
am.put("add", newStringAction);
im.put(prefs.getKey("String dialog, remove string"), "remove");
am.put("remove", removeStringAction);
//im.put(prefs.getKey("String dialog, move string up"), "up");
//am.put("up", stringUpAction);
//im.put(prefs.getKey("String dialog, move string down"), "down");
//am.put("down", stringDownAction);
im.put(prefs.getKey("Close dialog"), "close");
am.put("close", closeAction);
im.put(prefs.getKey("Help"), "help");
am.put("help", helpAction);
im.put(prefs.getKey("Undo"), "undo");
am.put("undo", undoAction);
im.put(prefs.getKey("Redo"), "redo");
am.put("redo", redoAction);
//tlb.add(closeAction);
//tlb.addSeparator();
tlb.add(newStringAction);
tlb.add(removeStringAction);
tlb.addSeparator();
//tlb.add(stringUpAction);
//tlb.add(stringDownAction);
tlb.addSeparator();
tlb.add(helpAction);
conPane.add(tlb, BorderLayout.NORTH);
conPane.add(pan, BorderLayout.CENTER);
if (panel.getFile() != null)
setTitle(Globals.lang(GUIGlobals.stringsTitle)+": "+panel.getFile().getName());
else
setTitle(Globals.lang(GUIGlobals.stringsTitle)+": "+Globals.lang(GUIGlobals.untitledTitle));
}
class StringTable extends JTable {
JScrollPane sp = new JScrollPane(this);
public StringTable(StringTableModel stm) {
super(stm);
setShowVerticalLines(true);
setShowHorizontalLines(true);
setColumnSelectionAllowed(true);
DefaultCellEditor dce = new DefaultCellEditor(new JTextField());
dce.setClickCountToStart(2);
setDefaultEditor(String.class, dce);
TableColumnModel cm = getColumnModel();
cm.getColumn(0).setPreferredWidth(800);
cm.getColumn(1).setPreferredWidth(2000);
sp.getViewport().setBackground(Globals.prefs.getColor("tableBackground"));
// getInputMap().remove(GUIGlobals.exitDialog);
getInputMap().put(frame.prefs.getKey("Close dialog"), "close");
getActionMap().put("close", closeAction);
getInputMap().put(frame.prefs.getKey("Help"), "help");
getActionMap().put("help", helpAction);
}
public JComponent getPane() {
return sp;
}
}
private void sortStrings() {
// Rebuild our sorted set of strings:
stringsSet = new TreeSet<BibtexString>(new BibtexStringComparator(false));
for (String s : base.getStringKeySet()){
stringsSet.add(base.getString(s));
}
strings = stringsSet.toArray();
}
public void refreshTable() {
sortStrings();
table.revalidate();
table.clearSelection();
table.repaint();
}
class StringTableModel extends AbstractTableModel {
BibtexDatabase base;
StringDialog parent;
public StringTableModel(StringDialog parent, BibtexDatabase base) {
this.parent = parent;
this.base = base;
}
public Object getValueAt(int row, int col) {
return ((col == 0) ?
((BibtexString)strings[row]).getName() :
((BibtexString)strings[row]).getContent());
}
public void setValueAt(Object value, int row, int col) {
// if (row >= base.getStringCount())
// return; // After a Remove operation the program somehow
// thinks the user is still editing an entry,
// which might now be outside
if (col == 0) {
// Change name of string.
if (!((String)value).equals(((BibtexString)strings[row]).getName())) {
if (base.hasStringLabel((String)value))
JOptionPane.showMessageDialog(parent,
Globals.lang("A string with that label "
+"already exists"),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
else if (((String)value).indexOf(" ") >= 0) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not contain spaces."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
}
else if (((String)value).indexOf("#") >= 0) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not contain the '#' character."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
}
else if (isNumber((String)value)) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not be a number."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
}
else {
// Store undo information.
BibtexString subject = (BibtexString)strings[row];
panel.undoManager.addEdit
(new UndoableStringChange
(panel, subject, true,
subject.getName(), (String)value));
subject.setName((String)value);
panel.markBaseChanged();
refreshTable();
}
}
} else {
// Change content of string.
BibtexString subject = (BibtexString)strings[row];
if (!((String)value).equals(subject.getContent())) {
try {
(new LatexFieldFormatter()).format((String)value, "__dummy");
} catch (IllegalArgumentException ex) {
return;
}
// Store undo information.
panel.undoManager.addEdit
(new UndoableStringChange
(panel, subject, false,
subject.getContent(), (String)value));
subject.setContent((String)value);
panel.markBaseChanged();
}
}
}
public int getColumnCount() {
return 2;
}
public int getRowCount() {
return strings.length; //base.getStringCount();
}
public String getColumnName(int col) {
return ((col == 0) ?
Globals.lang("Name") : Globals.lang("Content"));
}
public boolean isCellEditable(int row, int col) {
return true;
}
}
protected boolean isNumber(String name) {
// A pure integer number can not be used as a string label,
// since Bibtex will read it as a number.
try {
Integer.parseInt(name);
return true;
} catch (NumberFormatException ex) {
return false;
}
}
protected void assureNotEditing() {
if (table.isEditing()) {
int col = table.getEditingColumn(),
row = table.getEditingRow();
table.getCellEditor(row, col).stopCellEditing();
}
}
// The action concerned with closing the window.
CloseAction closeAction = new CloseAction(this);
class CloseAction extends AbstractAction {
StringDialog parent;
public CloseAction(StringDialog parent) {
super("Close window");
//, new ImageIcon(GUIGlobals.closeIconFile));
putValue(SHORT_DESCRIPTION, Globals.lang("Close dialog"));
this.parent = parent;
}
public void actionPerformed(ActionEvent e) {
panel.stringsClosing();
dispose();
Point p = getLocation();
Dimension d = getSize();
prefs.putInt("stringsPosX", p.x);
prefs.putInt("stringsPosY", p.y);
prefs.putInt("stringsSizeX", d.width);
prefs.putInt("stringsSizeY", d.height);
}
}
NewStringAction newStringAction = new NewStringAction(this);
class NewStringAction extends AbstractAction {
StringDialog parent;
public NewStringAction(StringDialog parent) {
super("New string",
GUIGlobals.getImage("add"));
putValue(SHORT_DESCRIPTION, Globals.lang("New string"));
this.parent = parent;
}
public void actionPerformed(ActionEvent e) {
String name =
JOptionPane.showInputDialog(parent,
Globals.lang("Please enter the string's label"));
if (name == null)
return;
if (isNumber(name)) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not be a number."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
return;
}
if (name.indexOf("#") >= 0) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not contain the '#' character."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
return;
}
if (name.indexOf(" ") >= 0) {
JOptionPane.showMessageDialog
(parent,
Globals.lang("The label of the string can not contain spaces."),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
return;
}
try {
String newId = Util.createNeutralId();
BibtexString bs = new BibtexString(newId, name, "");
// Store undo information:
panel.undoManager.addEdit
(new UndoableInsertString
(panel, panel.database, bs));
base.addString(bs);
refreshTable();
// table.revalidate();
panel.markBaseChanged();
} catch (KeyCollisionException ex) {
JOptionPane.showMessageDialog(parent,
Globals.lang("A string with that label "
+"already exists"),
Globals.lang("Label"),
JOptionPane.ERROR_MESSAGE);
}
}
}
StoreContentAction storeContentAction = new StoreContentAction(this);
class StoreContentAction extends AbstractAction {
StringDialog parent;
public StoreContentAction(StringDialog parent) {
super("Store string",
GUIGlobals.getImage("add"));
putValue(SHORT_DESCRIPTION, Globals.lang("Store string"));
this.parent = parent;
}
public void actionPerformed(ActionEvent e) {
}
}
RemoveStringAction removeStringAction = new RemoveStringAction(this);
class RemoveStringAction extends AbstractAction {
StringDialog parent;
public RemoveStringAction(StringDialog parent) {
super("Remove selected strings",
GUIGlobals.getImage("remove"));
putValue(SHORT_DESCRIPTION, Globals.lang("Remove selected strings"));
this.parent = parent;
}
public void actionPerformed(ActionEvent e) {
int[] sel = table.getSelectedRows();
if (sel.length > 0) {
// Make sure no cell is being edited, as caused by the
// keystroke. This makes the content hang on the screen.
assureNotEditing();
String msg = Globals.lang("Really delete the selected")+" "+
((sel.length>1) ? sel.length+" "+Globals.lang("entries")
: Globals.lang("entry"))+"?";
int answer = JOptionPane.showConfirmDialog(parent, msg, Globals.lang("Delete strings"),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (answer == JOptionPane.YES_OPTION) {
CompoundEdit ce = new CompoundEdit();
for (int i=sel.length-1; i>=0; i--) {
// Delete the strings backwards to avoid moving indexes.
BibtexString subject = (BibtexString)strings[sel[i]];
// Store undo information:
ce.addEdit(new UndoableRemoveString
(panel, base,
subject));
base.removeString(subject.getId());
}
ce.end();
panel.undoManager.addEdit(ce);
//table.revalidate();
refreshTable();
if (base.getStringCount() > 0)
table.setRowSelectionInterval(0,0);
//table.repaint();
//panel.markBaseChanged();
}
}
}
}
/* StringUpAction stringUpAction = new StringUpAction();
class StringUpAction extends AbstractAction {
public StringUpAction() {
super("Move string up",
new ImageIcon(GUIGlobals.upIconFile));
putValue(SHORT_DESCRIPTION, Globals.lang("Move string up"));
}
public void actionPerformed(ActionEvent e) {
int[] sel = table.getSelectedRows();
if ((sel.length == 1) && (sel[0] > 0)) {
// Make sure no cell is being edited, as caused by the
// keystroke. This makes the content hang on the screen.
assureNotEditing();
// Store undo information:
panel.undoManager.addEdit(new UndoableMoveString
(panel, base, sel[0], true));
BibtexString bs = base.getString(sel[0]);
base.removeString(sel[0]);
try {
base.addString(bs, sel[0]-1);
} catch (KeyCollisionException ex) {}
table.revalidate();
table.setRowSelectionInterval(sel[0]-1, sel[0]-1);
table.repaint();
panel.markBaseChanged();
}
}
}
StringDownAction stringDownAction = new StringDownAction();
class StringDownAction extends AbstractAction {
public StringDownAction() {
super("Move string down",
new ImageIcon(GUIGlobals.downIconFile));
putValue(SHORT_DESCRIPTION, Globals.lang("Move string down"));
}
public void actionPerformed(ActionEvent e) {
int[] sel = table.getSelectedRows();
if ((sel.length == 1) && (sel[0]+1 < base.getStringCount())) {
// Make sure no cell is being edited, as caused by the
// keystroke. This makes the content hang on the screen.
assureNotEditing();
// Store undo information:
panel.undoManager.addEdit(new UndoableMoveString
(panel, base, sel[0], false));
BibtexString bs = base.getString(sel[0]);
base.removeString(sel[0]);
try {
base.addString(bs, sel[0]+1);
} catch (KeyCollisionException ex) {}
table.revalidate();
table.setRowSelectionInterval(sel[0]+1, sel[0]+1);
table.repaint();
panel.markBaseChanged();
}
}
}*/
UndoAction undoAction = new UndoAction();
class UndoAction extends AbstractAction {
public UndoAction() {
super("Undo", GUIGlobals.getImage("undo"));
putValue(SHORT_DESCRIPTION, Globals.lang("Undo"));
}
public void actionPerformed(ActionEvent e) {
try {
panel.runCommand("undo");
} catch (Throwable ex) {}
}
}
RedoAction redoAction = new RedoAction();
class RedoAction extends AbstractAction {
public RedoAction() {
super("Undo", GUIGlobals.getImage("redo"));
putValue(SHORT_DESCRIPTION, Globals.lang("Redo"));
}
public void actionPerformed(ActionEvent e) {
try {
panel.runCommand("redo");
} catch (Throwable ex) {}
}
}
}