9830e7e9324eb77549f152e22dba22c8d5511716
[debian/jabref.git] / src / java / net / sf / jabref / gui / CleanUpAction.java
1 /*  Copyright (C) 2012 JabRef contributors.
2     This program is free software; you can redistribute it and/or modify
3     it under the terms of the GNU General Public License as published by
4     the Free Software Foundation; either version 2 of the License, or
5     (at your option) any later version.
6
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10     GNU General Public License for more details.
11
12     You should have received a copy of the GNU General Public License along
13     with this program; if not, write to the Free Software Foundation, Inc.,
14     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15 */
16 package net.sf.jabref.gui;
17
18 import java.io.File;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.logging.Logger;
23
24 import javax.swing.JCheckBox;
25 import javax.swing.JLabel;
26 import javax.swing.JOptionPane;
27 import javax.swing.JPanel;
28 import javax.swing.event.ChangeEvent;
29 import javax.swing.event.ChangeListener;
30
31 import net.sf.jabref.AbstractWorker;
32 import net.sf.jabref.BasePanel;
33 import net.sf.jabref.BibtexEntry;
34 import net.sf.jabref.CheckBoxMessage;
35 import net.sf.jabref.GUIGlobals;
36 import net.sf.jabref.Globals;
37 import net.sf.jabref.ImportSettingsTab;
38 import net.sf.jabref.JabRefFrame;
39 import net.sf.jabref.Util;
40 import net.sf.jabref.external.ExternalFileType;
41 import net.sf.jabref.imports.HTMLConverter;
42 import net.sf.jabref.imports.CaseKeeper;
43 import net.sf.jabref.undo.NamedCompound;
44 import net.sf.jabref.undo.UndoableFieldChange;
45
46 import com.jgoodies.forms.builder.DefaultFormBuilder;
47 import com.jgoodies.forms.layout.CellConstraints;
48 import com.jgoodies.forms.layout.FormLayout;
49
50 public class CleanUpAction extends AbstractWorker {
51         private Logger logger = Logger.getLogger(CleanUpAction.class.getName());
52
53         public final static String 
54                 AKS_AUTO_NAMING_PDFS_AGAIN = "AskAutoNamingPDFsAgain",
55                 CLEANUP_DOI = "CleanUpDOI",
56                 CLEANUP_MONTH = "CleanUpMonth",
57                 CLEANUP_PAGENUMBERS = "CleanUpPageNumbers",
58                 CLEANUP_MAKEPATHSRELATIVE = "CleanUpMakePathsRelative",
59                 CLEANUP_RENAMEPDF = "CleanUpRenamePDF",
60                 CLEANUP_RENAMEPDF_ONLYRELATIVE_PATHS = "CleanUpRenamePDFonlyRelativePaths",
61                 CLEANUP_UPGRADE_EXTERNAL_LINKS = "CleanUpUpgradeExternalLinks",
62                 CLEANUP_SUPERSCRIPTS = "CleanUpSuperscripts",
63                 CLEANUP_HTML = "CleanUpHTML",
64                 CLEANUP_CASE = "CleanUpCase";
65         
66         public static void putDefaults(HashMap<String, Object> defaults) {
67                 defaults.put(AKS_AUTO_NAMING_PDFS_AGAIN, Boolean.TRUE);
68                 defaults.put(CLEANUP_SUPERSCRIPTS, Boolean.TRUE);
69                 defaults.put(CLEANUP_DOI, Boolean.TRUE);
70                 defaults.put(CLEANUP_MONTH, Boolean.TRUE);
71                 defaults.put(CLEANUP_PAGENUMBERS, Boolean.TRUE);
72                 defaults.put(CLEANUP_MAKEPATHSRELATIVE, Boolean.TRUE);
73                 defaults.put(CLEANUP_RENAMEPDF, Boolean.TRUE);
74                 defaults.put(CLEANUP_RENAMEPDF_ONLYRELATIVE_PATHS, Boolean.FALSE);
75                 defaults.put(CLEANUP_UPGRADE_EXTERNAL_LINKS, Boolean.FALSE);
76                 defaults.put(CLEANUP_MAKEPATHSRELATIVE, Boolean.TRUE);
77                 defaults.put(CLEANUP_HTML, Boolean.TRUE);
78                 defaults.put(CLEANUP_CASE, Boolean.TRUE);
79         }
80         
81         private JCheckBox cleanUpSuperscrips;
82         private JCheckBox cleanUpDOI;
83         private JCheckBox cleanUpMonth;
84         private JCheckBox cleanUpPageNumbers;
85         private JCheckBox cleanUpMakePathsRelative;
86         private JCheckBox cleanUpRenamePDF;
87         private JCheckBox cleanUpRenamePDFonlyRelativePaths;
88         private JCheckBox cleanUpUpgradeExternalLinks;
89         private JCheckBox cleanUpHTML;
90         private JCheckBox cleanUpCase;
91         private JPanel optionsPanel = new JPanel();
92         private BasePanel panel;
93         private JabRefFrame frame;
94         
95         // global variable to count unsucessful Renames
96     int unsuccesfullRenames = 0;
97         
98         public CleanUpAction(BasePanel panel) {
99                 this.panel = panel;
100                 this.frame = panel.frame();
101                 initOptionsPanel();
102         }
103         
104         private void initOptionsPanel() {
105             cleanUpSuperscrips = new JCheckBox(Globals.lang("Convert 1st, 2nd, ... to real superscripts"));
106                 cleanUpDOI = new JCheckBox(Globals.lang("Move DOIs from note and URL field to DOI field and remove http prefix"));
107                 cleanUpMonth = new JCheckBox(Globals.lang("Format content of month field to #mon#"));
108                 cleanUpPageNumbers = new JCheckBox(Globals.lang("Ensure that page ranges are of the form num1--num2"));
109                 cleanUpMakePathsRelative = new JCheckBox(Globals.lang("Make paths of linked files relative (if possible)"));
110                 cleanUpRenamePDF = new JCheckBox(Globals.lang("Rename PDFs to given file name format pattern"));
111                 cleanUpRenamePDF.addChangeListener(new ChangeListener() {
112                         @Override
113                         public void stateChanged(ChangeEvent arg0) {
114                                 cleanUpRenamePDFonlyRelativePaths.setEnabled(cleanUpRenamePDF.isSelected());
115                         }
116                 });
117                 cleanUpRenamePDFonlyRelativePaths = new JCheckBox(Globals.lang("Rename only PDFs having a relative path"));
118                 cleanUpUpgradeExternalLinks = new JCheckBox(Globals.lang("Upgrade external PDF/PS links to use the '%0' field.", GUIGlobals.FILE_FIELD));
119                 cleanUpHTML = new JCheckBox(Globals.lang("Run HTML converter on title"));
120                 cleanUpCase = new JCheckBox(Globals.lang("Run filter on title keeping the case of selected words"));
121                 optionsPanel = new JPanel();
122                 retrieveSettings();
123
124                 FormLayout layout = new FormLayout("left:15dlu,pref:grow", "pref, pref, pref, pref, pref, pref, pref, pref, pref, pref, pref");
125         DefaultFormBuilder builder = new DefaultFormBuilder(layout,     optionsPanel);
126         builder.setDefaultDialogBorder();
127         CellConstraints cc = new CellConstraints();
128         builder.add(cleanUpHTML, cc.xyw(1,1,2));
129         builder.add(cleanUpCase, cc.xyw(1,2,2));
130         builder.add(cleanUpSuperscrips, cc.xyw(1,3,2));
131         builder.add(cleanUpDOI, cc.xyw(1,4,2));
132         builder.add(cleanUpMonth, cc.xyw(1,5,2));
133         builder.add(cleanUpPageNumbers, cc.xyw(1,6,2));
134         builder.add(cleanUpUpgradeExternalLinks, cc.xyw(1, 7, 2));
135         builder.add(cleanUpMakePathsRelative, cc.xyw(1,8,2));
136         builder.add(cleanUpRenamePDF, cc.xyw(1,9,2));
137         String currentPattern = Globals.lang("File name format pattern").concat(": ").concat(Globals.prefs.get(ImportSettingsTab.PREF_IMPORT_FILENAMEPATTERN));
138         builder.add(new JLabel(currentPattern), cc.xyw(2,10,1));
139         builder.add(cleanUpRenamePDFonlyRelativePaths, cc.xyw(2,11,1));
140         }
141         
142         private void retrieveSettings() {
143             cleanUpSuperscrips.setSelected(Globals.prefs.getBoolean(CLEANUP_SUPERSCRIPTS));
144                 cleanUpDOI.setSelected(Globals.prefs.getBoolean(CLEANUP_DOI));
145                 cleanUpMonth.setSelected(Globals.prefs.getBoolean(CLEANUP_MONTH));
146                 cleanUpPageNumbers.setSelected(Globals.prefs.getBoolean(CLEANUP_PAGENUMBERS));
147                 cleanUpMakePathsRelative.setSelected(Globals.prefs.getBoolean(CLEANUP_MAKEPATHSRELATIVE));
148                 cleanUpRenamePDF.setSelected(Globals.prefs.getBoolean(CLEANUP_RENAMEPDF));
149                 cleanUpRenamePDFonlyRelativePaths.setSelected(Globals.prefs.getBoolean(CLEANUP_RENAMEPDF_ONLYRELATIVE_PATHS));
150                 cleanUpRenamePDFonlyRelativePaths.setEnabled(cleanUpRenamePDF.isSelected());
151                 cleanUpUpgradeExternalLinks.setSelected(Globals.prefs.getBoolean(CLEANUP_UPGRADE_EXTERNAL_LINKS));
152                 cleanUpHTML.setSelected(Globals.prefs.getBoolean(CLEANUP_HTML));
153                 cleanUpCase.setSelected(Globals.prefs.getBoolean(CLEANUP_CASE));
154         }
155         
156         private void storeSettings() {
157             Globals.prefs.putBoolean(CLEANUP_SUPERSCRIPTS, cleanUpSuperscrips.isSelected());
158                 Globals.prefs.putBoolean(CLEANUP_DOI, cleanUpDOI.isSelected());
159                 Globals.prefs.putBoolean(CLEANUP_MONTH, cleanUpMonth.isSelected());
160                 Globals.prefs.putBoolean(CLEANUP_PAGENUMBERS, cleanUpPageNumbers.isSelected());
161                 Globals.prefs.putBoolean(CLEANUP_MAKEPATHSRELATIVE, cleanUpMakePathsRelative.isSelected());
162                 Globals.prefs.putBoolean(CLEANUP_RENAMEPDF, cleanUpRenamePDF.isSelected());
163                 Globals.prefs.putBoolean(CLEANUP_RENAMEPDF_ONLYRELATIVE_PATHS, cleanUpRenamePDFonlyRelativePaths.isSelected());
164                 Globals.prefs.putBoolean(CLEANUP_UPGRADE_EXTERNAL_LINKS, cleanUpUpgradeExternalLinks.isSelected());
165                 Globals.prefs.putBoolean(CLEANUP_HTML, cleanUpHTML.isSelected());
166                 Globals.prefs.putBoolean(CLEANUP_CASE, cleanUpCase.isSelected());
167         }
168
169         private int showCleanUpDialog() {
170         String dialogTitle = Globals.lang("Cleanup entries");
171
172         Object[] messages = {Globals.lang("What would you like to clean up?"), optionsPanel}; 
173         return JOptionPane.showConfirmDialog(frame, messages, dialogTitle,
174             JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
175         }
176         
177         
178     boolean cancelled;
179         int modifiedEntriesCount;
180     int numSelected;
181     
182     public void init() {
183         cancelled = false;
184         modifiedEntriesCount = 0;
185         int numSelected = panel.getSelectedEntries().length;
186         if (numSelected == 0) { // None selected. Inform the user to select entries first.
187             JOptionPane.showMessageDialog(frame, Globals.lang("First select entries to clean up."), 
188                                           Globals.lang("Cleanup entry"), JOptionPane.INFORMATION_MESSAGE);
189             cancelled = true;
190             return;
191         }
192         frame.block();
193         panel.output(Globals.lang("Doing a cleanup for %0 entries...", Integer.toString(numSelected)));
194     }
195     
196     public void run() {
197         if (cancelled) return;
198         int choice = showCleanUpDialog();
199         if (choice != JOptionPane.OK_OPTION) {
200                 cancelled = true;
201                 return;
202         }
203         storeSettings();
204         boolean
205             choiceCleanUpSuperscripts = cleanUpSuperscrips.isSelected(),
206                 choiceCleanUpDOI = cleanUpDOI.isSelected(),
207                 choiceCleanUpMonth = cleanUpMonth.isSelected(),
208                 choiceCleanUpPageNumbers = cleanUpPageNumbers.isSelected(),
209                 choiceCleanUpUpgradeExternalLinks = cleanUpUpgradeExternalLinks.isSelected(),
210                 choiceMakePathsRelative = cleanUpMakePathsRelative.isSelected(),
211                 choiceRenamePDF = cleanUpRenamePDF.isSelected(),
212                 choiceConvertHTML = cleanUpHTML.isSelected(),
213                 choiceConvertCase = cleanUpCase.isSelected();
214         
215         if (choiceRenamePDF && Globals.prefs.getBoolean(AKS_AUTO_NAMING_PDFS_AGAIN)) { 
216                 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("Auto-generating PDF-Names does not support undo. Continue?"),
217                         Globals.lang("Disable this confirmation dialog"), false);
218                 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Autogenerate PDF Names"),
219                 JOptionPane.YES_NO_OPTION);
220                 if (cbm.isSelected())
221                         Globals.prefs.putBoolean(AKS_AUTO_NAMING_PDFS_AGAIN, false);
222                 if (answer == JOptionPane.NO_OPTION) {
223                     cancelled = true;
224                     return;
225                 }
226             }
227         
228         // first upgrade the external links
229         // we have to use it separately as the Utils function generates a separate Named Compound
230         if (choiceCleanUpUpgradeExternalLinks) {
231                 NamedCompound ce = Util.upgradePdfPsToFile(Arrays.asList(panel.getSelectedEntries()), new String[] {"pdf", "ps"});
232                 if (ce.hasEdits()) {
233                         panel.undoManager.addEdit(ce);
234                         panel.markBaseChanged();
235                         panel.updateEntryEditorIfShowing();
236                         panel.output(Globals.lang("Upgraded links."));
237                 }
238         }
239
240         for (BibtexEntry entry : panel.getSelectedEntries()) {
241                 // undo granularity is on entry level
242                 NamedCompound ce = new NamedCompound(Globals.lang("Cleanup entry"));
243                 
244                 if (choiceCleanUpSuperscripts) doCleanUpSuperscripts(entry, ce);
245                 if (choiceCleanUpDOI) doCleanUpDOI(entry, ce);
246                 if (choiceCleanUpMonth) doCleanUpMonth(entry, ce);
247                 if (choiceCleanUpPageNumbers) doCleanUpPageNumbers(entry, ce);
248                 fixWrongFileEntries(entry, ce);
249                 if (choiceMakePathsRelative) doMakePathsRelative(entry, ce);
250                 if (choiceRenamePDF) doRenamePDFs(entry, ce);
251                 if (choiceConvertHTML) doConvertHTML(entry, ce);
252                 if (choiceConvertCase) doConvertCase(entry, ce);
253                 
254             ce.end();
255             if (ce.hasEdits()) {
256                 modifiedEntriesCount++;
257                 panel.undoManager.addEdit(ce);
258             }
259         }
260     }
261
262         public void update() {
263         if (cancelled) {
264             frame.unblock();
265             return;
266         }
267             if(unsuccesfullRenames>0) { //Rename failed for at least one entry
268                 JOptionPane.showMessageDialog(frame, Globals.lang("File rename failed for")+" "
269                                 + unsuccesfullRenames 
270                                 + " "+Globals.lang("entries") + ".",
271                         Globals.lang("Autogenerate PDF Names"), JOptionPane.INFORMATION_MESSAGE);
272             }
273         if (modifiedEntriesCount>0) {
274                 panel.updateEntryEditorIfShowing();
275                 panel.markBaseChanged() ;
276         }
277         String message;
278         switch (modifiedEntriesCount) {
279         case 0:
280                 message = Globals.lang("No entry needed a clean up");
281                 break;
282         case 1:
283                 message = Globals.lang("One entry needed a clean up");
284                 break;
285         default:
286                 message = Globals.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount));
287                 break;
288         }
289         panel.output(message);
290         frame.unblock();
291     }
292         
293         /**
294          * Converts the text in 1st, 2nd, ... to real superscripts by wrapping in \textsuperscript{st}, ...
295          */
296     private void doCleanUpSuperscripts(BibtexEntry entry, NamedCompound ce) {
297         final String field = "booktitle";
298         String oldValue = entry.getField(field);
299         if (oldValue == null) return;
300         String newValue = oldValue.replaceAll(" (\\d+)(st|nd|rd|th) ", " $1\\\\textsuperscript{$2} ");
301         if (!oldValue.equals(newValue)) {
302             entry.setField(field, newValue);
303             ce.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
304         }
305     }
306
307     /**
308      * Removes the http://... for each DOI
309      * Moves DOIs from URL and NOTE filed to DOI field
310      * @param ce 
311      */
312     private void doCleanUpDOI(BibtexEntry bes, NamedCompound ce) {
313         
314         // fields to check
315         String[] fields = {"note", "url", "ee"};
316
317         // First check if the DOI Field is empty
318         if (bes.getField("doi") != null) {
319                 String doiFieldValue = bes.getField("doi");
320                 if (Util.checkForDOIwithHTTPprefix(doiFieldValue)) {
321                         String newValue = Util.getDOI(doiFieldValue);
322                         ce.addEdit(new UndoableFieldChange(bes, "doi", doiFieldValue, newValue));
323                         bes.setField("doi", newValue);
324                 };
325                 if (Util.checkForPlainDOI(doiFieldValue)) {
326                         // DOI field seems to contain DOI
327                         // cleanup note, url, ee field
328                         // we do NOT copy values to the DOI field as the DOI field contains a DOI!
329                 for (String field: fields) {
330                         if (Util.checkForPlainDOI(bes.getField(field))){
331                                 Util.removeDOIfromBibtexEntryField(bes, field, ce);
332                         }
333                 }
334                 }
335         } else {
336                 // As the DOI field is empty we now check if note, url, or ee field contains a DOI
337                 
338                 for (String field: fields) {
339                         if (Util.checkForPlainDOI(bes.getField(field))){
340                                 // update DOI
341                         String oldValue = bes.getField("doi");
342                         String newValue = Util.getDOI(bes.getField(field));                                     
343                                 ce.addEdit(new UndoableFieldChange(bes, "doi", oldValue, newValue));
344                                 bes.setField("doi", newValue);
345                                 
346                                 Util.removeDOIfromBibtexEntryField(bes, field, ce);
347                         }
348             } 
349         }
350     }
351     
352         private void doCleanUpMonth(BibtexEntry entry, NamedCompound ce) {
353                 // implementation based on patch 3470076 by Mathias Walter
354                 String oldValue = entry.getField("month");
355                 if (oldValue == null) return;
356                 String newValue = oldValue;
357                 try {
358                 int month = Integer.parseInt(newValue);
359                 newValue = new StringBuffer("#").append(Globals.MONTHS[month - 1]).append('#').toString();
360         } catch (NumberFormatException e) {
361                 // adapt casing of newValue to follow entry in Globals_MONTH_STRINGS
362                 String casedString = newValue.substring(0, 1).toUpperCase().concat(newValue.substring(1).toLowerCase());
363                 if (Globals.MONTH_STRINGS.containsKey(newValue.toLowerCase()) ||
364                                 Globals.MONTH_STRINGS.containsValue(casedString)) {
365                         newValue = new StringBuffer("#").append(newValue.substring(0, 3).toLowerCase()).append('#').toString();
366                 }
367         }
368         if (!oldValue.equals(newValue)) {
369                 entry.setField("month", newValue);
370                 ce.addEdit(new UndoableFieldChange(entry, "month", oldValue, newValue));
371         }
372         }
373         
374         private void doCleanUpPageNumbers(BibtexEntry entry, NamedCompound ce) {
375                 String oldValue = entry.getField("pages");              
376                 if (oldValue == null) return;
377                 String newValue = oldValue.replaceAll("(\\d+) *- *(\\d+)", "$1--$2");
378                 if (!oldValue.equals(newValue)) {
379                         entry.setField("pages", newValue);
380                         ce.addEdit(new UndoableFieldChange(entry, "pages", oldValue, newValue));
381                 }
382         }
383         
384         private void fixWrongFileEntries(BibtexEntry entry, NamedCompound ce) {
385                 String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
386                 if (oldValue == null) return;
387                 FileListTableModel flModel = new FileListTableModel();
388                 flModel.setContent(oldValue);
389                 if (flModel.getRowCount() == 0) {
390                         return;
391                 }
392                 boolean changed = false;
393                 for (int i = 0; i<flModel.getRowCount(); i++) {
394                         FileListEntry flEntry = flModel.getEntry(i);
395                         String link = flEntry.getLink();
396                         String description = flEntry.getDescription();
397                 if (link.equals("") && (!description.equals(""))) {
398                         // link and description seem to be switched, quickly fix that
399                         flEntry.setLink(flEntry.getDescription());
400                         flEntry.setDescription("");
401                         changed = true;
402                 }
403                 }
404                 if (changed) {
405                 String newValue = flModel.getStringRepresentation();
406                         assert(!oldValue.equals(newValue));
407                         entry.setField(GUIGlobals.FILE_FIELD, newValue);
408                         ce.addEdit(new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD, oldValue, newValue));
409                 }
410     }
411
412         private void doMakePathsRelative(BibtexEntry entry, NamedCompound ce) {
413                 String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
414                 if (oldValue == null) return;
415                 FileListTableModel flModel = new FileListTableModel();
416                 flModel.setContent(oldValue);
417                 if (flModel.getRowCount() == 0) {
418                         return;
419                 }
420                 boolean changed = false;
421                 for (int i = 0; i<flModel.getRowCount(); i++) {
422                         FileListEntry flEntry = flModel.getEntry(i);
423                         String oldFileName = flEntry.getLink();
424                         String newFileName = Util.shortenFileName(new File(oldFileName), panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD)).toString();
425                         if (!oldFileName.equals(newFileName)) {
426                                 flEntry.setLink(newFileName);
427                                 changed = true;
428                         }
429                 }
430                 if (changed) {
431                 String newValue = flModel.getStringRepresentation();
432                         assert(!oldValue.equals(newValue));
433                         entry.setField(GUIGlobals.FILE_FIELD, newValue);
434                         ce.addEdit(new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD, oldValue, newValue));
435                 }
436         }
437
438     private void doRenamePDFs(BibtexEntry entry, NamedCompound ce) {
439                 //Extract the path
440                 String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
441                 if (oldValue == null) return;
442                 FileListTableModel flModel = new FileListTableModel();
443                 flModel.setContent(oldValue);
444                 if (flModel.getRowCount() == 0) {
445                         return;
446                 }
447                 boolean changed = false;
448                 
449                 for (int i=0; i<flModel.getRowCount(); i++) {
450                         String realOldFilename = flModel.getEntry(i).getLink();
451                         
452                         if (cleanUpRenamePDFonlyRelativePaths.isSelected() && (new File(realOldFilename).isAbsolute()))
453                                 continue;
454         
455                         String newFilename = Util.getLinkedFileName(panel.database(), entry);
456                         //String oldFilename = bes.getField(GUIGlobals.FILE_FIELD); // would have to be stored for undoing purposes
457                         
458                         //Add extension to newFilename
459                         newFilename = newFilename + "." + flModel.getEntry(i).getType().getExtension();
460                         
461                         //get new Filename with path
462                     //Create new Path based on old Path and new filename
463                     File expandedOldFile = Util.expandFilename(realOldFilename, panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD));
464                     if (expandedOldFile.getParent() == null) {
465                         // something went wrong. Just skipt his entry
466                         continue;
467                     }
468                     String newPath = expandedOldFile.getParent().concat(System.getProperty("file.separator")).concat(newFilename);
469                     
470                     if (new File(newPath).exists())
471                         // we do not overwrite files
472                         // TODO: we could check here if the newPath file is linked with the current entry. And if not, we could add a link
473                         continue;
474                     
475                         //do rename
476                         boolean renameSuccesfull = Util.renameFile(expandedOldFile.toString(), newPath);
477                         
478                         if (renameSuccesfull) {
479                                 changed = true;
480                                 
481                                 //Change the path for this entry
482                                 String description = flModel.getEntry(i).getDescription();
483                                 ExternalFileType type = flModel.getEntry(i).getType();
484                                 flModel.removeEntry(i);
485                                 
486                                 // we cannot use "newPath" to generate a FileListEntry as newPath is absolute, but we want to keep relative paths whenever possible
487                                 File parent = (new File(realOldFilename)).getParentFile();
488                                 String newFileEntryFileName;
489                                 if (parent == null) {
490                                         newFileEntryFileName = newFilename;
491                                 } else {
492                                         newFileEntryFileName = parent.toString().concat(System.getProperty("file.separator")).concat(newFilename);
493                                 }
494                         flModel.addEntry(i, new FileListEntry(description, newFileEntryFileName, type));
495                         }
496                         else {
497                                 unsuccesfullRenames++;
498                         }
499                 }
500                 
501                 if (changed) {
502                 String newValue = flModel.getStringRepresentation();
503                         assert(!oldValue.equals(newValue));
504                         entry.setField(GUIGlobals.FILE_FIELD, newValue);
505                         //we put an undo of the field content here
506                         //the file is not being renamed back, which leads to inconsostencies
507                         //if we put a null undo object here, the change by "doMakePathsRelative" would overwrite the field value nevertheless.
508                         ce.addEdit(new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD, oldValue, newValue));
509                 }
510         }
511
512         /**
513          * Converts HTML code to LaTeX code
514          */
515     private void doConvertHTML(BibtexEntry entry, NamedCompound ce) {
516         final String field = "title";
517         String oldValue = entry.getField(field);
518         if (oldValue == null) return;
519         final HTMLConverter htmlConverter = new HTMLConverter();
520         String newValue = htmlConverter.format(oldValue);
521         if (!oldValue.equals(newValue)) {
522             entry.setField(field, newValue);
523             ce.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
524         }
525     }
526
527         /**
528          * Adds curly brackets {} around keywords
529          */
530     private void doConvertCase(BibtexEntry entry, NamedCompound ce) {
531         final String field = "title";
532         String oldValue = entry.getField(field);
533         if (oldValue == null) return;
534         final CaseKeeper caseKeeper = new CaseKeeper();
535         String newValue = caseKeeper.format(oldValue);
536         if (!oldValue.equals(newValue)) {
537             entry.setField(field, newValue);
538             ce.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
539         }
540     }
541
542 }