6b8a8f4e8f3656ed12d6182b602a6536cd273dbf
[debian/jabref.git] / src / java / net / sf / jabref / EntryEditorTab.java
1 /*
2  * Copyright (C) 2003 Morten O. Alver, Nizar N. Batada
3  *
4  * All programs in this directory and subdirectories are published under the GNU
5  * General Public License as described below.
6  *
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option) any later
10  * version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19  * Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  * Further information about the GNU GPL is available at:
22  * http://www.gnu.org/copyleft/gpl.ja.html
23  *
24  */
25 package net.sf.jabref;
26
27 import java.awt.AWTKeyStroke;
28 import java.awt.BorderLayout;
29 import java.awt.Component;
30 import java.awt.Dimension;
31 import java.awt.KeyboardFocusManager;
32 import java.awt.event.FocusEvent;
33 import java.awt.event.FocusListener;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38
39 import javax.swing.ActionMap;
40 import javax.swing.InputMap;
41 import javax.swing.JComponent;
42 import javax.swing.JPanel;
43 import javax.swing.KeyStroke;
44 import javax.swing.event.DocumentEvent;
45 import javax.swing.event.DocumentListener;
46 import javax.swing.text.JTextComponent;
47
48 import com.jgoodies.forms.builder.DefaultFormBuilder;
49 import com.jgoodies.forms.layout.FormLayout;
50 import net.sf.jabref.gui.AutoCompleter;
51 import net.sf.jabref.gui.AutoCompleteListener;
52 import net.sf.jabref.gui.FileListEditor;
53
54 /**
55  * A single tab displayed in the EntryEditor holding several FieldEditors.
56  * 
57  * @author $Author: mortenalver $
58  * @version $Revision: 2114 $ ($Date: 2007-06-13 22:47:58 +0200 (Wed, 13 Jun 2007) $)
59  * 
60  */
61 public class EntryEditorTab {
62
63         private JPanel panel = new JPanel();
64
65         private String[] fields;
66
67         private EntryEditor parent;
68
69         private HashMap editors = new HashMap();
70
71         private FieldEditor activeField = null;
72
73         private Component firstComponent;
74
75         public EntryEditorTab(JabRefFrame frame, BasePanel panel, List fields, EntryEditor parent,
76                           boolean addKeyField, String name) {
77                 if (fields != null)
78                         this.fields = (String[]) fields.toArray(new String[0]);
79                 else
80                         this.fields = new String[] {};
81
82                 this.parent = parent;
83
84                 setupPanel(frame, panel, addKeyField, name);
85
86                 /*
87                  * The following line makes sure focus cycles inside tab instead of
88                  * being lost to other parts of the frame:
89                  */
90                 panel.setFocusCycleRoot(true);
91         }
92
93
94     void setupPanel(JabRefFrame frame, BasePanel bPanel, boolean addKeyField, String title) {
95         
96         InputMap im = panel.getInputMap(JComponent.WHEN_FOCUSED);
97                 ActionMap am = panel.getActionMap();
98
99                 im.put(Globals.prefs.getKey("Entry editor, previous entry"), "prev");
100                 am.put("prev", parent.prevEntryAction);
101                 im.put(Globals.prefs.getKey("Entry editor, next entry"), "next");
102                 am.put("next", parent.nextEntryAction);
103
104                 im.put(Globals.prefs.getKey("Entry editor, store field"), "store");
105                 am.put("store", parent.storeFieldAction);
106                 im.put(Globals.prefs.getKey("Entry editor, next panel"), "right");
107                 im.put(Globals.prefs.getKey("Entry editor, next panel 2"), "right");
108                 am.put("left", parent.switchLeftAction);
109                 im.put(Globals.prefs.getKey("Entry editor, previous panel"), "left");
110                 im.put(Globals.prefs.getKey("Entry editor, previous panel 2"), "left");
111                 am.put("right", parent.switchRightAction);
112                 im.put(Globals.prefs.getKey("Help"), "help");
113                 am.put("help", parent.helpAction);
114                 im.put(Globals.prefs.getKey("Save database"), "save");
115                 am.put("save", parent.saveDatabaseAction);
116                 im.put(Globals.prefs.getKey("Next tab"), "nexttab");
117                 am.put("nexttab", parent.frame.nextTab);
118                 im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
119                 am.put("prevtab", parent.frame.prevTab);
120         
121                 
122         panel.setName(title);
123         //String rowSpec = "left:pref, 4dlu, fill:pref:grow, 4dlu, fill:pref";
124         String colSpec = "fill:pref, 1dlu, fill:pref:grow, 1dlu, fill:pref";
125         StringBuffer sb = new StringBuffer();
126         for (int i = 0; i < fields.length; i++) {
127             sb.append("fill:pref:grow, ");
128         }
129         if (addKeyField)
130             sb.append("4dlu, fill:pref");
131         else
132             sb.delete(sb.length()-2, sb.length());
133         String rowSpec = sb.toString();
134
135         DefaultFormBuilder builder = new DefaultFormBuilder
136                 (panel, new FormLayout(colSpec, rowSpec));
137
138         for (int i = 0; i < fields.length; i++) {
139             // Create the text area:
140             int editorType = BibtexFields.getEditorType(fields[i]);
141
142             final FieldEditor ta;
143             if (editorType == GUIGlobals.FILE_LIST_EDITOR)
144                 ta = new FileListEditor(frame, bPanel.metaData(), fields[i], null, parent);
145             else
146                 ta = new FieldTextArea(fields[i], null);
147
148             JComponent ex = parent.getExtra(fields[i], ta);
149             setupJTextComponent(ta.getTextComponent());
150
151             // Add autocompleter listener, if required for this field:
152             AutoCompleter autoComp = bPanel.getAutoCompleter(fields[i]);
153             if (autoComp != null) {
154                 ta.getTextComponent().addKeyListener(new AutoCompleteListener(autoComp));
155             }
156
157             // Store the editor for later reference:
158             editors.put(fields[i], ta);
159             if (i == 0)
160                 activeField = ta;
161             //System.out.println(fields[i]+": "+BibtexFields.getFieldWeight(fields[i]));
162             ta.getPane().setPreferredSize(new Dimension(100,
163                     (int)(50.0*BibtexFields.getFieldWeight(fields[i]))));
164             builder.append(ta.getLabel());
165             if (ex == null)
166                 builder.append(ta.getPane(), 3);
167             else {
168                 builder.append(ta.getPane());
169                 JPanel pan = new JPanel();
170                 pan.setLayout(new BorderLayout());
171                 pan.add(ex, BorderLayout.NORTH);
172                 builder.append(pan);
173             }
174             builder.nextLine();
175         }
176
177         // Add the edit field for Bibtex-key.
178                 if (addKeyField) {
179                         final FieldTextField tf = new FieldTextField(BibtexFields.KEY_FIELD, (String) parent
180                                 .getEntry().getField(BibtexFields.KEY_FIELD), true);
181                         setupJTextComponent(tf);
182
183                         editors.put("bibtexkey", tf);
184                         /*
185                          * If the key field is the only field, we should have only one
186                          * editor, and this one should be set as active initially:
187                          */
188                         if (editors.size() == 1)
189                                 activeField = tf;
190             builder.nextLine();
191                         builder.append(tf.getLabel());
192                         builder.append(tf, 3);
193                 }
194     }
195
196
197         BibtexEntry entry;
198
199         public BibtexEntry getEntry() {
200                 return entry;
201         }
202
203         boolean isFieldModified(FieldEditor f) {
204                 String text = f.getText().trim();
205
206                 if (text.length() == 0) {
207                         return getEntry().getField(f.getFieldName()) != null;
208                 } else {
209                         Object entryValue = getEntry().getField(f.getFieldName());
210                         return entryValue == null || !entryValue.toString().equals(text);
211                 }
212         }
213
214         public void markIfModified(FieldEditor f) {
215                 // Only mark as changed if not already is and the field was indeed
216                 // modified
217                 if (!updating && !parent.panel.isBaseChanged() && isFieldModified(f)) {
218                         markBaseChanged();
219                 }
220         }
221
222         void markBaseChanged() {
223                 parent.panel.markBaseChanged();
224         }
225
226         /**
227          * Only sets the activeField variable but does not focus it.
228          * 
229          * Call activate afterwards.
230          * 
231          * @param c
232          */
233         public void setActive(FieldEditor c) {
234                 activeField = c;
235         }
236
237         public FieldEditor getActive() {
238                 return activeField;
239         }
240
241         public List getFields() {
242                 return java.util.Arrays.asList(fields);
243         }
244
245         public void activate() {
246                 if (activeField != null){
247                         /**
248                          * Corrected to fix [ 1594169 ] Entry editor: navigation between panels
249                          */
250                         new FocusRequester(activeField.getTextComponent());
251                 }
252         }
253
254         /**
255          * Reset all fields from the data in the BibtexEntry.
256          * 
257          */
258         public void updateAll() {
259                 setEntry(getEntry());
260         }
261
262         protected boolean updating = false;
263
264         public void setEntry(BibtexEntry entry) {
265                 try {
266                         updating = true;
267                         Iterator i = editors.values().iterator();
268                         while (i.hasNext()) {
269                                 FieldEditor editor = (FieldEditor) i.next();
270                                 Object content = entry.getField(editor.getFieldName());
271                                 editor.setText((content == null) ? "" : content.toString());
272                         }
273                         this.entry = entry;
274                 } finally {
275                         updating = false;
276                 }
277         }
278
279         public boolean updateField(String field, String content) {
280                 if (!editors.containsKey(field))
281                         return false;
282                 FieldEditor ed = (FieldEditor) editors.get(field);
283                 ed.setText(content);
284                 return true;
285         }
286
287         public void validateAllFields() {
288                 for (Iterator i = editors.keySet().iterator(); i.hasNext();) {
289                         String field = (String) i.next();
290                         FieldEditor ed = (FieldEditor) editors.get(field);
291                         ed.setEnabled(true);
292                         if (((Component) ed).hasFocus())
293                                 ed.setBackground(GUIGlobals.activeEditor);
294                         else
295                                 ed.setBackground(GUIGlobals.validFieldBackground);
296                 }
297         }
298
299         public void setEnabled(boolean enabled) {
300                 Iterator i = editors.values().iterator();
301                 while (i.hasNext()) {
302                         FieldEditor editor = (FieldEditor) i.next();
303                         editor.setEnabled(enabled);
304                 }
305         }
306
307         public Component getPane() {
308                 return panel;
309         }
310
311         /**
312          * Set up key bindings and focus listener for the FieldEditor.
313          * 
314          * @param component
315          */
316         public void setupJTextComponent(final JComponent component) {
317
318                 component.addFocusListener(fieldListener);
319
320                 InputMap im = component.getInputMap(JComponent.WHEN_FOCUSED);
321                 ActionMap am = component.getActionMap();
322
323                 im.put(Globals.prefs.getKey("Entry editor, previous entry"), "prev");
324                 am.put("prev", parent.prevEntryAction);
325                 im.put(Globals.prefs.getKey("Entry editor, next entry"), "next");
326                 am.put("next", parent.nextEntryAction);
327
328                 im.put(Globals.prefs.getKey("Entry editor, store field"), "store");
329                 am.put("store", parent.storeFieldAction);
330                 im.put(Globals.prefs.getKey("Entry editor, next panel"), "right");
331                 im.put(Globals.prefs.getKey("Entry editor, next panel 2"), "right");
332                 am.put("left", parent.switchLeftAction);
333                 im.put(Globals.prefs.getKey("Entry editor, previous panel"), "left");
334                 im.put(Globals.prefs.getKey("Entry editor, previous panel 2"), "left");
335                 am.put("right", parent.switchRightAction);
336                 im.put(Globals.prefs.getKey("Help"), "help");
337                 am.put("help", parent.helpAction);
338                 im.put(Globals.prefs.getKey("Save database"), "save");
339                 am.put("save", parent.saveDatabaseAction);
340                 im.put(Globals.prefs.getKey("Next tab"), "nexttab");
341                 am.put("nexttab", parent.frame.nextTab);
342                 im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
343                 am.put("prevtab", parent.frame.prevTab);
344
345                 try {
346                         HashSet keys = new HashSet(component
347                                 .getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
348                         keys.clear();
349                         keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB"));
350                         component.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
351                         keys = new HashSet(component
352                                 .getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
353                         keys.clear();
354                         keys.add(KeyStroke.getKeyStroke("shift pressed TAB"));
355                         component.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys);
356                 } catch (Throwable t) {
357                         System.err.println(t);
358                 }
359
360     }
361
362         /*
363          * Focus listener that fires the storeFieldAction when a FieldTextArea loses
364          * focus.
365          * 
366          * TODO: It would be nice to test this thoroughly.
367          */
368         FocusListener fieldListener = new FocusListener() {
369         
370                 JTextComponent c;
371
372                 DocumentListener d;
373
374                 public void focusGained(FocusEvent e) {
375
376                         synchronized (this){
377                                 if (c != null) {
378                                         c.getDocument().removeDocumentListener(d);
379                                         c = null;
380                                         d = null;
381                                 }
382
383                                 if (e.getSource() instanceof JTextComponent) {
384
385                                         c = (JTextComponent) e.getSource();
386                                         /**
387                                          * [ 1553552 ] Not properly detecting changes to flag as
388                                          * changed
389                                          */
390                                         d = new DocumentListener() {
391
392                                                 void fire(DocumentEvent e) {
393                                                         if (c.isFocusOwner()) {
394                                                                 markIfModified((FieldEditor) c);
395                                                         }
396                                                 }
397
398                                                 public void changedUpdate(DocumentEvent e) {
399                                                         fire(e);
400                                                 }
401
402                                                 public void insertUpdate(DocumentEvent e) {
403                                                         fire(e);
404                                                 }
405
406                                                 public void removeUpdate(DocumentEvent e) {
407                                                         fire(e);
408                                                 }
409                                         };
410                                         c.getDocument().addDocumentListener(d);
411                                 }
412                         }
413
414                         setActive((FieldEditor) e.getSource());
415
416                 }
417
418                 public void focusLost(FocusEvent e) {
419             synchronized (this) {
420                                 if (c != null) {
421                                         c.getDocument().removeDocumentListener(d);
422                                         c = null;
423                                         d = null;
424                                 }
425                         }
426                         if (!e.isTemporary())
427                                 parent.updateField((FieldEditor) e.getSource());
428                 }
429         };
430 }