39f394465889e1b57956b26187938e9350a325ba
[debian/jabref.git] / src / java / net / sf / jabref / JabRefPreferences.java
1 /*
2 Copyright (C) 2003 Morten O. Alver, Nizar N. Batada
3
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
6 described below.
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA
22
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
25
26 */
27
28 package net.sf.jabref;
29
30 import java.awt.Color;
31 import java.awt.event.KeyEvent;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.io.Reader;
39 import java.io.StringReader;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.TreeSet;
46 import java.util.Vector;
47 import java.util.prefs.BackingStoreException;
48 import java.util.prefs.InvalidPreferencesFormatException;
49 import java.util.prefs.Preferences;
50
51 import javax.swing.JTable;
52 import javax.swing.KeyStroke;
53
54 import net.sf.jabref.export.CustomExportList;
55 import net.sf.jabref.export.ExportComparator;
56 import net.sf.jabref.external.ExternalFileType;
57 import net.sf.jabref.external.UnknownExternalFileType;
58 import net.sf.jabref.imports.CustomImportList;
59 import net.sf.jabref.labelPattern.DefaultLabelPatterns;
60 import net.sf.jabref.labelPattern.LabelPattern;
61
62 public class JabRefPreferences {
63
64     public final String
65         CUSTOM_TYPE_NAME = "customTypeName_",
66         CUSTOM_TYPE_REQ = "customTypeReq_",
67         CUSTOM_TYPE_OPT = "customTypeOpt_",
68         CUSTOM_TAB_NAME = "customTabName_",
69         CUSTOM_TAB_FIELDS = "customTabFields_";
70
71     // This String is used in the encoded list in prefs of external file type
72     // modifications, in order to indicate a removed default file type:
73     public static final String FILE_TYPE_REMOVED_FLAG = "REMOVED";
74
75     public String WRAPPED_USERNAME;
76
77     Preferences prefs;
78     public HashMap<String, Object> defaults = new HashMap<String, Object>();
79     public HashMap<String, String>
80         keyBinds = new HashMap<String, String>(),
81         defKeyBinds = new HashMap<String, String>();
82     private HashSet<String> putBracesAroundCapitalsFields = new HashSet<String>(4);
83     private HashSet<String> nonWrappableFields = new HashSet<String>(4);
84     private static final LabelPattern KEY_PATTERN = new DefaultLabelPatterns();
85     private static LabelPattern keyPattern;
86
87     // Object containing custom export formats:
88     public CustomExportList customExports;
89
90     /** Set with all custom {@link net.sf.jabref.imports.ImportFormat}s */
91     public CustomImportList customImports;
92
93     // Object containing info about customized entry editor tabs.
94     private EntryEditorTabList tabList = null;
95
96     // Map containing all registered external file types:
97     private TreeSet<ExternalFileType> externalFileTypes = new TreeSet<ExternalFileType>();
98
99     // The following field is used as a global variable during the export of a database.
100     // By setting this field to the path of the database's default file directory, formatters
101     // that should resolve external file paths can access this field. This is an ugly hack
102     // to solve the problem of formatters not having access to any context except for the
103     // string to be formatted and possible formatter arguments.
104     public String fileDirForDatabase = null;
105
106     // The only instance of this class:
107     private static JabRefPreferences singleton = null;
108
109     public static JabRefPreferences getInstance() {
110                 if (singleton == null)
111                         singleton = new JabRefPreferences();
112                 return singleton;
113         }
114
115     // The constructor is made private to enforce this as a singleton class:
116     private JabRefPreferences() {
117
118         try {
119             if (new File("jabref.xml").exists()){
120                 importPreferences("jabref.xml");
121             }
122         } catch (IOException e) {
123             Globals.logger("Could not import preferences from jabref.xml:" + e.getLocalizedMessage());
124         }
125         
126         prefs = Preferences.userNodeForPackage(JabRef.class);
127         
128         if (Globals.osName.equals(Globals.MAC)) {
129                         defaults.put("pdfviewer", "/Applications/Preview.app");
130                         defaults.put("psviewer", "/Applications/Preview.app");
131                         defaults.put("htmlviewer", "/Applications/Safari.app");
132                 } else if (Globals.osName.toLowerCase().startsWith("windows")) {
133                         defaults.put("pdfviewer", "cmd.exe /c start /b");
134                         defaults.put("psviewer", "cmd.exe /c start /b");
135                         defaults.put("htmlviewer", "cmd.exe /c start /b");
136                         defaults.put("lookAndFeel", "com.jgoodies.plaf.windows.ExtWindowsLookAndFeel");
137             defaults.put("winEdtPath", "C:\\Program Files\\WinEdt Team\\WinEdt\\WinEdt.exe");
138             defaults.put("latexEditorPath", "C:\\Program Files\\LEd\\LEd.exe");
139         } else {
140                         defaults.put("pdfviewer", "evince");
141                         defaults.put("psviewer", "gv");
142                         defaults.put("htmlviewer", "mozilla");
143                         defaults.put("lookAndFeel", "com.jgoodies.plaf.plastic.Plastic3DLookAndFeel");
144                 }
145         defaults.put("useDefaultLookAndFeel", Boolean.TRUE);
146         defaults.put("lyxpipe", System.getProperty("user.home")+File.separator+".lyx/lyxpipe");
147         defaults.put("vim", "vim");
148         defaults.put("vimServer", "vim");
149         defaults.put("posX", new Integer(0));
150         defaults.put("posY", new Integer(0));
151         defaults.put("sizeX", new Integer(840));
152         defaults.put("sizeY", new Integer(680));
153         defaults.put("rememberWindowLocation", Boolean.TRUE);
154         defaults.put("autoResizeMode", new Integer(JTable.AUTO_RESIZE_OFF));
155         defaults.put("tableColorCodesOn", Boolean.TRUE);
156         defaults.put("namesAsIs", Boolean.FALSE);
157         defaults.put("namesFf", Boolean.FALSE);
158         defaults.put("namesLf", Boolean.FALSE);
159         defaults.put("namesNatbib", Boolean.TRUE);
160         defaults.put("abbrAuthorNames", Boolean.TRUE);
161         defaults.put("namesLastOnly", Boolean.TRUE);
162         defaults.put("language", "en");
163         defaults.put("showShort", Boolean.TRUE);
164         defaults.put("priSort", "author");
165         defaults.put("priDescending", Boolean.FALSE);
166         defaults.put("priBinary", Boolean.FALSE);
167         defaults.put("secSort", "year");
168         defaults.put("secDescending", Boolean.TRUE);
169         defaults.put("terSort", "author");
170         defaults.put("terDescending", Boolean.FALSE);
171         defaults.put("columnNames", "entrytype;author;title;year;journal;owner;timestamp;bibtexkey");
172         defaults.put("columnWidths","75;280;400;60;100;100;100;100");
173         defaults.put("xmpPrivacyFilters", "pdf;timestamp;keywords;owner;note;review");
174         defaults.put("useXmpPrivacyFilter", Boolean.FALSE);
175         defaults.put("numberColWidth",new Integer(GUIGlobals.NUMBER_COL_LENGTH));
176         defaults.put("workingDirectory", System.getProperty("user.home"));
177         defaults.put("exportWorkingDirectory", System.getProperty("user.home"));
178         defaults.put("importWorkingDirectory", System.getProperty("user.home"));
179         defaults.put("fileWorkingDirectory", System.getProperty("user.home"));
180         defaults.put("autoOpenForm", Boolean.TRUE);
181         defaults.put("entryTypeFormHeightFactor", new Integer(1));
182         defaults.put("entryTypeFormWidth", new Integer(1));
183         defaults.put("backup", Boolean.TRUE);
184         defaults.put("openLastEdited", Boolean.TRUE);
185         defaults.put("lastEdited", null);
186         defaults.put("stringsPosX", new Integer(0));
187         defaults.put("stringsPosY", new Integer(0));
188         defaults.put("stringsSizeX", new Integer(600));
189         defaults.put("stringsSizeY", new Integer(400));
190         defaults.put("defaultShowSource", Boolean.FALSE);
191         defaults.put("showSource", Boolean.TRUE);
192         defaults.put("defaultAutoSort", Boolean.FALSE);
193         defaults.put("enableSourceEditing", Boolean.TRUE);
194         defaults.put("caseSensitiveSearch", Boolean.FALSE);
195         defaults.put("searchReq", Boolean.TRUE);
196         defaults.put("searchOpt", Boolean.TRUE);
197         defaults.put("searchGen", Boolean.TRUE);
198         defaults.put("searchAll", Boolean.FALSE);
199         defaults.put("incrementS", Boolean.FALSE);
200         defaults.put("saveInStandardOrder", Boolean.TRUE);
201         defaults.put("saveInOriginalOrder", Boolean.FALSE);
202         defaults.put("selectS", Boolean.FALSE);
203         defaults.put("regExpSearch", Boolean.TRUE);
204         defaults.put("searchPanePosX", new Integer(0));
205         defaults.put("searchPanePosY", new Integer(0));
206         defaults.put("autoComplete", Boolean.TRUE);
207         defaults.put("autoCompleteFields", "author;editor;title;journal;publisher;keywords");
208         defaults.put("groupSelectorVisible", Boolean.TRUE);
209         defaults.put("groupFloatSelections", Boolean.TRUE);
210         defaults.put("groupIntersectSelections", Boolean.TRUE);
211         defaults.put("groupInvertSelections", Boolean.FALSE);
212         defaults.put("groupShowOverlapping", Boolean.FALSE);
213         defaults.put("groupSelectMatches", Boolean.FALSE);
214         defaults.put("groupsDefaultField", "keywords");
215         defaults.put("groupShowIcons", Boolean.TRUE);
216         defaults.put("groupShowDynamic", Boolean.TRUE);
217         defaults.put("groupExpandTree", Boolean.TRUE);
218         defaults.put("groupAutoShow", Boolean.TRUE);
219         defaults.put("groupAutoHide", Boolean.TRUE);
220         defaults.put("groupKeywordSeparator", ", ");
221         defaults.put("highlightGroupsMatchingAny", Boolean.FALSE);
222         defaults.put("highlightGroupsMatchingAll", Boolean.FALSE);
223         defaults.put("searchPanelVisible", Boolean.FALSE);
224         defaults.put("defaultEncoding", System.getProperty("file.encoding"));
225         defaults.put("groupsVisibleRows", new Integer(8));
226         defaults.put("defaultOwner", System.getProperty("user.name"));
227         defaults.put("preserveFieldFormatting", Boolean.FALSE);
228         defaults.put("memoryStickMode", Boolean.FALSE);
229         defaults.put("renameOnMoveFileToFileDir", Boolean.TRUE);
230
231     // The general fields stuff is made obsolete by the CUSTOM_TAB_... entries.
232         defaults.put("generalFields", "crossref;keywords;file;doi;url;urldate;citeseerurl;"+
233                      "pdf;comment;owner");
234
235         defaults.put("useCustomIconTheme", Boolean.FALSE);
236         defaults.put("customIconThemeFile", "/home/alver/div/crystaltheme_16/Icons.properties");
237
238     // Entry editor tab 0:
239     defaults.put(CUSTOM_TAB_NAME+"_def0", Globals.lang("General"));
240         defaults.put(CUSTOM_TAB_FIELDS+"_def0", "crossref;keywords;file;doi;url;citeseerurl;"+
241                      "comment;owner;timestamp");
242
243     // Entry editor tab 1:
244         defaults.put(CUSTOM_TAB_FIELDS+"_def1", "abstract");
245     defaults.put(CUSTOM_TAB_NAME+"_def1", Globals.lang("Abstract"));
246
247   // Entry editor tab 2: Review Field - used for research comments, etc.
248         defaults.put(CUSTOM_TAB_FIELDS+"_def2", "review");
249     defaults.put(CUSTOM_TAB_NAME+"_def2", Globals.lang("Review"));
250
251         //defaults.put("recentFiles", "/home/alver/Documents/bibk_dok/hovedbase.bib");
252         defaults.put("historySize", new Integer(8));
253         defaults.put("fontFamily", "Times");
254         defaults.put("fontStyle", new Integer(java.awt.Font.PLAIN));
255         defaults.put("fontSize", new Integer(12));
256         defaults.put("overrideDefaultFonts", Boolean.FALSE);
257         defaults.put("menuFontFamily", "Times");
258         defaults.put("menuFontStyle", new Integer(java.awt.Font.PLAIN));
259         defaults.put("menuFontSize", new Integer(11));
260         // Main table color settings:
261         defaults.put("tableBackground", "255:255:255");
262         defaults.put("tableReqFieldBackground", "230:235:255");
263         defaults.put("tableOptFieldBackground", "230:255:230");
264         defaults.put("tableText", "0:0:0");
265         defaults.put("gridColor", "210:210:210");
266         defaults.put("grayedOutBackground", "210:210:210");
267         defaults.put("grayedOutText", "40:40:40");
268         defaults.put("veryGrayedOutBackground", "180:180:180");
269         defaults.put("veryGrayedOutText", "40:40:40");
270         defaults.put("markedEntryBackground", "255:255:180");
271         defaults.put("incompleteEntryBackground", "250:175:175");
272
273         defaults.put("antialias", Boolean.FALSE);
274         defaults.put("ctrlClick", Boolean.FALSE);
275         defaults.put("disableOnMultipleSelection", Boolean.FALSE);
276         defaults.put("pdfColumn", Boolean.FALSE);
277         defaults.put("urlColumn", Boolean.TRUE);
278         defaults.put("fileColumn", Boolean.TRUE);
279         defaults.put("citeseerColumn", Boolean.FALSE);
280         defaults.put("useOwner", Boolean.TRUE);
281         defaults.put("overwriteOwner", Boolean.FALSE);
282         defaults.put("allowTableEditing", Boolean.FALSE);
283         defaults.put("dialogWarningForDuplicateKey", Boolean.TRUE);
284         defaults.put("dialogWarningForEmptyKey", Boolean.TRUE);
285         defaults.put("displayKeyWarningDialogAtStartup", Boolean.TRUE);
286         defaults.put("avoidOverwritingKey", Boolean.FALSE);
287         defaults.put("warnBeforeOverwritingKey", Boolean.TRUE);
288         defaults.put("confirmDelete", Boolean.TRUE);
289         defaults.put("grayOutNonHits", Boolean.TRUE);
290         defaults.put("showSearchInDialog", Boolean.FALSE);
291         defaults.put("searchAllBases", Boolean.FALSE);
292         defaults.put("defaultLabelPattern", "[auth][year]");
293         defaults.put("previewEnabled", Boolean.TRUE);
294         defaults.put("preview0", "<font face=\"arial\">"
295                      +"<b><i>\\bibtextype</i><a name=\"\\bibtexkey\">\\begin{bibtexkey} (\\bibtexkey)</a>"
296                      +"\\end{bibtexkey}</b><br>__NEWLINE__"
297                      +"\\begin{author} \\format[HTMLChars,AuthorAbbreviator,AuthorAndsReplacer]{\\author}<BR>\\end{author}__NEWLINE__"
298                      +"\\begin{editor} \\format[HTMLChars,AuthorAbbreviator,AuthorAndsReplacer]{\\editor} <i>(ed.)</i><BR>\\end{editor}__NEWLINE__"
299                      +"\\begin{title} \\format[HTMLChars]{\\title} \\end{title}<BR>__NEWLINE__"
300                      +"\\begin{chapter} \\format[HTMLChars]{\\chapter}<BR>\\end{chapter}__NEWLINE__"
301                      +"\\begin{journal} <em>\\format[HTMLChars]{\\journal}, </em>\\end{journal}__NEWLINE__"
302                      // Include the booktitle field for @inproceedings, @proceedings, etc.
303                      +"\\begin{booktitle} <em>\\format[HTMLChars]{\\booktitle}, </em>\\end{booktitle}__NEWLINE__"
304                      +"\\begin{school} <em>\\format[HTMLChars]{\\school}, </em>\\end{school}__NEWLINE__"
305                      +"\\begin{institution} <em>\\format[HTMLChars]{\\institution}, </em>\\end{institution}__NEWLINE__"
306                      +"\\begin{publisher} <em>\\format[HTMLChars]{\\publisher}, </em>\\end{publisher}__NEWLINE__"
307                      +"\\begin{year}<b>\\year</b>\\end{year}\\begin{volume}<i>, \\volume</i>\\end{volume}"
308                      +"\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}"
309                      +"</dd>__NEWLINE__<p></p></font>");
310
311         defaults.put("preview1", "<font face=\"arial\">"
312                      +"<b><i>\\bibtextype</i><a name=\"\\bibtexkey\">\\begin{bibtexkey} (\\bibtexkey)</a>"
313                      +"\\end{bibtexkey}</b><br>__NEWLINE__"
314                      +"\\begin{author} \\format[HTMLChars,AuthorAbbreviator,AuthorAndsReplacer]{\\author}<BR>\\end{author}__NEWLINE__"
315                      +"\\begin{editor} \\format[HTMLChars,AuthorAbbreviator,AuthorAndsReplacer]{\\editor} <i>(ed.)</i><BR>\\end{editor}__NEWLINE__"
316                      +"\\begin{title} \\format[HTMLChars]{\\title} \\end{title}<BR>__NEWLINE__"
317                      +"\\begin{chapter} \\format[HTMLChars]{\\chapter}<BR>\\end{chapter}__NEWLINE__"
318                      +"\\begin{journal} <em>\\format[HTMLChars]{\\journal}, </em>\\end{journal}__NEWLINE__"
319                      // Include the booktitle field for @inproceedings, @proceedings, etc.
320                      +"\\begin{booktitle} <em>\\format[HTMLChars]{\\booktitle}, </em>\\end{booktitle}__NEWLINE__"
321                      +"\\begin{school} <em>\\format[HTMLChars]{\\school}, </em>\\end{school}__NEWLINE__"
322                      +"\\begin{institution} <em>\\format[HTMLChars]{\\institution}, </em>\\end{institution}__NEWLINE__"
323                      +"\\begin{publisher} <em>\\format[HTMLChars]{\\publisher}, </em>\\end{publisher}__NEWLINE__"
324                      +"\\begin{year}<b>\\year</b>\\end{year}\\begin{volume}<i>, \\volume</i>\\end{volume}"
325                      +"\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__"
326                      +"\\begin{abstract}<BR><BR><b>Abstract: </b> \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__"
327                      +"\\begin{review}<BR><BR><b>Review: </b> \\format[HTMLChars]{\\review} \\end{review}"
328                      +"</dd>__NEWLINE__<p></p></font>");
329         // TODO: Currently not possible to edit this setting:
330         defaults.put("previewPrintButton", Boolean.FALSE);
331         defaults.put("autoDoubleBraces", Boolean.FALSE);
332         defaults.put("doNotResolveStringsFor", "url");
333         defaults.put("resolveStringsAllFields", Boolean.FALSE);
334         defaults.put("putBracesAroundCapitals","");//"title;journal;booktitle;review;abstract");
335         defaults.put("nonWrappableFields", "pdf;ps;url;doi");
336         defaults.put("useImportInspectionDialog", Boolean.TRUE);
337         defaults.put("useImportInspectionDialogForSingle", Boolean.TRUE);
338         defaults.put("generateKeysAfterInspection", Boolean.TRUE);
339         defaults.put("warnAboutDuplicatesInInspection", Boolean.TRUE);
340         defaults.put("useTimeStamp", Boolean.TRUE);
341         defaults.put("overwriteTimeStamp", Boolean.FALSE);
342         defaults.put("timeStampFormat", "yyyy.MM.dd");
343 //        defaults.put("timeStampField", "timestamp");
344         defaults.put("timeStampField", BibtexFields.TIMESTAMP);
345         defaults.put("generateKeysBeforeSaving", Boolean.FALSE);
346
347         defaults.put("useRemoteServer", Boolean.FALSE);
348         defaults.put("remoteServerPort", new Integer(6050));
349
350         defaults.put("personalJournalList", null);
351         defaults.put("externalJournalLists", null);
352         defaults.put("citeCommand", "cite"); // obsoleted by the app-specific ones
353         defaults.put("citeCommandVim", "\\cite");
354         defaults.put("citeCommandEmacs", "\\cite");
355         defaults.put("citeCommandWinEdt", "\\cite");
356         defaults.put("citeCommandLed", "\\cite");
357         defaults.put("floatMarkedEntries", Boolean.TRUE);
358
359         defaults.put("useNativeFileDialogOnMac", Boolean.FALSE);
360
361         defaults.put("lastUsedExport", null);
362         defaults.put("sidePaneWidth", new Integer(-1));
363
364         defaults.put("importInspectionDialogWidth", new Integer(650));
365         defaults.put("importInspectionDialogHeight", new Integer(650));
366         defaults.put("searchDialogWidth", new Integer(650));
367         defaults.put("searchDialogHeight", new Integer(500));
368
369         defaults.put("showFileLinksUpgradeWarning", Boolean.TRUE);
370
371         defaults.put("autolinkExactKeyOnly", Boolean.TRUE);
372         //defaults.put("lastAutodetectedImport", "");
373
374         //defaults.put("autoRemoveExactDuplicates", Boolean.FALSE);
375         //defaults.put("confirmAutoRemoveExactDuplicates", Boolean.TRUE);
376         
377         //defaults.put("tempDir", System.getProperty("java.io.tmpdir"));
378         //Util.pr(System.getProperty("java.io.tempdir"));
379
380         //defaults.put("keyPattern", new LabelPattern(KEY_PATTERN));
381
382         restoreKeyBindings();
383
384         customExports = new CustomExportList(this, new ExportComparator());
385         customImports = new CustomImportList(this);
386
387         //defaults.put("oooWarning", Boolean.TRUE);
388         updateSpecialFieldHandling();
389         WRAPPED_USERNAME = "["+get("defaultOwner")+"]";
390
391         String defaultExpression = "**/.*[bibtexkey].*\\\\.[extension]";
392         defaults.put(DEFAULT_REG_EXP_SEARCH_EXPRESSION_KEY, defaultExpression);
393         defaults.put(REG_EXP_SEARCH_EXPRESSION_KEY, defaultExpression);
394         defaults.put(USE_REG_EXP_SEARCH_KEY, Boolean.FALSE);
395     }
396     
397     public static final String DEFAULT_REG_EXP_SEARCH_EXPRESSION_KEY = "defaultRegExpSearchExpression";
398     public static final String REG_EXP_SEARCH_EXPRESSION_KEY = "regExpSearchExpression";
399     public static final String USE_REG_EXP_SEARCH_KEY = "useRegExpSearch";
400     
401     public boolean putBracesAroundCapitals(String fieldName) {
402         return putBracesAroundCapitalsFields.contains(fieldName);
403     }
404
405     public void updateSpecialFieldHandling() {
406         putBracesAroundCapitalsFields.clear();
407         String fieldString = get("putBracesAroundCapitals");
408         if (fieldString.length() > 0) {
409             String[] fields = fieldString.split(";");
410             for (int i=0; i<fields.length; i++)
411                 putBracesAroundCapitalsFields.add(fields[i].trim());
412         }
413         nonWrappableFields.clear();
414         fieldString = get("nonWrappableFields");
415         if (fieldString.length() > 0) {
416             String[] fields = fieldString.split(";");
417             for (int i=0; i<fields.length; i++)
418                 nonWrappableFields.add(fields[i].trim());
419         }
420
421     }
422
423     /**
424      * Check whether a key is set (differently from null).
425      * @param key The key to check.
426      * @return true if the key is set, false otherwise.
427      */
428     public boolean hasKey(String key) {
429         return prefs.get(key, null) != null;
430     }
431
432     public String get(String key) {
433         return prefs.get(key, (String)defaults.get(key));
434     }
435
436     public String get(String key, String def) {
437         return prefs.get(key, def);
438     }
439
440     public boolean getBoolean(String key) {
441         return prefs.getBoolean(key, getBooleanDefault(key));
442     }
443     
444     public boolean getBooleanDefault(String key){
445         return ((Boolean)defaults.get(key)).booleanValue();
446     }
447
448     public double getDouble(String key) {
449         return prefs.getDouble(key, getDoubleDefault(key));
450     }
451     
452     public double getDoubleDefault(String key){
453         return ((Double)defaults.get(key)).doubleValue();
454     }
455
456     public int getInt(String key) {
457         return prefs.getInt(key, getIntDefault(key));
458     }
459
460     public int getIntDefault(String key) {
461         return ((Integer)defaults.get(key)).intValue();
462     }
463     
464     public byte[] getByteArray(String key) {
465         return prefs.getByteArray(key, getByteArrayDefault(key));
466     }
467
468     public byte[] getByteArrayDefault(String key){
469         return (byte[])defaults.get(key);   
470     }
471     
472     public void put(String key, String value) {
473         prefs.put(key, value);
474     }
475
476     public void putBoolean(String key, boolean value) {
477         prefs.putBoolean(key, value);
478     }
479
480     public void putDouble(String key, double value) {
481         prefs.putDouble(key, value);
482     }
483
484     public void putInt(String key, int value) {
485         prefs.putInt(key, value);
486     }
487
488     public void putByteArray(String key, byte[] value) {
489         prefs.putByteArray(key, value);
490     }
491
492     public void remove(String key) {
493         prefs.remove(key);
494     }
495
496     /**
497      * Puts a string array into the Preferences, by linking its elements
498      * with ';' into a single string. Escape characters make the process
499      * transparent even if strings contain ';'.
500      */
501     public void putStringArray(String key, String[] value) {
502         if (value == null) {
503             remove(key);
504             return;
505         }
506
507         if (value.length > 0) {
508             StringBuffer linked = new StringBuffer();
509             for (int i=0; i<value.length-1; i++) {
510                 linked.append(makeEscape(value[i]));
511                 linked.append(";");
512             }
513             linked.append(makeEscape(value[value.length-1]));
514             put(key, linked.toString());
515         } else {
516             put(key, "");
517         }
518     }
519
520     /**
521      * Returns a String[] containing the chosen columns.
522      */
523     public String[] getStringArray(String key) {
524         String names = get(key);
525         if (names == null)
526             return null;
527
528         StringReader rd = new StringReader(names);
529         Vector<String> arr = new Vector<String>();
530         String rs;
531         try {
532             while ((rs = getNextUnit(rd)) != null) {
533                 arr.add(rs);
534             }
535         } catch (IOException ex) {}
536         String[] res = new String[arr.size()];
537         for (int i=0; i<res.length; i++)
538             res[i] = arr.elementAt(i);
539
540         return res;
541     }
542
543     /**
544      * Looks up a color definition in preferences, and returns the Color object.
545      * @param key The key for this setting.
546      * @return The color corresponding to the setting.
547      */
548     public Color getColor(String key) {
549         String value = get(key);
550         int[] rgb = getRgb(value);
551         return new Color(rgb[0], rgb[1], rgb[2]);
552     }
553
554     public Color getDefaultColor(String key) {
555         String value = (String)defaults.get(key);
556         int[] rgb = getRgb(value);
557         return new Color(rgb[0], rgb[1], rgb[2]);
558     }
559
560     /**
561      * Set the default value for a key. This is useful for plugins that need to
562      * add default values for the prefs keys they use.
563      * @param key The preferences key.
564      * @param value The default value.
565      */
566     public void putDefaultValue(String key, Object value) {
567         defaults.put(key, value);
568     }
569
570     /**
571      * Stores a color in preferences.
572      * @param key The key for this setting.
573      * @param color The Color to store.
574      */
575     public void putColor(String key, Color color) {
576         StringBuffer sb = new StringBuffer();
577         sb.append(String.valueOf(color.getRed()));
578         sb.append(':');
579         sb.append(String.valueOf(color.getGreen()));
580         sb.append(':');
581         sb.append(String.valueOf(color.getBlue()));
582         put(key, sb.toString());
583     }
584
585     /**
586      * Looks up a color definition in preferences, and returns an array containing the RGB values.
587      * @param value The key for this setting.
588      * @return The RGB values corresponding to this color setting.
589      */
590     public int[] getRgb(String value) {
591         String[] elements = value.split(":");
592         int[] values = new int[3];
593         values[0] = Integer.parseInt(elements[0]);
594         values[1] = Integer.parseInt(elements[1]);
595         values[2] = Integer.parseInt(elements[2]);
596         return values;
597     }
598
599     /**
600      * Returns the KeyStroke for this binding, as defined by the
601      * defaults, or in the Preferences.
602      */
603     public KeyStroke getKey(String bindName) {
604
605         String s = keyBinds.get(bindName);
606         // If the current key bindings don't contain the one asked for,
607         // we fall back on the default. This should only happen when a
608         // user has his own set in Preferences, and has upgraded to a
609         // new version where new bindings have been introduced.
610         if (s == null) {
611             s = defKeyBinds.get(bindName);
612             // So, if this happens, we add the default value to the current
613             // hashmap, so this doesn't happen again, and so this binding
614             // will appear in the KeyBindingsDialog.
615             keyBinds.put(bindName, s);
616         }
617         if (s == null) {
618           Globals.logger("Could not get key binding for \"" + bindName + "\"");
619         }
620
621         if (Globals.ON_MAC)
622           return getKeyForMac(KeyStroke.getKeyStroke(s));
623         else
624           return KeyStroke.getKeyStroke(s);
625     }
626
627     /**
628      * Returns the KeyStroke for this binding, as defined by the
629      * defaults, or in the Preferences, but adapted for Mac users,
630      * with the Command key preferred instead of Control.
631      */
632     private KeyStroke getKeyForMac(KeyStroke ks) {
633       if (ks == null) return null;
634       int keyCode = ks.getKeyCode();
635       if ((ks.getModifiers() & KeyEvent.CTRL_MASK) == 0) {
636         return ks;
637       }
638       else {
639         if ((ks.getModifiers() & KeyEvent.SHIFT_MASK) != 0) {
640           return KeyStroke.getKeyStroke(keyCode, Globals.SHORTCUT_MASK+KeyEvent.SHIFT_MASK);
641         }
642         return KeyStroke.getKeyStroke(keyCode, Globals.SHORTCUT_MASK);
643       }
644     }
645
646     /**
647      * Returns the HashMap containing all key bindings.
648      */
649     public HashMap<String, String> getKeyBindings() {
650         return keyBinds;
651     }
652
653     /**
654      * Returns the HashMap containing default key bindings.
655      */
656     public HashMap<String, String> getDefaultKeys() {
657         return defKeyBinds;
658     }
659
660     /**
661      * Calling this method will write all preferences into the preference store.
662      */
663     public void flush() {
664         if (getBoolean("memoryStickMode")){
665             try {
666                 exportPreferences("jabref.xml");
667             } catch (IOException e) {
668                 Globals.logger("Could not save preferences for memory stick mode: " + e.getLocalizedMessage());
669             }
670         }
671         try {
672             prefs.flush();
673         } catch (BackingStoreException ex) {
674             ex.printStackTrace();
675         }
676     }
677
678     /**
679      * Stores new key bindings into Preferences, provided they
680      * actually differ from the old ones.
681      */
682     public void setNewKeyBindings(HashMap<String, String> newBindings) {
683         if (!newBindings.equals(keyBinds)) {
684             // This confirms that the bindings have actually changed.
685             String[] bindNames = new String[newBindings.size()],
686                 bindings = new String[newBindings.size()];
687             int index = 0;
688             for (Iterator<String> i=newBindings.keySet().iterator();
689                  i.hasNext();) {
690                 String nm = i.next();
691                 String bnd = newBindings.get(nm);
692                 bindNames[index] = nm;
693                 bindings[index] = bnd;
694                 index++;
695             }
696             putStringArray("bindNames", bindNames);
697             putStringArray("bindings", bindings);
698             keyBinds = newBindings;
699         }
700     }
701
702
703         public LabelPattern getKeyPattern(){
704
705             keyPattern = new LabelPattern(KEY_PATTERN);
706             Preferences pre = Preferences.userNodeForPackage
707                 (net.sf.jabref.labelPattern.LabelPattern.class);
708             try {
709                 String[] keys = pre.keys();
710             if (keys.length > 0) for (int i=0; i<keys.length; i++)
711                 keyPattern.addLabelPattern(keys[i], pre.get(keys[i], null));
712             } catch (BackingStoreException ex) {
713                 Globals.logger("BackingStoreException in JabRefPreferences.getKeyPattern");
714             }
715
716             ///
717             //keyPattern.addLabelPattern("article", "[author][year]");
718             //putKeyPattern(keyPattern);
719             ///
720
721             return keyPattern;
722         }
723
724         public void putKeyPattern(LabelPattern pattern){
725             keyPattern = pattern;
726             LabelPattern parent = pattern.getParent();
727             if (parent == null)
728                 return;
729
730             // Store overridden definitions to Preferences.
731             Preferences pre = Preferences.userNodeForPackage
732                 (net.sf.jabref.labelPattern.LabelPattern.class);
733             try {
734                 pre.clear(); // We remove all old entries.
735             } catch (BackingStoreException ex) {
736                 Globals.logger("BackingStoreException in JabRefPreferences.putKeyPattern");
737             }
738
739             for (String s: pattern.keySet()){
740                 if (!(pattern.get(s)).equals(parent.get(s)))
741                     pre.put(s, pattern.getValue(s).get(0).toString());
742             }
743         }
744
745     private void restoreKeyBindings() {
746         // Define default keybindings.
747         defineDefaultKeyBindings();
748
749         // First read the bindings, and their names.
750         String[] bindNames = getStringArray("bindNames"),
751             bindings = getStringArray("bindings");
752
753         // Then set up the key bindings HashMap.
754         if ((bindNames == null) || (bindings == null)
755             || (bindNames.length != bindings.length)) {
756             // Nothing defined in Preferences, or something is wrong.
757             setDefaultKeyBindings();
758             return;
759         }
760
761         for (int i=0; i<bindNames.length; i++)
762             keyBinds.put(bindNames[i], bindings[i]);
763     }
764
765     private void setDefaultKeyBindings() {
766         keyBinds = defKeyBinds;
767     }
768
769     private void defineDefaultKeyBindings() {
770         defKeyBinds.put("Push to application","ctrl L");
771       defKeyBinds.put("Push to LyX","ctrl L");
772       defKeyBinds.put("Push to WinEdt","ctrl shift W");
773       defKeyBinds.put("Push to Emacs","ctrl shift E");
774         defKeyBinds.put("Quit JabRef", "ctrl Q");
775         defKeyBinds.put("Open database", "ctrl O");
776         defKeyBinds.put("Save database", "ctrl S");
777         defKeyBinds.put("Save database as ...", "ctrl shift S");
778         defKeyBinds.put("Save all", "ctrl alt S");
779         defKeyBinds.put("Close database", "ctrl W");
780         defKeyBinds.put("New entry", "ctrl N");
781         defKeyBinds.put("Cut", "ctrl X");
782         defKeyBinds.put("Copy", "ctrl C");
783         defKeyBinds.put("Paste", "ctrl V");
784         defKeyBinds.put("Undo", "ctrl Z");
785         defKeyBinds.put("Redo", "ctrl Y");
786         defKeyBinds.put("Help", "F1");
787         defKeyBinds.put("New article", "ctrl shift A");
788         defKeyBinds.put("New book", "ctrl shift B");
789         defKeyBinds.put("New phdthesis", "ctrl shift T");
790         defKeyBinds.put("New inbook", "ctrl shift I");
791         defKeyBinds.put("New mastersthesis", "ctrl shift M");
792         defKeyBinds.put("New proceedings", "ctrl shift P");
793         defKeyBinds.put("New unpublished", "ctrl shift U");
794         defKeyBinds.put("Edit strings", "ctrl T");
795         defKeyBinds.put("Edit preamble", "ctrl P");
796         defKeyBinds.put("Select all", "ctrl A");
797         defKeyBinds.put("Toggle groups interface", "ctrl shift G");
798         defKeyBinds.put("Autogenerate BibTeX keys", "ctrl G");
799         defKeyBinds.put("Search", "ctrl F");
800         defKeyBinds.put("Incremental search", "ctrl shift F");
801         defKeyBinds.put("Repeat incremental search", "ctrl shift F");
802         defKeyBinds.put("Close dialog", "ESCAPE");
803         defKeyBinds.put("Close entry editor", "ESCAPE");
804         defKeyBinds.put("Close preamble editor", "ESCAPE");
805         defKeyBinds.put("Back, help dialog", "LEFT");
806         defKeyBinds.put("Forward, help dialog", "RIGHT");
807         defKeyBinds.put("Preamble editor, store changes", "alt S");
808         defKeyBinds.put("Clear search", "ESCAPE");
809         defKeyBinds.put("Entry editor, next panel", "ctrl TAB");//"ctrl PLUS");//"shift Right");
810         defKeyBinds.put("Entry editor, previous panel", "ctrl shift TAB");//"ctrl MINUS");
811         defKeyBinds.put("Entry editor, next panel 2", "ctrl PLUS");//"ctrl PLUS");//"shift Right");
812         defKeyBinds.put("Entry editor, previous panel 2", "ctrl MINUS");//"ctrl MINUS");
813         defKeyBinds.put("Entry editor, next entry", "ctrl shift DOWN");
814         defKeyBinds.put("Entry editor, previous entry", "ctrl shift UP");
815         defKeyBinds.put("Entry editor, store field", "alt S");
816         defKeyBinds.put("String dialog, add string", "ctrl N");
817         defKeyBinds.put("String dialog, remove string", "shift DELETE");
818         defKeyBinds.put("String dialog, move string up", "ctrl UP");
819         defKeyBinds.put("String dialog, move string down", "ctrl DOWN");
820         defKeyBinds.put("Save session", "F11");
821         defKeyBinds.put("Load session", "F12");
822         defKeyBinds.put("Copy \\cite{BibTeX key}", "ctrl K");
823         defKeyBinds.put("Next tab", "ctrl PAGE_DOWN");
824         defKeyBinds.put("Previous tab", "ctrl PAGE_UP");
825         defKeyBinds.put("Replace string", "ctrl R");
826         defKeyBinds.put("Delete", "DELETE");
827         defKeyBinds.put("Open file", "F4");
828         defKeyBinds.put("Open PDF or PS", "shift F5");
829         defKeyBinds.put("Open URL or DOI", "F3");
830         defKeyBinds.put("Open SPIRES entry", "ctrl F3");
831         defKeyBinds.put("Toggle entry preview", "ctrl F9");
832         defKeyBinds.put("Switch preview layout", "F9");
833         defKeyBinds.put("Edit entry", "ctrl E");
834         defKeyBinds.put("Mark entries", "ctrl M");
835         defKeyBinds.put("Unmark entries", "ctrl shift M");
836         defKeyBinds.put("Fetch Medline", "F5");
837         defKeyBinds.put("Fetch CiteSeer", "F6");
838         defKeyBinds.put("New from plain text", "ctrl shift N");
839         defKeyBinds.put("Import Fields from CiteSeer", "ctrl shift C");
840         defKeyBinds.put("Fetch citations from CiteSeer", "F7");
841         defKeyBinds.put("Synchronize files", "ctrl F4");
842         defKeyBinds.put("Synchronize PDF", "shift F4");
843         defKeyBinds.put("Synchronize PS", "ctrl shift F4");
844
845         defKeyBinds.put("Abbreviate", "ctrl alt A");
846         defKeyBinds.put("Unabbreviate", "ctrl alt shift A");
847         defKeyBinds.put("Search IEEEXplore", "F8");
848         defKeyBinds.put("Fetch ArXiv.org", "shift F8");
849         defKeyBinds.put("Search JSTOR", "shift F9");
850         defKeyBinds.put("Fetch SPIRES", "ctrl F8");
851         defKeyBinds.put("Write XMP", "ctrl F4");
852         defKeyBinds.put("New file link", "ctrl N");
853         defKeyBinds.put("Fetch SPIRES", "ctrl F8");
854
855         defKeyBinds.put("Import into current database", "ctrl I");
856         defKeyBinds.put("Import into new database", "ctrl shift I");
857     }
858
859     private String getNextUnit(Reader data) throws IOException {
860         int c;
861         boolean escape = false, done = false;
862         StringBuffer res = new StringBuffer();
863         while (!done && ((c = data.read()) != -1)) {
864             if (c == '\\') {
865                 if (!escape)
866                     escape = true;
867                 else {
868                     escape = false;
869                     res.append('\\');
870                 }
871             } else {
872                 if (c == ';') {
873                     if (!escape)
874                         done = true;
875                     else
876                         res.append(';');
877                 } else {
878                     res.append((char)c);
879                 }
880                 escape = false;
881             }
882         }
883         if (res.length() > 0)
884             return res.toString();
885         else
886             return null;
887     }
888
889     private String makeEscape(String s) {
890         StringBuffer sb = new StringBuffer();
891         int c;
892         for (int i=0; i<s.length(); i++) {
893             c = s.charAt(i);
894             if ((c == '\\') || (c == ';'))
895                 sb.append('\\');
896             sb.append((char)c);
897         }
898         return sb.toString();
899     }
900
901     /**
902      * Stores all information about the entry type in preferences, with
903      * the tag given by number.
904      */
905     public void storeCustomEntryType(CustomEntryType tp, int number) {
906         String nr = ""+number;
907         put(CUSTOM_TYPE_NAME+nr, tp.getName());
908         putStringArray(CUSTOM_TYPE_REQ+nr, tp.getRequiredFields());
909         putStringArray(CUSTOM_TYPE_OPT+nr, tp.getOptionalFields());
910
911     }
912
913     /**
914      * Retrieves all information about the entry type in preferences,
915      * with the tag given by number.
916      */
917     public CustomEntryType getCustomEntryType(int number) {
918         String nr = ""+number;
919         String
920             name = get(CUSTOM_TYPE_NAME+nr);
921         String[]
922             req = getStringArray(CUSTOM_TYPE_REQ+nr),
923             opt = getStringArray(CUSTOM_TYPE_OPT+nr);
924         if (name == null)
925             return null;
926         else return new CustomEntryType
927             (Util.nCase(name), req, opt);
928
929
930     }
931
932
933
934     public List<ExternalFileType> getDefaultExternalFileTypes() {
935         List<ExternalFileType> list = new ArrayList<ExternalFileType>();
936         list.add(new ExternalFileType("PDF", "pdf", "evince", "pdfSmall"));
937         list.add(new ExternalFileType("PostScript", "ps", "evince", "psSmall"));
938         list.add(new ExternalFileType("Word", "doc", "oowriter", "openoffice"));
939         list.add(new ExternalFileType("OpenDocument text", "odt", "oowriter", "openoffice"));
940         list.add(new ExternalFileType("Excel", "xls", "oocalc", "openoffice"));
941         list.add(new ExternalFileType("OpenDocument spreadsheet", "ods", "oocalc", "openoffice"));
942         list.add(new ExternalFileType("PowerPoint", "ppt", "ooimpress", "openoffice"));
943         list.add(new ExternalFileType("OpenDocument presentation", "odp", "ooimpress", "openoffice"));
944         list.add(new ExternalFileType("Rich Text Format", "rtf", "oowriter", "openoffice"));
945         list.add(new ExternalFileType("PNG image", "png", "gimp", "picture"));
946         list.add(new ExternalFileType("GIF image", "gif", "gimp", "picture"));
947         list.add(new ExternalFileType("JPG image", "jpg", "gimp", "picture"));
948         list.add(new ExternalFileType("Djvu", "djvu", "evince", "psSmall"));
949         list.add(new ExternalFileType("Text", "txt", "emacs", "emacs"));
950         list.add(new ExternalFileType("LaTeX", "tex", "emacs", "emacs"));
951         list.add(new ExternalFileType("CHM", "chm", "gnochm", "www"));
952         list.add(new ExternalFileType("TIFF image", "tiff", "gimp", "picture"));
953         ExternalFileType tp = new ExternalFileType("URL", "html", "firefox", "www");
954         list.add(tp);
955
956         // Under Windows we initialize all file types with an empty viewer app, to
957         // rely on the default app instead:
958         if (Globals.ON_WIN) {
959             for (Iterator<ExternalFileType> iterator = list.iterator(); iterator.hasNext();) {
960                 ExternalFileType type = iterator.next();
961                 type.setOpenWith("");
962             }
963         }
964
965         return list;
966     }
967
968     public ExternalFileType[] getExternalFileTypeSelection() {
969         return externalFileTypes.toArray
970                 (new ExternalFileType[externalFileTypes.size()]);
971     }
972
973     /**
974      * Look up the external file type registered with this name, if any.
975      * @param name The file type name.
976      * @return The ExternalFileType registered, or null if none.
977      */
978     public ExternalFileType getExternalFileTypeByName(String name) {
979         for (Iterator<ExternalFileType> iterator = externalFileTypes.iterator(); iterator.hasNext();) {
980             ExternalFileType type = iterator.next();
981             if (type.getName().equals(name))
982                 return type;
983         }
984         // Return an instance that signifies an unknown file type:
985         return new UnknownExternalFileType(name);
986     }
987
988     /**
989      * Look up the external file type registered for this extension, if any.
990      * @param extension The file extension.
991      * @return The ExternalFileType registered, or null if none.
992      */
993     public ExternalFileType getExternalFileTypeByExt(String extension) {
994         for (Iterator<ExternalFileType> iterator = externalFileTypes.iterator(); iterator.hasNext();) {
995             ExternalFileType type = iterator.next();
996             if (type.getExtension().equals(extension))
997                 return type;
998         }
999         return null;
1000     }
1001
1002
1003     /**
1004      * Reset the List of external file types after user customization.
1005      * @param types The new List of external file types. This is the complete list, not
1006      *  just new entries.
1007      */
1008     public void setExternalFileTypes(List<ExternalFileType> types) {
1009
1010         // First find a list of the default types:
1011         List<ExternalFileType> defTypes = getDefaultExternalFileTypes();
1012         // Make a list of types that are unchanged:
1013         List<ExternalFileType> unchanged = new ArrayList<ExternalFileType>();
1014
1015         externalFileTypes.clear();
1016         for (Iterator<ExternalFileType> iterator = types.iterator(); iterator.hasNext();) {
1017             ExternalFileType type = iterator.next();
1018             externalFileTypes.add(type);
1019
1020             // See if we can find a type with matching name in the default type list:
1021             ExternalFileType found = null;
1022             for (ExternalFileType defType : defTypes) {
1023                 if (defType.getName().equals(type.getName())) {
1024                     found = defType;
1025                     break;
1026                 }
1027             }
1028             if (found != null) {
1029                 // Found it! Check if it is an exact match, or if it has been customized:
1030                 if (found.equals(type))
1031                     unchanged.add(type);
1032                 else {
1033                     // It was modified. Remove its entry from the defaults list, since
1034                     // the type hasn't been removed:
1035                     defTypes.remove(found);
1036                 }
1037             }
1038         }
1039
1040         // Go through unchanged types. Remove them from the ones that should be stored,
1041         // and from the list of defaults, since we don't need to mention these in prefs:
1042         for (ExternalFileType type : unchanged) {
1043             defTypes.remove(type);
1044             types.remove(type);
1045         }
1046
1047         // Now set up the array to write to prefs, containing all new types, all modified
1048         // types, and a flag denoting each default type that has been removed:
1049         String[][] array = new String[types.size()+defTypes.size()][];
1050         int i=0;
1051         for (ExternalFileType type : types) {
1052             array[i] = type.getStringArrayRepresentation();
1053             i++;
1054         }
1055         for (ExternalFileType type : defTypes) {
1056             array[i] = new String[] {type.getName(), FILE_TYPE_REMOVED_FLAG};
1057             i++;
1058         }
1059         //System.out.println("Encoded: '"+Util.encodeStringArray(array)+"'");
1060         put("externalFileTypes", Util.encodeStringArray(array));
1061     }
1062
1063     
1064     /**
1065      * Set up the list of external file types, either from default values, or from values
1066      * recorded in Preferences.
1067      */
1068     public void updateExternalFileTypes() {
1069         // First get a list of the default file types as a starting point:
1070         List<ExternalFileType> types = getDefaultExternalFileTypes();
1071         // If no changes have been stored, simply use the defaults:
1072         if (prefs.get("externalFileTypes", null) == null) {
1073             externalFileTypes.clear();
1074             externalFileTypes.addAll(types);
1075             return;
1076         }
1077         // Read the prefs information for file types:
1078         String[][] vals = Util.decodeStringDoubleArray(prefs.get("externalFileTypes", ""));
1079         for (int i = 0; i < vals.length; i++) {
1080             if ((vals[i].length == 2) && (vals[i][1].equals(FILE_TYPE_REMOVED_FLAG))) {
1081                 // This entry indicates that a default entry type should be removed:
1082                 ExternalFileType toRemove = null;
1083                 for (ExternalFileType type : types) {
1084                     if (type.getName().equals(vals[i][0])) {
1085                         toRemove = type;
1086                         break;
1087                     }
1088                 }
1089                 // If we found it, remove it from the type list:
1090                 if (toRemove != null)
1091                     types.remove(toRemove);
1092             }
1093             else {
1094                 // A new or modified entry type. Construct it from the string array:
1095                 ExternalFileType type = new ExternalFileType(vals[i]);
1096                 // Check if there is a default type with the same name. If so, this is a
1097                 // modification of that type, so remove the default one:
1098                 ExternalFileType toRemove = null;
1099                 for (ExternalFileType defType : types) {
1100                     if (type.getName().equals(defType.getName())) {
1101                         toRemove = defType;
1102                         break;
1103                     }
1104                 }
1105                 // If we found it, remove it from the type list:
1106                 if (toRemove != null) {
1107                     types.remove(toRemove);
1108                 }
1109                 
1110                 // Then add the new one:
1111                 types.add(type);
1112             }
1113         }
1114
1115         // Finally, build the list of types based on the modified defaults list:
1116         for (ExternalFileType type : types) {
1117             externalFileTypes.add(type);
1118         }
1119     }
1120
1121
1122     /**
1123      * Removes all information about custom entry types with tags of
1124      * @param number or higher.
1125      */
1126     public void purgeCustomEntryTypes(int number) {
1127     purgeSeries(CUSTOM_TYPE_NAME, number);
1128     purgeSeries(CUSTOM_TYPE_REQ, number);
1129     purgeSeries(CUSTOM_TYPE_OPT, number);
1130
1131         /*while (get(CUSTOM_TYPE_NAME+number) != null) {
1132             remove(CUSTOM_TYPE_NAME+number);
1133             remove(CUSTOM_TYPE_REQ+number);
1134             remove(CUSTOM_TYPE_OPT+number);
1135             number++;
1136             }*/
1137     }
1138
1139     /**
1140      * Removes all entries keyed by prefix+number, where number
1141      * is equal to or higher than the given number.
1142      * @param number or higher.
1143      */
1144     public void purgeSeries(String prefix, int number) {
1145         while (get(prefix+number) != null) {
1146             remove(prefix+number);
1147             number++;
1148         }
1149     }
1150
1151     public EntryEditorTabList getEntryEditorTabList() {
1152     if (tabList == null)
1153         updateEntryEditorTabList();
1154     return tabList;
1155     }
1156
1157     public void updateEntryEditorTabList() {
1158     tabList = new EntryEditorTabList();
1159     }
1160
1161     /**
1162      * Exports Preferences to an XML file.
1163      *
1164      * @param filename String File to export to
1165      */
1166     public void exportPreferences(String filename) throws IOException {
1167       File f = new File(filename);
1168       OutputStream os = new FileOutputStream(f);
1169       try {
1170         prefs.exportSubtree(os);
1171       } catch (BackingStoreException ex) {
1172         throw new IOException(ex.getMessage());
1173       }
1174     }
1175
1176       /**
1177        * Imports Preferences from an XML file.
1178        *
1179        * @param filename String File to import from
1180        */
1181       public void importPreferences(String filename) throws IOException {
1182         File f = new File(filename);
1183         InputStream is = new FileInputStream(f);
1184         try {
1185           Preferences.importPreferences(is);
1186         } catch (InvalidPreferencesFormatException ex) {
1187           throw new IOException(ex.getMessage());
1188         }
1189       }
1190
1191     /**
1192      * Determines whether the given field should be written without any sort of wrapping.
1193      * @param fieldName The field name.
1194      * @return true if the field should not be wrapped.
1195      */
1196     public boolean isNonWrappableField(String fieldName) {
1197         return nonWrappableFields.contains(fieldName);
1198     }
1199 }