/*
* Copyright (C) 2003 Morten O. Alver, Nizar N. Batada
*
* All programs in this directory and subdirectories are published under the GNU
* General Public License as described below.
*
* 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., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*
* Further information about the GNU GPL is available at:
* http://www.gnu.org/copyleft/gpl.ja.html
*
*/
package net.sf.jabref;
import java.awt.AWTKeyStroke;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.KeyboardFocusManager;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import net.sf.jabref.autocompleter.AbstractAutoCompleter;
import net.sf.jabref.gui.AutoCompleteListener;
import net.sf.jabref.gui.FileListEditor;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
/**
* A single tab displayed in the EntryEditor holding several FieldEditors.
*
* @author $Author: mortenalver $
* @version $Revision: 3140 $ ($Date: 2009-11-26 20:09:21 +0100 (Thu, 26 Nov 2009) $)
*
*/
public class EntryEditorTab {
private JPanel panel = new JPanel();
private String[] fields;
private EntryEditor parent;
private HashMap<String, FieldEditor> editors = new HashMap<String, FieldEditor>();
private FieldEditor activeField = null;
public EntryEditorTab(JabRefFrame frame, BasePanel panel, List<String> fields, EntryEditor parent,
boolean addKeyField, String name) {
if (fields != null)
this.fields = fields.toArray(new String[0]);
else
this.fields = new String[] {};
this.parent = parent;
setupPanel(frame, panel, addKeyField, name);
/*
* The following line makes sure focus cycles inside tab instead of
* being lost to other parts of the frame:
*/
panel.setFocusCycleRoot(true);
}
void setupPanel(JabRefFrame frame, BasePanel bPanel, boolean addKeyField, String title) {
InputMap im = panel.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = panel.getActionMap();
im.put(Globals.prefs.getKey("Entry editor, previous entry"), "prev");
am.put("prev", parent.prevEntryAction);
im.put(Globals.prefs.getKey("Entry editor, next entry"), "next");
am.put("next", parent.nextEntryAction);
im.put(Globals.prefs.getKey("Entry editor, store field"), "store");
am.put("store", parent.storeFieldAction);
im.put(Globals.prefs.getKey("Entry editor, next panel"), "right");
im.put(Globals.prefs.getKey("Entry editor, next panel 2"), "right");
am.put("left", parent.switchLeftAction);
im.put(Globals.prefs.getKey("Entry editor, previous panel"), "left");
im.put(Globals.prefs.getKey("Entry editor, previous panel 2"), "left");
am.put("right", parent.switchRightAction);
im.put(Globals.prefs.getKey("Help"), "help");
am.put("help", parent.helpAction);
im.put(Globals.prefs.getKey("Save database"), "save");
am.put("save", parent.saveDatabaseAction);
im.put(Globals.prefs.getKey("Next tab"), "nexttab");
am.put("nexttab", parent.frame.nextTab);
im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
am.put("prevtab", parent.frame.prevTab);
panel.setName(title);
//String rowSpec = "left:pref, 4dlu, fill:pref:grow, 4dlu, fill:pref";
String colSpec = "fill:pref, 1dlu, fill:pref:grow, 1dlu, fill:pref";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < fields.length; i++) {
sb.append("fill:pref:grow, ");
}
if (addKeyField)
sb.append("4dlu, fill:pref");
else
sb.delete(sb.length()-2, sb.length());
String rowSpec = sb.toString();
DefaultFormBuilder builder = new DefaultFormBuilder
(new FormLayout(colSpec, rowSpec), panel);
for (int i = 0; i < fields.length; i++) {
// Create the text area:
int editorType = BibtexFields.getEditorType(fields[i]);
final FieldEditor ta;
if (editorType == GUIGlobals.FILE_LIST_EDITOR)
ta = new FileListEditor(frame, bPanel.metaData(), fields[i], null, parent);
else
ta = new FieldTextArea(fields[i], null);
//ta.addUndoableEditListener(bPanel.undoListener);
JComponent ex = parent.getExtra(fields[i], ta);
// Add autocompleter listener, if required for this field:
AbstractAutoCompleter autoComp = bPanel.getAutoCompleter(fields[i]);
AutoCompleteListener acl = null;
if (autoComp != null) {
acl = new AutoCompleteListener(autoComp);
}
setupJTextComponent(ta.getTextComponent(), acl);
ta.setAutoCompleteListener(acl);
// Store the editor for later reference:
editors.put(fields[i], ta);
if (i == 0)
activeField = ta;
//System.out.println(fields[i]+": "+BibtexFields.getFieldWeight(fields[i]));
ta.getPane().setPreferredSize(new Dimension(100,
(int)(50.0*BibtexFields.getFieldWeight(fields[i]))));
builder.append(ta.getLabel());
if (ex == null)
builder.append(ta.getPane(), 3);
else {
builder.append(ta.getPane());
JPanel pan = new JPanel();
pan.setLayout(new BorderLayout());
pan.add(ex, BorderLayout.NORTH);
builder.append(pan);
}
builder.nextLine();
}
// Add the edit field for Bibtex-key.
if (addKeyField) {
final FieldTextField tf = new FieldTextField(BibtexFields.KEY_FIELD, parent
.getEntry().getField(BibtexFields.KEY_FIELD), true);
//tf.addUndoableEditListener(bPanel.undoListener);
setupJTextComponent(tf, null);
editors.put("bibtexkey", tf);
/*
* If the key field is the only field, we should have only one
* editor, and this one should be set as active initially:
*/
if (editors.size() == 1)
activeField = tf;
builder.nextLine();
builder.append(tf.getLabel());
builder.append(tf, 3);
}
}
BibtexEntry entry;
public BibtexEntry getEntry() {
return entry;
}
boolean isFieldModified(FieldEditor f) {
String text = f.getText().trim();
if (text.length() == 0) {
return getEntry().getField(f.getFieldName()) != null;
} else {
Object entryValue = getEntry().getField(f.getFieldName());
return entryValue == null || !entryValue.toString().equals(text);
}
}
public void markIfModified(FieldEditor f) {
// Only mark as changed if not already is and the field was indeed
// modified
if (!updating && !parent.panel.isBaseChanged() && isFieldModified(f)) {
markBaseChanged();
}
}
void markBaseChanged() {
parent.panel.markBaseChanged();
}
/**
* Only sets the activeField variable but does not focus it.
*
* Call activate afterwards.
*
* @param c
*/
public void setActive(FieldEditor c) {
activeField = c;
}
public FieldEditor getActive() {
return activeField;
}
public List<String> getFields() {
return java.util.Arrays.asList(fields);
}
public void activate() {
if (activeField != null){
/**
* Corrected to fix [ 1594169 ] Entry editor: navigation between panels
*/
new FocusRequester(activeField.getTextComponent());
}
}
/**
* Reset all fields from the data in the BibtexEntry.
*
*/
public void updateAll() {
setEntry(getEntry());
}
protected boolean updating = false;
public void setEntry(BibtexEntry entry) {
try {
updating = true;
Iterator<FieldEditor> i = editors.values().iterator();
while (i.hasNext()) {
FieldEditor editor = i.next();
Object content = entry.getField(editor.getFieldName());
String toSet = (content == null) ? "" : content.toString();
if (!toSet.equals(editor.getText()))
editor.setText(toSet);
}
this.entry = entry;
} finally {
updating = false;
}
}
public boolean updateField(String field, String content) {
if (!editors.containsKey(field))
return false;
FieldEditor ed = editors.get(field);
ed.setText(content);
return true;
}
public void validateAllFields() {
for (Iterator<String> i = editors.keySet().iterator(); i.hasNext();) {
String field = i.next();
FieldEditor ed = editors.get(field);
ed.setEnabled(true);
if (((Component) ed).hasFocus())
ed.setBackground(GUIGlobals.activeEditor);
else
ed.setBackground(GUIGlobals.validFieldBackground);
}
}
public void setEnabled(boolean enabled) {
Iterator<FieldEditor> i = editors.values().iterator();
while (i.hasNext()) {
FieldEditor editor = i.next();
editor.setEnabled(enabled);
}
}
public Component getPane() {
return panel;
}
/**
* Set up key bindings and focus listener for the FieldEditor.
*
* @param component
*/
public void setupJTextComponent(final JComponent component, final AutoCompleteListener acl) {
// Here we add focus listeners to the component. The funny code is because we need
// to guarantee that the AutoCompleteListener - if used - is called before fieldListener
// on a focus lost event. The AutoCompleteListener is responsible for removing any
// current suggestion when focus is lost, and this must be done before fieldListener
// stores the current edit. Swing doesn't guarantee the order of execution of event
// listeners, so we handle this by only adding the AutoCompleteListener and telling
// it to call fieldListener afterwards. If no AutoCompleteListener is used, we
// add the fieldListener normally.
if (acl != null) {
component.addKeyListener(acl);
component.addFocusListener(acl);
acl.setNextFocusListener(fieldListener);
}
else
component.addFocusListener(fieldListener);
InputMap im = component.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = component.getActionMap();
im.put(Globals.prefs.getKey("Entry editor, previous entry"), "prev");
am.put("prev", parent.prevEntryAction);
im.put(Globals.prefs.getKey("Entry editor, next entry"), "next");
am.put("next", parent.nextEntryAction);
im.put(Globals.prefs.getKey("Entry editor, store field"), "store");
am.put("store", parent.storeFieldAction);
im.put(Globals.prefs.getKey("Entry editor, next panel"), "right");
im.put(Globals.prefs.getKey("Entry editor, next panel 2"), "right");
am.put("left", parent.switchLeftAction);
im.put(Globals.prefs.getKey("Entry editor, previous panel"), "left");
im.put(Globals.prefs.getKey("Entry editor, previous panel 2"), "left");
am.put("right", parent.switchRightAction);
im.put(Globals.prefs.getKey("Help"), "help");
am.put("help", parent.helpAction);
im.put(Globals.prefs.getKey("Save database"), "save");
am.put("save", parent.saveDatabaseAction);
im.put(Globals.prefs.getKey("Next tab"), "nexttab");
am.put("nexttab", parent.frame.nextTab);
im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
am.put("prevtab", parent.frame.prevTab);
try {
HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(component
.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
keys.clear();
keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB"));
component.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
keys = new HashSet<AWTKeyStroke>(component
.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
keys.clear();
keys.add(KeyStroke.getKeyStroke("shift pressed TAB"));
component.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys);
} catch (Throwable t) {
System.err.println(t);
}
}
/*
* Focus listener that fires the storeFieldAction when a FieldTextArea loses
* focus.
*
* TODO: It would be nice to test this thoroughly.
*/
FocusListener fieldListener = new FocusListener() {
JTextComponent c;
DocumentListener d;
public void focusGained(FocusEvent e) {
synchronized (this){
if (c != null) {
c.getDocument().removeDocumentListener(d);
c = null;
d = null;
}
if (e.getSource() instanceof JTextComponent) {
c = (JTextComponent) e.getSource();
/**
* [ 1553552 ] Not properly detecting changes to flag as
* changed
*/
d = new DocumentListener() {
void fire(DocumentEvent e) {
if (c.isFocusOwner()) {
markIfModified((FieldEditor) c);
}
}
public void changedUpdate(DocumentEvent e) {
fire(e);
}
public void insertUpdate(DocumentEvent e) {
fire(e);
}
public void removeUpdate(DocumentEvent e) {
fire(e);
}
};
c.getDocument().addDocumentListener(d);
}
}
setActive((FieldEditor) e.getSource());
}
public void focusLost(FocusEvent e) {
synchronized (this) {
if (c != null) {
c.getDocument().removeDocumentListener(d);
c = null;
d = null;
}
}
if (!e.isTemporary())
parent.updateField(e.getSource());
}
};
}