8e587891ceca4ad075b990250ddeb1f3498d6ea9
[debian/jabref.git] / src / java / net / sf / jabref / gui / FileListTableModel.java
1 package net.sf.jabref.gui;
2
3 import net.sf.jabref.Globals;
4
5 import javax.swing.table.AbstractTableModel;
6 import javax.swing.event.TableModelEvent;
7 import javax.swing.*;
8 import java.util.ArrayList;
9 import java.util.Iterator;
10
11 /**
12  * Data structure to contain a list of file links, parseable from a coded string.
13  * Doubles as a table model for the file list editor.
14 */
15 public class FileListTableModel extends AbstractTableModel {
16
17     private final ArrayList list = new ArrayList();
18
19     public FileListTableModel() {
20     }
21
22     public int getRowCount() {
23         synchronized (list) {
24             return list.size();
25         }
26     }
27
28     public int getColumnCount() {
29         return 3;
30     }
31
32     public Class getColumnClass(int columnIndex) {
33         return String.class;
34     }
35
36     public Object getValueAt(int rowIndex, int columnIndex) {
37         synchronized (list) {
38             FileListEntry entry = (FileListEntry)list.get(rowIndex);
39             switch (columnIndex) {
40                 case 0: return entry.getDescription();
41                 case 1: return entry.getLink();
42                 default: return entry.getType() != null ?
43                         entry.getType().getName() : "";
44             }
45         }
46     }
47
48     public FileListEntry getEntry(int index) {
49         synchronized (list) {
50             return (FileListEntry)list.get(index);
51         }
52     }
53
54     public void removeEntry(int index) {
55         synchronized (list) {
56             list.remove(index);
57             fireTableRowsDeleted(index, index);
58         }
59
60     }
61
62     /**
63      * Add an entry to the table model, and fire a change event. The change event
64      * is fired on the event dispatch thread.
65      * @param index The row index to insert the entry at.
66      * @param entry The entry to insert.
67      */
68     public void addEntry(final int index, final FileListEntry entry) {
69         synchronized (list) {
70             list.add(index, entry);
71             if (!SwingUtilities.isEventDispatchThread()) {
72                 SwingUtilities.invokeLater(new Runnable() {
73                     public void run() {
74                         fireTableRowsInserted(index, index);
75                     }
76                 });
77             } else
78                 fireTableRowsInserted(index, index);
79         }
80
81     }
82
83     public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
84     }
85
86     /**
87      * Set up the table contents based on the flat string representation of the file list
88      * @param value The string representation
89      */
90     public void setContent(String value) {
91         setContent(value, false);
92     }
93
94     private FileListEntry setContent(String value, boolean firstOnly) {
95         if (value == null)
96             value = "";
97         ArrayList newList = new ArrayList();
98         StringBuilder sb = new StringBuilder();
99         ArrayList thisEntry = new ArrayList();
100         boolean inXmlChar = false;
101         boolean escaped = false;
102         for (int i=0; i<value.length(); i++) {
103             char c = value.charAt(i);
104             if (!escaped && (c == '\\')) {
105                 escaped = true;
106                 continue;
107             }
108             // Check if we are entering an XML special character construct such
109             // as "&#44;", because we need to know in order to ignore the semicolon.
110             else if (!escaped && (c == '&') && !inXmlChar) {
111                 sb.append(c);
112                 if ((value.length() > i+1) && (value.charAt(i+1) == '#'))
113                     inXmlChar = true;
114             }
115             // Check if we are exiting an XML special character construct:
116             else if (!escaped && inXmlChar && (c == ';')) {
117                 sb.append(c);
118                 inXmlChar = false;
119             }
120             else if (!escaped && (c == ':')) {
121                 thisEntry.add(sb.toString());
122                 sb = new StringBuilder();
123             }
124             else if (!escaped && (c == ';') && !inXmlChar) {
125                 thisEntry.add(sb.toString());
126                 sb = new StringBuilder();
127                 if (firstOnly)
128                     return decodeEntry(thisEntry);
129                 else {
130                     newList.add(decodeEntry(thisEntry));
131                     thisEntry.clear();
132                 }
133             }
134             else sb.append(c);
135             escaped = false;
136         }
137         if (sb.length() > 0)
138             thisEntry.add(sb.toString());
139         if (thisEntry.size() > 0) {
140             if (firstOnly)
141                 return decodeEntry(thisEntry);
142             else
143                 newList.add(decodeEntry(thisEntry));
144         }
145           
146         synchronized (list) {
147             list.clear();
148             list.addAll(newList);
149         }
150         fireTableChanged(new TableModelEvent(this));
151         return null;
152     }
153
154
155     /**
156      * Convenience method for finding a label corresponding to the type of the
157      * first file link in the given field content. The difference between using
158      * this method and using setContent() on an instance of FileListTableModel
159      * is a slight optimization: with this method, parsing is discontinued after
160      * the first entry has been found.
161      * @param content The file field content, as fed to this class' setContent() method.
162      * @return A JLabel set up with no text and the icon of the first entry's file type,
163      *  or null if no entry was found.
164      */
165     public static JLabel getFirstLabel(String content) {
166         FileListTableModel tm = new FileListTableModel();
167         FileListEntry entry = tm.setContent(content, true);
168         return entry != null ? entry.getType().getIconLabel() : null;
169     }
170
171     
172     private FileListEntry decodeEntry(ArrayList contents) {
173         return new FileListEntry(getElementIfAvailable(contents, 0),
174                 getElementIfAvailable(contents, 1),
175                 Globals.prefs.getExternalFileTypeByName
176                         (getElementIfAvailable(contents, 2)));
177     }
178
179     private String getElementIfAvailable(ArrayList contents, int index) {
180         if (index < contents.size())
181             return (String)contents.get(index);
182         else return "";
183     }
184
185     /**
186      * Transform the file list shown in the table into a flat string representable
187      * as a BibTeX field:
188      * @return String representation.
189      */
190     public String getStringRepresentation() {
191         StringBuilder sb = new StringBuilder();
192         for (Iterator iterator = list.iterator(); iterator.hasNext();) {
193             FileListEntry entry = (FileListEntry) iterator.next();
194             sb.append(encodeEntry(entry));
195             if (iterator.hasNext())
196                 sb.append(';');
197         }
198         return sb.toString();
199     }
200
201     /**
202      * Transform the file list shown in the table into a HTML string representation
203      * suitable for displaying the contents in a tooltip.
204      * @return Tooltip representation.
205      */
206     public String getToolTipHTMLRepresentation() {
207         StringBuilder sb = new StringBuilder("<html>");
208         for (Iterator iterator = list.iterator(); iterator.hasNext();) {
209             FileListEntry entry = (FileListEntry) iterator.next();
210             sb.append(entry.getDescription()).append(" (").append(entry.getLink()).append(')');
211             if (iterator.hasNext())
212                 sb.append("<br>");
213         }
214         return sb.append("</html>").toString();
215     }
216
217     private String encodeEntry(FileListEntry entry) {
218         StringBuilder sb = new StringBuilder();
219         sb.append(encodeString(entry.getDescription()));
220         sb.append(':');
221         sb.append(encodeString(entry.getLink()));
222         sb.append(':');
223         sb.append(encodeString(entry.getType() != null ? entry.getType().getName() : ""));
224         return sb.toString();
225     }
226
227     private String encodeString(String s) {
228         StringBuilder sb = new StringBuilder();
229         for (int i=0; i<s.length(); i++) {
230             char c = s.charAt(i);
231             if ((c == ';') || (c == ':') || (c == '\\'))
232                 sb.append('\\');
233             sb.append(c);
234         }
235         return sb.toString();
236     }
237
238     public void print() {
239         System.out.println("----");
240         for (Iterator iterator = list.iterator(); iterator.hasNext();) {
241             FileListEntry fileListEntry = (FileListEntry) iterator.next();
242             System.out.println(fileListEntry);
243         }
244         System.out.println("----");
245     }
246
247    
248 }