1 package net.sf.bibkeeper;
2
3 import net.sf.bibkeeper.undo;
4 import net.sf.bibkeeper.export;
5 import net.sf.bibkeeper.groups.QuickSearchRule;
6 import javax.swing;
7 import java.awt;
8 import java.awt.event;
9 import java.io;
10 import java.util;
11 import java.awt.datatransfer;
12 import javax.swing.undo;
13
14 public class BibtexBaseFrame extends JFrame implements MouseListener, ClipboardOwner {
15
16 static int frameCount = 0;
17 // This variable keeps track of how many Bibtex bases are open.
18 // When the last one is closed, the program shuts down.
19
20 BibtexDatabase database;
21 // The database we are representing.
22 File file = null, fileToOpen = null; // The filename of the database.
23
24 Hashtable autoCompleters = new Hashtable();
25 // Hashtable that holds as keys the names of the fields where
26 // autocomplete is active, and references to the autocompleter objects.
27
28 // The undo manager.
29 public CountingUndoManager undoManager = new CountingUndoManager();
30
31 //BibkeeperPreferences prefs = BibkeeperPreferences.loadPreferences();
32 BibkeeperPrefs prefs = new BibkeeperPrefs();
33 // The user preferences are contained in this objects.
34
35 ExampleFileFilter fileFilter;
36 // File filter for .bib files.
37
38 boolean baseChanged = false;
39 // Used to track whether the base has changed since last save.
40
41 //JPanel mainPanel = new JPanel();
42 JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
43 EntryTableModel tableModel = null;
44 EntryTable entryTable = null;
45 JLabel statusLine = new JLabel("", SwingConstants.LEFT);
46
47 HashMap entryTypeForms = new HashMap();
48 // Hashmap to keep track of which entries currently have open
49 // EntryTypeForm dialogs.
50 PreambleEditor preambleEditor = null;
51 // Keeps track of the preamble dialog if it is open.
52 StringDialog stringDialog = null;
53 // Keeps track of the string dialog if it is open.
54 public HelpDialog helpDiag = new HelpDialog(this);
55 // The help window.
56 SearchPane searchDialog = null;
57 // The search pane.
58 boolean showingSearchResults = false,
59 showingGroup = false;
60
61 SidePaneManager sidePaneManager;
62 // The sidepane manager takes care of populating the sidepane.
63 MetaData metaData;
64 // MetaData parses, keeps and writes meta data.
65
66 protected final static String KEY_PROPERTY = "bibtexkey";
67 protected final static String SEARCH_PROPERTY = "search";
68
69 public BibtexBaseFrame() {
70 init();
71 if ((prefs.getBoolean("openLastEdited")) && (prefs.get("lastEdited") != null)) {
72 // Try to load latest edited database.
73 fileToOpen = new File(prefs.get("lastEdited"));
74 Util.pr("Opening last edited database: '"+fileToOpen.getPath()+"'");
75 openDatabaseAction.openIt();
76 }
77 }
78
79 public BibtexBaseFrame(BibtexDatabase db, MetaData meta) {
80 // Create a BibtexBaseFrame for an existing unnamed database.
81 init();
82 database = db;
83 metaData = meta;
84 if (prefs.getBoolean("autoComplete")) {
85 db.setCompleters(autoCompleters);
86 }
87 setupDatabaseLayout();
88 }
89
90 public BibtexBaseFrame(BibtexDatabase db, File file, HashMap meta) {
91 // Create a BibtexBaseFrame for an existing named database.
92 init();
93 database = db;
94 parseMetaData(meta);
95 if (prefs.getBoolean("autoComplete")) {
96 db.setCompleters(autoCompleters);
97 }
98
99 this.file = file;
100 setupDatabaseLayout();
101 }
102
103 public BibtexBaseFrame(File file) {
104 init();
105 // We're given a file to load the database from.
106 if (file.exists()) {
107 fileToOpen = file;
108 openDatabaseAction.openIt();
109 }
110 }
111
112 public void init () {
113 // Create a BibtexBaseFrame independent of database. Called by all
114 // constructors.
115 frameCount++; // Another one is opened.
116
117 // Add a ref to this frame to the global HashMap.
118 GUIGlobals.frames.put(""+frameCount, this);
119
120 // Set up which fields should be autocompleted.
121 assignAutoCompleters();
122
123 // Prevent the frame from closing on its own, and redirect closing events
124 // to the CloseAction.
125 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
126 addWindowListener(new WindowAdapter() {
127 public void windowClosing(WindowEvent e) {
128 closeAction.actionPerformed(null);
129 }
130 });
131
132 // Set up a file filter to choose .bib files. We use Sun's ExampleFileFilter.
133 fileFilter = new ExampleFileFilter();
134 fileFilter.addExtension("bib");
135 fileFilter.setDescription("BibTeX databases");
136
137 //setIconImage((new ImageIcon(GUIGlobals.appIconFile)).getImage());
138
139 // initialize the labelMaker
140 labelMaker = new LabelMaker() ;
141 labelMaker.addRule(new ArticleLabelRule(),BibtexEntryType.ARTICLE) ;
142 labelMaker.addRule(new BookLabelRule(),BibtexEntryType.BOOK) ;
143 labelMaker.addRule(new IncollectionLabelRule(),BibtexEntryType.INCOLLECTION) ;
144 labelMaker.addRule(new InproceedingsLabelRule(),BibtexEntryType.INPROCEEDINGS) ;
145
146 setupLayout();
147 setEmptyState();
148 }
149
150 private void setupLayout() {
151 GridBagLayout gbl = new GridBagLayout();
152 GridBagConstraints con = new GridBagConstraints();
153 getContentPane().setLayout(gbl);
154 setJMenuBar(createMenuBar());
155 JToolBar tb = createToolBar();
156 con.weightx = 1;
157 con.weighty = 0;
158 con.anchor = GridBagConstraints.NORTHWEST;
159 con.gridwidth = GridBagConstraints.REMAINDER;
160 con.fill = GridBagConstraints.BOTH;
161 gbl.setConstraints(tb,con);
162 getContentPane().add(tb);
163 con.weighty = 1;
164 con.anchor = GridBagConstraints.CENTER;
165 gbl.setConstraints(mainPanel,con);
166 getContentPane().add(mainPanel);
167 con.anchor = GridBagConstraints.WEST;
168 con.weighty = 0;
169 con.insets = new Insets(0, 5, 0, 0);
170 gbl.setConstraints(statusLine,con);
171 getContentPane().add(statusLine);
172
173 setLocation(getSetLocation());
174 setSize(getSetSize());
175
176 output("Welcome to Bibkeeper!");
177 }
178
179 public Dimension getSetSize() {
180 return new Dimension(prefs.getInt("sizeX"), prefs.getInt("sizeY"));
181 }
182
183 public Point getSetLocation() {
184 return new Point(prefs.getInt("posX"), prefs.getInt("posY"));
185 }
186 private void setEmptyState() {
187 // Disable actions that demand an open database.
188 copyKeyAction.setEnabled(false);
189 saveDatabaseAction.setEnabled(false);
190 saveAsDatabaseAction.setEnabled(false);
191 newEntryAction.setEnabled(false);
192 copyAction.setEnabled(false);
193 pasteAction.setEnabled(false);
194 removeEntryAction.setEnabled(false);
195 editEntryAction.setEnabled(false);
196 editPreambleAction.setEnabled(false);
197 editStringsAction.setEnabled(false);
198 closeDatabaseAction.setEnabled(false);
199 mergeDatabaseAction.setEnabled(false);
200 searchPaneAction.setEnabled(false);
201 makeLabelAction.setEnabled(false);
202 showGroupsAction.setEnabled(false);
203 for (int i=0; i<newSpecificEntryAction.length; i++)
204 newSpecificEntryAction[i].setEnabled(false);
205 setTitle(GUIGlobals.emptyTitle);
206 }
207
208 private void setNonEmptyState() {
209 // Enable actions that demand an open database.
210 copyKeyAction.setEnabled(true);
211 saveDatabaseAction.setEnabled(true);
212 saveAsDatabaseAction.setEnabled(true);
213 newEntryAction.setEnabled(true);
214 removeEntryAction.setEnabled(true);
215 copyAction.setEnabled(true);
216 pasteAction.setEnabled(true);
217 editEntryAction.setEnabled(true);
218 editPreambleAction.setEnabled(true);
219 editStringsAction.setEnabled(true);
220 closeDatabaseAction.setEnabled(true);
221 mergeDatabaseAction.setEnabled(true);
222 searchPaneAction.setEnabled(true);
223 makeLabelAction.setEnabled(true);
224 showGroupsAction.setEnabled(true);
225 for (int i=0; i<newSpecificEntryAction.length; i++)
226 newSpecificEntryAction[i].setEnabled(true);
227 //setupTableAction.setEnabled(true);
228 }
229
230 private void setupDatabaseLayout() {
231 // This method is called whenever this frame has been provided
232 // with a database, and completes the layout.
233
234 setupMainPanel();
235 //getContentPane().add(mainPanel, BorderLayout.CENTER);
236 if (file != null)
237 setTitle(GUIGlobals.baseTitle+file.getName());
238 else
239 setTitle(GUIGlobals.untitledTitle);
240
241 //DragNDropManager dndm = new DragNDropManager(this);
242
243
244 setNonEmptyState();
245
246 }
247
248 private JToolBar createToolBar() {
249 JToolBar tb = new JToolBar();
250 tb.setFloatable(false);
251 tb.add(newDatabaseAction);
252 tb.add(openDatabaseAction);
253 tb.add(saveDatabaseAction);
254 tb.addSeparator();
255 //tb.add(closeDatabaseAction);
256 //tb.addSeparator();
257 tb.add(copyKeyAction);
258 tb.add(makeLabelAction);
259 tb.addSeparator();
260 tb.add(editPreambleAction);
261 tb.add(editStringsAction);
262 tb.add(newEntryAction);
263 tb.add(editEntryAction);
264 tb.add(removeEntryAction);
265 tb.add(copyAction);
266 tb.add(pasteAction);
267 tb.add(searchPaneAction);
268 tb.addSeparator();
269 tb.add(setupTableAction);
270 tb.addSeparator();
271 tb.add(new HelpAction(helpDiag,GUIGlobals.baseFrameHelp, "Help"));
272 //tb.setBackground(Color.white);
273 return tb;
274 }
275
276 private JMenuBar createMenuBar() {
277 JMenuBar mb = new JMenuBar();
278 JMenu file = new JMenu("File");
279 file.add(mItem(newDatabaseAction, null/*GUIGlobals.newKeyStroke*/));
280 file.add(mItem(openDatabaseAction, GUIGlobals.openKeyStroke));
281 file.add(mItem(saveDatabaseAction, GUIGlobals.saveKeyStroke));
282 file.add(mItem(saveAsDatabaseAction, null));
283 file.add(mItem(saveSpecialAction, null));
284 file.addSeparator();
285 file.add(mItem(mergeDatabaseAction, null));
286 file.addSeparator();
287 file.add(mItem(closeDatabaseAction, null));
288 file.add(mItem(closeAction, GUIGlobals.closeKeyStroke));
289 mb.add(file);
290
291 JMenu view = new JMenu("View"),
292 entry = new JMenu("Edit"),
293 entryType = new JMenu("New ..."),
294 bibtex = new JMenu("Bibtex");
295 for (int i=0; i<newSpecificEntryAction.length; i++)
296 entryType.add(mItem(newSpecificEntryAction[i],
297 newSpecificEntryAction[i].keyStroke));
298 entry.add(mItem(undoAction, GUIGlobals.undoStroke));
299 entry.add(mItem(redoAction, GUIGlobals.redoStroke));
300 entry.addSeparator();
301 entry.add(mItem(removeEntryAction, GUIGlobals.removeEntryKeyStroke));
302 entry.add(mItem(copyAction, GUIGlobals.copyStroke));
303 entry.add(mItem(pasteAction, GUIGlobals.pasteStroke));
304 entry.addSeparator();
305 entry.add(mItem(selectAllAction, GUIGlobals.selectAllKeyStroke));
306
307 view.add(mItem(showGroupsAction, GUIGlobals.showGroupsKeyStroke));
308
309 bibtex.add(entryType);
310 bibtex.add(mItem(newEntryAction, GUIGlobals.newEntryKeyStroke));
311 bibtex.addSeparator();
312 bibtex.add(mItem(copyKeyAction, GUIGlobals.copyKeyStroke));
313 bibtex.add(mItem(editPreambleAction, GUIGlobals.editPreambleKeyStroke));
314 bibtex.add(mItem(editStringsAction, GUIGlobals.editStringsKeyStroke));
315 bibtex.add(mItem(editEntryAction, GUIGlobals.editEntryKeyStroke));
316
317
318 mb.add(entry);
319 mb.add(view);
320 mb.add(bibtex);
321
322 JMenu tools = new JMenu("Tools");
323 tools.add(mItem(searchPaneAction, GUIGlobals.simpleSearchKeyStroke));
324 JMenu autoGenerateMenu = new JMenu("Autogenerate Bibtexkey") ;
325 tools.add(mItem(makeLabelAction, GUIGlobals.generateKeyStroke));
326 tools.add(mItem(checkUniqueLabelAction, null));
327 //tools.add(autoGenerateMenu) ;
328 mb.add(tools);
329
330 JMenu options = new JMenu("Options");
331 //options.add(mItem(setupTableAction, GUIGlobals.setupTableKeyStroke));
332 options.add(setupTableAction);
333 mb.add(options);
334
335 JMenu help = new JMenu("Help");
336 help.add(mItem(new HelpAction(helpDiag, GUIGlobals.baseFrameHelp, "Help"),
337 GUIGlobals.helpKeyStroke));
338 help.add(new HelpAction("Help contents", helpDiag,
339 GUIGlobals.helpContents, "Help contents"));
340 help.addSeparator();
341 help.add(mItem(aboutAction, null));
342 mb.add(help);
343
344 return mb;
345 }
346
347 private JMenuItem mItem(AbstractAction a, KeyStroke ks) {
348 // Set up a menu item with action and accelerator key.
349 JMenuItem mi = new JMenuItem();
350 mi.setAction(a);
351 if (ks != null)
352 mi.setAccelerator(ks);
353 return mi;
354 }
355
356 //private void setupMainPanel() {
357 public void setupMainPanel() {
358 tableModel = new EntryTableModel(this, database);
359 entryTable = new EntryTable(tableModel, prefs);
360 entryTable.addMouseListener(this);
361 entryTable.getInputMap().put(GUIGlobals.copyStroke, "copy");
362 entryTable.getInputMap().put(GUIGlobals.pasteStroke, "paste");
363 entryTable.getActionMap().put("copy", copyAction);
364 entryTable.getActionMap().put("paste", pasteAction);
365
366 // Set the right-click menu for the entry table.
367 RightClickMenu rcm = new RightClickMenu(this, metaData);
368 entryTable.setRightClickMenu(rcm);
369
370 //mainPanel.removeAll();
371 //mainPanel.setLayout(new GridLayout(1,1));
372 mainPanel.setRightComponent(entryTable.getPane());
373 sidePaneManager = new SidePaneManager
374 (prefs, metaData, this, mainPanel);
375 sidePaneManager.populatePanel();
376
377 //mainPanel.setDividerLocation(GUIGlobals.SPLIT_PANE_DIVIDER_LOCATION);
378 mainPanel.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
379 mainPanel.setResizeWeight(0);
380 mainPanel.revalidate();
381 }
382
383 public void updatePreamble() {
384 if (preambleEditor != null)
385 preambleEditor.updatePreamble();
386 }
387
388 public void assureStringDialogNotEditing() {
389 if (stringDialog != null)
390 stringDialog.assureNotEditing();
391 }
392
393 public void updateStringDialog() {
394 if (stringDialog != null)
395 stringDialog.refreshTable();
396 }
397
398 public void refreshTable() {
399 // This method is called by EntryTypeForm when a field value is
400 // stored. The table is scheduled for repaint.
401 tableModel.remap();
402 entryTable.revalidate();
403 entryTable.repaint();
404 }
405
406 public void markBaseChanged() {
407 baseChanged = true;
408
409 // Put an asterix behind the file name to indicate the database has changed.
410 if (!getTitle().endsWith("*"))
411 setTitle(getTitle()+"*");
412
413 // If the status line states that the base has been saved, we remove this
414 // message, since it is no longer relevant. If a different message is shown,
415 // we leave it.
416 if (statusLine.getText().startsWith("Saved database"))
417 output(" ");
418
419 // Update undo/redo states:
420 undoAction.updateUndoState();
421 redoAction.updateRedoState();
422
423 }
424
425 public synchronized void markChangedOrUnChanged() {
426 if (undoManager.hasChanged()) {
427 if (!baseChanged)
428 markBaseChanged();
429 }
430 else if (baseChanged) {
431 baseChanged = false;
432 if (file != null)
433 setTitle(GUIGlobals.baseTitle+file.getName());
434 else
435 setTitle(GUIGlobals.untitledTitle);
436 }
437 }
438
439 public Completer getAutoCompleter(String field) {
440 return (Completer)autoCompleters.get(field);
441 }
442
443 public void assignAutoCompleters() {
444 // Set up which fields should have autocompletion. This should
445 // probably be made customizable. Existing Completer objects are
446 // forgotten. The completers must be updated towards the database.
447 byte[] fields = prefs.getByteArray("autoCompFields");
448 autoCompleters = new Hashtable();
449 for (int i=0; i<fields.length; i++) {
450 autoCompleters.put(GUIGlobals.ALL_FIELDS[fields[i]], new Completer());
451 }
452
453 }
454
455 public void updateAutoCompleters() {
456 if (database != null)
457 database.setCompleters(autoCompleters);
458 }
459
460 /**
461 * This method is called after a database has been parsed. The
462 * hashmap contains the contents of all comments in the .bib file
463 * that started with the meta flag (GUIGlobals.META_FLAG).
464 * In this method, the meta data are input to their respective
465 * handlers.
466 */
467 public void parseMetaData(HashMap meta) {
468 metaData = new MetaData(meta);
469 }
470
471 public void showSearchResults(String searchValueField) {
472 //entryTable.scrollTo(0);
473 if (searchValueField == DatabaseSearch.SEARCH)
474 showingSearchResults = true;
475 else if (searchValueField == DatabaseSearch.GROUPSEARCH)
476 showingGroup = true;
477
478 entryTable.setShowingSearchResults(showingSearchResults,
479 showingGroup);
480 entryTable.clearSelection();
481 entryTable.scrollTo(0);
482 refreshTable();
483 }
484
485 public void stopShowingSearchResults() {
486 showingSearchResults = false;
487 entryTable.setShowingSearchResults(showingSearchResults,
488 showingGroup);
489 refreshTable();
490 }
491
492 public void stopShowingGroup() {
493 showingGroup = false;
494 entryTable.setShowingSearchResults(showingSearchResults,
495 showingGroup);
496 refreshTable();
497 }
498
499 protected EntryTableModel getTableModel(){
500 return tableModel ;
501 }
502
503 protected BibtexDatabase getDatabase(){
504 return database ;
505 }
506
507 public void preambleEditorClosing() {
508 preambleEditor = null;
509 }
510
511 public void stringsClosing() {
512 stringDialog = null;
513 }
514
515 public void output(String s) {
516 statusLine.setText(s);
517 }
518
519 protected ParserResult loadDatabase(File fileToOpen) throws IOException {
520 // Temporary (old method):
521 //FileLoader fl = new FileLoader();
522 //BibtexDatabase db = fl.load(fileToOpen.getPath());
523
524 BibtexParser bp = new BibtexParser(new FileReader(fileToOpen));
525 ParserResult pr = bp.parse();
526 return pr;
527 }
528
529 // The action concerned with closing the window.
530 CloseAction closeAction = new CloseAction(this);
531 class CloseAction extends AbstractAction {
532 BibtexBaseFrame parent;
533 public CloseAction(BibtexBaseFrame parent_) {
534 super("Close window");
535 parent = parent_;
536 putValue(SHORT_DESCRIPTION, "Close this window");
537 //putValue(ACCELERATOR_KEY, GUIGlobals.closeKeyStroke);
538 }
539 public void actionPerformed(ActionEvent e) {
540 // Ask here if the user really wants to close, if the base
541 // has not been saved since last save.
542 boolean close = true;
543 if (baseChanged) {
544 int answer = JOptionPane.showConfirmDialog(parent, "Database has changed. Do you want to save before closing?", "Save before closing", JOptionPane.YES_NO_CANCEL_OPTION);
545 if ((answer == JOptionPane.CANCEL_OPTION) ||
546 (answer == JOptionPane.CLOSED_OPTION))
547 close = false; // The user has cancelled.
548 if (answer == JOptionPane.YES_OPTION) {
549 // The user wants to save.
550 saveDatabaseAction.actionPerformed(null);
551 }
552 }
553
554 if (close) {
555 dispose();
556 if (--frameCount == 0) {
557 // We save the window's size and location so the frame
558 // will look the same next time. Also, if the program is
559 // set to automatically open the latest database on
560 // startup, we save the currently active file.
561 prefs.putInt("posX", parent.getLocation().x);
562 prefs.putInt("posY", parent.getLocation().y);
563 prefs.putInt("sizeX", parent.getSize().width);
564 prefs.putInt("sizeY", parent.getSize().height);
565
566 if (prefs.getBoolean("openLastEdited")) {
567 // Here we store the current file. If there is no current
568 // file, we remove any previously stored file name.
569 if ((database == null) || (file == null))
570 prefs.remove("lastEdited");
571 else
572 prefs.put("lastEdited", file.getPath());
573 }
574 System.exit(0); // All in order? End program.
575 } else {
576 // There are other frames still open. We need to remove
577 // this frame from the global HashMap.
578 GUIGlobals.frames.remove(this);
579 }
580 }
581 }
582 }
583
584 // The action for closing the current database and leaving the window open.
585 CloseDatabaseAction closeDatabaseAction = new CloseDatabaseAction(this);
586 class CloseDatabaseAction extends AbstractAction {
587 BibtexBaseFrame parent;
588 public CloseDatabaseAction(BibtexBaseFrame parent_) {
589 super("Close database");
590 parent = parent_;
591 putValue(SHORT_DESCRIPTION, "Close the current database");
592 }
593 public void actionPerformed(ActionEvent e) {
594 // Ask here if the user really wants to close, if the base
595 // has not been saved since last save.
596 boolean close = true;
597 if (baseChanged) {
598 int answer = JOptionPane.showConfirmDialog(parent, "Database has changed. Do you want to save before closing?", "Save before closing", JOptionPane.YES_NO_CANCEL_OPTION);
599 if ((answer == JOptionPane.CANCEL_OPTION) ||
600 (answer == JOptionPane.CLOSED_OPTION))
601 close = false; // The user has cancelled.
602 if (answer == JOptionPane.YES_OPTION) {
603 // The user wants to save.
604 saveDatabaseAction.actionPerformed(null);
605 }
606 }
607
608 if (close) {
609 mainPanel.removeAll();
610 mainPanel.repaint();
611 database = null;
612 file = null;
613 baseChanged = false;
614 setEmptyState();
615 output("Closed database.");
616 }
617 }
618 }
619
620
621
622 // The action concerned with opening an existing database.
623 OpenDatabaseAction openDatabaseAction = new OpenDatabaseAction(this);
624 class OpenDatabaseAction extends AbstractAction {
625 BibtexBaseFrame parent;
626 public OpenDatabaseAction(BibtexBaseFrame parent_) {
627 super("Open database",
628 new ImageIcon(GUIGlobals.openIconFile));
629 putValue(SHORT_DESCRIPTION, "Open BibTeX database");
630 //putValue(MNEMONIC_KEY, GUIGlobals.openKeyCode);
631 parent = parent_;
632 }
633 public void actionPerformed(ActionEvent e) {
634 // Open a new database.
635 if ((e.getActionCommand() == null) || (e.getActionCommand().equals("Open database"))) {
636 JFileChooser chooser = (prefs.get("workingDirectory") == null) ?
637 new JFileChooser((File)null) :
638 new JFileChooser(new File(prefs.get("workingDirectory")));
639 chooser.setFileFilter(fileFilter);
640 int returnVal = chooser.showOpenDialog(parent);
641 if(returnVal == JFileChooser.APPROVE_OPTION) {
642 fileToOpen = chooser.getSelectedFile();
643 }
644 } else {
645 Util.pr(NAME);
646 Util.pr(e.getActionCommand());
647 fileToOpen = new File(Util.checkName(e.getActionCommand()));
648 }
649 openIt();
650 }
651
652 public void openIt() {
653 if ((fileToOpen != null) && (fileToOpen.exists())) {
654 try {
655 prefs.put("workingDirectory", fileToOpen.getPath());
656 // Should this be done _after_ we know it was successfully opened?
657
658 ParserResult pr = loadDatabase(fileToOpen);
659 BibtexDatabase db = pr.getDatabase();
660 HashMap meta = pr.getMetaData();
661
662 // Put into this or a new window.
663 if (database == null) {
664 // No database is yet open in this window, so we'll open
665 // it and just put it here.
666 file = fileToOpen; // Set the current file name pointer.
667 fileToOpen = null;
668 database = db;
669 parseMetaData(meta);
670 if (prefs.getBoolean("autoComplete")) {
671 db.setCompleters(autoCompleters);
672 }
673 setupDatabaseLayout(); // Configure the frame.
674 output("Opened database '"+file.getPath()+"' with "+
675 db.getEntryCount()+" entries.");
676 } else {
677 // This window contains a database, so we'll have to create a new
678 // window for the database we open.
679 BibtexBaseFrame bbf = new BibtexBaseFrame
680 (db, fileToOpen, meta);
681 bbf.output("Opened database '"+fileToOpen.getPath()+"'"
682 +" with "+db.getEntryCount()+" entries.");
683 bbf.setVisible(true);
684 fileToOpen = null;
685 }
686
687 } catch (Throwable ex) {
688 JOptionPane.showMessageDialog
689 (parent, ex.getMessage(),
690 "Open database", JOptionPane.ERROR_MESSAGE);
691 //output("Could not open: "+ex.getMessage());
692 file = null;
693 }
694 }
695 }
696 }
697
698 // The action concerned with opening a new database.
699 NewDatabaseAction newDatabaseAction = new NewDatabaseAction();
700 class NewDatabaseAction extends AbstractAction {
701 public NewDatabaseAction() {
702 super("New database",
703 new ImageIcon(GUIGlobals.newIconFile));
704 putValue(SHORT_DESCRIPTION, "New BibTeX database");
705 //putValue(MNEMONIC_KEY, GUIGlobals.newKeyCode);
706 }
707 public void actionPerformed(ActionEvent e) {
708
709 // Create a new, empty, database.
710 BibtexDatabase db = new BibtexDatabase();
711 if (prefs.getBoolean("autoComplete"))
712 db.setCompleters(autoCompleters);
713
714 // Put into this or a new window.
715 if (database == null) {
716 // No database is yet open in this window, so we'll
717 // just put it here.
718 database = db;
719 metaData = new MetaData();
720 setupDatabaseLayout(); // Configure the frame.
721 output("New database created.");
722 } else {
723 // This window contains a database, so we'll have to create
724 // a new window for the new database.
725 BibtexBaseFrame bbf = new BibtexBaseFrame(db, new MetaData());
726 bbf.output("New database created.");
727 bbf.setVisible(true);
728 }
729
730 }
731 }
732
733 // The action concerned with saving a database.
734 SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(this);
735 class SaveDatabaseAction extends AbstractAction {
736 BibtexBaseFrame parent;
737 public SaveDatabaseAction(BibtexBaseFrame parent_) {
738 super("Save database",
739 new ImageIcon(GUIGlobals.saveIconFile));
740 putValue(SHORT_DESCRIPTION, "Save current database");
741 parent = parent_;
742 }
743 public void actionPerformed(ActionEvent e) {
744
745 if (file == null)
746 saveAsDatabaseAction.actionPerformed(null);
747 else {
748 try {
749 FileActions.saveDatabase(database, metaData, file,
750 prefs, false, false);
751 undoManager.markUnchanged();
752 // (Only) after a successful save the following
753 // statement marks that the base is unchanged
754 // since last save:
755 baseChanged = false;
756 setTitle(GUIGlobals.baseTitle+file.getName());
757 output("Saved database '"+file.getPath()+"'.");
758 } catch (SaveException ex) {
759 if (ex.specificEntry()) {
760 // Error occured during processing of
761 // be. Highlight it:
762 int row = tableModel.getNumberFromName
763 (ex.getEntry().getId()),
764 topShow = Math.max(0, row-3);
765 //Util.pr(""+row);
766 entryTable.setRowSelectionInterval(row, row);
767 entryTable.setColumnSelectionInterval
768 (0, entryTable.getColumnCount()-1);
769 entryTable.scrollTo(topShow);
770 }
771 JOptionPane.showMessageDialog
772 (parent, "Could not save file.\n"+ex.getMessage(),
773 "Save database", JOptionPane.ERROR_MESSAGE);
774 }
775 }
776 }
777 }
778
779 // The other action concerned with saving a database.
780 SaveAsDatabaseAction saveAsDatabaseAction = new SaveAsDatabaseAction(this);
781 class SaveAsDatabaseAction extends AbstractAction {
782 BibtexBaseFrame parent;
783 public SaveAsDatabaseAction(BibtexBaseFrame parent_) {
784 super("Save database as ..",
785 new ImageIcon(GUIGlobals.saveAsIconFile));
786 putValue(SHORT_DESCRIPTION, "Save current database");
787 parent = parent_;
788 }
789 public void actionPerformed(ActionEvent e) {
790
791 JFileChooser chooser = new JFileChooser(prefs.get("workingDirectory"));
792 chooser.setFileFilter(fileFilter);
793 int returnVal = chooser.showSaveDialog(parent);
794 if(returnVal == JFileChooser.APPROVE_OPTION) {
795 String name = chooser.getSelectedFile().getName(),
796 path = chooser.getSelectedFile().getParent();
797 if (!name.endsWith(".bib"))
798 name = name+".bib";
799 file = new File(path, name);
800 if (!file.exists() || (JOptionPane.showConfirmDialog(parent, "File '"+name+"' exists. Overwrite?", "Save database", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)) {
801 saveDatabaseAction.actionPerformed(null);
802 prefs.put("workingDirectory", path);
803 }
804 else
805 file = null;
806 } else {
807 // Cancelled.
808 }
809
810
811 }
812 }
813
814 // The action for removing selected entries.
815 RemoveEntryAction removeEntryAction = new RemoveEntryAction(this);
816 class RemoveEntryAction extends AbstractAction {
817 BibtexBaseFrame parent;
818 public RemoveEntryAction(BibtexBaseFrame parent_) {
819 super("Remove selected entries",
820 new ImageIcon(GUIGlobals.removeIconFile));
821 parent = parent_;
822 putValue(SHORT_DESCRIPTION, "Remove selected entries");
823 }
824 public void actionPerformed(ActionEvent e) {
825 int[]
826 rows = entryTable.getSelectedRows(),
827 cols = entryTable.getSelectedColumns();
828
829 // Only works if the first column, or all columns, is selected.
830 if ((((cols.length == 1) && (cols[0] == 0))
831 || (cols.length == tableModel.getColumnCount()))
832 && (rows.length > 0)) {
833 //&& (database.getEntryCount() > 0) && (entryTable.getSelectedRow() < database.getEntryCount())) {
834
835 String msg = "Really delete the selected entry?",
836 title = "Delete entry";
837 if (rows.length > 1) {
838 msg = "Really delete the selected "+rows.length+" entries?";
839 title = "Delete multiple entries";
840 }
841 int answer = JOptionPane.showConfirmDialog(parent, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
842 if (answer == JOptionPane.YES_OPTION) {
843 // Create a CompoundEdit to make the action undoable.
844 NamedCompound ce = new NamedCompound
845 (rows.length > 1 ? "delete entries" : "delete entry");
846 // Loop through the array of entries, and delete them.
847 for (int i=0; i<rows.length; i++) {
848 String id = tableModel.getNameFromNumber(rows[i]);
849 BibtexEntry entry = database.getEntryByID(id);
850 database.removeEntry(id);
851 Object o = entryTypeForms.get(id);
852 if (o != null) {
853 ((EntryTypeForm)o).dispose();
854 }
855 ce.addEdit(new UndoableRemoveEntry(database, entry,
856 entryTypeForms));
857 }
858 entryTable.clearSelection();
859 output("Deleted "+(rows.length>1 ? rows.length+" entries" : "entry")+".");
860 ce.end();
861 undoManager.addEdit(ce);
862 refreshTable();
863 markBaseChanged();
864 }
865 }
866 }
867
868 };
869
870 // The action for adding a new entry of unspecified type.
871 NewEntryAction newEntryAction = new NewEntryAction(this);
872 NewEntryAction[] newSpecificEntryAction = new NewEntryAction[] {
873 new NewEntryAction(this, BibtexEntryType.ARTICLE,
874 GUIGlobals.newArticleKeyStroke),
875 new NewEntryAction(this, BibtexEntryType.BOOK,
876 GUIGlobals.newBookKeyStroke),
877 new NewEntryAction(this, BibtexEntryType.PHDTHESIS,
878 GUIGlobals.newPhdthesisKeyStroke),
879 new NewEntryAction(this, BibtexEntryType.INBOOK,
880 GUIGlobals.newInBookKeyStroke),
881 new NewEntryAction(this, BibtexEntryType.MASTERSTHESIS,
882 GUIGlobals.newMasterKeyStroke),
883 new NewEntryAction(this, BibtexEntryType.PROCEEDINGS,
884 GUIGlobals.newProcKeyStroke),
885 new NewEntryAction(this, BibtexEntryType.INPROCEEDINGS),
886 new NewEntryAction(this, BibtexEntryType.INCOLLECTION),
887 new NewEntryAction(this, BibtexEntryType.BOOKLET),
888 new NewEntryAction(this, BibtexEntryType.MANUAL),
889 new NewEntryAction(this, BibtexEntryType.TECHREPORT),
890 new NewEntryAction(this, BibtexEntryType.UNPUBLISHED,
891 GUIGlobals.newUnpublKeyStroke),
892 new NewEntryAction(this, BibtexEntryType.MISC)
893
894 };
895 class NewEntryAction extends AbstractAction {
896
897 BibtexBaseFrame parent = null;
898 BibtexEntryType type = null; // The type of item to create.
899 KeyStroke keyStroke = null; // Used for the specific instances.
900
901 public NewEntryAction(BibtexBaseFrame parent_) {
902 // This action leads to a dialog asking for entry type.
903 super("New entry",
904 new ImageIcon(GUIGlobals.addIconFile));
905 putValue(SHORT_DESCRIPTION, "New BibTeX entry");
906 //putValue(MNEMONIC_KEY, GUIGlobals.newEntryKeyCode);
907 parent = parent_;
908 }
909
910 public NewEntryAction(BibtexBaseFrame parent_, BibtexEntryType type_) {
911 // This action leads to the creation of a specific entry type.
912 // No shortcut key is set for this action.
913 super(type_.getName());
914 type = type_;
915 parent = parent_;
916 }
917
918 public NewEntryAction(BibtexBaseFrame parent_, BibtexEntryType type_,
919 KeyStroke keyStroke_) {
920 // This action leads to the creation of a specific entry type.
921 super(type_.getName());
922 type = type_;
923 parent = parent_;
924 keyStroke = keyStroke_;
925 //putValue(MNEMONIC_KEY, keyCode);
926 }
927
928 public void actionPerformed(ActionEvent e) {
929 if (database == null)
930 throw new NullPointerException("Can't create new entry without an open database.");
931 BibtexEntryType newType = type;
932 if (newType == null) {
933 // Find out what type is wanted.
934 EntryTypeDialog etd = new EntryTypeDialog(parent);
935 // We want to center the dialog, to make it look nicer.
936 Util.placeDialog(etd, parent);
937 etd.setVisible(true);
938 newType = etd.getChoice();
939 }
940 if (newType != null) { // Only if the dialog was not cancelled.
941 String id = Util.createID(newType, database);
942 BibtexEntry be = new BibtexEntry(id, newType);
943 try {
944 database.insertEntry(be);
945
946 // Create an UndoableInsertEntry object.
947 undoManager.addEdit(new UndoableInsertEntry(database, be,
948 entryTypeForms));
949 output("Added new "+newType.getName().toLowerCase()+" entry.");
950 refreshTable();
951 markBaseChanged(); // The database just changed.
952 if (prefs.getBoolean("autoOpenForm")) {
953 EntryTypeForm etf = new EntryTypeForm(parent, be, prefs);
954 Util.placeDialog(etf, parent);
955 etf.setVisible(true);
956 entryTypeForms.put(id, etf);
957 }
958 } catch (KeyCollisionException ex) {
959 Util.pr(ex.getMessage());
960 }
961 }
962
963 }
964 }
965
966 // The action for copying selected entries.
967 SelectAllAction selectAllAction = new SelectAllAction(this);
968 class SelectAllAction extends AbstractAction {
969 BibtexBaseFrame parent;
970 public SelectAllAction(BibtexBaseFrame parent_) {
971 super("Select All", null);
972 putValue(SHORT_DESCRIPTION, "SelectAll");
973 parent = parent_;
974 }
975
976 public void actionPerformed(ActionEvent e) {
977 entryTable.selectAll() ;
978 }
979
980 }
981
982
983 // The action for copying selected entries.
984 CopyAction copyAction = new CopyAction(this);
985 class CopyAction extends AbstractAction {
986 BibtexBaseFrame parent;
987 public CopyAction(BibtexBaseFrame parent_) {
988 super("Copy",
989 new ImageIcon(GUIGlobals.copyIconFile));
990 putValue(SHORT_DESCRIPTION, "Copy");
991 parent = parent_;
992 }
993
994 public void actionPerformed(ActionEvent e) {
995 BibtexEntry[] bes = entryTable.getSelectedEntries();
996
997 // Entries are copied if only the first or multiple
998 // columns are selected.
999 if ((bes != null) && (bes.length > 0)) {
1000 TransferableBibtexEntry trbe = new TransferableBibtexEntry(bes);
1001 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trbe, parent);
1002 output("Copied "+(bes.length>1 ? bes.length+" entries." : "1 entry."));
1003 } else {
1004 // The user maybe selected a single cell.
1005 int[] rows = entryTable.getSelectedRows(),
1006 cols = entryTable.getSelectedColumns();
1007 if ((cols.length == 1) && (rows.length == 1)) {
1008 // Copy single value.
1009 Object o = tableModel.getValueAt(rows[0], cols[0]);
1010 if (o != null) {
1011 StringSelection ss = new StringSelection(o.toString());
1012 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, parent);
1013 output("Copied cell contents.");
1014 }
1015 } /*else
1016 output("Copy multiple cell values: not yet supported.");*/
1017 }
1018 }
1019 }
1020
1021
1022 // Run through the entire list, making sure that each of the
1023 // selected Bibtexkeys is unique
1024 CheckUniqueLabelAction checkUniqueLabelAction = new CheckUniqueLabelAction(this);
1025 class CheckUniqueLabelAction extends AbstractAction {
1026 BibtexBaseFrame parent;
1027 public CheckUniqueLabelAction(BibtexBaseFrame parent_) {
1028 super("Remove duplicate bibtex keys", null);
1029 parent = parent_ ;
1030 putValue(SHORT_DESCRIPTION, "Remove duplicate bibtex keys");
1031 }
1032
1033 public void actionPerformed(ActionEvent e) {
1034 int[] rows = entryTable.getSelectedRows() ;
1035
1036 // If one or none are selected, we do as if all were selected:
1037 if (rows.length < 2) {
1038 rows = new int[database.getEntryCount()];
1039 for (int i=0; i<database.getEntryCount(); i++)
1040 rows[i] = i;
1041 }
1042
1043 int numSelected = rows.length ;
1044
1045 // JOptionPane.showMessageDialog( parent, "Trying to check unique labels","Run it",JOptionPane.INFORMATION_MESSAGE) ;
1046
1047 Hashtable uniqueTable = new Hashtable() ;
1048 Hashtable ignoreTable = new Hashtable() ;
1049 BibtexEntry bes = null ;
1050 String bibtexKey = null ;
1051 boolean finish = false ;
1052 NamedCompound ce;
1053
1054 for(int i = 0 ; i < numSelected && !finish ; i++){
1055 bes = database.getEntryByID(tableModel.getNameFromNumber(rows[i]));
1056 bibtexKey = (String) bes.getField(KEY_PROPERTY) ;
1057 // do compare
1058 if( uniqueTable.containsKey(bibtexKey) ){
1059 Object[] options = { "stop search","ignore","fix"} ;
1060 int answer = JOptionPane.showOptionDialog
1061 (parent, "Not a unique key: "+bibtexKey,
1062 "Make unique",
1063 JOptionPane.YES_NO_CANCEL_OPTION,
1064 JOptionPane.QUESTION_MESSAGE,null, options,options[2]);
1065 // " FIX or DELETE, IGNORE, STOP_SEARCH?"
1066
1067
1068 // if match, query=delete,ignore,ignoreall,fix
1069 // fix entry
1070 // if ((answer == JOptionPane.CLOSED_OPTION)) {
1071 if (answer == 2) {
1072 // do label stuff here
1073
1074 bes = database.getEntryByID
1075 (tableModel.getNameFromNumber(rows[i]));
1076 // append the label, test and re-insert
1077 while( uniqueTable.containsKey(bibtexKey) ){
1078 // if(bibtexKey.indexOf(":")<0){
1079 // bibtexKey += ":" ;
1080 // }
1081 bibtexKey += "i" ;
1082 }
1083 Object oldValue = bes.getField(GUIGlobals.KEY_FIELD);
1084 bes.setField(GUIGlobals.KEY_FIELD,bibtexKey) ;
1085
1086 // Store undo info:
1087 ce = new NamedCompound("key change");
1088 ce.addEdit(new UndoableFieldChange
1089 (bes, GUIGlobals.KEY_FIELD, oldValue,
1090 bibtexKey));
1091 ce.end();
1092 undoManager.addEdit(ce);
1093
1094 markBaseChanged();
1095 refreshTable();
1096 } // end of if actions from dialog
1097 /*else
1098 // delete entry, deletes the duplicate entry
1099 if ((answer == JOptionPane.CANCEL_OPTION)) {
1100 bes = database.removeEntry(tableModel.getNameFromNumber(rows[i]));
1101 }*/
1102 else
1103 // skip it
1104 if ((answer == JOptionPane.NO_OPTION)) {
1105 // do nothing
1106 }
1107 // else
1108 // if ((answer == JOptionPane.YES_OPTION)) {
1109 // // stop processing
1110 // finish = true ;
1111 // }
1112 else
1113 {
1114 finish = true ;
1115 }
1116
1117 } // end of if a match
1118
1119 uniqueTable.put(bibtexKey,bibtexKey) ;
1120
1121
1122 } // end of for loop
1123
1124 // markBaseChanged() ;
1125 // refreshTable() ;
1126 JOptionPane.showMessageDialog( parent, "Finished search","Finished",JOptionPane.INFORMATION_MESSAGE) ;
1127 } // end of action performed
1128 } // end of class
1129
1130
1131
1132
1133 // The action for auto-generating labels.
1134 MakeLabelAction makeLabelAction = new MakeLabelAction(this);
1135 class MakeLabelAction extends AbstractAction {
1136 BibtexBaseFrame parent;
1137 public MakeLabelAction(BibtexBaseFrame parent_) {
1138 super("Autogenerate bibtex keys", new ImageIcon(GUIGlobals.genKeyIconFile));
1139 parent = parent_ ;
1140 putValue(SHORT_DESCRIPTION, "Autogenerate bibtex keys");
1141 }
1142
1143 public void actionPerformed(ActionEvent e) {
1144 int[] rows = entryTable.getSelectedRows() ;
1145 int numSelected = rows.length ;
1146 BibtexEntry bes = null ;
1147 if (numSelected > 0) {
1148 int answer = JOptionPane.showConfirmDialog
1149 (parent, "Generate bibtex key"+
1150 (numSelected>1 ? "s for the selected "+numSelected+" entries?" :
1151 " for the selected entry?"), "Autogenerate Bibtexkey",
1152 JOptionPane.YES_NO_CANCEL_OPTION);
1153 if (answer != JOptionPane.YES_OPTION) {
1154 return ;
1155 }
1156 } else { // None selected. Inform the user to select entries first.
1157 JOptionPane.showMessageDialog(parent, "First select the entries you want keys to be generated for.",
1158 "Autogenerate Bibtexkey", JOptionPane.INFORMATION_MESSAGE);
1159 return ;
1160 }
1161
1162 output("Generating Bibtexkey for "+numSelected+(numSelected>1 ? " entries" : "entry"));
1163
1164 NamedCompound ce = new NamedCompound("autogenerate keys");
1165 //BibtexEntry be;
1166 Object oldValue;
1167 for(int i = 0 ; i < numSelected ; i++){
1168 bes = database.getEntryByID(tableModel.getNameFromNumber(rows[i]));
1169 oldValue = bes.getField(GUIGlobals.KEY_FIELD);
1170 bes = labelMaker.applyRule(bes) ;
1171 ce.addEdit(new UndoableFieldChange
1172 (bes, GUIGlobals.KEY_FIELD, oldValue,
1173 bes.getField(GUIGlobals.KEY_FIELD)));
1174 }
1175 ce.end();
1176 undoManager.addEdit(ce);
1177 markBaseChanged() ;
1178 refreshTable() ;
1179 }
1180 }
1181
1182 // The action for reading another database and adding its contents into the currently
1183 // open one.
1184 MergeDatabaseAction mergeDatabaseAction = new MergeDatabaseAction(this);
1185 class MergeDatabaseAction extends AbstractAction {
1186 BibtexBaseFrame parent;
1187 public MergeDatabaseAction(BibtexBaseFrame parent_) {
1188 super("Merge with database...", null);
1189 parent = parent_ ;
1190 putValue(SHORT_DESCRIPTION, "Import the contents of another database");
1191 }
1192 public void actionPerformed(ActionEvent e) {
1193 JFileChooser chooser = (prefs.get("workingDirectory") == null) ?
1194 new JFileChooser((File)null) :
1195 new JFileChooser(new File(prefs.get("workingDirectory")));
1196 chooser.setDialogTitle("Merge with database");
1197 chooser.setFileFilter(fileFilter);
1198 int returnVal = chooser.showOpenDialog(parent);
1199 if(returnVal == JFileChooser.APPROVE_OPTION) {
1200 fileToOpen = chooser.getSelectedFile();
1201 if ((fileToOpen != null) && (fileToOpen.exists())) {
1202 try {
1203 //prefs.put("workingDirectory", fileToOpen.getPath());
1204 // I think it is reasonable not to update the working directory
1205 // here, since the current database is the same.
1206
1207 ParserResult pr = loadDatabase(fileToOpen);
1208 BibtexDatabase db = pr.getDatabase();
1209 //HashMap metaData = pr.getMetaData();
1210 // We ignore metadata, and keep the ones we have.
1211
1212 // Database has been successfully opened, and
1213 // now we insert its contents. Entries are OK,
1214 // just need to avoid key collisions. Strings
1215 // must be put in the right order, and equal
1216 // ones must be resolved. The preambles are
1217 // appended, but this might require user
1218 // supervision.
1219
1220 boolean ignoredString = false;
1221 String message = "Merged with database: ";
1222 MergeDialog md = new MergeDialog(parent);
1223 Util.placeDialog(md, parent);
1224 md.setVisible(true);
1225 if (md.ok()) {
1226 NamedCompound ce = new NamedCompound("merge");
1227 // Preamble:
1228 if (md.mergePreamble()) {
1229 String s = db.getPreamble(),
1230 oldValue = database.getPreamble();
1231 if (s != null) {
1232 if (database.getPreamble() == null)
1233 database.setPreamble(s);
1234 else
1235 database.setPreamble(database.getPreamble()
1236 +"\n"
1237 +s);
1238 updatePreamble();
1239
1240 // Store undo info:
1241 ce.addEdit(new UndoablePreambleChange
1242 (database, parent, oldValue, database.getPreamble()));
1243 }
1244 }
1245 // Strings:
1246 if (md.mergeStrings()) {
1247 int count = 0;
1248 for (int i=0; i<db.getStringCount(); i++) {
1249 BibtexString bs = db.getString(i);
1250 if (!database.hasStringLabel(bs.getName()))
1251 try {
1252 int pos = database.getStringCount();
1253 database.addString(bs, pos);
1254 count++;
1255 // Store undo info:
1256 ce.addEdit(new UndoableInsertString
1257 (parent, database, bs, pos));
1258
1259 } catch (KeyCollisionException ex) {}
1260 else
1261 ignoredString = true; // A string had to be skipped.
1262 }
1263 message = message
1264 +(count == 0 ? "0 strings" :
1265 count+" string"+(count > 1 ? "s" : ""));
1266
1267 }
1268
1269 // Entries:
1270 if (md.mergeEntries()) {
1271 for (Iterator i=db.getKeySet().iterator(); i.hasNext();) {
1272 try {
1273 BibtexEntry be = db.getEntryByID((String)(i.next()));
1274 be = (BibtexEntry)(be.clone());
1275 // Change the ID to ensure that there is no key collision.
1276 be.setId(Util.createID(be.getType(), database));
1277 database.insertEntry(be);
1278
1279 // Store undo info:
1280 ce.addEdit(new UndoableInsertEntry
1281 (database, be, entryTypeForms));
1282
1283 } catch (KeyCollisionException exx) {}
1284 }
1285 message = message
1286 +(md.mergeStrings() ? " and " : "")
1287 +db.getEntryCount()+" entr"
1288 +(((db.getEntryCount() > 1)
1289 || (db.getEntryCount() == 0)) ? "ies" : "y");
1290 }
1291
1292 // Finish undo information:
1293 ce.end();
1294 undoManager.addEdit(ce);
1295
1296 if (ignoredString)
1297 JOptionPane.showMessageDialog
1298 (parent, "At least one string could not be imported "
1299 +"because its name was already used",
1300 "Could not import all strings",
1301 JOptionPane.WARNING_MESSAGE);
1302 output(message+".");
1303 markBaseChanged() ;
1304 refreshTable() ;
1305 }
1306
1307 } catch (IOException ex) {
1308 ex.printStackTrace();
1309 JOptionPane.showMessageDialog
1310 (parent, ex.getMessage(),
1311 "Open database", JOptionPane.ERROR_MESSAGE);
1312
1313 //output("Could not open: "+ex.getMessage());
1314 }
1315 }
1316 }
1317 }
1318 }
1319
1320
1321 // The action for pasting entries.
1322 PasteAction pasteAction = new PasteAction();
1323 class PasteAction extends AbstractAction {
1324 public PasteAction() {
1325 super("Paste",
1326 new ImageIcon(GUIGlobals.pasteIconFile));
1327 putValue(SHORT_DESCRIPTION, "Paste");
1328 }
1329
1330 public void actionPerformed(ActionEvent e) {
1331 // We pick an object from the clipboard, check if it exists, and if it is a set of entries.
1332 Transferable content = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
1333 if (content != null) {
1334 DataFlavor[] flavor = content.getTransferDataFlavors();
1335 if ((flavor != null) && (flavor.length > 0) && flavor[0].equals(TransferableBibtexEntry.entryFlavor)) {
1336 // We have determined that the clipboard data is a set of entries.
1337 BibtexEntry[] bes = null;
1338 try {
1339 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
1340 } catch (UnsupportedFlavorException ex) {
1341 } catch (IOException ex) {}
1342
1343 if ((bes != null) && (bes.length > 0)) {
1344 NamedCompound ce = new NamedCompound
1345 (bes.length > 1 ? "paste entries" : "paste entry");
1346 for (int i=0; i<bes.length; i++) {
1347 try {
1348 BibtexEntry be = (BibtexEntry)(bes[i].clone());
1349 // We have to clone the entries, since the pasted
1350 // entries must exist independently of the copied
1351 // ones.
1352 be.setId(Util.createID(be.getType(), database));
1353 database.insertEntry(be);
1354 ce.addEdit(new UndoableInsertEntry
1355 (database, be, entryTypeForms));
1356 } catch (KeyCollisionException ex) {
1357 Util.pr("KeyCollisionException... this shouldn't happen.");
1358 }
1359 }
1360 ce.end();
1361 undoManager.addEdit(ce);
1362 tableModel.remap();
1363 entryTable.clearSelection();
1364 entryTable.revalidate();
1365 output("Pasted "+(bes.length>1 ? bes.length+" entries." : "1 entry."));
1366 refreshTable();
1367 markBaseChanged();
1368 }
1369 }
1370 if ((flavor != null) && (flavor.length > 0) && flavor[0].equals(DataFlavor.stringFlavor)) {
1371 // We have determined that the clipboard data is a string.
1372 int[] rows = entryTable.getSelectedRows(),
1373 cols = entryTable.getSelectedColumns();
1374 if ((cols != null) && (cols.length == 1) && (cols[0] != 0)
1375 && (rows != null) && (rows.length == 1)) {
1376 try {
1377 tableModel.setValueAt((String)(content.getTransferData(DataFlavor.stringFlavor)), rows[0], cols[0]);
1378 refreshTable();
1379 markBaseChanged();
1380 output("Pasted cell contents");
1381 } catch (UnsupportedFlavorException ex) {
1382 } catch (IOException ex) {
1383 } catch (IllegalArgumentException ex) {
1384 output("Can't paste.");
1385 }
1386 }
1387 }
1388 }
1389 }
1390 }
1391
1392 // The action concerned with copying a BibTeX key to the clipboard.
1393 CopyKeyAction copyKeyAction = new CopyKeyAction();
1394 class CopyKeyAction extends AbstractAction {
1395 public CopyKeyAction() {
1396 super("Copy BibTeX key",
1397 // new ImageIcon(GUIGlobals.pre + "copyKey2.gif"));
1398 new ImageIcon(GUIGlobals.copyKeyIconFile));
1399 putValue(SHORT_DESCRIPTION, "Copy BibTeX key of the selected entry to clipboard");
1400 //putValue(ACCELERATOR_KEY, GUIGlobals.copyKeyStroke);
1401 //putValue(MNEMONIC_KEY, GUIGlobals.copyKeyCode);
1402 }
1403
1404 public void actionPerformed(ActionEvent e) {
1405 int clickedOn = entryTable.getSelectedRow();
1406 if (clickedOn >= 0) {
1407 String id = tableModel.getNameFromNumber(clickedOn);
1408 BibtexEntry be = database.getEntryByID(id);
1409 //Util.pr(be.getId());
1410 String s = (String)(be.getField(KEY_PROPERTY));
1411 if (s != null) {
1412 StringSelection ss = new StringSelection(s);
1413 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,ss);
1414 output("Copied BibTeX key: '"+s+"'.");
1415 } else {
1416 output("Entry has no BibTeX key!");
1417 }
1418 }
1419 }
1420 }
1421
1422
1423 // The action for searching columns for info using regular expressions.
1424 SearchPaneAction searchPaneAction = new SearchPaneAction(this);
1425 class SearchPaneAction extends AbstractAction {
1426 BibtexBaseFrame parent;
1427 // SearchPane searchDialog = null ;
1428 public SearchPaneAction(BibtexBaseFrame parent_) {
1429 super("Search",
1430 new ImageIcon(GUIGlobals.searchIconFile));
1431 putValue(SHORT_DESCRIPTION, "Search");
1432 parent = parent_;
1433 }
1434 public void actionPerformed(ActionEvent e) {
1435 if(searchDialog==null){
1436 searchDialog = new SearchPane(parent,prefs,"SearchPane",false) ;
1437 //Util.placeDialog(searchDialog, parent);
1438 }else{
1439 searchDialog.show() ;
1440 }
1441 }
1442 }
1443
1444
1445
1446 // The action for opening the preferences dialog.
1447 SetupTableAction setupTableAction = new SetupTableAction(this);
1448 class SetupTableAction extends AbstractAction {
1449 BibtexBaseFrame parent;
1450 public SetupTableAction(BibtexBaseFrame parent_) {
1451 super("Preferences",
1452 new ImageIcon(GUIGlobals.prefsIconFile));
1453 putValue(SHORT_DESCRIPTION, "Preferences");
1454 parent = parent_;
1455 }
1456 public void actionPerformed(ActionEvent e) {
1457 PrefsDialog.showPrefsDialog(parent, prefs);
1458 // This action can be invoked without an open database, so
1459 // we have to check if we have one before trying to invoke
1460 // methods to execute changes in the preferences.
1461
1462 // We want to notify all frames about the changes to
1463 // avoid problems when changing the column set.
1464 java.util.Iterator i = GUIGlobals.frames.keySet().iterator();
1465 for (; i.hasNext();) {
1466 BibtexBaseFrame bf = (BibtexBaseFrame)
1467 (GUIGlobals.frames.get(i.next()));
1468 if (bf.database != null) {
1469 bf.setupMainPanel();
1470 }
1471 }
1472 }
1473 }
1474
1475
1476 EditEntryAction editEntryAction = new EditEntryAction(this);
1477 class EditEntryAction extends AbstractAction {
1478 BibtexBaseFrame parent;
1479 public EditEntryAction(BibtexBaseFrame parent_) {
1480 super("Edit selected entry", new ImageIcon(GUIGlobals.editEntryIconFile));
1481 putValue(SHORT_DESCRIPTION, "Edit selected entry");
1482 parent = parent_;
1483 }
1484 public void actionPerformed(ActionEvent e) {
1485 int clickedOn = -1;
1486 // We demand that one and only one row is selected.
1487 if (entryTable.getSelectedRowCount() == 1) {
1488 clickedOn = entryTable.getSelectedRow();
1489 }
1490 if (clickedOn >= 0) {
1491 String id = tableModel.getNameFromNumber(clickedOn);
1492
1493 // First we check that no editor is already open for this
1494 // entry.
1495 if (!entryTypeForms.containsKey(id)) {
1496 BibtexEntry be = database.getEntryByID(id);
1497 EntryTypeForm form = new EntryTypeForm(parent, be, prefs);
1498 Util.placeDialog(form, parent); // We want to center the editor.
1499 form.setVisible(true);
1500 entryTypeForms.put(id, form);
1501 } else {
1502 ((EntryTypeForm)(entryTypeForms.get(id))).setVisible(true);
1503 }
1504 }
1505 }
1506 }
1507
1508 EditPreambleAction editPreambleAction = new EditPreambleAction(this);
1509 class EditPreambleAction extends AbstractAction {
1510 BibtexBaseFrame parent;
1511 public EditPreambleAction(BibtexBaseFrame parent_) {
1512 super("Edit preamble", new ImageIcon(GUIGlobals.preambleIconFile));
1513 putValue(SHORT_DESCRIPTION, "Edit preamble");
1514 parent = parent_;
1515 }
1516 public void actionPerformed(ActionEvent e) {
1517 if (preambleEditor == null) {
1518 PreambleEditor form = new PreambleEditor(parent, database, prefs);
1519 Util.placeDialog(form, parent);
1520 form.setVisible(true);
1521 preambleEditor = form;
1522 } else {
1523 preambleEditor.setVisible(true);
1524 }
1525
1526 }
1527 }
1528
1529 EditStringsAction editStringsAction = new EditStringsAction(this);
1530 class EditStringsAction extends AbstractAction {
1531 BibtexBaseFrame parent;
1532 public EditStringsAction(BibtexBaseFrame parent_) {
1533 super("Edit strings", new ImageIcon(GUIGlobals.stringsIconFile));
1534 putValue(SHORT_DESCRIPTION, "Edit strings");
1535 parent = parent_;
1536 }
1537 public void actionPerformed(ActionEvent e) {
1538 if (stringDialog == null) {
1539 StringDialog form = new StringDialog(parent, database, prefs);
1540 form.setVisible(true);
1541 stringDialog = form;
1542 } else {
1543 stringDialog.setVisible(true);
1544 }
1545
1546 }
1547 }
1548
1549 UndoAction undoAction = new UndoAction(this);
1550 class UndoAction extends AbstractAction {
1551 BibtexBaseFrame parent;
1552 public UndoAction(BibtexBaseFrame parent_) {
1553 super(undoManager.getUndoPresentationName(),
1554 new ImageIcon(GUIGlobals.undoIconFile));
1555 //putValue(SHORT_DESCRIPTION, "Undo action");
1556 parent = parent_;
1557 //setEnabled(false);
1558 }
1559 public void actionPerformed(ActionEvent e) {
1560 try {
1561 String name = undoManager.getUndoPresentationName();
1562 undoManager.undo();
1563 markBaseChanged();
1564 refreshTable();
1565 output(name);
1566 } catch (CannotUndoException ex) {
1567 output("Nothing to undo.");
1568 /*JOptionPane.showMessageDialog
1569 (parent, "Error: cannot undo!", "Cannot undo",
1570 JOptionPane.ERROR_MESSAGE);*/
1571 }
1572 // After everything, enable/disable the undo/redo actions
1573 // appropriately.
1574 updateUndoState();
1575 redoAction.updateRedoState();
1576 markChangedOrUnChanged();
1577 }
1578
1579 public void updateUndoState() {
1580 //setEnabled(undoManager.canUndo());
1581 putValue(AbstractAction.NAME,
1582 undoManager.getUndoPresentationName());
1583 }
1584 }
1585
1586 RedoAction redoAction = new RedoAction(this);
1587 class RedoAction extends AbstractAction {
1588 BibtexBaseFrame parent;
1589 public RedoAction(BibtexBaseFrame parent_) {
1590 super(undoManager.getRedoPresentationName(),
1591 new ImageIcon(GUIGlobals.redoIconFile));
1592 //putValue(SHORT_DESCRIPTION, "Undo action");
1593 parent = parent_;
1594 //setEnabled(false);
1595 }
1596 public void actionPerformed(ActionEvent e) {
1597 try {
1598 String name = undoManager.getRedoPresentationName();
1599 undoManager.redo();
1600 markBaseChanged();
1601 refreshTable();
1602 output(name);
1603 } catch (CannotRedoException ex) {
1604 output("Nothing to redo.");
1605 /*JOptionPane.showMessageDialog
1606 (parent, "Error: cannot redo!", "Cannot redo",
1607 JOptionPane.ERROR_MESSAGE);*/
1608 }
1609 // After everything, enable/disable the undo/redo actions
1610 // appropriately.
1611 updateRedoState();
1612 undoAction.updateUndoState();
1613 markChangedOrUnChanged();
1614 }
1615
1616 public void updateRedoState() {
1617 //setEnabled(undoManager.canRedo());
1618 putValue(AbstractAction.NAME,
1619 undoManager.getRedoPresentationName());
1620 }
1621
1622 }
1623
1624 AboutAction aboutAction = new AboutAction(this);
1625 class AboutAction extends AbstractAction {
1626 BibtexBaseFrame parent;
1627 public AboutAction(BibtexBaseFrame parent_) {
1628 super("About Bibkeeper");//,
1629 // new ImageIcon(GUIGlobals.addColumnIconFile));
1630 parent = parent_;
1631 }
1632 public void actionPerformed(ActionEvent e) {
1633 JDialog about = new JDialog(parent, "About Bibkeeper", true);
1634 JEditorPane jp = new JEditorPane();
1635 JScrollPane sp = new JScrollPane(jp, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
1636 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
1637 jp.setEditable(false);
1638 try {
1639 jp.setPage(GUIGlobals.aboutPage);
1640 // We need a hyperlink listener to be able to switch to the license
1641 // terms and back.
1642 jp.addHyperlinkListener(new javax.swing.event.HyperlinkListener() {
1643 public void hyperlinkUpdate(javax.swing.event.HyperlinkEvent e) {
1644 if (e.getEventType()
1645 == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED)
1646 try {
1647 ((JEditorPane)e.getSource()).setPage(e.getURL());
1648 } catch (IOException ex) {}
1649 }
1650 });
1651 about.getContentPane().add(sp);
1652 about.setSize(GUIGlobals.aboutSize);
1653 Util.placeDialog(about, parent);
1654 about.setVisible(true);
1655 } catch (IOException ex) {
1656 JOptionPane.showMessageDialog(parent, "Could not load file 'About.html'", "Error", JOptionPane.ERROR_MESSAGE);
1657 }
1658
1659 }
1660 }
1661
1662 ShowGroupsAction showGroupsAction = new ShowGroupsAction(this);
1663 class ShowGroupsAction extends AbstractAction {
1664 BibtexBaseFrame parent;
1665 public ShowGroupsAction(BibtexBaseFrame parent_) {
1666 super("Toggle groups interface",
1667 new ImageIcon(GUIGlobals.groupsIconFile));
1668 parent = parent_;
1669 }
1670 public void actionPerformed(ActionEvent e) {
1671 sidePaneManager.togglePanel("groups");
1672 }
1673 }
1674
1675 public void addToGroup(String groupName, String regexp, String field) {
1676
1677 boolean giveWarning = false;
1678 for (int i=0; i<GUIGlobals.ALL_FIELDS.length; i++) {
1679 if (field.equals(GUIGlobals.ALL_FIELDS[i])
1680 && !field.equals("keywords")) {
1681 giveWarning = true;
1682 break;
1683 }
1684 }
1685 if (giveWarning) {
1686 String message = "This action will modify the '"+field+"' field "
1687 +"of your entries.\nThis could cause undesired changes to "
1688 +"your entries, so it\nis recommended that you change the field "
1689 +"in your group\ndefinition to 'keywords' or a non-standard name."
1690 +"\n\nDo you still want to continue?";
1691 int choice = JOptionPane.showConfirmDialog
1692 (this, message, "Warning", JOptionPane.YES_NO_OPTION,
1693 JOptionPane.WARNING_MESSAGE);
1694
1695 if (choice == JOptionPane.NO_OPTION)
1696 return;
1697 }
1698
1699 BibtexEntry[] bes = entryTable.getSelectedEntries();
1700 if ((bes != null) && (bes.length > 0)) {
1701 QuickSearchRule qsr = new QuickSearchRule(field, regexp);
1702 NamedCompound ce = new NamedCompound("add to group");
1703 boolean hasEdits = false;
1704 for (int i=0; i<bes.length; i++) {
1705 if (qsr.applyRule(null, bes[i]) == 0) {
1706 String oldContent = (String)bes[i].getField(field),
1707 pre = " ",
1708 post = "";
1709 String newContent =
1710 (oldContent==null ? "" : oldContent+pre)
1711 +regexp+post;
1712 bes[i].setField
1713 (field, newContent);
1714
1715 // Store undo information.
1716 ce.addEdit(new UndoableFieldChange
1717 (bes[i], field, oldContent, newContent));
1718 hasEdits = true;
1719 }
1720 }
1721 if (hasEdits) {
1722 ce.end();
1723 undoManager.addEdit(ce);
1724 refreshTable();
1725 markBaseChanged();
1726 }
1727
1728 output("Appended '"+regexp+"' to the '"
1729 +field+"' field of "+bes.length+" entr"+
1730 (bes.length > 1 ? "ies." : "y."));
1731 }
1732 }
1733
1734 public void removeFromGroup
1735 (String groupName, String regexp, String field) {
1736
1737 boolean giveWarning = false;
1738 for (int i=0; i<GUIGlobals.ALL_FIELDS.length; i++) {
1739 if (field.equals(GUIGlobals.ALL_FIELDS[i])
1740 && !field.equals("keywords")) {
1741 giveWarning = true;
1742 break;
1743 }
1744 }
1745 if (giveWarning) {
1746 String message = "This action will modify the '"+field+"' field "
1747 +"of your entries.\nThis could cause undesired changes to "
1748 +"your entries, so it\nis recommended that you change the field "
1749 +"in your group\ndefinition to 'keywords' or a non-standard name."
1750 +"\n\nDo you still want to continue?";
1751 int choice = JOptionPane.showConfirmDialog
1752 (this, message, "Warning", JOptionPane.YES_NO_OPTION,
1753 JOptionPane.WARNING_MESSAGE);
1754
1755 if (choice == JOptionPane.NO_OPTION)
1756 return;
1757 }
1758
1759 BibtexEntry[] bes = entryTable.getSelectedEntries();
1760 if ((bes != null) && (bes.length > 0)) {
1761 QuickSearchRule qsr = new QuickSearchRule(field, regexp);
1762 NamedCompound ce = new NamedCompound("remove from group");
1763 boolean hasEdits = false;
1764 for (int i=0; i<bes.length; i++) {
1765 if (qsr.applyRule(null, bes[i]) > 0) {
1766 String oldContent = (String)bes[i].getField(field);
1767 qsr.removeMatches(bes[i]);
1768 // Store undo information.
1769 ce.addEdit(new UndoableFieldChange
1770 (bes[i], field, oldContent,
1771 bes[i].getField(field)));
1772 hasEdits = true;
1773 }
1774 }
1775 if (hasEdits) {
1776 ce.end();
1777 undoManager.addEdit(ce);
1778 refreshTable();
1779 markBaseChanged();
1780 }
1781
1782 output("Removed '"+regexp+"' from the '"
1783 +field+"' field of "+bes.length+" entr"+
1784 (bes.length > 1 ? "ies." : "y."));
1785 }
1786
1787 }
1788
1789
1790 SaveSpecialAction saveSpecialAction = new SaveSpecialAction(this);
1791 class SaveSpecialAction extends AbstractAction {
1792 BibtexBaseFrame parent;
1793 public SaveSpecialAction(BibtexBaseFrame parent_) {
1794 super("Save special",
1795 new ImageIcon(GUIGlobals.saveAsIconFile));
1796 //, new ImageIcon(GUIGlobals.groupsIconFile));
1797 parent = parent_;
1798 }
1799 public void actionPerformed(ActionEvent e) {
1800 SaveSpecialDialog ssd = new SaveSpecialDialog(parent);
1801 ssd.show();
1802 if (!ssd.okPressed())
1803 return; // Cancelled.
1804 JFileChooser fs = new JFileChooser();
1805 String[] fileType = ssd.getFileType();
1806 ExampleFileFilter filter = new ExampleFileFilter
1807 (fileType[0], fileType[1]);
1808 fs.setFileFilter(filter); //addChoosableFileFilter(filter);
1809 int returnVal = fs.showSaveDialog(parent);
1810 if(returnVal != JFileChooser.APPROVE_OPTION) {
1811 return; // Cancelled.
1812 }
1813
1814 String name = fs.getSelectedFile().getPath();
1815 if (!name.endsWith("."+fileType[0]))
1816 name = name+"."+fileType[0];
1817 File saveTo = new File(name);
1818
1819 if (ssd.getChoice() == SaveSpecialDialog.SAVE_SEARCH) {
1820 try {
1821 FileActions.saveDatabase(database, metaData, saveTo,
1822 prefs, showingSearchResults,
1823 showingGroup);
1824 } catch (SaveException ex) {
1825 if (ex.specificEntry()) {
1826 // Error occured during processing of
1827 // be. Highlight it:
1828 int row = tableModel.getNumberFromName
1829 (ex.getEntry().getId()),
1830 topShow = Math.max(0, row-3);
1831 //Util.pr(""+row);
1832 entryTable.setRowSelectionInterval(row, row);
1833 entryTable.setColumnSelectionInterval
1834 (0, entryTable.getColumnCount()-1);
1835 entryTable.scrollTo(topShow);
1836 }
1837 JOptionPane.showMessageDialog
1838 (parent, "Could not save file.\n"+ex.getMessage(),
1839 "Save database", JOptionPane.ERROR_MESSAGE);
1840
1841 }
1842 } else if (ssd.getChoice() ==
1843 SaveSpecialDialog.OPENOFFICE_EXPORT) {
1844
1845 // Show a warning message.
1846 JOptionPane.showMessageDialog
1847 (parent, "Warning: this feature is rather experimental "
1848 +"so far.\nCrossreferences will be resolved where "
1849 +"possible, but\nLaTeX commands will not be removed.",
1850 "Export to OpenOffice.org format",
1851 JOptionPane.WARNING_MESSAGE);
1852
1853 // The openoffice exporter.
1854 OpenofficeTextExport expo =
1855 new OpenofficeTextExport(prefs);
1856 try {
1857 expo.export(database, saveTo);
1858 output("Exported in OpenOffice CSV format to '"
1859 +saveTo.getPath()+"'.");
1860 } catch (SaveException ex) {
1861 JOptionPane.showMessageDialog
1862 (null, "Error exporting database:\n"+ex.getMessage(),
1863 "Error exporting database",
1864 JOptionPane.ERROR_MESSAGE);
1865 }
1866
1867
1868
1869 }
1870 }
1871 }
1872
1873 public void mouseClicked(MouseEvent e) {
1874 // Intercepts mouse clicks from the JTable showing the base contents.
1875 // A double click on an entry should open the entry's editor.
1876 if (e.getClickCount() == 2) {
1877 editEntryAction.actionPerformed(null);
1878 }
1879
1880 }
1881
1882
1883
1884 public void entryTypeFormClosing(String id) {
1885 // Called by EntryTypeForm when closing.
1886 entryTypeForms.remove(id);
1887 }
1888
1889 public void mouseEntered(MouseEvent e) {}
1890 public void mouseExited(MouseEvent e) {}
1891 public void mousePressed(MouseEvent e) {}
1892 public void mouseReleased(MouseEvent e) {}
1893
1894 // Method pertaining to the ClipboardOwner interface.
1895 public void lostOwnership(Clipboard clipboard, Transferable contents) {}
1896
1897 // a field which contains the label, the rule(s) are made only if called
1898 // add more LabelMaker's if more patterns are needed
1899 protected LabelMaker labelMaker = null ;
1900
1901
1902 }