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