Java tutorial
/* 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.AWTKeyStroke; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.KeyboardFocusManager; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.VetoableChangeListener; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.JTextComponent; import org.apache.commons.lang.StringUtils; import com.ironiacorp.string.StringUtil; import net.sf.jabref.autocompleter.AbstractAutoCompleter; import net.sf.jabref.export.LatexFieldFormatter; import net.sf.jabref.gui.VerticalLabelUI; import net.sf.jabref.imports.BibtexParser; import net.sf.jabref.undo.NamedCompound; import net.sf.jabref.undo.UndoableChangeType; import net.sf.jabref.undo.UndoableFieldChange; import net.sf.jabref.undo.UndoableKeyChange; import net.sf.jabref.undo.UndoableRemoveEntry; /** * GUI component that allows editing of the fields of a BibtexEntry (i.e. the * one that shows up, when you double click on an entry in the table) * * It hosts the tabs (required, general, optional) and the buttons to the left. * * EntryEditor also registers itself as a VetoableChangeListener, receiving * events whenever a field of the entry changes, enabling the text fields to * update themselves if the change is made from somewhere else. */ public class EntryEditor extends JPanel implements VetoableChangeListener, EntryContainer { /** * Reference to the entry this editor works on. */ private BibtexEntry entry; // TODO: this field should be final, but for now there violations of this rule /** * Type of entry when created the editor. If the type of the current entry has been * changed, a new EntryEditor should be created. */ private final BibtexEntryType type; // The action concerned with copying the BibTeX key to the clipboard. AbstractAction nextEntryAction = new NextEntryAction(); // Actions for switching to next/previous entry. AbstractAction prevEntryAction = new PrevEntryAction(); // The action concerned with storing a field value. public StoreFieldAction storeFieldAction = new StoreFieldAction(); // The actions concerned with switching the panels. SwitchLeftAction switchLeftAction = new SwitchLeftAction(); SwitchRightAction switchRightAction = new SwitchRightAction(); SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(); protected HelpAction helpAction; private JPanel srcPanel; private JTextArea source; private JTabbedPane tabbed; JabRefFrame frame; BasePanel panel; EntryEditor ths = this; HashSet<FieldContentSelector> contentSelectors = new HashSet<FieldContentSelector>(); private boolean shouldUpdateSourcePanel = true; // This can be set to false to stop the source // Indicates that we are about to go to the next or previous entry // text area from gettin updated. This is used in cases where the source // couldn't be parsed, and the user is given the option to edit it. private boolean movingToDifferentEntry = false; // This indicates whether the last // attempt at parsing the source was successful. It is used to determine whether the // dialog should close; it should stay open if the user received an error // message about the source, whatever he or she chose to do about it. private boolean lastSourceAccepted = true; private String lastAcceptedSourceString = null; // This is used to prevent double fields private JabRefPreferences prefs; private TabListener tabListener = new TabListener(); public EntryEditor(JabRefFrame frame, BasePanel panel, BibtexEntry entry) { this.frame = frame; this.panel = panel; this.entry = entry; prefs = Globals.prefs; type = entry.getType(); entry.addPropertyChangeListener(this); helpAction = new HelpAction(frame.helpDiag, GUIGlobals.entryEditorHelp, "Help"); tabbed = new JTabbedPane(); BorderLayout bl = new BorderLayout(); setLayout(bl); setupToolBar(); setupFieldPanels(); setupSourcePanel(); add(tabbed, BorderLayout.CENTER); tabbed.addChangeListener(tabListener); updateAllFields(); } private void setupFieldPanels() { EntryEditorTab tab; EntryEditorTabList tabList; String[] fields = entry.getRequiredFields(); List<String> fieldList = null; if (fields != null) { fieldList = Arrays.asList(fields); } tabbed.removeAll(); tab = new EntryEditorTab(frame, panel, fieldList, this, true, Globals.lang("Required fields")); tabbed.addTab(Globals.lang("Required fields"), GUIGlobals.getImage("required"), tab, Globals.lang("Show required fields")); if (entry.getOptionalFields() != null && entry.getOptionalFields().length > 0) { tab = new EntryEditorTab(frame, panel, Arrays.asList(entry.getOptionalFields()), this, false, Globals.lang("Optional fields")); tabbed.addTab(Globals.lang("Optional fields"), GUIGlobals.getImage("optional"), tab, Globals.lang("Show optional fields")); } tabList = Globals.prefs.getEntryEditorTabList(); for (int i = 0; i < tabList.getTabCount(); i++) { tab = new EntryEditorTab(frame, panel, tabList.getTabFields(i), this, false, tabList.getTabName(i)); tabbed.addTab(tabList.getTabName(i), GUIGlobals.getImage("general"), tab); } } public BibtexEntryType getType() { return type; } public BibtexEntry getEntry() { return entry; } public BibtexDatabase getDatabase() { return panel.getDatabase(); } /** * Create toolbar for entry editor. */ private void setupToolBar() { JToolBar tlb = new JToolBar(JToolBar.VERTICAL); CloseAction closeAction = new CloseAction(); ; StoreFieldAction storeFieldAction = new StoreFieldAction(); DeleteAction deleteAction = new DeleteAction(); UndoAction undoAction = new UndoAction(); RedoAction redoAction = new RedoAction(); tlb.setBorder(null); tlb.setRollover(true); tlb.setMargin(new Insets(0, 0, 0, 2)); tlb.setFloatable(false); tlb.addSeparator(); tlb.add(deleteAction); tlb.addSeparator(); tlb.add(prevEntryAction); tlb.add(nextEntryAction); tlb.addSeparator(); tlb.add(helpAction); for (Component comp : tlb.getComponents()) { ((JComponent) comp).setOpaque(false); } // The toolbar carries all the key bindings that are valid for the whole window. ActionMap am = tlb.getActionMap(); InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); im.put(prefs.getKey("Close entry editor"), "close"); am.put("close", closeAction); im.put(prefs.getKey("Entry editor, store field"), "store"); am.put("store", storeFieldAction); im.put(prefs.getKey("Entry editor, previous entry"), "prev"); am.put("prev", prevEntryAction); im.put(prefs.getKey("Entry editor, next entry"), "next"); am.put("next", nextEntryAction); im.put(prefs.getKey("Undo"), "undo"); am.put("undo", undoAction); im.put(prefs.getKey("Redo"), "redo"); am.put("redo", redoAction); im.put(prefs.getKey("Help"), "help"); am.put("help", helpAction); // Add actions (and thus buttons) JButton closeBut = new JButton(closeAction); closeBut.setText(null); closeBut.setBorder(null); // Create type-label TypeLabel typeLabel = new TypeLabel(entry.getType().getName()); JPanel leftPan = new JPanel(); leftPan.setLayout(new BorderLayout()); leftPan.add(closeBut, BorderLayout.NORTH); leftPan.add(typeLabel, BorderLayout.CENTER); leftPan.add(tlb, BorderLayout.SOUTH); add(leftPan, BorderLayout.WEST); } /** * Rebuild the field tabs. This is called e.g. when a new content selector * has been added. */ protected void rebuildPanels() { // Remove change listener, because the rebuilding causes meaningless events and trouble tabbed.removeChangeListener(tabListener); setupFieldPanels(); // Add the change listener again: tabbed.addChangeListener(tabListener); revalidate(); repaint(); } private void setupSourcePanel() { JScrollPane sp; source = new JTextAreaWithHighlighting(); source.setLineWrap(true); source.setTabSize(GUIGlobals.INDENT); source.setFont(new Font("Monospaced", Font.PLAIN, Globals.prefs.getInt("fontSize"))); setupJTextFieldForSourceArea(source); updateSource(); // Must call once to setup the initial text of the source panel source.addFocusListener(new FieldEditorFocusListener()); source.addFocusListener(Globals.focusListener); // Add the global focus listener, so a menu item can see // if this field was focused when an action was called. frame.getSearchManager().addSearchListener((SearchTextListener) source); sp = new JScrollPane(source, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); srcPanel = new JPanel(); srcPanel.setLayout(new BorderLayout()); srcPanel.setName(Globals.lang("BibTeX source")); srcPanel.setFocusCycleRoot(true); srcPanel.add(sp, BorderLayout.CENTER); tabbed.addTab(Globals.lang("BibTeX source"), GUIGlobals.getImage("source"), srcPanel, Globals.lang("Show/edit BibTeX source")); } /** * If the entry has been just loaded or it has been changed, update the content of the BibTeX * panel with the new source. */ public void updateSource() { if (shouldUpdateSourcePanel) { StringWriter sw = new StringWriter(200); try { LatexFieldFormatter formatter = new LatexFieldFormatter(); formatter.setNeverFailOnHashes(true); entry.write(sw, formatter, false); String srcString = sw.getBuffer().toString(); source.setText(srcString); lastAcceptedSourceString = srcString; ////////////////////////////////////////////////////////// // Set the current Entry to be selected. // Fixes the bug of losing selection after, e.g. // an autogeneration of a BibTeX key. // - ILC (16/02/2010) - ////////////////////////////////////////////////////////// SwingUtilities.invokeLater(new Runnable() { public void run() { final int row = panel.mainTable.findEntry(entry); if (row >= 0) { if (panel.mainTable.getSelectedRowCount() == 0) { panel.mainTable.setRowSelectionInterval(row, row); } panel.mainTable.ensureVisible(row); } } }); } catch (IOException ex) { source.setText(ex.getMessage() + "\n\n" + Globals.lang("Correct the entry, and reopen editor to display/edit source.")); source.setEditable(false); } } } protected void setupSwingComponentKeyBindings(JComponent component) { // Set up key bindings and focus listener for the FieldEditor. InputMap im = component.getInputMap(JComponent.WHEN_FOCUSED); ActionMap am = component.getActionMap(); im.put(prefs.getKey("Entry editor, store field"), "store"); am.put("store", storeFieldAction); im.put(prefs.getKey("Entry editor, next panel"), "right"); im.put(prefs.getKey("Entry editor, next panel 2"), "right"); am.put("right", switchRightAction); im.put(prefs.getKey("Entry editor, previous panel"), "left"); im.put(prefs.getKey("Entry editor, previous panel 2"), "left"); am.put("left", switchLeftAction); im.put(prefs.getKey("Help"), "help"); am.put("help", helpAction); im.put(prefs.getKey("Save database"), "save"); am.put("save", saveDatabaseAction); im.put(Globals.prefs.getKey("Next tab"), "nexttab"); am.put("nexttab", frame.nextTab); im.put(Globals.prefs.getKey("Previous tab"), "prevtab"); am.put("prevtab", frame.prevTab); } /** * NOTE: This method is only used for the source panel, not for the * other tabs. Look at EntryEditorTab for the setup of text components * in the other tabs. */ private void setupJTextFieldForSourceArea(JTextComponent ta) { setupSwingComponentKeyBindings(ta); // HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(ta.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(); keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB")); ta.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys); // keys = new HashSet<AWTKeyStroke>(ta.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); keys = new HashSet<AWTKeyStroke>(); keys.add(KeyStroke.getKeyStroke("shift pressed TAB")); ta.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys); ta.addFocusListener(new FieldListener()); } public void requestFocus() { activateVisible(); } private void activateVisible() { Object activeTab = tabbed.getComponentAt(tabbed.getSelectedIndex()); if (activeTab instanceof EntryEditorTab) { ((EntryEditorTab) activeTab).activate(); } else { new FocusRequester(source); } } /** * Reports the enabled status of the editor, as set by setEnabled() */ public boolean isEnabled() { return source.isEnabled(); } /** * Sets the enabled status of all text fields of the entry editor. */ public void setEnabled(boolean enabled) { for (int i = 0; i < tabbed.getTabCount(); i++) { Object o = tabbed.getComponentAt(i); if (o instanceof EntryEditorTab) { ((EntryEditorTab) o).setEnabled(enabled); } } source.setEnabled(enabled); } /** * Centers the given row, and highlights it. * * @param row * an <code>int</code> value */ private void scrollTo(int row) { movingToDifferentEntry = true; panel.mainTable.setRowSelectionInterval(row, row); panel.mainTable.ensureVisible(row); } /** * Makes sure the current edit is stored. */ public void storeCurrentEdit() { Component comp = Globals.focusListener.getFocused(); if (comp == source || (comp instanceof FieldEditor && this.isAncestorOf(comp))) { if (comp instanceof FieldEditor) { ((FieldEditor) comp).clearAutoCompleteSuggestion(); } storeFieldAction.actionPerformed(new ActionEvent(comp, 0, "")); } } /** * Returns the index of the active (visible) panel. * * @return an <code>int</code> value */ public int getVisiblePanel() { return tabbed.getSelectedIndex(); } /** Returns the name of the currently selected component. */ public String getVisiblePanelName() { return tabbed.getSelectedComponent().getName(); } /** * Sets the panel with the given index visible. * * @param i * an <code>int</code> value */ public void setVisiblePanel(int i) { tabbed.setSelectedIndex(Math.min(i, tabbed.getTabCount() - 1)); } public void setVisiblePanel(String name) { for (int i = 0; i < tabbed.getTabCount(); ++i) { if (name.equals(tabbed.getComponent(i).getName())) { tabbed.setSelectedIndex(i); return; } } if (tabbed.getTabCount() > 0) tabbed.setSelectedIndex(0); } /** * Updates this editor to show the given entry, regardless of type * correspondence. * * @param be * a <code>BibtexEntry</code> value */ public synchronized void switchTo(BibtexEntry be) { if (entry == be) { /** * Even if the editor is already showing the same entry, update * the source panel. I'm not sure if this is the correct place to * do this, but in some cases the source panel will otherwise not * be up-to-date when an entry is changed while the entry editor * is existing, set to the same entry, but not visible. */ // updateSource(); return; } storeCurrentEdit(); // Remove this instance as property listener for the entry, and register new entry as listener: entry.removePropertyChangeListener(this); be.addPropertyChangeListener(this); entry = be; updateAllFields(); validateAllFields(); shouldUpdateSourcePanel = true; updateSource(); panel.newEntryShowing(be); } /** * Returns false if the contents of the source panel has not been validated, * true othervise. */ public boolean lastSourceAccepted() { if (tabbed.getSelectedComponent() == srcPanel) { storeSource(false); } return lastSourceAccepted; } public boolean storeSource(boolean showError) { // Store edited bibtex code. BibtexParser bp = new BibtexParser(new StringReader(source.getText())); try { BibtexDatabase db = bp.parse().getDatabase(); if (db.getEntryCount() > 1) { throw new RuntimeException("More than one entry found."); } if (db.getEntryCount() < 1) { throw new RuntimeException("No entries found."); } BibtexEntry newEntry = db.getEntryById(db.getKeySet().iterator().next()); int id = entry.getId(); String newKey = newEntry.getCiteKey(); boolean hasChangesBetweenCurrentAndNew = false; boolean changedType = false; boolean duplicateWarning = false; boolean emptyWarning = StringUtils.isEmpty(newKey); if (panel.database.setCiteKeyForEntry(id, newKey)) { duplicateWarning = true; } NamedCompound compound = new NamedCompound(Globals.lang("source edit")); // First, remove fields that the user have removed (and add undo information to revert it if necessary) for (String fieldName : entry.getAllFields()) { String oldValue = entry.getField(fieldName); String newValue = newEntry.getField(fieldName); if (newValue == null) { compound.addEdit(new UndoableFieldChange(entry, fieldName, oldValue, null)); entry.clearField(fieldName); hasChangesBetweenCurrentAndNew = true; } } // Then set all fields that have been set by the user. for (String fieldName : newEntry.getAllFields()) { String oldValue = entry.getField(fieldName); String newValue = newEntry.getField(fieldName); if (newValue != null && !newValue.equals(oldValue)) { LatexFieldFormatter lff = new LatexFieldFormatter(); lff.format(newValue, fieldName); compound.addEdit(new UndoableFieldChange(entry, fieldName, oldValue, newValue)); entry.setField(fieldName, newValue); hasChangesBetweenCurrentAndNew = true; } } // See if the user has changed the entry type: if (newEntry.getType() != entry.getType()) { compound.addEdit(new UndoableChangeType(entry, entry.getType(), newEntry.getType())); entry.setType(newEntry.getType()); hasChangesBetweenCurrentAndNew = true; changedType = true; } compound.end(); if (!hasChangesBetweenCurrentAndNew) { return true; } panel.undoManager.addEdit(compound); if (duplicateWarning) { warnDuplicateBibtexkey(); } else if (emptyWarning && showError) { warnEmptyBibtexkey(); } else { panel.output(Globals.lang("Stored entry") + "."); } lastAcceptedSourceString = source.getText(); if (!changedType) { updateAllFields(); lastSourceAccepted = true; shouldUpdateSourcePanel = true; } else { panel.updateEntryEditorIfShowing(); // We will throw away the current EntryEditor, so we do not have to update it } // TODO: does updating work properly after source stored? // panel.tableModel.remap(); // panel.entryTable.repaint(); // panel.refreshTable(); panel.markBaseChanged(); SwingUtilities.invokeLater(new Runnable() { public void run() { final int row = panel.mainTable.findEntry(entry); if (row >= 0) { //if (panel.mainTable.getSelectedRowCount() == 0) // panel.mainTable.setRowSelectionInterval(row, row); //scrollTo(row); panel.mainTable.ensureVisible(row); } } }); return true; } catch (Throwable ex) { ex.printStackTrace(); // The source couldn't be parsed, so the user is given an // error message, and the choice to keep or revert the contents // of the source text field. shouldUpdateSourcePanel = false; lastSourceAccepted = false; tabbed.setSelectedComponent(srcPanel); if (showError) { Object[] options = { Globals.lang("Edit"), Globals.lang("Revert to original source") }; int answer = JOptionPane.showOptionDialog(frame, Globals.lang("Error") + ": " + ex.getMessage(), Globals.lang("Problem with parsing entry"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[0]); if (answer != 0) { shouldUpdateSourcePanel = true; updateSource(); } } return false; } } public void setField(String fieldName, String newFieldData) { for (int i = 0; i < tabbed.getTabCount(); i++) { Object o = tabbed.getComponentAt(i); if (o instanceof EntryEditorTab) { ((EntryEditorTab) o).updateField(fieldName, newFieldData); } } } /** * Sets all the text areas according to the shown entry. */ public void updateAllFields() { for (int i = 0; i < tabbed.getTabCount(); i++) { Object o = tabbed.getComponentAt(i); if (o instanceof EntryEditorTab) { ((EntryEditorTab) o).setEntry(entry); } } } /** * Removes the "invalid field" color from all text areas. */ public void validateAllFields() { for (int i = 0; i < tabbed.getTabCount(); i++) { Object o = tabbed.getComponentAt(i); if (o instanceof EntryEditorTab) { ((EntryEditorTab) o).validateAllFields(); } } } public void updateAllContentSelectors() { if (contentSelectors.size() > 0) { for (Iterator<FieldContentSelector> i = contentSelectors.iterator(); i.hasNext();) i.next().rebuildComboBox(); } } /** * Update the JTextArea when a field has changed. * * @see java.beans.VetoableChangeListener#vetoableChange(java.beans.PropertyChangeEvent) */ public void vetoableChange(PropertyChangeEvent e) { String newValue = ((e.getNewValue() != null) ? e.getNewValue().toString() : ""); setField(e.getPropertyName(), newValue); } public void updateField(final Object source) { storeFieldAction.actionPerformed(new ActionEvent(source, 0, "")); } public void setMovingToDifferentEntry() { movingToDifferentEntry = true; } private class TypeLabel extends JLabel { public TypeLabel(String type) { super(type + " "); setUI(new VerticalLabelUI(false)); setForeground(GUIGlobals.entryEditorLabelColor); setHorizontalAlignment(RIGHT); setFont(GUIGlobals.typeNameFont); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { boolean ctrlClick = prefs.getBoolean("ctrlClick"); if ((e.getButton() == MouseEvent.BUTTON3) || (ctrlClick && (e.getButton() == MouseEvent.BUTTON1) && e.isControlDown())) { JPopupMenu typeMenu = new JPopupMenu(); // typeMenu.addSeparator(); for (String s : BibtexEntryType.ALL_TYPES.keySet()) typeMenu.add(new ChangeTypeAction(BibtexEntryType.getType(s), panel)); typeMenu.show(ths, e.getX(), e.getY()); } } }); } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; //g2.setColor(GUIGlobals.entryEditorLabelColor); //g2.setFont(GUIGlobals.typeNameFont); //FontMetrics fm = g2.getFontMetrics(); //int width = fm.stringWidth(label); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); super.paintComponent(g2); //g2.rotate(-Math.PI / 2, 0, 0); //g2.drawString(label, -width - 7, 28); } } /* * Focus listener that fires the storeFieldAction when a FieldTextArea * loses focus. */ class FieldListener extends FocusAdapter { public void focusGained(FocusEvent e) { } public void focusLost(FocusEvent e) { if (!e.isTemporary()) { updateField(e.getSource()); } else { Util.pr("Lost focus temporarily at " + e.getSource().toString().substring(0, 30)); } } } class TabListener implements ChangeListener { public void stateChanged(ChangeEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { activateVisible(); } }); // After the initial event train has finished, we tell the editor // tab to update all // its fields. This makes sure they are updated even if the tab we // just left contained one // or more of the same fields as this one: SwingUtilities.invokeLater(new Runnable() { public void run() { Object activeTab = tabbed.getComponentAt(tabbed.getSelectedIndex()); if (activeTab instanceof EntryEditorTab) ((EntryEditorTab) activeTab).updateAll(); } }); } } /* * Action that deletes the current entry, and closes the editor. */ class DeleteAction extends AbstractAction { public DeleteAction() { super(Globals.lang("Delete"), GUIGlobals.getImage("delete")); putValue(SHORT_DESCRIPTION, Globals.lang("Delete entry")); } public void actionPerformed(ActionEvent e) { // Show confirmation dialog if not disabled: boolean goOn = panel.showDeleteConfirmationDialog(1); if (!goOn) return; panel.entryEditorClosing(EntryEditor.this); panel.database.removeEntry(entry.getId()); panel.markBaseChanged(); panel.undoManager.addEdit(new UndoableRemoveEntry(panel.database, entry, panel)); panel.output(Globals.lang("Deleted") + " " + Globals.lang("entry")); } } class CloseAction extends AbstractAction { public CloseAction() { super(Globals.lang("Close window"), GUIGlobals.getImage("close")); putValue(SHORT_DESCRIPTION, Globals.lang("Close window")); } public void actionPerformed(ActionEvent e) { if (tabbed.getSelectedComponent() == srcPanel) { updateField(source); if (lastSourceAccepted) panel.entryEditorClosing(EntryEditor.this); } else panel.entryEditorClosing(EntryEditor.this); } } public class StoreFieldAction extends AbstractAction { public StoreFieldAction() { super("Store field value"); putValue(SHORT_DESCRIPTION, "Store field value"); } public void actionPerformed(ActionEvent e) { boolean movingAway = movingToDifferentEntry; movingToDifferentEntry = false; if (e.getSource() instanceof FieldTextField) { // Storage of bibtex key field FieldTextField fe = (FieldTextField) e.getSource(); String oldValue = entry.getCiteKey(); String newValue = fe.getText(); if (StringUtil.isEmpty(newValue)) { newValue = null; } if (((oldValue == null) && (newValue == null)) || ((oldValue != null) && (newValue != null) && oldValue.equals(newValue))) { return; // No change } // Make sure the key is legal: String cleaned = Util.checkLegalKey(newValue); if (cleaned != null && !cleaned.equals(newValue)) { JOptionPane.showMessageDialog(frame, Globals.lang("Invalid BibTeX key"), Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE); fe.setInvalidBackgroundColor(); return; } else { fe.setValidBackgroundColor(); } if (newValue == null) { warnEmptyBibtexkey(); } if (newValue != null) { // Check key boolean isDuplicate = panel.database.setCiteKeyForEntry(entry.getId(), newValue); if (isDuplicate) { warnDuplicateBibtexkey(); } else { panel.output(Globals.lang("BibTeX key is unique.")); } fe.setValidBackgroundColor(); } // Add an UndoableKeyChange to the baseframe's undoManager. panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(), oldValue, newValue)); if (fe.getTextComponent().hasFocus()) { fe.setActiveBackgroundColor(); } updateSource(); panel.markBaseChanged(); } else if (e.getSource() instanceof FieldEditor) { // Storage of any bibtex field (but not the key) FieldEditor fe = (FieldEditor) e.getSource(); String newValue = fe.getText(); String oldValue = entry.getField(fe.getFieldName()); if (StringUtil.isEmpty(newValue)) { newValue = null; } else { newValue = newValue.trim(); } boolean fieldHasChanged; // We check if the field has changed, since we don't want to // mark the base as changed unless we have a real change. if (newValue == null) { if (entry.getField(fe.getFieldName()) == null) { fieldHasChanged = false; } else { fieldHasChanged = true; } } else { if (entry.getField(fe.getFieldName()) != null && newValue.equals(oldValue)) { fieldHasChanged = false; } else { fieldHasChanged = true; } } if (fieldHasChanged) { try { // The following statement attempts to write the new contents into a // StringWriter, and this will cause an IOException if the field is not // properly formatted. If that happens, the field is not stored and // the textarea turns red. if (newValue != null) { (new LatexFieldFormatter()).format(newValue, fe.getFieldName()); } if (newValue != null) { entry.setField(fe.getFieldName(), newValue); } else { entry.clearField(fe.getFieldName()); } fe.setValidBackgroundColor(); // See if we need to update an AutoCompleter instance: AbstractAutoCompleter aComp = panel.getAutoCompleter(fe.getFieldName()); if (aComp != null) { aComp.addBibtexEntry(entry); } // Add an UndoableFieldChange to the baseframe's undoManager. panel.undoManager .addEdit(new UndoableFieldChange(entry, fe.getFieldName(), oldValue, newValue)); updateSource(); panel.markBaseChanged(); } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(frame, Globals.lang("Error") + ": " + ex.getMessage(), Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE); fe.setInvalidBackgroundColor(); } } else { fe.setValidBackgroundColor(); } if (fe.getTextComponent().hasFocus()) { fe.setBackground(GUIGlobals.activeEditor); } } else if (source.isEditable() && (!source.getText().equals(lastAcceptedSourceString))) { boolean accepted = storeSource(true); if (!accepted) { System.out.println("Error using updated data from BibTeX source"); } } //////////////////////////////////// // Make sure we scroll to the entry if it moved in the table. // Should only be done if this editor is currently showing. if (!movingAway && isShowing()) { SwingUtilities.invokeLater(new Runnable() { public void run() { final int row = panel.mainTable.findEntry(entry); if (row >= 0) { panel.mainTable.ensureVisible(row); } } }); } } } class SwitchLeftAction extends AbstractAction { public SwitchLeftAction() { super("Switch to the panel to the left"); } public void actionPerformed(ActionEvent e) { // System.out.println("switch left"); int i = tabbed.getSelectedIndex(); tabbed.setSelectedIndex(((i > 0) ? (i - 1) : (tabbed.getTabCount() - 1))); activateVisible(); } } class SwitchRightAction extends AbstractAction { public SwitchRightAction() { super("Switch to the panel to the right"); } public void actionPerformed(ActionEvent e) { // System.out.println("switch right"); int i = tabbed.getSelectedIndex(); tabbed.setSelectedIndex((i < (tabbed.getTabCount() - 1)) ? (i + 1) : 0); activateVisible(); } } class NextEntryAction extends AbstractAction { public NextEntryAction() { super(Globals.lang("Next entry"), GUIGlobals.getImage("down")); putValue(SHORT_DESCRIPTION, Globals.lang("Next entry")); } public void actionPerformed(ActionEvent e) { int thisRow = panel.mainTable.findEntry(entry); int newRow = -1; if ((thisRow + 1) < panel.database.getEntryCount()) newRow = thisRow + 1; else if (thisRow > 0) newRow = 0; else return; // newRow is still -1, so we can assume the database has // only one entry. scrollTo(newRow); panel.mainTable.setRowSelectionInterval(newRow, newRow); } } class PrevEntryAction extends AbstractAction { public PrevEntryAction() { super(Globals.lang("Previous entry"), GUIGlobals.getImage("up")); putValue(SHORT_DESCRIPTION, Globals.lang("Previous entry")); } public void actionPerformed(ActionEvent e) { int thisRow = panel.mainTable.findEntry(entry); int newRow = -1; if ((thisRow - 1) >= 0) newRow = thisRow - 1; else if (thisRow != (panel.database.getEntryCount() - 1)) newRow = panel.database.getEntryCount() - 1; else return; // newRow is still -1, so we can assume the database has // only one entry. // id = panel.tableModel.getIdForRow(newRow); // switchTo(id); scrollTo(newRow); panel.mainTable.setRowSelectionInterval(newRow, newRow); } } class UndoAction extends AbstractAction { public UndoAction() { super("Undo", GUIGlobals.getImage("undo")); putValue(SHORT_DESCRIPTION, "Undo"); } public void actionPerformed(ActionEvent e) { try { panel.runCommand("undo"); } catch (Throwable ex) { } } } class RedoAction extends AbstractAction { public RedoAction() { super("Undo", GUIGlobals.getImage("redo")); putValue(SHORT_DESCRIPTION, "Redo"); } public void actionPerformed(ActionEvent e) { try { panel.runCommand("redo"); } catch (Throwable ex) { } } } class SaveDatabaseAction extends AbstractAction { public SaveDatabaseAction() { super("Save database"); } public void actionPerformed(ActionEvent e) { Object activeTab = tabbed.getComponentAt(tabbed.getSelectedIndex()); if (activeTab instanceof EntryEditorTab) { // Normal panel. EntryEditorTab fp = (EntryEditorTab) activeTab; FieldEditor fe = fp.getActive(); fe.clearAutoCompleteSuggestion(); updateField(fe); } else { // Source panel. updateField(activeTab); } try { panel.runCommand("save"); } catch (Throwable ex) { System.err.println("Error saving file."); } } } class ExternalViewerListener extends MouseAdapter { public void mouseClicked(MouseEvent evt) { if (evt.getClickCount() == 2) { FieldTextArea tf = (FieldTextArea) evt.getSource(); if (tf.getText().equals("")) return; tf.selectAll(); String link = tf.getText(); // get selected ? String // getSelectedText() try { Util.openExternalViewer(panel.metaData(), link, tf.getFieldName()); } catch (IOException ex) { System.err.println("Error opening file."); } } } } class ChangeTypeAction extends AbstractAction { BibtexEntryType type; BasePanel panel; public ChangeTypeAction(BibtexEntryType type, BasePanel bp) { super(type.getName()); this.type = type; panel = bp; } public void actionPerformed(ActionEvent evt) { panel.changeType(entry, type); } } private void warnDuplicateBibtexkey() { panel.output(Globals.lang("Duplicate BibTeX key. Grouping may not work for this entry.")); } private void warnEmptyBibtexkey() { panel.output(Globals.lang("Empty BibTeX key. Grouping may not work for this entry.")); } }