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