/** * If the user has signalled the opening of a context menu, the event gets redirected to this * method. Here we open a file link menu if the user is pointing at a file link icon. Otherwise * a general context menu should be shown. * * @param e The triggering mouse event. */ public void processPopupTrigger(MouseEvent e) { BibtexEntry entry = sortedEntries.get(entryTable.rowAtPoint(e.getPoint())); BasePanel p = entryHome.get(entry); int col = entryTable.columnAtPoint(e.getPoint()); JPopupMenu menu = new JPopupMenu(); int count = 0; if (col == FILE_COL) { // We use a FileListTableModel to parse the field content: Object o = entry.getField(GUIGlobals.FILE_FIELD); FileListTableModel fileList = new FileListTableModel(); fileList.setContent((String) o); // If there are one or more links, open the first one: for (int i = 0; i < fileList.getRowCount(); i++) { FileListEntry flEntry = fileList.getEntry(i); String description = flEntry.getDescription(); if ((description == null) || (description.trim().length() == 0)) description = flEntry.getLink(); menu.add( new ExternalFileMenuItem( p.frame(), entry, description, flEntry.getLink(), flEntry.getType().getIcon(), p.metaData(), flEntry.getType())); count++; } } if (count > 0) menu.show(entryTable, e.getX(), e.getY()); }
public Object getColumnValue(BibtexEntry entry, int column) { if (column < PAD) { Object o; switch (column) { case FILE_COL: o = entry.getField(GUIGlobals.FILE_FIELD); if (o != null) { FileListTableModel model = new FileListTableModel(); model.setContent((String) o); fileLabel.setToolTipText(model.getToolTipHTMLRepresentation()); if (model.getRowCount() > 0) fileLabel.setIcon(model.getEntry(0).getType().getIcon()); return fileLabel; } else return null; case URL_COL: o = entry.getField("url"); if (o != null) { urlLabel.setToolTipText((String) o); return urlLabel; } else return null; default: return null; } } else { String field = fields[column - PAD]; if (field.equals("author") || field.equals("editor")) { // For name fields, tap into a MainTableFormat instance and use // the same name formatting as is used in the entry table: if (frame.basePanel() != null) return frame.basePanel().tableFormat.formatName(entry.getField(field)); } return entry.getField(field); } }
public void mouseClicked(MouseEvent e) { if (e.isPopupTrigger()) { processPopupTrigger(e); return; } // if (e.) final int col = entryTable.columnAtPoint(e.getPoint()), row = entryTable.rowAtPoint(e.getPoint()); if (col < PAD) { BibtexEntry entry = sortedEntries.get(row); BasePanel p = entryHome.get(entry); switch (col) { case FILE_COL: Object o = entry.getField(GUIGlobals.FILE_FIELD); if (o != null) { FileListTableModel tableModel = new FileListTableModel(); tableModel.setContent((String) o); if (tableModel.getRowCount() == 0) return; FileListEntry fl = tableModel.getEntry(0); (new ExternalFileMenuItem( frame, entry, "", fl.getLink(), null, p.metaData(), fl.getType())) .actionPerformed(null); } break; case URL_COL: Object link = entry.getField("url"); try { if (link != null) Util.openExternalViewer(p.metaData(), (String) link, "url"); } catch (IOException ex) { ex.printStackTrace(); } break; } } }
@Override public void actionPerformed(ActionEvent actionEvent) { setEnabled(false); panel.output(Localization.lang("Writing XMP-metadata...")); panel.frame().setProgressBarIndeterminate(true); panel.frame().setProgressBarVisible(true); BibEntry entry = editor.getEntry(); // Make a list of all PDFs linked from this entry: List<File> files = new ArrayList<>(); // First check the (legacy) "pdf" field: entry .getField(FieldName.PDF) .ifPresent( pdf -> FileUtil.expandFilename( pdf, panel .getBibDatabaseContext() .getFileDirectory( FieldName.PDF, Globals.prefs.getFileDirectoryPreferences())) .ifPresent(files::add)); // Then check the "file" field: List<String> dirs = panel.getBibDatabaseContext().getFileDirectory(Globals.prefs.getFileDirectoryPreferences()); if (entry.hasField(FieldName.FILE)) { FileListTableModel tm = new FileListTableModel(); entry.getField(FieldName.FILE).ifPresent(tm::setContent); for (int j = 0; j < tm.getRowCount(); j++) { FileListEntry flEntry = tm.getEntry(j); if ((flEntry.type.isPresent()) && "pdf".equalsIgnoreCase(flEntry.type.get().getName())) { FileUtil.expandFilename(flEntry.link, dirs).ifPresent(files::add); } } } // We want to offload the actual work to a background thread, so we have a worker // thread: AbstractWorker worker = new WriteXMPWorker(files, entry); // Using Spin, we get a thread that gets synchronously offloaded to a new thread, // blocking the execution of this method: worker.getWorker().run(); // After the worker thread finishes, we are unblocked and ready to print the // status message: panel.output(message); panel.frame().setProgressBarVisible(false); setEnabled(true); }
private void doMakePathsRelative(BibtexEntry entry, NamedCompound ce) { String oldValue = entry.getField(Globals.FILE_FIELD); if (oldValue == null) { return; } FileListTableModel flModel = new FileListTableModel(); flModel.setContent(oldValue); if (flModel.getRowCount() == 0) { return; } boolean changed = false; for (int i = 0; i < flModel.getRowCount(); i++) { FileListEntry flEntry = flModel.getEntry(i); String oldFileName = flEntry.getLink(); String newFileName = FileUtil.shortenFileName( new File(oldFileName), panel.metaData().getFileDirectory(Globals.FILE_FIELD)) .toString(); if (!oldFileName.equals(newFileName)) { flEntry.setLink(newFileName); changed = true; } } if (changed) { String newValue = flModel.getStringRepresentation(); assert (!oldValue.equals(newValue)); entry.setField(Globals.FILE_FIELD, newValue); ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue)); } }
private static void fixWrongFileEntries(BibtexEntry entry, NamedCompound ce) { String oldValue = entry.getField(Globals.FILE_FIELD); if (oldValue == null) { return; } FileListTableModel flModel = new FileListTableModel(); flModel.setContent(oldValue); if (flModel.getRowCount() == 0) { return; } boolean changed = false; for (int i = 0; i < flModel.getRowCount(); i++) { FileListEntry flEntry = flModel.getEntry(i); String link = flEntry.getLink(); String description = flEntry.getDescription(); if ("".equals(link) && (!"".equals(description))) { // link and description seem to be switched, quickly fix that flEntry.setLink(flEntry.getDescription()); flEntry.setDescription(""); changed = true; } } if (changed) { String newValue = flModel.getStringRepresentation(); assert (!oldValue.equals(newValue)); entry.setField(Globals.FILE_FIELD, newValue); ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue)); } }
@Test @Ignore public void testInsertTestData() throws Exception { entry1 = new BibtexEntry(); JabRefPreferences jabRefPreferences = JabRefPreferences.getInstance(); ExternalFileType fileType = jabRefPreferences.getExternalFileTypeByExt("PDF"); FileListEntry fileListEntry = new FileListEntry("", ImportDataTest.FILE_IN_DATABASE.getAbsolutePath(), fileType); FileListTableModel model = new FileListTableModel(); model.addEntry(0, fileListEntry); entry1.setField("file", model.getStringRepresentation()); database.insertEntry(entry1); // #################### SETUP END ##################### // UnlinkedFilesCrawler crawler = new UnlinkedFilesCrawler(database); CheckableTreeNode treeNode = crawler.searchDirectory(ImportDataTest.EXISTING_FOLDER, new EntryFromPDFCreator()); Assert.assertNotNull(treeNode); /** Select all nodes manually. */ @SuppressWarnings("unchecked") Enumeration<CheckableTreeNode> enumeration = treeNode.breadthFirstEnumeration(); while (enumeration.hasMoreElements()) { CheckableTreeNode nextElement = enumeration.nextElement(); nextElement.setSelected(true); } List<File> resultList = getFileListFromNode(treeNode); Assert.assertFalse(resultList.isEmpty()); Assert.assertTrue(resultList.contains(ImportDataTest.FILE_NOT_IN_DATABASE)); Assert.assertFalse(resultList.contains(ImportDataTest.FILE_IN_DATABASE)); }
private Icon getFileIconForSelectedEntry() { if (panel.getMainTable().getSelectedRowCount() == 1) { BibEntry entry = panel.getMainTable().getSelected().get(0); if (entry.hasField(FieldName.FILE)) { JLabel label = FileListTableModel.getFirstLabel(entry.getFieldOptional(FieldName.FILE).get()); if (label != null) { return label.getIcon(); } } } return IconTheme.JabRefIcon.FILE.getSmallIcon(); }
@Override public String format(String field) { FileListTableModel tableModel = new FileListTableModel(); if (field == null) { return ""; } tableModel.setContent(field); String link = null; if (fileType == null) { // No file type specified. Simply take the first link. if (tableModel.getRowCount() > 0) { link = tableModel.getEntry(0).getLink(); } } else { // A file type is specified: for (int i = 0; i < tableModel.getRowCount(); i++) { FileListEntry flEntry = tableModel.getEntry(i); if (flEntry.getType().getName().toLowerCase().equals(fileType)) { link = flEntry.getLink(); break; } } } if (link == null) { return ""; } String[] dirs; // We need to resolve the file directory from the database's metadata, // but that is not available from a formatter. Therefore, as an // ugly hack, the export routine has set a global variable before // starting the export, which contains the database's file directory: if (Globals.prefs.fileDirForDatabase != null) { dirs = Globals.prefs.fileDirForDatabase; } else { dirs = new String[] {Globals.prefs.get(Globals.FILE_FIELD + "Directory")}; } File f = FileUtil.expandFilename(link, dirs); /* * Stumbled over this while investigating * * https://sourceforge.net/tracker/index.php?func=detail&aid=1469903&group_id=92314&atid=600306 */ if (f != null) { try { return f.getCanonicalPath(); // f.toURI().toString(); } catch (IOException e) { e.printStackTrace(); return f.getPath(); } } else { return link; } }
public String format(String field) { StringBuilder sb = new StringBuilder(); // Build the table model containing the links: FileListTableModel tableModel = new FileListTableModel(); if (field == null) return ""; tableModel.setContent(field); int piv = 1; // counter for relevant iterations for (int i = 0; i < tableModel.getRowCount(); i++) { FileListEntry flEntry = tableModel.getEntry(i); // Use this entry if we don't discriminate on types, or if the type fits: if ((fileType == null) || flEntry.getType().getName().toLowerCase().equals(fileType)) { for (FormatEntry entry : format) { switch (entry.getType()) { case STRING: sb.append(entry.getString()); break; case ITERATION_COUNT: sb.append(String.valueOf(piv)); break; case FILE_PATH: if (flEntry.getLink() == null) break; String dir; // We need to resolve the file directory from the database's metadata, // but that is not available from a formatter. Therefore, as an // ugly hack, the export routine has set a global variable before // starting the export, which contains the database's file directory: if (Globals.prefs.fileDirForDatabase != null) dir = Globals.prefs.fileDirForDatabase; else dir = Globals.prefs.get(GUIGlobals.FILE_FIELD + "Directory"); File f = Util.expandFilename(flEntry.getLink(), new String[] {dir}); /* * Stumbled over this while investigating * * https://sourceforge.net/tracker/index.php?func=detail&aid=1469903&group_id=92314&atid=600306 */ if (f != null) { try { sb.append(replaceStrings(f.getCanonicalPath())); // f.toURI().toString(); } catch (IOException ex) { ex.printStackTrace(); sb.append(replaceStrings(f.getPath())); } } else { sb.append(replaceStrings(flEntry.getLink())); } break; case RELATIVE_FILE_PATH: if (flEntry.getLink() == null) break; /* * Stumbled over this while investigating * * https://sourceforge.net/tracker/index.php?func=detail&aid=1469903&group_id=92314&atid=600306 */ sb.append(replaceStrings(flEntry.getLink())); // f.toURI().toString(); break; case FILE_EXTENSION: if (flEntry.getLink() == null) break; int index = flEntry.getLink().lastIndexOf('.'); if ((index >= 0) && (index < flEntry.getLink().length() - 1)) sb.append(replaceStrings(flEntry.getLink().substring(index + 1))); break; case FILE_TYPE: sb.append(replaceStrings(flEntry.getType().getName())); break; case FILE_DESCRIPTION: sb.append(replaceStrings(flEntry.getDescription())); break; } } piv++; // update counter } } return sb.toString(); }
private void doRenamePDFs(BibtexEntry entry, NamedCompound ce) { // Extract the path String oldValue = entry.getField(Globals.FILE_FIELD); if (oldValue == null) { return; } FileListTableModel flModel = new FileListTableModel(); flModel.setContent(oldValue); if (flModel.getRowCount() == 0) { return; } boolean changed = false; for (int i = 0; i < flModel.getRowCount(); i++) { String realOldFilename = flModel.getEntry(i).getLink(); if (cleanUpRenamePDFonlyRelativePaths.isSelected() && (new File(realOldFilename).isAbsolute())) { continue; } String newFilename = Util.getLinkedFileName(panel.database(), entry); // String oldFilename = bes.getField(GUIGlobals.FILE_FIELD); // would have to be stored for // undoing purposes // Add extension to newFilename newFilename = newFilename + "." + flModel.getEntry(i).getType().getExtension(); // get new Filename with path // Create new Path based on old Path and new filename File expandedOldFile = FileUtil.expandFilename( realOldFilename, panel.metaData().getFileDirectory(Globals.FILE_FIELD)); if (expandedOldFile.getParent() == null) { // something went wrong. Just skip this entry continue; } String newPath = expandedOldFile .getParent() .concat(System.getProperty("file.separator")) .concat(newFilename); if (new File(newPath).exists()) { // we do not overwrite files // TODO: we could check here if the newPath file is linked with the current entry. And if // not, we could add a link continue; } // do rename boolean renameSuccessful = FileUtil.renameFile(expandedOldFile.toString(), newPath); if (renameSuccessful) { changed = true; // Change the path for this entry String description = flModel.getEntry(i).getDescription(); ExternalFileType type = flModel.getEntry(i).getType(); flModel.removeEntry(i); // we cannot use "newPath" to generate a FileListEntry as newPath is absolute, but we want // to keep relative paths whenever possible File parent = (new File(realOldFilename)).getParentFile(); String newFileEntryFileName; if (parent == null) { newFileEntryFileName = newFilename; } else { newFileEntryFileName = parent.toString().concat(System.getProperty("file.separator")).concat(newFilename); } flModel.addEntry(i, new FileListEntry(description, newFileEntryFileName, type)); } else { unsuccessfulRenames++; } } if (changed) { String newValue = flModel.getStringRepresentation(); assert (!oldValue.equals(newValue)); entry.setField(Globals.FILE_FIELD, newValue); // we put an undo of the field content here // the file is not being renamed back, which leads to inconsistencies // if we put a null undo object here, the change by "doMakePathsRelative" would overwrite the // field value nevertheless. ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue)); } }
public void run() { if (!goOn) return; for (int i = 0; i < entries.length; i++) { BibtexEntry entry = entries[i]; // Make a list of all PDFs linked from this entry: List<File> files = new ArrayList<File>(); // First check the (legacy) "pdf" field: String pdf = entry.getField("pdf"); String dir = panel.metaData().getFileDirectory("pdf"); File f = Util.expandFilename(pdf, new String[] {dir, "."}); if (f != null) files.add(f); // Then check the "file" field: dir = panel.metaData().getFileDirectory(GUIGlobals.FILE_FIELD); String field = entry.getField(GUIGlobals.FILE_FIELD); if (field != null) { FileListTableModel tm = new FileListTableModel(); tm.setContent(field); for (int j = 0; j < tm.getRowCount(); j++) { FileListEntry flEntry = tm.getEntry(j); if ((flEntry.getType() != null) && (flEntry.getType().getName().toLowerCase().equals("pdf"))) { f = Util.expandFilename(flEntry.getLink(), new String[] {dir, "."}); if (f != null) files.add(f); } } } optDiag.progressArea.append(entry.getCiteKey() + "\n"); if (files.size() == 0) { skipped++; optDiag.progressArea.append(" " + Globals.lang("Skipped - No PDF linked") + ".\n"); } else for (File file : files) { if (!file.exists()) { skipped++; optDiag.progressArea.append( " " + Globals.lang("Skipped - PDF does not exist") + ":\n"); optDiag.progressArea.append(" " + file.getPath() + "\n"); } else { try { XMPUtil.writeXMP(file, entry, database); optDiag.progressArea.append(" " + Globals.lang("Ok") + ".\n"); entriesChanged++; } catch (Exception e) { optDiag.progressArea.append( " " + Globals.lang("Error while writing") + " '" + file.getPath() + "':\n"); optDiag.progressArea.append(" " + e.getLocalizedMessage() + "\n"); errors++; } } } if (optDiag.canceled) { optDiag.progressArea.append("\n" + Globals.lang("Operation canceled.\n")); break; } } optDiag.progressArea.append( "\n" + Globals.lang( "Finished writing XMP for %0 file (%1 skipped, %2 errors).", String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); optDiag.done(); }
@Override public void run() { if (!goOn) { panel.output( Localization.lang("This operation requires one or more entries to be selected.")); return; } entriesChangedCount = 0; panel.frame().setProgressBarValue(0); panel.frame().setProgressBarVisible(true); int weightAutoSet = 10; // autoSet takes 10 (?) times longer than checkExisting int progressBarMax = (autoSet ? weightAutoSet * sel.size() : 0) + (checkExisting ? sel.size() : 0); panel.frame().setProgressBarMaximum(progressBarMax); int progress = 0; final NamedCompound ce = new NamedCompound(Localization.lang("Automatically set file links")); Set<BibEntry> changedEntries = new HashSet<>(); // First we try to autoset fields if (autoSet) { Collection<BibEntry> entries = new ArrayList<>(sel); // Start the automatically setting process: Runnable r = AutoSetLinks.autoSetLinks( entries, ce, changedEntries, null, panel.getBibDatabaseContext(), null, null); JabRefExecutorService.INSTANCE.executeAndWait(r); } progress += sel.size() * weightAutoSet; panel.frame().setProgressBarValue(progress); // The following loop checks all external links that are already set. if (checkExisting) { boolean removeAllBroken = false; mainLoop: for (BibEntry aSel : sel) { panel.frame().setProgressBarValue(progress++); final String old = aSel.getField(Globals.FILE_FIELD); // Check if a extension is set: if ((old != null) && !(old.isEmpty())) { FileListTableModel tableModel = new FileListTableModel(); tableModel.setContentDontGuessTypes(old); // We need to specify which directories to search in for Util.expandFilename: List<String> dirsS = panel.getBibDatabaseContext().getFileDirectory(); List<File> dirs = new ArrayList<>(); for (String dirs1 : dirsS) { dirs.add(new File(dirs1)); } for (int j = 0; j < tableModel.getRowCount(); j++) { FileListEntry flEntry = tableModel.getEntry(j); // See if the link looks like an URL: boolean httpLink = flEntry.link.toLowerCase(Locale.ENGLISH).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: Optional<File> file = FileUtil.expandFilename(flEntry.link, dirsS); if ((!file.isPresent()) || !file.get().exists()) { int answer; if (removeAllBroken) { answer = 2; // We should delete this link. } else { answer = JOptionPane.showOptionDialog( panel.frame(), Localization.lang( "<HTML>Could not find file '%0'<BR>linked from entry '%1'</HTML>", flEntry.link, aSel.getCiteKey()), Localization.lang("Broken link"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, brokenLinkOptions, brokenLinkOptions[0]); } switch (answer) { case 1: // Assign new file. FileListEntryEditor flEditor = new FileListEntryEditor( panel.frame(), flEntry, false, true, panel.getBibDatabaseContext()); 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; default: // Cancel break mainLoop; } } // Unless we deleted this link, see if its file type is recognized: if (!deleted && flEntry.type.isPresent() && (flEntry.type.get() instanceof UnknownExternalFileType)) { String[] options = new String[] { Localization.lang("Define '%0'", flEntry.type.get().getName()), Localization.lang("Change file type"), Localization.lang("Cancel") }; String defOption = options[0]; int answer = JOptionPane.showOptionDialog( panel.frame(), Localization.lang( "One or more file links are of the type '%0', which is undefined. What do you want to do?", flEntry.type.get().getName()), Localization.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.type.get().getName(), "", "", "", "new", IconTheme.JabRefIcon.FILE.getSmallIcon()); ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(panel.frame(), newType); editor.setVisible(true); if (editor.okPressed()) { // Get the old list of types, add this one, and update the list in prefs: List<ExternalFileType> fileTypes = new ArrayList<>( ExternalFileTypes.getInstance().getExternalFileTypeSelection()); fileTypes.add(newType); Collections.sort(fileTypes); ExternalFileTypes.getInstance().setExternalFileTypes(fileTypes); panel.getMainTable().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.getBibDatabaseContext()); editor.setVisible(true, false); } } } if (!tableModel.getStringRepresentation().equals(old)) { // The table has been modified. Store the change: String toSet = tableModel.getStringRepresentation(); if (toSet.isEmpty()) { ce.addEdit(new UndoableFieldChange(aSel, Globals.FILE_FIELD, old, null)); aSel.clearField(Globals.FILE_FIELD); } else { ce.addEdit(new UndoableFieldChange(aSel, Globals.FILE_FIELD, old, toSet)); aSel.setField(Globals.FILE_FIELD, toSet); } changedEntries.add(aSel); } } } } if (!changedEntries.isEmpty()) { // Add the undo edit: ce.end(); panel.getUndoManager().addEdit(ce); panel.markBaseChanged(); entriesChangedCount = changedEntries.size(); } }
/** * Convenience method for finding a label corresponding to the type of the first file link in the * given field content. The difference between using this method and using setContent() on an * instance of FileListTableModel is a slight optimization: with this method, parsing is * discontinued after the first entry has been found. * * @param content The file field content, as fed to this class' setContent() method. * @return A JLabel set up with no text and the icon of the first entry's file type, or null if no * entry was found or the entry had no icon. */ public static JLabel getFirstLabel(String content) { FileListTableModel tm = new FileListTableModel(); FileListEntry entry = tm.setContent(content, true, true); if (entry == null || entry.getType() == null) return null; return entry.getType().getIconLabel(); }