package net.sf.jabref.external; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; import net.sf.jabref.*; import net.sf.jabref.gui.FileListEditor; import net.sf.jabref.gui.FileListEntry; import net.sf.jabref.gui.FileListEntryEditor; import net.sf.jabref.gui.FileListTableModel; import net.sf.jabref.undo.NamedCompound; import net.sf.jabref.undo.UndoableFieldChange; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.*; /** * This action goes through all selected entries in the BasePanel, and attempts to autoset the * given external file (pdf, ps, ...) based on the same algorithm used for the "Auto" button in * EntryEditor. */ public class SynchronizeFileField extends AbstractWorker { private String fieldName = GUIGlobals.FILE_FIELD; private BasePanel panel; private BibtexEntry[] sel = null; private SynchronizeFileField.OptionsDialog optDiag = null; Object[] brokenLinkOptions = {Globals.lang("Ignore"), Globals.lang("Assign new file"), Globals.lang("Remove link"), Globals.lang("Remove all broken links"), Globals.lang("Quit synchronization")}; private boolean goOn = true, autoSet = true, checkExisting = true; private int brokenLinks = 0, entriesChangedCount = 0; public SynchronizeFileField(BasePanel panel) { this.panel = panel; } public void init() { Collection<BibtexEntry> col = panel.database().getEntries(); goOn = true; sel = new BibtexEntry[col.size()]; sel = col.toArray(sel); // Ask about rules for the operation: if (optDiag == null) optDiag = new SynchronizeFileField.OptionsDialog(panel.frame().getFrame(), panel.metaData(), fieldName); Util.placeDialog(optDiag, panel.frame()); optDiag.setVisible(true); if (optDiag.canceled()) { goOn = false; return; } autoSet = !optDiag.autoSetNone.isSelected(); checkExisting = optDiag.checkLinks.isSelected(); panel.output(Globals.lang("Synchronizing %0 links...", fieldName.toUpperCase())); } public void run() { if (!goOn) { panel.output(Globals.lang("No entries selected.")); return; } panel.frame().setProgressBarValue(0); panel.frame().setProgressBarVisible(true); int weightAutoSet = 10; // autoSet takes 10 (?) times longer than checkExisting int progressBarMax = (autoSet ? weightAutoSet * sel.length : 0) + (checkExisting ? sel.length : 0); panel.frame().setProgressBarMaximum(progressBarMax); int progress = 0; brokenLinks = 0; final NamedCompound ce = new NamedCompound(Globals.lang("Autoset %0 field", fieldName)); //final OpenFileFilter off = Util.getFileFilterForField(fieldName); //ExternalFilePanel extPan = new ExternalFilePanel(fieldName, panel.metaData(), null, null, off); //FieldTextField editor = new FieldTextField(fieldName, "", false); // Find the default directory for this field type: String dir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD); Set<BibtexEntry> changedEntries = new HashSet<BibtexEntry>(); // First we try to autoset fields if (autoSet) { Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>(); for (int i = 0; i < sel.length; i++) { entries.add(sel[i]); } // We need to specify which directories to search in: ArrayList<File> dirs = new ArrayList<File>(); String dr = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD); if (dr != null) dirs.add(new File(dr)); // Start the autosetting process: Thread t = FileListEditor.autoSetLinks(entries, ce, changedEntries, dirs); // Wait for the autosetting to finish: try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } /* progress += weightAutoSet; panel.frame().setProgressBarValue(progress); Object old = sel[i].getField(fieldName); FileListTableModel tableModel = new FileListTableModel(); if (old != null) tableModel.setContent((String)old); Thread t = FileListEditor.autoSetLinks(sel[i], tableModel, null, null); if (!tableModel.getStringRepresentation().equals(old)) { String toSet = tableModel.getStringRepresentation(); if (toSet.length() == 0) toSet = null; ce.addEdit(new UndoableFieldChange(sel[i], fieldName, old, toSet)); sel[i].setField(fieldName, toSet); entriesChanged++; } } */ } progress += sel.length*weightAutoSet; panel.frame().setProgressBarValue(progress); //System.out.println("Done setting"); // The following loop checks all external links that are already set. if (checkExisting) { boolean removeAllBroken = false; mainLoop: for (int i = 0; i < sel.length; i++) { panel.frame().setProgressBarValue(progress++); final String old = sel[i].getField(fieldName); // Check if a extension is set: if ((old != null) && !old.equals("")) { FileListTableModel tableModel = new FileListTableModel(); tableModel.setContentDontGuessTypes(old); for (int j=0; j<tableModel.getRowCount(); j++) { FileListEntry flEntry = tableModel.getEntry(j); // See if the link looks like an URL: boolean httpLink = flEntry.getLink().toLowerCase().startsWith("http"); if (httpLink) continue; // Don't check the remote file. // TODO: should there be an option to check remote links? // A variable to keep track of whether this link gets deleted: boolean deleted = false; // Get an absolute path representation: File file = Util.expandFilename(flEntry.getLink(), new String[]{dir, "."}); if ((file == null) || !file.exists()) { int answer; if (!removeAllBroken) { answer = JOptionPane.showOptionDialog(panel.frame(), Globals.lang("<HTML>Could not find file '%0'<BR>linked from entry '%1'</HTML>", new String[]{flEntry.getLink(), sel[i].getCiteKey()}), Globals.lang("Broken link"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, brokenLinkOptions, brokenLinkOptions[0]); } else { answer = 2; // We should delete this link. } switch (answer) { case 1: // Assign new file. FileListEntryEditor flEditor = new FileListEntryEditor (panel.frame(), flEntry, false, true, panel.metaData()); flEditor.setVisible(true, true); break; case 2: // Clear field: tableModel.removeEntry(j); deleted = true; // Make sure we don't investigate this link further. j--; // Step back in the iteration, because we removed an entry. break; case 3: // Clear field: tableModel.removeEntry(j); deleted = true; // Make sure we don't investigate this link further. j--; // Step back in the iteration, because we removed an entry. removeAllBroken = true; // Notify for further cases. break; case 4: // Cancel break mainLoop; } brokenLinks++; } // Unless we deleted this link, see if its file type is recognized: if (!deleted && (flEntry.getType() instanceof UnknownExternalFileType)) { String[] options = new String[] {Globals.lang("Define '%0'", flEntry.getType().getName()), Globals.lang("Change file type"), Globals.lang("Cancel")}; String defOption = options[0]; int answer = JOptionPane.showOptionDialog(panel.frame(), Globals.lang("One or more file links are of the type '%0', which is undefined. What do you want to do?", flEntry.getType().getName()), Globals.lang("Undefined file type"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, defOption); if (answer == JOptionPane.CANCEL_OPTION) { // User doesn't want to handle this unknown link type. } else if (answer == JOptionPane.YES_OPTION) { // User wants to define the new file type. Show the dialog: ExternalFileType newType = new ExternalFileType(flEntry.getType().getName(), "", "", "", "new"); ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(panel.frame().getFrame(), newType); editor.setVisible(true); if (editor.okPressed()) { // Get the old list of types, add this one, and update the list in prefs: java.util.List<ExternalFileType> fileTypes = new ArrayList<ExternalFileType>(); ExternalFileType[] oldTypes = Globals.prefs.getExternalFileTypeSelection(); for (int k = 0; k < oldTypes.length; k++) { fileTypes.add(oldTypes[k]); } fileTypes.add(newType); Collections.sort(fileTypes); Globals.prefs.setExternalFileTypes(fileTypes); panel.mainTable.repaint(); } } else { // User wants to change the type of this link. // First get a model of all file links for this entry: FileListEntryEditor editor = new FileListEntryEditor (panel.frame(), flEntry, false, true, panel.metaData()); editor.setVisible(true, false); } } } if (!tableModel.getStringRepresentation().equals(old)) { // The table has been modified. Store the change: String toSet = tableModel.getStringRepresentation(); if (toSet.length() == 0) toSet = null; ce.addEdit(new UndoableFieldChange(sel[i], fieldName, old, toSet)); sel[i].setField(fieldName, toSet); changedEntries.add(sel[i]); //System.out.println("Changed to: "+tableModel.getStringRepresentation()); } } } } entriesChangedCount = changedEntries.size(); //for (BibtexEntry entr : changedEntries) // System.out.println(entr.getCiteKey()); if (entriesChangedCount > 0) { // Add the undo edit: ce.end(); panel.undoManager.addEdit(ce); } } public void update() { if (!goOn) return; panel.output(Globals.lang("Finished synchronizing %0 links. Entries changed%c %1.", new String[]{fieldName.toUpperCase(), String.valueOf(entriesChangedCount)})); panel.frame().setProgressBarVisible(false); if (entriesChangedCount > 0) { panel.markBaseChanged(); } } static class OptionsDialog extends JDialog { JRadioButton autoSetUnset, autoSetAll, autoSetNone; JCheckBox checkLinks; JButton ok = new JButton(Globals.lang("Ok")), cancel = new JButton(Globals.lang("Cancel")); JLabel description; private boolean canceled = true; private MetaData metaData; public OptionsDialog(JFrame parent, MetaData metaData, String fieldName) { super(parent, Globals.lang("Synchronize %0 links", fieldName.toUpperCase()), true); this.metaData = metaData; final String fn = Globals.lang("file"); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { canceled = false; dispose(); } }); Action closeAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { dispose(); } }; cancel.addActionListener(closeAction); InputMap im = cancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = cancel.getActionMap(); im.put(Globals.prefs.getKey("Close dialog"), "close"); am.put("close", closeAction); autoSetUnset = new JRadioButton(Globals.lang("Autoset %0 links. Do not overwrite existing links.", fn), true); autoSetAll = new JRadioButton(Globals.lang("Autoset %0 links. Allow overwriting existing links.", fn), false); autoSetNone = new JRadioButton(Globals.lang("Do not autoset"), false); checkLinks = new JCheckBox(Globals.lang("Check existing %0 links", fn), true); ButtonGroup bg = new ButtonGroup(); bg.add(autoSetUnset); bg.add(autoSetNone); bg.add(autoSetAll); FormLayout layout = new FormLayout("fill:pref", ""); DefaultFormBuilder builder = new DefaultFormBuilder(layout); description = new JLabel("<HTML>" + Globals.lang(//"This function helps you keep your external %0 links up-to-date." + "Attempt to autoset %0 links for your entries. Autoset works if " + "a %0 file in your %0 directory or a subdirectory<BR>is named identically to an entry's BibTeX key, plus extension.", fn) + "</HTML>"); // name.setVerticalAlignment(JLabel.TOP); builder.appendSeparator(Globals.lang("Autoset")); builder.append(description); builder.nextLine(); builder.append(autoSetUnset); builder.nextLine(); builder.append(autoSetAll); builder.nextLine(); builder.append(autoSetNone); builder.nextLine(); builder.appendSeparator(Globals.lang("Check links")); description = new JLabel("<HTML>" + Globals.lang("This makes JabRef look up each %0 extension and check if the file exists. If not, you will " + "be given options<BR>to resolve the problem.", fn) + "</HTML>"); builder.append(description); builder.nextLine(); builder.append(checkLinks); builder.nextLine(); builder.appendSeparator(); JPanel main = builder.getPanel(); main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); bb.addGridded(ok); bb.addGridded(cancel); bb.addGlue(); getContentPane().add(main, BorderLayout.CENTER); getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); pack(); } public void setVisible(boolean visible) { if (visible) canceled = true; String dir = metaData.getFileDirectory(GUIGlobals.FILE_FIELD); if ((dir == null) || (dir.trim().length() == 0)) { autoSetNone.setSelected(true); autoSetNone.setEnabled(false); autoSetAll.setEnabled(false); autoSetUnset.setEnabled(false); } else { autoSetNone.setEnabled(true); autoSetAll.setEnabled(true); autoSetUnset.setEnabled(true); } new FocusRequester(ok); super.setVisible(visible); } public boolean canceled() { return canceled; } } }