ebf0026377d64b37d3d7ddfd0170370d919e1b29
[debian/jabref.git] / src / java / net / sf / jabref / EntryTableModel.java
1 /*
2  Copyright (C) 2003 Nizar N. Batada, Morten O. Alver
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 javax.swing.*;
31 import javax.swing.table.*;
32 import net.sf.jabref.export.LatexFieldFormatter;
33 import java.util.*;
34
35 public class EntryTableModel
36     extends AbstractTableModel {
37
38   BibtexDatabase db;
39   BasePanel panel;
40   JabRefFrame frame;
41   String[] columns; // Contains the current column names.
42   private EntrySorter sorter;
43   private int visibleRows = 0;
44
45
46   // Testing something:
47   Object[][] allCache = null;
48
49   //private Object[] entryIDs; // Temporary
50
51   // Constants used to define how a cell should be rendered.
52   public static final int REQUIRED = 1, OPTIONAL = 2,
53       REQ_STRING = 1,
54       REQ_NUMBER = 2,
55       OPT_STRING = 3,
56       OTHER = 3,
57       BOOLEAN = 4,
58       //PDF_COL = 1, // The column displaying icons for linked pdfs.
59       ICON_COL = 8; // Constant to indicate that an icon cell renderer should be used.
60   public static final String[]
61       PDF = {"pdf", "ps"},
62       URL_ = {"url", "doi"},
63         CITESEER = {"citeseerurl"};
64
65   public int padleft = -1; // padleft indicates how many columns (starting from left) are
66   // special columns (number column or icon column).
67   private HashMap iconCols = new HashMap();
68   int[] nameCols = null;
69   boolean showShort, namesNatbib, namesLastOnly;                               //MK:
70   boolean namesAsIs, namesFf, namesLf, abbr_names;              //MK:
71
72     //ImageIcon pdfIcon = new ImageIcon(GUIGlobals.pdfSmallIcon);
73
74   public EntryTableModel(JabRefFrame frame_,
75                          BasePanel panel_,
76                          BibtexDatabase db_) {
77     panel = panel_;
78     frame = frame_;
79     db = db_;
80
81     columns = Globals.prefs
82         .getStringArray("columnNames"); // This must be done again if the column
83     // preferences get changed.
84
85     remap();
86   }
87
88   /* This is the old getColumnName().
89    * This function now returns the field name
90    * with the original lower/upper case of the field name */
91   public String getFieldName(int col) {
92     if (col == 0) {
93       return GUIGlobals.NUMBER_COL;
94     }
95     else if (getIconTypeForColumn(col) != null) {
96       return getIconTypeForColumn(col)[0];
97     }
98     return columns[col - padleft];
99   }
100
101   public String getColumnName(int col) {
102       if (col == 0) {
103       return GUIGlobals.NUMBER_COL;
104     }
105     else if (getIconTypeForColumn(col) != null) {
106       return "";
107     }
108     else if(GUIGlobals.FIELD_DISPLAYS.get(columns[col - padleft]) != null) {
109         return((String) GUIGlobals.FIELD_DISPLAYS.get(columns[col - padleft]));
110     }
111     return Util.nCase(columns[col - padleft]);
112   }
113
114     public void showAllEntries() {
115     visibleRows = sorter.getEntryCount();
116     }
117
118     public void setRowCount(int rows) {
119     visibleRows = rows;
120     }
121
122   public int getRowCount() {
123     //Util.pr("rc "+sorter.getEntryCount());
124     //return sorter.getEntryCount();
125       return visibleRows;
126     //entryIDs.length;  // Temporary?
127   }
128
129   public int getColumnCount() {
130     return padleft + columns.length;
131   }
132
133   public Class getColumnClass(int column) {
134
135     //return (getIconTypeForColumn(column) != null ? Icon.class : String.class);
136       if (column == 0)
137       return Boolean.class;
138       else
139       return (getIconTypeForColumn(column) != null ? JLabel.class : String.class);
140   }
141
142   public Object getValueAt_(int row, int col) {
143       return allCache[row][col];
144   }
145
146   public void updateAllCache() {
147       /*long start = System.currentTimeMillis();
148       int rows = getRowCount();
149       int cols = getColumnCount();
150       allCache = new Object[rows][cols];
151       for (int row=0; row<rows; row++)
152           for (int col=0; col<cols; col++)
153               allCache[row][col] = getValueAt_old(row, col);
154       Globals.logger("Time spent: "+(System.currentTimeMillis()-start));*/
155   }
156
157   public Object getValueAt(int row, int col) {
158     // Return the field named frame.prefs.columnNames[col] from the Entry
159     // corresponding to the row.
160     Object o;
161     BibtexEntry be = sorter.getEntryAt(row);
162     String[] iconType = getIconTypeForColumn(col); // If non-null, indicates an icon column's type.
163     if (col == 0) {
164         o = "" + (row + 1);
165     }
166 /*      if (!isComplete(row)) {
167         //JLabel incomplete = new JLabel("" + (row + 1),GUIGlobals.incompleteLabel.getIcon(), JLabel.RIGHT);
168         //JLabel incomplete = new JLabel("" + (row + 1));
169         //incomplete.setToolTipText(Globals.lang("This entry is incomplete"));
170         //return incomplete;        
171       } else
172 */
173
174     else if (iconType != null) {
175       int hasField = -1;
176       for (int i=iconType.length-1; i>= 0; i--)
177         if (hasField(row, iconType[i]))
178           hasField = i;
179       if (hasField < 0)
180         return null;
181
182       // Ok, so we are going to display an icon. Find out which one, and return it:
183       return GUIGlobals.getTableIcon(iconType[hasField]);
184     }
185     //  if (col == 1)
186     //  o = be.getType().getName();
187     //else {
188     else if (columns[col - padleft].equals(GUIGlobals.TYPE_HEADER)) {
189       o = be.getType().getName();
190     }
191     //else if (columns[col-PADLEFT].equals(GUIGlobals.NUMBER_COL)) {
192     //  o = ""+(row+1);
193     //}
194     else {
195
196     //MK:vvv
197     o = null; if (showShort) o = be.getField("short"+columns[col-padleft]);   //MK:vvv
198        if (o==null) {
199          o = be.getField(columns[col - padleft]);
200          for (int i = 0; i < nameCols.length; i++) {
201            if (col - padleft == nameCols[i]) {
202              if (o == null) { return null; }
203              if (namesAsIs) return o;
204              if (namesNatbib) o = AuthorList.fixAuthor_Natbib((String)o);
205              else if (namesLastOnly) o = AuthorList.fixAuthor_lastNameOnlyCommas((String)o);
206              else if (namesFf) o = AuthorList.fixAuthor_firstNameFirstCommas((String) o, abbr_names);
207              else if (namesLf) o = AuthorList.fixAuthor_lastNameFirstCommas((String) o, abbr_names);
208
209              return o;
210  //            if (!namesAsIs) {
211  //              if (namesFf) {
212  //                return ImportFormatReader.fixAuthor_firstNameFirst( (String) o);
213  //              }
214  //              else {
215  //                return ImportFormatReader.fixAuthor_lastNameFirst( (String)o);
216  //              }
217  //            }
218   //MK:^^^
219           }
220      }
221        }
222     }
223     /*if (o != null) {
224         String processed = Globals.getCached((String)o);
225         if (processed == null) {
226             StringBuffer sb = new StringBuffer("");//"<html>");
227             sb.append((String)o);
228             //sb.append("</html>");
229             processed = sb.toString();
230             Globals.cache((String)o, processed);
231             o = processed;
232         } else
233             o = processed;
234         
235             
236     }*/
237     return o;
238   }
239
240   /**
241    * This method returns a string array indicating the types of icons to be displayed in the given column.
242    * It returns null if the column is not an icon column, and thereby also serves to identify icon
243    * columns.
244    */
245   public String[] getIconTypeForColumn(int col) {
246     Object o = iconCols.get(new Integer(col));
247     if (o != null)
248       return (String[])o;
249     else
250       return null;
251   }
252
253   public int getCellStatus(int row, int col) {
254     //if ((col == 0)  || (col == 1)) return OTHER;
255     if (col == 0) {
256       return BOOLEAN;
257     }
258     if (getIconTypeForColumn(col) != null) {
259       return ICON_COL;
260     }
261
262     BibtexEntryType type = (db.getEntryById(getIdForRow(row)))
263         .getType();
264     if (columns[col - padleft].equals(GUIGlobals.KEY_FIELD)
265         || type.isRequired(columns[col - padleft])) {
266       return REQUIRED;
267     }
268     if (type.isOptional(columns[col - padleft])) {
269       return OPTIONAL;
270     }
271     return OTHER;
272   }
273
274   public boolean isComplete(int row) {
275     BibtexEntry be = db.getEntryById(getIdForRow(row));
276     return (be != null ? be.hasAllRequiredFields() : false);
277   }
278
279   public boolean hasCrossRef(int row) {
280     BibtexEntry be = db.getEntryById(getIdForRow(row));
281     return (be.getField("crossref") != null);
282   }
283
284   public boolean nonZeroField(int row, String field) {
285     // Returns true iff the entry has a nonzero value in its
286     // 'search' field.
287     BibtexEntry be = db.getEntryById(getIdForRow(row));
288     if (be == null)
289         return false; // TODO: JZ: I think this should never happen, but it does
290     String o = (String) (be.getField(field));
291     return ( (o != null) && !o.equals("0"));
292   }
293
294   public boolean hasField(int row, String field) {
295     // Returns true iff the entry has a nonzero value in its
296     // 'search' field.
297     BibtexEntry be = db.getEntryById(getIdForRow(row));
298     return ((be != null) && (be.getField(field) != null));
299   }
300
301   private void updateSorter() {
302
303     // Set the icon columns, indicating the number of special columns to the left.
304     // We add those that are enabled in preferences.
305     iconCols.clear();
306     int coln = 1;
307     if (Globals.prefs.getBoolean("pdfColumn"))
308       iconCols.put(new Integer(coln++), PDF);
309     if (Globals.prefs.getBoolean("urlColumn"))
310       iconCols.put(new Integer(coln++), URL_);
311     if (Globals.prefs.getBoolean("citeseerColumn"))
312         iconCols.put(new Integer(coln++), CITESEER);
313
314     // Add 1 to the number of icon columns to get padleft.
315     padleft = 1+iconCols.size();
316
317     // Set up the int[] nameCols, to mark which columns should be
318     // treated as lists of names. This is to provide a correct presentation
319     // of names as efficiently as possible.
320     Vector tmp = new Vector(2, 1);
321     for (int i = 0; i < columns.length; i++) {
322       if (columns[i].equals("author")
323           || columns[i].equals("editor")) {
324         tmp.add(new Integer(i));
325       }
326     }
327     nameCols = new int[tmp.size()];
328     for (int i = 0; i < nameCols.length; i++) {
329       nameCols[i] = ( (Integer) tmp.elementAt(i)).intValue();
330     }
331     showShort = Globals.prefs.getBoolean("showShort");        //MK:
332     namesNatbib = Globals.prefs.getBoolean("namesNatbib");    //MK:
333     namesLastOnly = Globals.prefs.getBoolean("namesLastOnly");
334
335     namesAsIs = Globals.prefs.getBoolean("namesAsIs");
336     abbr_names = Globals.prefs.getBoolean("abbrAuthorNames"); //MK:
337     namesFf = Globals.prefs.getBoolean("namesFf");
338     namesLf = !(namesAsIs || namesFf || namesNatbib || namesLastOnly); // None of the above.
339         //namesLastOnly = Globals.prefs.getBoolean("namesLastOnly");
340     // Build a vector of prioritized search objectives,
341     // then pick the 3 first.
342     List fields = new ArrayList(6),
343         directions = new ArrayList(6),
344         binary = new ArrayList(6); // Signifies whether the sort criterion should only separate on/off or
345                                     // also sort within set field values.
346
347     // For testing MARKED feature. With this IF clause, the marked entries will only float to the top when
348     // no sorting/grouping reordering is active.
349     if  (!panel.sortingBySearchResults && !panel.sortingByCiteSeerResults && !panel.sortingByGroup) {
350         fields.add(Globals.MARKED);
351         directions.add(Boolean.TRUE);
352         binary.add(Boolean.FALSE);
353     }
354     if (panel.sortingByGroup) {
355       // Group search has the highest priority if active.
356       fields.add(Globals.GROUPSEARCH);
357       directions.add(Boolean.TRUE);
358         binary.add(Boolean.FALSE);
359     }
360     if (panel.sortingBySearchResults) {
361       // Normal search has priority over regular sorting.
362       fields.add(Globals.SEARCH);
363       directions.add(Boolean.TRUE);
364         binary.add(Boolean.FALSE);
365     }
366     if(panel.sortingByCiteSeerResults) {
367         fields.add("citeseercitationcount");
368         directions.add(Boolean.TRUE);
369         binary.add(Boolean.FALSE);
370     }
371
372     // Then the sort options:
373     directions.add(Boolean.valueOf(frame.prefs.getBoolean("priDescending")));
374     directions.add(Boolean.valueOf(frame.prefs.getBoolean("secDescending")));
375     directions.add(Boolean.valueOf(frame.prefs.getBoolean("terDescending")));
376     fields.add(frame.prefs.get("priSort"));
377     fields.add(frame.prefs.get("secSort"));
378     fields.add(frame.prefs.get("terSort"));
379     binary.add(Boolean.valueOf(Globals.prefs.getBoolean("priBinary"))); // TRUE if we are sorting on an icon.
380     binary.add(Boolean.FALSE);
381     binary.add(Boolean.FALSE);
382
383     // Remove the old sorter as change listener for the database:
384     if (sorter != null)
385     db.removeDatabaseChangeListener(sorter);
386
387     // Then pick the up to four highest ranking ones, and go.
388       int piv = Math.min(directions.size()-1, 3);
389       Comparator comp = new EntryComparator(
390               ((Boolean)binary.get(piv)).booleanValue(),
391               ((Boolean)directions.get(piv)).booleanValue(),
392               (String)fields.get(piv));
393       piv--;
394       while (piv >= 0) {
395           // Loop down towards the highest ranking criterion, wrapping new sorters around the
396           // ones we have:
397           String field = (String)fields.get(piv);
398           if (field.equals(Globals.MARKED)) {
399                 comp = new MarkedComparator(comp);
400           }
401           else
402             comp = new EntryComparator(
403                   ((Boolean)binary.get(piv)).booleanValue(),
404                   ((Boolean)directions.get(piv)).booleanValue(),
405                   field,
406                   comp);
407           piv--;
408       }
409
410       sorter = db.getSorter(comp);
411
412
413   }
414
415     /**
416      * Remaps and resorts the table model.
417      */
418     public void remap() {
419     updateSorter();
420     showAllEntries(); // Update the visible row count.
421         updateAllCache();
422     fireTableDataChanged();
423
424     }
425
426     /**
427      * Remaps and resorts the table model, and restricts the row number
428      * as directed.
429      */
430     public void remap(int rows) {
431     updateSorter();
432     setRowCount(rows);
433         updateAllCache();
434     fireTableDataChanged();
435     }
436
437     /**
438      * Quick remap of the table model. Sufficient for all operations except
439      * those that require a changed sort regime.
440      */
441     public void update() {
442     sorter.index();
443     showAllEntries();
444         updateAllCache();
445     fireTableDataChanged();
446
447     }
448
449     /**
450      * Quick remap of the table model. Sufficient for all operations except
451      * those that require a changed sort regime.
452      * Restricts the row number as directed.
453      */
454     public void update(int rows) {
455     sorter.index();
456     setRowCount(rows);
457         updateAllCache();
458     fireTableDataChanged();
459     }
460
461   public boolean isCellEditable(int row, int col) {
462     if (!Globals.prefs.getBoolean("allowTableEditing"))
463       return false;
464
465     if (col < padleft) {
466       return false;
467     }
468     // getColumnClass will throw a NullPointerException if there is no
469     // entry in FieldTypes.GLOBAL_FIELD_TYPES for the column.
470     try {
471       if (!getFieldName(col).equals(GUIGlobals.TYPE_HEADER)) {
472
473 //          getColumnClass(col);
474         return true;
475       }
476       else {
477         return false;
478       }
479     }
480     catch (NullPointerException ex) {
481       return false;
482     }
483   }
484
485   public void setValueAt(Object value, int row, int col) {
486     // Called by the table cell editor when the user has edited a
487     // field. From here the edited value is stored.
488
489     BibtexEntry be = db.getEntryById(getIdForRow(row));
490     boolean set = false;
491     String toSet = null,
492         fieldName = getFieldName(col),
493         text;
494     if (value != null) {
495       text = value.toString();
496       if (text.length() > 0) {
497         toSet = text;
498         Object o;
499         if ( ( (o = be.getField(fieldName)) == null)
500             || ( (o != null)
501                 && !o.toString().equals(toSet))) {
502           set = true;
503         }
504       }
505       else if (be.getField(fieldName) != null) {
506         set = true;
507       }
508     }
509     if (set) {
510       try {
511         if (toSet != null) {
512           (new LatexFieldFormatter()).format
513               (toSet, fieldName);
514         }
515
516         // Store this change in the UndoManager to facilitate undo.
517         Object oldVal = be.getField(fieldName);
518         panel.undoManager.addEdit
519             (new net.sf.jabref.undo.UndoableFieldChange
520              (be, fieldName.toLowerCase(), oldVal, toSet));
521         // .. ok.
522
523         be.setField(fieldName, toSet);
524         panel.markBaseChanged();
525         //panel.updateViewToSelected();
526         //panel.updateEntryEditorIfShowing();
527         // Should the table also be scheduled for repaint?
528       }
529       catch (IllegalArgumentException ex) {
530         //frame.output("Invalid field format. Use '#' only in pairs wrapping "
531         //        +"string names.");
532         frame.output("Invalid field format: " + ex.getMessage());
533       }
534     }
535   }
536
537    /**
538     * Returns the internal ID of the entry at the given row.
539     * @param number The row number.
540     * @return The ID for the entry at the given row.
541     */
542   public String getIdForRow(int number) {
543     // Return the name of the Entry corresponding to the row. The
544     // Entry will be retrieved from a DatabaseQuery. This is just
545     // a temporary implementation.
546     return sorter.getIdAt(number);
547     //entryIDs[number].toString();
548   }
549
550     /**
551      * Returns the entry currently displayed at the given row.
552      * @param row The row.
553      * @return The entry at the given row.
554      */
555   public BibtexEntry getEntryForRow(int row) {
556       return sorter.getEntryAt(row);
557   }
558
559   public int getNumberFromName(String name) {
560     // Not very fast. Intended for use only in highlighting erronous
561     // entry if save fails.
562     int res = -1, i = 0;
563     while ( (i < sorter.getEntryCount()) && (res < 0)) {
564       if (name.equals(sorter.getIdAt(i))) {
565         res = i;
566       }
567       i++;
568     }
569     return res;
570   }
571
572     /**
573      * Returns true iff the entry's Globals.MARKED field contains the
574      * current user's wrapped username.
575      * @param row The table row where the entry is.
576      * @return true if the MARKED field contains the wrapped username.
577      */
578     public boolean isMarked(int row) {
579         BibtexEntry be = db.getEntryById(getIdForRow(row));
580         if (be == null)
581             return false;
582         return Util.isMarked(be);
583
584     }
585
586
587 }