diff --git a/CHANGELOG.md b/CHANGELOG.md index 0661a825372..a91272bce55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ### Changed - Source tab in the entry editor displays "BibLaTeX Source" when using biblatex mode +- [koppor#171](https://github.com/koppor/jabref/issues/171): Add Shortcuts to context menu - Add session restoring functionality for shared database. Related to [#1703](https://github.com/JabRef/jabref/issues/1703) - Implementation of LiveUpdate for PostgreSQL & Oracle systems. Related to [#970](https://github.com/JabRef/jabref/issues/970). - [koppor#31](https://github.com/koppor/jabref/issues/31): Number column in the main table is always Left aligned @@ -59,8 +60,11 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Improve language quality of the German translation of shared database - Change "Recent files" to "Recent databases" to keep the file menu consistent - Customized importer files need to be slightly changed since the class `ImportFormat` was renamed to `Importer` +- [koppor#5](https://github.com/koppor/jabref/issues/5) When entries are found while dropping a pdf with xmp meta data the found entries will be displayed in the import dialog +- [koppor#61](https://github.com/koppor/jabref/issues/61) Display gray background text in "Author" and "Editor" field to assist newcomers ### Fixed +- Fixed [#2089](https://github.com/JabRef/jabref/issues/2089): Fixed faulty cite key generation - Fixed [#2092](https://github.com/JabRef/jabref/issues/2092): "None"-button in date picker clears the date field - Fixed [#1993](https://github.com/JabRef/jabref/issues/1993): Various optimizations regarding search performance - Fixed [koppor#160](https://github.com/koppor/jabref/issues/160): Tooltips now working in the main table @@ -88,6 +92,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Fixed InvalidBackgroundColor flickering with Ctrl-s and File > Save database - Fixed loop when pulling changes (shared database) when current selected field has changed - Fixed [#1958](https://github.com/JabRef/jabref/issues/1958): Verbatim fields are no longer checked for HTML encoded characters by integrity checks +- Fixed [#1808](https://github.com/JabRef/jabref/issues/1808): Font preference dialog now keeps changes - Fixed [#1937](https://github.com/JabRef/jabref/issues/1937): If no help page for the current chosen language exists, the english help page will be shown - Fixed [#2060](https://github.com/JabRef/jabref/issues/2060): Medline fetcher now imports data in UTF-8 encoding - Fixed file menu displays wrong hotkey in the German translation diff --git a/build.gradle b/build.gradle index 14b7b05b901..ad1d98ec1c3 100644 --- a/build.gradle +++ b/build.gradle @@ -14,13 +14,11 @@ buildscript { } plugins { - id "com.install4j.gradle" version "6.1.2" + id "com.install4j.gradle" version "6.1.3" id 'com.github.johnrengelman.shadow' version '1.2.3' - // TODO replaced by spotless plugin, needs to be updated - id "com.github.youribonnaffe.gradle.format" version "1.5" // If this is updated to 0.0.9, check configurations.errorprone id "net.ltgt.errorprone" version "0.0.8" - id 'me.champeau.gradle.jmh' version '0.3.0' + id 'me.champeau.gradle.jmh' version '0.3.1' } apply plugin: "java" @@ -247,6 +245,11 @@ test { } } +task copyTestResources(type: Copy) { + from "${projectDir}/src/test/resources" + into "${buildDir}/classes/test" +} +processTestResources.dependsOn copyTestResources task databaseTest(type: Test) { testClassesDir = sourceSets.databaseTest.output.classesDir @@ -272,13 +275,6 @@ jacocoTestReport { } } -// enables `gradlew format`. Currently `LabelPatternUtil.java` is destroyed. Use with care! -format { - configurationFile = file('ide-settings/formatter_settings.xml') - // default: reformat main and test - //files = sourceSets.main.java -} - shadowJar { classifier 'fat' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c..deedc7fa5e6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae762166352..b16a4020d80 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 06 12:44:33 CEST 2016 +#Tue Oct 11 00:24:20 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip diff --git a/gradlew b/gradlew index 27309d92314..9aa616c273d 100755 --- a/gradlew +++ b/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat index 832fdb6079b..f9553162f12 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/src/main/java/net/sf/jabref/collab/FileUpdatePanel.java b/src/main/java/net/sf/jabref/collab/FileUpdatePanel.java index 2d2ce422f6f..2694d3203df 100644 --- a/src/main/java/net/sf/jabref/collab/FileUpdatePanel.java +++ b/src/main/java/net/sf/jabref/collab/FileUpdatePanel.java @@ -17,10 +17,7 @@ import net.sf.jabref.gui.SidePaneManager; import net.sf.jabref.logic.l10n.Localization; -public class FileUpdatePanel extends SidePaneComponent implements ActionListener, - ChangeScanner.DisplayResultCallback { - - public static final String NAME = "fileUpdate"; +public class FileUpdatePanel extends SidePaneComponent implements ActionListener, ChangeScanner.DisplayResultCallback { private final SidePaneManager manager; @@ -65,7 +62,7 @@ public BasePanel getPanel() { */ @Override public void componentClosing() { - manager.unregisterComponent(FileUpdatePanel.NAME); + manager.unregisterComponent(FileUpdatePanel.class); } @Override @@ -73,6 +70,11 @@ public int getRescalingWeight() { return 0; } + @Override + public ToggleAction getToggleAction() { + throw new UnsupportedOperationException(); + } + /** * actionPerformed * diff --git a/src/main/java/net/sf/jabref/gui/BasePanel.java b/src/main/java/net/sf/jabref/gui/BasePanel.java index 013955a629c..f60b5721ba5 100644 --- a/src/main/java/net/sf/jabref/gui/BasePanel.java +++ b/src/main/java/net/sf/jabref/gui/BasePanel.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -70,6 +69,7 @@ import net.sf.jabref.gui.importer.actions.AppendDatabaseAction; import net.sf.jabref.gui.journals.AbbreviateAction; import net.sf.jabref.gui.journals.UnabbreviateAction; +import net.sf.jabref.gui.keyboard.KeyBinding; import net.sf.jabref.gui.maintable.MainTable; import net.sf.jabref.gui.maintable.MainTableDataModel; import net.sf.jabref.gui.maintable.MainTableFormat; @@ -112,6 +112,7 @@ import net.sf.jabref.logic.util.io.FileUtil; import net.sf.jabref.logic.util.io.RegExpFileSearch; import net.sf.jabref.model.FieldChange; +import net.sf.jabref.model.bibtexkeypattern.AbstractBibtexKeyPattern; import net.sf.jabref.model.database.BibDatabase; import net.sf.jabref.model.database.BibDatabaseContext; import net.sf.jabref.model.database.DatabaseLocation; @@ -395,12 +396,6 @@ private void setupActions() { }); - // The action for toggling the groups interface - actions.put(Actions.TOGGLE_GROUPS, (BaseAction) () -> { - sidePaneManager.toggle("groups"); - frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups")); - }); - actions.put(FindUnlinkedFilesDialog.ACTION_COMMAND, (BaseAction) () -> { final FindUnlinkedFilesDialog dialog = new FindUnlinkedFilesDialog(frame, frame, BasePanel.this); dialog.setLocationRelativeTo(frame); @@ -434,65 +429,47 @@ public void init() { // Run second, on a different thread: @Override public void run() { - BibEntry bes; - - // First check if any entries have keys set already. If so, possibly remove - // them from consideration, or warn about overwriting keys. - // This is a partial clone of net.sf.jabref.gui.entryeditor.EntryEditor.GenerateKeyAction.actionPerformed(ActionEvent) - for (final Iterator i = entries.iterator(); i.hasNext();) { - bes = i.next(); - if (bes.hasCiteKey()) { - if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { - // Remove the entry, because its key is already set: - i.remove(); - } else if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { - // Ask if the user wants to cancel the operation: - CheckBoxMessage cbm = new CheckBoxMessage( - Localization.lang("One or more keys will be overwritten. Continue?"), - Localization.lang("Disable this confirmation dialog"), false); - final int answer = JOptionPane.showConfirmDialog(frame, cbm, - Localization.lang("Overwrite keys"), JOptionPane.YES_NO_OPTION); - if (cbm.isSelected()) { - Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, false); - } - if (answer == JOptionPane.NO_OPTION) { - // Ok, break off the operation. - canceled = true; - return; - } - // No need to check more entries, because the user has already confirmed - // that it's ok to overwrite keys: - break; + // We don't want to generate keys for entries which already have one thus remove the entries + if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { + entries.removeIf(BibEntry::hasCiteKey); + + // if we're going to override some cite keys warn the user about it + } else if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { + if (entries.parallelStream().anyMatch(BibEntry::hasCiteKey)) { + CheckBoxMessage cbm = new CheckBoxMessage( + Localization.lang("One or more keys will be overwritten. Continue?"), + Localization.lang("Disable this confirmation dialog"), false); + final int answer = JOptionPane.showConfirmDialog(frame, cbm, + Localization.lang("Overwrite keys"), JOptionPane.YES_NO_OPTION); + Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !cbm.isSelected()); + + // The user doesn't want to overide cite keys + if (answer == JOptionPane.NO_OPTION) { + canceled = true; + return; } } } - Map oldvals = new HashMap<>(); - // Iterate again, removing already set keys. This is skipped if overwriting - // is disabled, since all entries with keys set will have been removed. - if (!Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { - for (BibEntry entry : entries) { - bes = entry; - // Store the old value: - oldvals.put(bes, bes.getCiteKeyOptional().orElse(null)); - bes.clearCiteKey(); - } - } - + // generate the new cite keys for each entry final NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); - - // Finally, set the new keys: + AbstractBibtexKeyPattern citeKeyPattern = bibDatabaseContext.getMetaData() + .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()); for (BibEntry entry : entries) { - bes = entry; - BibtexKeyPatternUtil.makeLabel(bibDatabaseContext.getMetaData() - .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), - bibDatabaseContext.getDatabase(), - bes, Globals.prefs.getBibtexKeyPatternPreferences()); - ce.addEdit(new UndoableKeyChange(bibDatabaseContext.getDatabase(), bes, oldvals.get(bes), - bes.getCiteKeyOptional().orElse(null))); + String oldCiteKey = entry.getCiteKeyOptional().orElse(""); + BibtexKeyPatternUtil.makeLabel(citeKeyPattern, bibDatabaseContext.getDatabase(), + entry, Globals.prefs.getBibtexKeyPatternPreferences()); + String newCiteKey = entry.getCiteKeyOptional().orElse(""); + if (!oldCiteKey.equals(newCiteKey)) { + ce.addEdit(new UndoableKeyChange(entry, oldCiteKey, newCiteKey)); + } } ce.end(); - getUndoManager().addEdit(ce); + + // register the undo event only if new cite keys were generated + if (ce.hasEdits()) { + getUndoManager().addEdit(ce); + } } // Run third, on EDT: @@ -1217,7 +1194,7 @@ public void listen(EntryAddedEvent addedEntryEvent) { } // Automatically add new entry to the selected group (or set of groups) - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP) && frame.groupToggle.isSelected()) { + if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP) && frame.getGroupSelector().getToggleAction().isSelected()) { final List entries = Collections.singletonList(addedEntryEvent.getBibEntry()); final TreePath[] selection = frame.getGroupSelector().getGroupsTree().getSelectionPaths(); if (selection != null) { @@ -1301,6 +1278,7 @@ public void listen(EntryChangedEvent entryChangedEvent) { @Subscribe public void listen(EntryRemovedEvent removedEntryEvent) { + // IMO only used to update the status (found X entries) frame.getGlobalSearchBar().performSearch(); } } @@ -1381,6 +1359,30 @@ private void createMainTable() { }; mainTable.addSelectionListener(groupsHighlightListener); + String clearSearch = "clearSearch"; + mainTable.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.CLEAR_SEARCH), clearSearch); + mainTable.getActionMap().put(clearSearch, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + // need to close these here, b/c this action overshadows the responsible actions when the main table is selected + switch (mode) { + case SHOWING_NOTHING: + frame.getGlobalSearchBar().endSearch(); + break; + case SHOWING_PREVIEW: + getPreviewPanel().close(); + break; + case SHOWING_EDITOR: + case WILL_SHOW_EDITOR: + getCurrentEditor().close(); + break; + default: + LOGGER.warn("unknown BasePanelMode: '" + mode + "', doing nothing"); + break; + } + } + }); + mainTable.getActionMap().put(Actions.CUT, new AbstractAction() { @Override @@ -1903,7 +1905,6 @@ public boolean showDeleteConfirmationDialog(int numberOfEntries) { public void autoGenerateKeysBeforeSaving() { if (Globals.prefs.getBoolean(JabRefPreferences.GENERATE_KEYS_BEFORE_SAVING)) { NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); - boolean any = false; for (BibEntry bes : bibDatabaseContext.getDatabase().getEntries()) { Optional oldKey = bes.getCiteKeyOptional(); @@ -1912,13 +1913,12 @@ public void autoGenerateKeysBeforeSaving() { .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), bibDatabaseContext.getDatabase(), bes, Globals.prefs.getBibtexKeyPatternPreferences()); - ce.addEdit(new UndoableKeyChange(bibDatabaseContext.getDatabase(), bes, null, - bes.getCiteKeyOptional().get())); // Cite key is set here - any = true; + ce.addEdit(new UndoableKeyChange(bes, oldKey.orElse(""), bes.getCiteKeyOptional().get())); } } + // Store undo information, if any: - if (any) { + if (ce.hasEdits()) { ce.end(); getUndoManager().addEdit(ce); } @@ -2117,15 +2117,15 @@ public void fileUpdated() { // Check if there is already a notification about external // changes: - boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME); + boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.class); if (hasAlready) { - sidePaneManager.hideComponent(FileUpdatePanel.NAME); - sidePaneManager.unregisterComponent(FileUpdatePanel.NAME); + sidePaneManager.hideComponent(FileUpdatePanel.class); + sidePaneManager.unregisterComponent(FileUpdatePanel.class); } FileUpdatePanel pan = new FileUpdatePanel(BasePanel.this, sidePaneManager, getBibDatabaseContext().getDatabaseFile().orElse(null), scanner); - sidePaneManager.register(FileUpdatePanel.NAME, pan); - sidePaneManager.show(FileUpdatePanel.NAME); + sidePaneManager.register(pan); + sidePaneManager.show(FileUpdatePanel.class); }; if (scanner.changesFound()) { @@ -2149,10 +2149,10 @@ public void cleanUp() { } // Check if there is a FileUpdatePanel for this BasePanel being shown. If so, // remove it: - if (sidePaneManager.hasComponent("fileUpdate")) { - FileUpdatePanel fup = (FileUpdatePanel) sidePaneManager.getComponent("fileUpdate"); + if (sidePaneManager.hasComponent(FileUpdatePanel.class)) { + FileUpdatePanel fup = (FileUpdatePanel) sidePaneManager.getComponent(FileUpdatePanel.class); if (fup.getPanel() == this) { - sidePaneManager.hideComponent("fileUpdate"); + sidePaneManager.hideComponent(FileUpdatePanel.class); } } } diff --git a/src/main/java/net/sf/jabref/gui/JabRefFrame.java b/src/main/java/net/sf/jabref/gui/JabRefFrame.java index 42f50fa6445..52a36e1cf92 100644 --- a/src/main/java/net/sf/jabref/gui/JabRefFrame.java +++ b/src/main/java/net/sf/jabref/gui/JabRefFrame.java @@ -99,6 +99,7 @@ import net.sf.jabref.gui.menus.FileHistoryMenu; import net.sf.jabref.gui.menus.RightClickMenu; import net.sf.jabref.gui.openoffice.OpenOfficePanel; +import net.sf.jabref.gui.openoffice.OpenOfficeSidePanel; import net.sf.jabref.gui.preftabs.PreferencesDialog; import net.sf.jabref.gui.protectedterms.ProtectedTermsDialog; import net.sf.jabref.gui.push.PushToApplicationButton; @@ -190,11 +191,7 @@ public class JabRefFrame extends JFrame implements OutputPrinter { // for the name and message strings. /* References to the toggle buttons in the toolbar */ - // the groups interface - public JToggleButton groupToggle; private JToggleButton previewToggle; - private JToggleButton fetcherToggle; - private final OpenDatabaseAction open = new OpenDatabaseAction(this, true); private final EditModeAction editModeAction = new EditModeAction(); @@ -351,11 +348,6 @@ public void actionPerformed(ActionEvent e) { } }); - private final Action toggleGroups = enableToggle(new GeneralAction(Actions.TOGGLE_GROUPS, - Localization.menuTitle("Toggle groups interface"), - Localization.lang("Toggle groups interface"), - Globals.getKeyPrefs().getKey(KeyBinding.TOGGLE_GROUPS_INTERFACE), - IconTheme.JabRefIcon.TOGGLE_GROUPS.getIcon())); private final AbstractAction addToGroup = new GeneralAction(Actions.ADD_TO_GROUP, Localization.lang("Add to group") + ELLIPSES); private final AbstractAction removeFromGroup = new GeneralAction(Actions.REMOVE_FROM_GROUP, Localization.lang("Remove from group") + ELLIPSES); @@ -394,8 +386,6 @@ public void actionPerformed(ActionEvent e) { Localization.lang("Will write XMP-metadata to the PDFs linked from selected entries."), Globals.getKeyPrefs().getKey(KeyBinding.WRITE_XMP)); - private JMenuItem optMenuItem; - private final AbstractAction openFolder = new GeneralAction(Actions.OPEN_FOLDER, Localization.menuTitle("Open folder"), Localization.lang("Open folder"), Globals.getKeyPrefs().getKey(KeyBinding.OPEN_FOLDER)); @@ -481,7 +471,7 @@ public void actionPerformed(ActionEvent e) { private PushToApplications pushApplications; private GeneralFetcher generalFetcher; - + private OpenOfficePanel openOfficePanel; private GroupSelector groupSelector; private int previousTabCount = -1; @@ -661,9 +651,10 @@ public void windowClosing(WindowEvent e) { currentBasePanel.getPreviewPanel().updateLayout(); - groupToggle.setSelected(sidePaneManager.isComponentVisible("groups")); + groupSelector.getToggleAction().setSelected(sidePaneManager.isComponentVisible(GroupSelector.class)); previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); - fetcherToggle.setSelected(sidePaneManager.isComponentVisible(generalFetcher.getTitle())); + generalFetcher.getToggleAction().setSelected(sidePaneManager.isComponentVisible(GeneralFetcher.class)); + openOfficePanel.getToggleAction().setSelected(sidePaneManager.isComponentVisible(OpenOfficeSidePanel.class)); Globals.getFocusListener().setFocused(currentBasePanel.getMainTable()); setWindowTitle(); editModeAction.initName(); @@ -723,10 +714,10 @@ private void initSidePane() { sidePaneManager = new SidePaneManager(this); groupSelector = new GroupSelector(this, sidePaneManager); + openOfficePanel = new OpenOfficePanel(this, sidePaneManager); + generalFetcher = new GeneralFetcher(this, sidePaneManager); - generalFetcher = new GeneralFetcher(sidePaneManager, this); - - sidePaneManager.register("groups", groupSelector); + sidePaneManager.register(groupSelector); } /** @@ -1235,14 +1226,14 @@ private void fillMenu() { search.add(normalSearch); search.add(replaceAll); search.addSeparator(); - search.add(new JCheckBoxMenuItem(generalFetcher.getAction())); + search.add(new JCheckBoxMenuItem(generalFetcher.getToggleAction())); if (prefs.getBoolean(JabRefPreferences.WEB_SEARCH_VISIBLE)) { - sidePaneManager.register(generalFetcher.getTitle(), generalFetcher); - sidePaneManager.show(generalFetcher.getTitle()); + sidePaneManager.register(generalFetcher); + sidePaneManager.show(GeneralFetcher.class); } mb.add(search); - groups.add(new JCheckBoxMenuItem(toggleGroups)); + groups.add(new JCheckBoxMenuItem(groupSelector.getToggleAction())); groups.addSeparator(); groups.add(addToGroup); groups.add(removeFromGroup); @@ -1282,8 +1273,8 @@ private void fillMenu() { view.add(decreseFontSize); view.addSeparator(); view.add(new JCheckBoxMenuItem(toggleToolbar)); - view.add(new JCheckBoxMenuItem(enableToggle(generalFetcher.getAction()))); - view.add(new JCheckBoxMenuItem(toggleGroups)); + view.add(new JCheckBoxMenuItem(enableToggle(generalFetcher.getToggleAction()))); + view.add(new JCheckBoxMenuItem(groupSelector.getToggleAction())); view.add(new JCheckBoxMenuItem(togglePreview)); view.add(getNextPreviewStyleAction()); view.add(getPreviousPreviewStyleAction()); @@ -1324,10 +1315,7 @@ private void fillMenu() { tools.add(newSubDatabaseAction); tools.add(writeXmpAction); - OpenOfficePanel otp = OpenOfficePanel.getInstance(); - otp.init(this, sidePaneManager); - optMenuItem = otp.getMenuItem(); - tools.add(optMenuItem); + tools.add(new JCheckBoxMenuItem(openOfficePanel.getToggleAction())); tools.add(pushExternalButton.getMenuAction()); tools.addSeparator(); tools.add(openFolder); @@ -1485,14 +1473,12 @@ private void createToolBar() { } tlb.addSeparator(); - fetcherToggle = new JToggleButton(generalFetcher.getAction()); - tlb.addJToggleButton(fetcherToggle); + tlb.addJToggleButton(new JToggleButton(generalFetcher.getToggleAction())); previewToggle = new JToggleButton(togglePreview); tlb.addJToggleButton(previewToggle); - groupToggle = new JToggleButton(toggleGroups); - tlb.addJToggleButton(groupToggle); + tlb.addJToggleButton(new JToggleButton(groupSelector.getToggleAction())); tlb.addSeparator(); @@ -1519,8 +1505,8 @@ private void initActions() { openDatabaseOnlyActions.addAll(Arrays.asList(mergeDatabaseAction, newSubDatabaseAction, save, globalSearch, saveAs, saveSelectedAs, saveSelectedAsPlain, editModeAction, undo, redo, cut, deleteEntry, copy, paste, mark, markSpecific, unmark, unmarkAll, rankSubMenu, editEntry, selectAll, copyKey, copyCiteKey, copyKeyAndTitle, copyKeyAndLink, editPreamble, editStrings, - toggleGroups, makeKeyAction, normalSearch, generalFetcher.getAction(), mergeEntries, cleanupEntries, exportToClipboard, replaceAll, - sendAsEmail, downloadFullText, writeXmpAction, optMenuItem, findUnlinkedFiles, addToGroup, removeFromGroup, + groupSelector.getToggleAction(), makeKeyAction, normalSearch, generalFetcher.getToggleAction(), mergeEntries, cleanupEntries, exportToClipboard, replaceAll, + sendAsEmail, downloadFullText, writeXmpAction, openOfficePanel.getToggleAction(), findUnlinkedFiles, addToGroup, removeFromGroup, moveToGroup, autoLinkFile, resolveDuplicateKeys, openUrl, openFolder, openFile, togglePreview, dupliCheck, autoSetFile, newEntryAction, newSpec, customizeAction, plainTextImport, getMassSetField(), getManageKeywords(), pushExternalButton.getMenuAction(), closeDatabaseAction, getNextPreviewStyleAction(), getPreviousPreviewStyleAction(), checkIntegrity, @@ -2381,10 +2367,6 @@ public GroupSelector getGroupSelector() { return groupSelector; } - public void setFetcherToggle(boolean enabled) { - fetcherToggle.setSelected(enabled); - } - public void setPreviewToggle(boolean enabled) { previewToggle.setSelected(enabled); } diff --git a/src/main/java/net/sf/jabref/gui/PreviewPanel.java b/src/main/java/net/sf/jabref/gui/PreviewPanel.java index d80721a0727..8f5f9562c6e 100644 --- a/src/main/java/net/sf/jabref/gui/PreviewPanel.java +++ b/src/main/java/net/sf/jabref/gui/PreviewPanel.java @@ -357,7 +357,10 @@ public void actionPerformed(ActionEvent arg0) { } }); } + } + public void close() { + basePanel.ifPresent(BasePanel::hideBottomComponent); } class CloseAction extends AbstractAction { @@ -366,12 +369,11 @@ public CloseAction() { super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window")); } + @Override public void actionPerformed(ActionEvent e) { - basePanel.ifPresent(BasePanel::hideBottomComponent); + close(); } - - } class CopyPreviewAction extends AbstractAction { diff --git a/src/main/java/net/sf/jabref/gui/SidePaneComponent.java b/src/main/java/net/sf/jabref/gui/SidePaneComponent.java index 3ed6190bde5..c3514399502 100644 --- a/src/main/java/net/sf/jabref/gui/SidePaneComponent.java +++ b/src/main/java/net/sf/jabref/gui/SidePaneComponent.java @@ -3,12 +3,17 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JToolBar; +import javax.swing.KeyStroke; import org.jdesktop.swingx.JXTitledPanel; import org.jdesktop.swingx.painter.MattePainter; @@ -17,7 +22,7 @@ public abstract class SidePaneComponent extends JXTitledPanel { protected final JButton close = new JButton(IconTheme.JabRefIcon.CLOSE.getSmallIcon()); - private final SidePaneManager manager; + protected final SidePaneManager manager; protected BasePanel panel; @@ -100,4 +105,51 @@ public Dimension getMinimumSize() { * 0: fixed height, 1: fill the remaining space */ public abstract int getRescalingWeight(); + + /** + * @return the action which toggles this {@link SidePaneComponent} + */ + public abstract ToggleAction getToggleAction(); + + + public class ToggleAction extends AbstractAction { + + public ToggleAction(String text, String description, KeyStroke key, IconTheme.JabRefIcon icon){ + super(text, icon.getSmallIcon()); + putValue(Action.ACCELERATOR_KEY, key); + putValue(Action.LARGE_ICON_KEY, icon.getIcon()); + putValue(Action.SHORT_DESCRIPTION, description); + } + + public ToggleAction(String text, String description, KeyStroke key, Icon icon){ + super(text, icon); + putValue(Action.ACCELERATOR_KEY, key); + putValue(Action.SHORT_DESCRIPTION, description); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!manager.hasComponent(SidePaneComponent.this.getClass())) { + manager.register(SidePaneComponent.this); + } + + // if clicked by mouse just toggle + if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) { + manager.toggle(SidePaneComponent.this.getClass()); + } else { + manager.toggleThreeWay(SidePaneComponent.this.getClass()); + } + putValue(Action.SELECTED_KEY, manager.isComponentVisible(SidePaneComponent.this.getClass())); + } + + public void setSelected(boolean selected){ + putValue(Action.SELECTED_KEY, selected); + } + + public boolean isSelected() { + return Boolean.TRUE.equals(getValue(Action.SELECTED_KEY)); + } + + } + } diff --git a/src/main/java/net/sf/jabref/gui/SidePaneManager.java b/src/main/java/net/sf/jabref/gui/SidePaneManager.java index 7e3febe55c4..92df0599a86 100644 --- a/src/main/java/net/sf/jabref/gui/SidePaneManager.java +++ b/src/main/java/net/sf/jabref/gui/SidePaneManager.java @@ -1,6 +1,5 @@ package net.sf.jabref.gui; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -31,8 +30,7 @@ public class SidePaneManager { private final SidePane sidep; - private final Map components = new LinkedHashMap<>(); - private final Map componentNames = new HashMap<>(); + private final Map, SidePaneComponent> components = new LinkedHashMap<>(); private final List visible = new LinkedList<>(); @@ -54,42 +52,59 @@ public SidePane getPanel() { return sidep; } - public synchronized boolean hasComponent(String name) { - return components.containsKey(name); + public synchronized boolean hasComponent(Class sidePaneComponent) { + return components.containsKey(sidePaneComponent); } - public synchronized boolean isComponentVisible(String name) { - SidePaneComponent sidePaneComponent = components.get(name); - if (sidePaneComponent == null) { + public synchronized boolean isComponentVisible(Class sidePaneComponent) { + SidePaneComponent component = components.get(sidePaneComponent); + if (component == null) { return false; } else { - return visible.contains(sidePaneComponent); + return visible.contains(component); } } - public synchronized void toggle(String name) { - if (isComponentVisible(name)) { - hide(name); + /** + * If panel is visible it will be hidden and the other way around + */ + public synchronized void toggle(Class sidePaneComponent) { + if (isComponentVisible(sidePaneComponent)) { + hide(sidePaneComponent); } else { - show(name); + show(sidePaneComponent); } } - public synchronized void show(String name) { - SidePaneComponent sidePaneComponent = components.get(name); - if (sidePaneComponent == null) { - LOGGER.warn("Side pane component '" + name + "' unknown."); + /** + * If panel is hidden it will be shown and focused + * If panel is visible but not focused it will be focused + * If panel is visible and focused it will be hidden + */ + public synchronized void toggleThreeWay(Class sidePaneComponent) { + boolean isPanelFocused = Globals.getFocusListener().getFocused() == components.get(sidePaneComponent); + if (isComponentVisible(sidePaneComponent) && isPanelFocused) { + hide(sidePaneComponent); } else { show(sidePaneComponent); } } - public synchronized void hide(String name) { - SidePaneComponent sidePaneComponent = components.get(name); - if (sidePaneComponent == null) { - LOGGER.warn("Side pane component '" + name + "' unknown."); + public synchronized void show(Class sidePaneComponent) { + SidePaneComponent component = components.get(sidePaneComponent); + if (component == null) { + LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown."); } else { - hideComponent(sidePaneComponent); + show(component); + } + } + + public synchronized void hide(Class sidePaneComponent) { + SidePaneComponent component = components.get(sidePaneComponent); + if (component == null) { + LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown."); + } else { + hideComponent(component); if (frame.getCurrentBasePanel() != null) { MainTable mainTable = frame.getCurrentBasePanel().getMainTable(); mainTable.setSelected(mainTable.getSelectedRow()); @@ -98,9 +113,8 @@ public synchronized void hide(String name) { } } - public synchronized void register(String name, SidePaneComponent comp) { - components.put(name, comp); - componentNames.put(comp, name); + public synchronized void register(SidePaneComponent comp) { + components.put(comp.getClass(), comp); } private synchronized void show(SidePaneComponent component) { @@ -114,14 +128,12 @@ private synchronized void show(SidePaneComponent component) { updateView(); component.componentOpening(); } + Globals.getFocusListener().setFocused(component); + component.grabFocus(); } - public synchronized SidePaneComponent getComponent(String name) { - return components.get(name); - } - - private synchronized String getComponentName(SidePaneComponent comp) { - return componentNames.get(comp); + public synchronized SidePaneComponent getComponent(Class sidePaneComponent) { + return components.get(sidePaneComponent); } public synchronized void hideComponent(SidePaneComponent comp) { @@ -132,30 +144,36 @@ public synchronized void hideComponent(SidePaneComponent comp) { } } - public synchronized void hideComponent(String name) { - SidePaneComponent comp = components.get(name); - if (comp == null) { + public synchronized void hideComponent(Class sidePaneComponent) { + SidePaneComponent component = components.get(sidePaneComponent); + if (component == null) { return; } - if (visible.contains(comp)) { - comp.componentClosing(); - visible.remove(comp); + if (visible.contains(component)) { + component.componentClosing(); + visible.remove(component); updateView(); } } - private static Map getPreferredPositions() { - Map preferredPositions = new HashMap<>(); + private static Map, Integer> getPreferredPositions() { + Map, Integer> preferredPositions = new HashMap<>(); List componentNames = Globals.prefs.getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_NAMES); List componentPositions = Globals.prefs .getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_PREFERRED_POSITIONS); for (int i = 0; i < componentNames.size(); ++i) { + String componentName = componentNames.get(i); try { - preferredPositions.put(componentNames.get(i), Integer.parseInt(componentPositions.get(i))); + Class componentClass = (Class) Class.forName(componentName); + preferredPositions.put(componentClass, Integer.parseInt(componentPositions.get(i))); + } catch (ClassNotFoundException e) { + LOGGER.error("Following side pane could not be found: " + componentName, e); + } catch (ClassCastException e) { + LOGGER.error("Following Class is no side pane: '" + componentName, e); } catch (NumberFormatException e) { - LOGGER.info("Invalid number format for side pane component '" + componentNames.get(i) + "'.", e); + LOGGER.info("Invalid number format for side pane component '" + componentName + "'.", e); } } @@ -163,18 +181,20 @@ private static Map getPreferredPositions() { } private void updatePreferredPositions() { - Map preferredPositions = getPreferredPositions(); + Map, Integer> preferredPositions = getPreferredPositions(); // Update the preferred positions of all visible components int index = 0; for (SidePaneComponent comp : visible) { - String componentName = getComponentName(comp); - preferredPositions.put(componentName, index); + preferredPositions.put(comp.getClass(), index); index++; } // Split the map into a pair of parallel String lists suitable for storage - List tmpComponentNames = new ArrayList<>(preferredPositions.keySet()); + List tmpComponentNames = preferredPositions.keySet().parallelStream() + .map(Class::getName) + .collect(Collectors.toList()); + List componentPositions = preferredPositions.values().stream().map(Object::toString) .collect(Collectors.toList()); @@ -183,10 +203,12 @@ private void updatePreferredPositions() { } - // Helper class for sorting visible components based on their preferred position + /** + * Helper class for sorting visible components based on their preferred position + */ private class PreferredIndexSort implements Comparator { - private final Map preferredPositions; + private final Map, Integer> preferredPositions; public PreferredIndexSort() { @@ -195,8 +217,8 @@ public PreferredIndexSort() { @Override public int compare(SidePaneComponent comp1, SidePaneComponent comp2) { - int pos1 = preferredPositions.getOrDefault(getComponentName(comp1), 0); - int pos2 = preferredPositions.getOrDefault(getComponentName(comp2), 0); + int pos1 = preferredPositions.getOrDefault(comp1.getClass(), 0); + int pos2 = preferredPositions.getOrDefault(comp2.getClass(), 0); return Integer.valueOf(pos1).compareTo(pos2); } } @@ -230,9 +252,8 @@ public synchronized void moveDown(SidePaneComponent comp) { } } - public synchronized void unregisterComponent(String name) { - componentNames.remove(components.get(name)); - components.remove(name); + public synchronized void unregisterComponent(Class sidePaneComponent) { + components.remove(sidePaneComponent); } /** @@ -241,10 +262,9 @@ public synchronized void unregisterComponent(String name) { * * @param panel */ - private synchronized void setActiveBasePanel(BasePanel panel) { - for (Map.Entry stringSidePaneComponentEntry : components.entrySet()) { - stringSidePaneComponentEntry.getValue().setActiveBasePanel(panel); + for (SidePaneComponent component : components.values()) { + component.setActiveBasePanel(panel); } } diff --git a/src/main/java/net/sf/jabref/gui/actions/Actions.java b/src/main/java/net/sf/jabref/gui/actions/Actions.java index 58c9e70a3f0..6013786b746 100644 --- a/src/main/java/net/sf/jabref/gui/actions/Actions.java +++ b/src/main/java/net/sf/jabref/gui/actions/Actions.java @@ -60,7 +60,6 @@ public class Actions { public static final String TOGGLE_HIGHLIGHTS_GROUPS_MATCHING_DISABLE = "toggleHighlightGroupsMatchingDisable"; public static final String TOGGLE_GROUPS = "toggleGroups"; public static final String TOGGLE_PREVIEW = "togglePreview"; - public static final String TOGGLE_TOOLBAR = "toggleToolbar"; public static final String UNABBREVIATE = "unabbreviate"; public static final String UNDO = "undo"; public static final String UNMARK_ALL = "unmarkAll"; diff --git a/src/main/java/net/sf/jabref/gui/autocompleter/AutoCompleteSupport.java b/src/main/java/net/sf/jabref/gui/autocompleter/AutoCompleteSupport.java index d508091e4da..59e941578c0 100644 --- a/src/main/java/net/sf/jabref/gui/autocompleter/AutoCompleteSupport.java +++ b/src/main/java/net/sf/jabref/gui/autocompleter/AutoCompleteSupport.java @@ -284,4 +284,8 @@ public void setVisible(boolean visible){ popup.setVisible(visible); } + public boolean isVisible() { + return popup.isVisible(); + } + } diff --git a/src/main/java/net/sf/jabref/gui/bibtexkeypattern/SearchFixDuplicateLabels.java b/src/main/java/net/sf/jabref/gui/bibtexkeypattern/SearchFixDuplicateLabels.java index 77dd0548489..2bdbe25039d 100644 --- a/src/main/java/net/sf/jabref/gui/bibtexkeypattern/SearchFixDuplicateLabels.java +++ b/src/main/java/net/sf/jabref/gui/bibtexkeypattern/SearchFixDuplicateLabels.java @@ -97,7 +97,7 @@ public void update() { .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), entry, Globals.prefs.getBibtexKeyPatternPreferences()); - ce.addEdit(new UndoableKeyChange(panel.getDatabase(), entry, oldKey, entry.getCiteKeyOptional().get())); + ce.addEdit(new UndoableKeyChange(entry, oldKey, entry.getCiteKeyOptional().get())); } ce.end(); panel.getUndoManager().addEdit(ce); diff --git a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java index 82fd5ac1e41..2df4c4d26ec 100644 --- a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditor.java @@ -778,12 +778,9 @@ private boolean storeSource() { BibEntry newEntry = database.getEntries().get(0); String newKey = newEntry.getCiteKeyOptional().orElse(null); boolean entryChanged = false; - boolean duplicateWarning = false; boolean emptyWarning = (newKey == null) || newKey.isEmpty(); - if (panel.getDatabase().setCiteKeyForEntry(entry, newKey)) { - duplicateWarning = true; - } + entry.setCiteKey(newKey); // First, remove fields that the user has removed. for (Entry field : entry.getFieldMap().entrySet()) { @@ -828,7 +825,7 @@ private boolean storeSource() { panel.getUndoManager().addEdit(compound); - if (duplicateWarning) { + if (panel.getDatabase().getDuplicationChecker().isDuplicateCiteKeyExisting(entry)) { warnDuplicateBibtexkey(); } else if (emptyWarning) { warnEmptyBibtexkey(); @@ -1044,6 +1041,17 @@ public void actionPerformed(ActionEvent e) { } } + public void close() { + if (tabbed.getSelectedComponent() == srcPanel) { + updateField(source); + if (lastSourceAccepted) { + panel.entryEditorClosing(EntryEditor.this); + } + } else { + panel.entryEditorClosing(EntryEditor.this); + } + } + class CloseAction extends AbstractAction { public CloseAction() { super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon()); @@ -1052,14 +1060,7 @@ public CloseAction() { @Override public void actionPerformed(ActionEvent e) { - if (tabbed.getSelectedComponent() == srcPanel) { - updateField(source); - if (lastSourceAccepted) { - panel.entryEditorClosing(EntryEditor.this); - } - } else { - panel.entryEditorClosing(EntryEditor.this); - } + close(); } } @@ -1104,11 +1105,12 @@ public void actionPerformed(ActionEvent event) { return; } - boolean isDuplicate = panel.getDatabase().setCiteKeyForEntry(entry, newValue); + entry.setCiteKey(newValue); if (newValue == null) { warnEmptyBibtexkey(); } else { + boolean isDuplicate = panel.getDatabase().getDuplicationChecker().isDuplicateCiteKeyExisting(entry); if (isDuplicate) { warnDuplicateBibtexkey(); } else { @@ -1117,7 +1119,7 @@ public void actionPerformed(ActionEvent event) { } // Add an UndoableKeyChange to the baseframe's undoManager. - UndoableKeyChange undoableKeyChange = new UndoableKeyChange(panel.getDatabase(), entry, oldValue, newValue); + UndoableKeyChange undoableKeyChange = new UndoableKeyChange(entry, oldValue, newValue); if (updateTimeStampIsSet()) { NamedCompound ce = new NamedCompound(undoableKeyChange.getPresentationName()); ce.addEdit(undoableKeyChange); @@ -1337,7 +1339,7 @@ public void actionPerformed(ActionEvent e) { // Store undo information: panel.getUndoManager().addEdit( - new UndoableKeyChange(panel.getDatabase(), entry, oldValue.orElse(null), + new UndoableKeyChange(entry, oldValue.orElse(null), entry.getCiteKeyOptional().get())); // Cite key always set here // here we update the field diff --git a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java index e20f0c9d07b..01f67e1936d 100644 --- a/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java +++ b/src/main/java/net/sf/jabref/gui/entryeditor/EntryEditorTab.java @@ -35,7 +35,9 @@ import net.sf.jabref.gui.keyboard.KeyBinding; import net.sf.jabref.gui.util.GUIUtil; import net.sf.jabref.logic.autocompleter.AutoCompleter; +import net.sf.jabref.logic.l10n.Localization; import net.sf.jabref.model.entry.BibEntry; +import net.sf.jabref.model.entry.FieldName; import net.sf.jabref.model.entry.FieldProperty; import net.sf.jabref.model.entry.InternalBibtexFields; @@ -151,7 +153,12 @@ private void setupPanel(JabRefFrame frame, BasePanel bPanel, boolean addKeyField false); defaultHeight = 0; } else { - fieldEditor = new TextArea(field, null); + String prompt = ""; + if (field.equals(FieldName.AUTHOR) || field.equals(FieldName.EDITOR)) { + prompt = String.format("%1$s and %1$s and others", Localization.lang("Firstname Lastname")); + } + + fieldEditor = new TextArea(field, null, prompt); bPanel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener((TextArea) fieldEditor); defaultHeight = fieldEditor.getPane().getPreferredSize().height; } diff --git a/src/main/java/net/sf/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/net/sf/jabref/gui/exporter/SaveDatabaseAction.java index 4e9ae829442..6aadc90727d 100644 --- a/src/main/java/net/sf/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/net/sf/jabref/gui/exporter/SaveDatabaseAction.java @@ -14,6 +14,7 @@ import net.sf.jabref.Globals; import net.sf.jabref.JabRefExecutorService; import net.sf.jabref.collab.ChangeScanner; +import net.sf.jabref.collab.FileUpdatePanel; import net.sf.jabref.gui.BasePanel; import net.sf.jabref.gui.FileDialog; import net.sf.jabref.gui.JabRefFrame; @@ -381,7 +382,7 @@ private boolean checkExternalModification() { scanner.displayResult(resolved -> { if (resolved) { panel.setUpdatedExternally(false); - SwingUtilities.invokeLater(() -> panel.getSidePaneManager().hide("fileUpdate")); + SwingUtilities.invokeLater(() -> panel.getSidePaneManager().hide(FileUpdatePanel.class)); } else { canceled = true; } @@ -399,7 +400,7 @@ private boolean checkExternalModification() { canceled = true; } else { panel.setUpdatedExternally(false); - panel.getSidePaneManager().hide("fileUpdate"); + panel.getSidePaneManager().hide(FileUpdatePanel.class); } } } diff --git a/src/main/java/net/sf/jabref/gui/externalfiles/DroppedFileHandler.java b/src/main/java/net/sf/jabref/gui/externalfiles/DroppedFileHandler.java index 70836ebec91..7b849872931 100644 --- a/src/main/java/net/sf/jabref/gui/externalfiles/DroppedFileHandler.java +++ b/src/main/java/net/sf/jabref/gui/externalfiles/DroppedFileHandler.java @@ -1,5 +1,6 @@ package net.sf.jabref.gui.externalfiles; +import java.awt.BorderLayout; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -8,12 +9,14 @@ import java.util.List; import java.util.Optional; +import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; +import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -235,10 +238,23 @@ private boolean tryXmpImport(String fileName, ExternalFileType fileType, NamedCo return false; } - JLabel confirmationMessage = new JLabel(Localization.lang("The PDF contains one or several BibTeX-records.") - + "\n" + Localization.lang("Do you want to import these as new entries into the current database?")); - - int reply = JOptionPane.showConfirmDialog(frame, confirmationMessage, + JLabel confirmationMessage = new JLabel( + Localization.lang("The PDF contains one or several BibTeX-records.") + + "\n" + + Localization.lang("Do you want to import these as new entries into the current database?")); + JPanel entriesPanel = new JPanel(); + entriesPanel.setLayout(new BoxLayout(entriesPanel, BoxLayout.Y_AXIS)); + xmpEntriesInFile.forEach(entry -> { + JTextArea entryArea = new JTextArea(entry.toString()); + entryArea.setEditable(false); + entriesPanel.add(entryArea); + }); + + JPanel contentPanel = new JPanel(new BorderLayout()); + contentPanel.add(confirmationMessage, BorderLayout.NORTH); + contentPanel.add(entriesPanel, BorderLayout.CENTER); + + int reply = JOptionPane.showConfirmDialog(frame, contentPanel, Localization.lang("XMP-metadata found in PDF: %0", fileName), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); diff --git a/src/main/java/net/sf/jabref/gui/fieldeditors/JTextAreaWithHighlighting.java b/src/main/java/net/sf/jabref/gui/fieldeditors/JTextAreaWithHighlighting.java index 94ef4d0c961..0f91af8ecbd 100644 --- a/src/main/java/net/sf/jabref/gui/fieldeditors/JTextAreaWithHighlighting.java +++ b/src/main/java/net/sf/jabref/gui/fieldeditors/JTextAreaWithHighlighting.java @@ -6,7 +6,6 @@ import java.util.regex.Pattern; import javax.swing.AbstractAction; -import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultHighlighter; @@ -20,13 +19,14 @@ import net.sf.jabref.Globals; import net.sf.jabref.gui.actions.Actions; import net.sf.jabref.gui.actions.PasteAction; +import net.sf.jabref.gui.util.component.JTextAreaWithPlaceholder; import net.sf.jabref.logic.search.SearchQueryHighlightListener; import net.sf.jabref.preferences.JabRefPreferences; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class JTextAreaWithHighlighting extends JTextArea implements SearchQueryHighlightListener { +public class JTextAreaWithHighlighting extends JTextAreaWithPlaceholder implements SearchQueryHighlightListener { private static final Log LOGGER = LogFactory.getLog(JTextAreaWithHighlighting.class); @@ -35,13 +35,22 @@ public class JTextAreaWithHighlighting extends JTextArea implements SearchQueryH private UndoManager undo; public JTextAreaWithHighlighting() { - super(); - setupUndoRedo(); - setupPasteListener(); + this(""); + } + + public JTextAreaWithHighlighting(String text) { + this(text, ""); } - JTextAreaWithHighlighting(String text) { - super(text); + /** + * Creates a text area with the ability to highlight parts of the content. + * It also defines a placeholder which will be displayed the content is empty. + * + * @param text + * @param placeholder + */ + public JTextAreaWithHighlighting(String text, String placeholder) { + super(text, placeholder); setupUndoRedo(); setupPasteListener(); } diff --git a/src/main/java/net/sf/jabref/gui/fieldeditors/TextArea.java b/src/main/java/net/sf/jabref/gui/fieldeditors/TextArea.java index fc38e399aeb..bc7b093cfdc 100644 --- a/src/main/java/net/sf/jabref/gui/fieldeditors/TextArea.java +++ b/src/main/java/net/sf/jabref/gui/fieldeditors/TextArea.java @@ -36,7 +36,11 @@ public class TextArea extends JTextAreaWithHighlighting implements FieldEditor { public TextArea(String fieldName, String content) { - super(content); + this(fieldName, content, ""); + } + + public TextArea(String fieldName, String content, String title) { + super(content, title); updateFont(); diff --git a/src/main/java/net/sf/jabref/gui/fieldeditors/TextField.java b/src/main/java/net/sf/jabref/gui/fieldeditors/TextField.java index dd4559078b8..8b9a93ed13a 100644 --- a/src/main/java/net/sf/jabref/gui/fieldeditors/TextField.java +++ b/src/main/java/net/sf/jabref/gui/fieldeditors/TextField.java @@ -7,7 +7,6 @@ import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JLabel; -import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.text.Document; import javax.swing.undo.CannotRedoException; @@ -20,6 +19,7 @@ import net.sf.jabref.gui.actions.PasteAction; import net.sf.jabref.gui.autocompleter.AutoCompleteListener; import net.sf.jabref.gui.fieldeditors.contextmenu.FieldTextMenu; +import net.sf.jabref.gui.util.component.JTextFieldWithPlaceholder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,7 +28,7 @@ * An implementation of the FieldEditor backed by a JTextField. Used for single-line input, only BibTex key at the * moment?! */ -public class TextField extends JTextField implements FieldEditor { +public class TextField extends JTextFieldWithPlaceholder implements FieldEditor { private static final Log LOGGER = LogFactory.getLog(TextField.class); @@ -39,7 +39,11 @@ public class TextField extends JTextField implements FieldEditor { public TextField(String fieldName, String content, boolean changeColorOnFocus) { - super(content); + this(fieldName, content, changeColorOnFocus, ""); + } + + public TextField(String fieldName, String content, boolean changeColorOnFocus, String title) { + super(content, title); setupPasteListener(); setupUndoRedo(); diff --git a/src/main/java/net/sf/jabref/gui/groups/GroupSelector.java b/src/main/java/net/sf/jabref/gui/groups/GroupSelector.java index 6ab6044123e..dcfc5e83c06 100644 --- a/src/main/java/net/sf/jabref/gui/groups/GroupSelector.java +++ b/src/main/java/net/sf/jabref/gui/groups/GroupSelector.java @@ -50,6 +50,7 @@ import net.sf.jabref.gui.SidePaneComponent; import net.sf.jabref.gui.SidePaneManager; import net.sf.jabref.gui.help.HelpAction; +import net.sf.jabref.gui.keyboard.KeyBinding; import net.sf.jabref.gui.maintable.MainTableDataModel; import net.sf.jabref.gui.undo.NamedCompound; import net.sf.jabref.gui.worker.AbstractWorker; @@ -125,6 +126,8 @@ public class GroupSelector extends SidePaneComponent implements TreeSelectionLis private final AddToGroupAction moveToGroup = new AddToGroupAction(true); private final RemoveFromGroupAction removeFromGroup = new RemoveFromGroupAction(); + private ToggleAction toggleAction; + /** * The first element for each group defines which field to use for the quicksearch. The next two define the name and @@ -133,6 +136,11 @@ public class GroupSelector extends SidePaneComponent implements TreeSelectionLis public GroupSelector(JabRefFrame frame, SidePaneManager manager) { super(manager, IconTheme.JabRefIcon.TOGGLE_GROUPS.getIcon(), Localization.lang("Groups")); + toggleAction = new ToggleAction(Localization.menuTitle("Toggle groups interface"), + Localization.menuTitle("Toggle groups interface"), + Globals.getKeyPrefs().getKey(KeyBinding.TOGGLE_GROUPS_INTERFACE), + IconTheme.JabRefIcon.TOGGLE_GROUPS); + this.frame = frame; hideNonHits = new JRadioButtonMenuItem(Localization.lang("Hide non-hits"), !Globals.prefs.getBoolean(JabRefPreferences.GRAY_OUT_NON_HITS)); @@ -710,7 +718,7 @@ public void componentClosing() { if (panel != null) {// panel may be null if no file is open any more panel.getMainTable().getTableModel().updateGroupingState(MainTableDataModel.DisplayOption.DISABLED); } - frame.groupToggle.setSelected(false); + getToggleAction().setSelected(false); } private void setGroups(GroupTreeNode groupsRoot) { @@ -1204,7 +1212,7 @@ public Enumeration getExpandedPaths() { public void setActiveBasePanel(BasePanel panel) { super.setActiveBasePanel(panel); if (panel == null) { // hide groups - frame.getSidePaneManager().hide("groups"); + frame.getSidePaneManager().hide(GroupSelector.class); return; } MetaData metaData = panel.getBibDatabaseContext().getMetaData(); @@ -1261,4 +1269,15 @@ public GroupsTree getGroupsTree() { public void listen(GroupUpdatedEvent updateEvent) { setGroups(updateEvent.getMetaData().getGroups().orElse(null)); } + + @Override + public void grabFocus() { + groupsTree.grabFocus(); + } + + @Override + public ToggleAction getToggleAction() { + return toggleAction; + } + } diff --git a/src/main/java/net/sf/jabref/gui/importer/fetcher/EntryFetchers.java b/src/main/java/net/sf/jabref/gui/importer/fetcher/EntryFetchers.java index ce479fe9da5..6d51fcfcf93 100644 --- a/src/main/java/net/sf/jabref/gui/importer/fetcher/EntryFetchers.java +++ b/src/main/java/net/sf/jabref/gui/importer/fetcher/EntryFetchers.java @@ -11,6 +11,7 @@ import net.sf.jabref.logic.importer.fetcher.AstrophysicsDataSystem; import net.sf.jabref.logic.importer.fetcher.DiVA; import net.sf.jabref.logic.importer.fetcher.DoiFetcher; +import net.sf.jabref.logic.importer.fetcher.GoogleScholar; import net.sf.jabref.logic.importer.fetcher.GvkFetcher; import net.sf.jabref.logic.importer.fetcher.IsbnFetcher; import net.sf.jabref.logic.importer.fetcher.MathSciNet; @@ -30,7 +31,6 @@ public EntryFetchers(JournalAbbreviationLoader abbreviationLoader) { // entryFetchers.add(new OAI2Fetcher()); - new arXiv fetcher in place, see below // entryFetchers.add(new ScienceDirectFetcher()); currently not working - removed see #409 entryFetchers.add(new ACMPortalFetcher()); - entryFetchers.add(new GoogleScholarFetcher()); entryFetchers.add(new DOAJFetcher()); entryFetchers.add(new SpringerFetcher()); @@ -40,6 +40,7 @@ public EntryFetchers(JournalAbbreviationLoader abbreviationLoader) { new SearchBasedEntryFetcher(new AstrophysicsDataSystem(Globals.prefs.getImportFormatPreferences()))); entryFetchers.add(new SearchBasedEntryFetcher(new MathSciNet(Globals.prefs.getImportFormatPreferences()))); entryFetchers.add(new SearchBasedEntryFetcher(new zbMATH(Globals.prefs.getImportFormatPreferences()))); + entryFetchers.add(new SearchBasedEntryFetcher(new GoogleScholar(Globals.prefs.getImportFormatPreferences()))); } public List getEntryFetchers() { diff --git a/src/main/java/net/sf/jabref/gui/importer/fetcher/GeneralFetcher.java b/src/main/java/net/sf/jabref/gui/importer/fetcher/GeneralFetcher.java index 5d95de98926..0897baad761 100644 --- a/src/main/java/net/sf/jabref/gui/importer/fetcher/GeneralFetcher.java +++ b/src/main/java/net/sf/jabref/gui/importer/fetcher/GeneralFetcher.java @@ -12,8 +12,6 @@ import java.util.Comparator; import java.util.List; -import javax.swing.AbstractAction; -import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; @@ -44,15 +42,13 @@ public class GeneralFetcher extends SidePaneComponent implements ActionListener private final JPanel optionsPanel = new JPanel(optionsCards); private final JPanel optPanel = new JPanel(new BorderLayout()); - private final SidePaneManager sidePaneManager; - private final Action action; + private final ToggleAction action; private final JabRefFrame frame; private EntryFetcher activeFetcher; - public GeneralFetcher(SidePaneManager p0, JabRefFrame frame) { - super(p0, IconTheme.JabRefIcon.WWW.getSmallIcon(), Localization.lang("Web search")); - this.sidePaneManager = p0; + public GeneralFetcher(JabRefFrame frame, SidePaneManager sidePaneManager) { + super(sidePaneManager, IconTheme.JabRefIcon.WWW.getSmallIcon(), Localization.lang("Web search")); this.frame = frame; List fetchers = new EntryFetchers(Globals.journalAbbreviationLoader).getEntryFetchers(); EntryFetcher[] fetcherArray = fetchers.toArray(new EntryFetcher[fetchers.size()]); @@ -93,9 +89,10 @@ public GeneralFetcher(SidePaneManager p0, JabRefFrame frame) { revalidate(); }); - action = new FetcherAction(); - - + action = new ToggleAction(Localization.lang("Web search"), + Localization.lang("Toggle web search interface"), + Globals.getKeyPrefs().getKey(KeyBinding.WEB_SEARCH), + IconTheme.JabRefIcon.WWW); helpBut.setMargin(new Insets(0, 0, 0, 0)); tf.setPreferredSize(new Dimension(1, tf.getPreferredSize().height)); @@ -158,7 +155,8 @@ private JTextField getTextField() { return tf; } - public Action getAction() { + @Override + public ToggleAction getToggleAction() { return action; } @@ -225,37 +223,15 @@ public void actionPerformed(ActionEvent e) { } } - - class FetcherAction extends AbstractAction { - - public FetcherAction() { - super(Localization.lang("Web search"), IconTheme.JabRefIcon.WWW.getSmallIcon()); - //if ((activeFetcher.getKeyName() != null) && (activeFetcher.getKeyName().length() > 0)) - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.WEB_SEARCH)); - putValue(Action.LARGE_ICON_KEY, IconTheme.JabRefIcon.WWW.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Toggle web search interface")); - } - - @Override - public void actionPerformed(ActionEvent e) { - if (!sidePaneManager.hasComponent(GeneralFetcher.this.getTitle())) { - sidePaneManager.register(GeneralFetcher.this.getTitle(), GeneralFetcher.this); - } - - if (frame.getTabbedPane().getTabCount() > 0) { - sidePaneManager.toggle(GeneralFetcher.this.getTitle()); - if (sidePaneManager.isComponentVisible(GeneralFetcher.this.getTitle())) { - getTextField().requestFocus(); - } - } - } + @Override + public void grabFocus() { + getTextField().grabFocus(); } - @Override public void componentClosing() { super.componentClosing(); - frame.setFetcherToggle(false); + getToggleAction().setSelected(false); Globals.prefs.putBoolean(JabRefPreferences.WEB_SEARCH_VISIBLE, Boolean.FALSE); } diff --git a/src/main/java/net/sf/jabref/gui/importer/fetcher/GoogleScholarFetcher.java b/src/main/java/net/sf/jabref/gui/importer/fetcher/GoogleScholarFetcher.java deleted file mode 100644 index a51733928fa..00000000000 --- a/src/main/java/net/sf/jabref/gui/importer/fetcher/GoogleScholarFetcher.java +++ /dev/null @@ -1,272 +0,0 @@ -package net.sf.jabref.gui.importer.fetcher; - -import java.io.IOException; -import java.io.StringReader; -import java.net.MalformedURLException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; - -import net.sf.jabref.Globals; -import net.sf.jabref.gui.importer.FetcherPreviewDialog; -import net.sf.jabref.logic.help.HelpFile; -import net.sf.jabref.logic.importer.ImportInspector; -import net.sf.jabref.logic.importer.OutputPrinter; -import net.sf.jabref.logic.importer.ParserResult; -import net.sf.jabref.logic.importer.fileformat.BibtexParser; -import net.sf.jabref.logic.l10n.Localization; -import net.sf.jabref.logic.net.URLDownload; -import net.sf.jabref.model.entry.BibEntry; -import net.sf.jabref.model.entry.FieldName; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class GoogleScholarFetcher implements PreviewEntryFetcher { - - private static final Log LOGGER = LogFactory.getLog(GoogleScholarFetcher.class); - - private static final String QUERY_MARKER = "___QUERY___"; - private static final String URL_START = "https://scholar.google.com"; - private static final String SEARCH_URL = GoogleScholarFetcher.URL_START + "/scholar?q=" + GoogleScholarFetcher.QUERY_MARKER - + "&hl=en&btnG=Search&oe=utf-8"; - private static final String CITATIONS_PAGE_URL_BASE = "https://scholar.google.de/scholar?q=info:"; - private static final String CITATIONS_PAGE_URL_SUFFIX = ":scholar.google.com/&output=cite&scirp=0&hl=en"; - - private static final Pattern SCHOLAR_ID_PATTERN = Pattern.compile("gs_ocit\\(event,'(\\w*)'"); - private static final Pattern BIBTEX_LINK_PATTERN = Pattern.compile("href=\"(.*)\">BibTeX"); - private static final Pattern TITLE_START_PATTERN = Pattern.compile("
"); - private static final Pattern LINK_PATTERN = Pattern.compile("

"); - private static final Pattern TITLE_END_PATTERN = Pattern.compile("
"); - - private static final Pattern INPUT_PATTERN = Pattern.compile(" ]+)"); - - private final Map entryLinks = new HashMap<>(); - - private boolean stopFetching; - - - @Override - public int getWarningLimit() { - return 10; - } - - @Override - public int getPreferredPreviewHeight() { - return 100; - } - - @Override - public boolean processQuery(String query, ImportInspector inspector, OutputPrinter status) { - return false; - } - - @Override - public boolean processQueryGetPreview(String query, FetcherPreviewDialog preview, OutputPrinter status) { - entryLinks.clear(); - stopFetching = false; - try { - Map citations = getCitations(query); - if (!citations.isEmpty()) { - for (Map.Entry linkEntry : citations.entrySet()) { - preview.addEntry(linkEntry.getKey(), linkEntry.getValue()); - } - return true; - } - - status.showMessage(Localization.lang("No entries found for the search string '%0'", query), - Localization.lang("Search %0", getTitle()), JOptionPane.INFORMATION_MESSAGE); - } catch (IOException e) { - LOGGER.error("Error while fetching from " + getTitle(), e); - preview.showErrorMessage(this.getTitle(), e.getLocalizedMessage()); - } - return false; - } - - @Override - public void getEntries(Map selection, ImportInspector inspector) { - int toDownload = 0; - for (Map.Entry selEntry : selection.entrySet()) { - boolean isSelected = selEntry.getValue(); - if (isSelected) { - toDownload++; - } - } - if (toDownload == 0) { - return; - } - - int downloaded = 0; - - for (Map.Entry selEntry : selection.entrySet()) { - if (stopFetching) { - break; - } - inspector.setProgress(downloaded, toDownload); - boolean isSelected = selEntry.getValue(); - if (isSelected) { - downloaded++; - try { - BibEntry entry = downloadEntry(selEntry.getKey()); - inspector.addEntry(entry); - } catch (IOException e) { - LOGGER.warn("Cannot download entry from Google scholar", e); - } - } - } - - } - - @Override - public String getTitle() { - return "Google Scholar"; - } - - @Override - public HelpFile getHelpPage() { - return HelpFile.FETCHER_GOOGLE_SCHOLAR; - } - - @Override - public JPanel getOptionsPanel() { - return null; - } - - @Override - public void stopFetching() { - stopFetching = true; - } - - /** - * @param query The search term to query Google Scholar for. - * @return a list of IDs - * @throws java.io.IOException - */ - private Map getCitations(String query) throws IOException { - String urlQuery; - LinkedHashMap res = new LinkedHashMap<>(); - - urlQuery = GoogleScholarFetcher.SEARCH_URL.replace(GoogleScholarFetcher.QUERY_MARKER, - URLEncoder.encode(query, StandardCharsets.UTF_8.name())); - int count = 1; - String nextPage; - while (((nextPage = getCitationsFromUrl(urlQuery, res)) != null) && (count < 2)) { - urlQuery = nextPage; - count++; - if (stopFetching) { - break; - } - } - return res; - } - - private String getCitationsFromUrl(String urlQuery, Map ids) throws IOException { - String cont = URLDownload.createURLDownloadWithBrowserUserAgent(urlQuery).downloadToString(StandardCharsets.UTF_8); - - Matcher m = GoogleScholarFetcher.SCHOLAR_ID_PATTERN.matcher(cont); - int lastRegionStart = 0; - - while (m.find()) { - - String citationsPageURL = CITATIONS_PAGE_URL_BASE+m.group(1)+CITATIONS_PAGE_URL_SUFFIX; - - String citationsPage = URLDownload.createURLDownloadWithBrowserUserAgent(citationsPageURL).downloadToString(StandardCharsets.UTF_8); - - Matcher citationPageMatcher = GoogleScholarFetcher.BIBTEX_LINK_PATTERN.matcher(citationsPage); - citationPageMatcher.find(); - String link = citationPageMatcher.group(1).replace("&", "&"); - - String pText; - String part = cont.substring(lastRegionStart, m.start()); - Matcher titleS = GoogleScholarFetcher.TITLE_START_PATTERN.matcher(part); - Matcher titleE = GoogleScholarFetcher.TITLE_END_PATTERN.matcher(part); - boolean fS = titleS.find(); - boolean fE = titleE.find(); - if (fS && fE) { - if (titleS.end() < titleE.start()) { - pText = part.substring(titleS.end(), titleE.start()); - } else { - pText = part; - } - } else { - pText = link; - } - - pText = pText.replace("[PDF]", ""); - pText = pText.replace("[HTML]", ""); - JLabel preview = new JLabel("" + pText + ""); - ids.put(link, preview); - - // See if we can extract the link Google Scholar puts on the entry's title. - // That will be set as "url" for the entry if downloaded: - Matcher linkMatcher = GoogleScholarFetcher.LINK_PATTERN.matcher(pText); - if (linkMatcher.find()) { - entryLinks.put(link, linkMatcher.group(1)); - } - - lastRegionStart = m.end(); - } - - /*m = NEXT_PAGE_PATTERN.matcher(cont); - if (m.find()) { - System.out.println("NEXT: "+URL_START+m.group(1).replace("&", "&")); - return URL_START+m.group(1).replace("&", "&"); - } - else*/ - return null; - } - - private BibEntry downloadEntry(String link) throws IOException { - try { - String s = URLDownload.createURLDownloadWithBrowserUserAgent(link).downloadToString(StandardCharsets.UTF_8); - BibtexParser bp = new BibtexParser(Globals.prefs.getImportFormatPreferences()); - ParserResult pr = bp.parse(new StringReader(s)); - if ((pr != null) && (pr.getDatabase() != null)) { - Collection entries = pr.getDatabase().getEntries(); - if (entries.size() == 1) { - BibEntry entry = entries.iterator().next(); - entry.clearField(BibEntry.KEY_FIELD); - // If the entry's url field is not set, and we have stored an url for this - // entry, set it: - if (!entry.hasField(FieldName.URL)) { - String storedUrl = entryLinks.get(link); - if (storedUrl != null) { - entry.setField(FieldName.URL, storedUrl); - } - } - - // Clean up some remaining HTML code from Elsevier(?) papers - // Search for: Poincare algebra - // to see an example - entry.getField(FieldName.TITLE).ifPresent(title -> { - String newtitle = title.replaceAll("<.?i>([^<]*)", "$1"); - if (!newtitle.equals(title)) { - entry.setField(FieldName.TITLE, newtitle); - } - }); - return entry; - } else if (entries.isEmpty()) { - LOGGER.warn("No entry found! (" + link + ")"); - return null; - } else { - LOGGER.debug(entries.size() + " entries found! (" + link + ")"); - return null; - } - } - LOGGER.warn("Parser failed! (" + link + ")"); - return null; - } catch (MalformedURLException ex) { - LOGGER.error("Malformed URL.", ex); - return null; - } - } -} diff --git a/src/main/java/net/sf/jabref/gui/maintable/MainTableSelectionListener.java b/src/main/java/net/sf/jabref/gui/maintable/MainTableSelectionListener.java index 92821260f1e..4208c9dc41e 100644 --- a/src/main/java/net/sf/jabref/gui/maintable/MainTableSelectionListener.java +++ b/src/main/java/net/sf/jabref/gui/maintable/MainTableSelectionListener.java @@ -102,6 +102,12 @@ public void listChanged(ListEvent e) { } final BibEntry newSelected = selected.get(0); + if ((panel.getMode() == BasePanelMode.SHOWING_EDITOR || panel.getMode() == BasePanelMode.WILL_SHOW_EDITOR) + && panel.getCurrentEditor() != null && newSelected == panel.getCurrentEditor().getEntry()) { + // entry already selected and currently editing it, do not steal the focus from the selected textfield + return; + } + if (newSelected != null) { final BasePanelMode mode = panel.getMode(); // What is the panel already showing? if ((mode == BasePanelMode.WILL_SHOW_EDITOR) || (mode == BasePanelMode.SHOWING_EDITOR)) { diff --git a/src/main/java/net/sf/jabref/gui/menus/RightClickMenu.java b/src/main/java/net/sf/jabref/gui/menus/RightClickMenu.java index 0d396133873..be474171ecd 100644 --- a/src/main/java/net/sf/jabref/gui/menus/RightClickMenu.java +++ b/src/main/java/net/sf/jabref/gui/menus/RightClickMenu.java @@ -7,8 +7,8 @@ import java.util.Optional; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.Icon; -import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; @@ -23,6 +23,7 @@ import net.sf.jabref.gui.JabRefFrame; import net.sf.jabref.gui.actions.Actions; import net.sf.jabref.gui.filelist.FileListTableModel; +import net.sf.jabref.gui.keyboard.KeyBinding; import net.sf.jabref.gui.mergeentries.FetchAndMergeEntry; import net.sf.jabref.gui.worker.MarkEntriesAction; import net.sf.jabref.logic.l10n.Localization; @@ -48,8 +49,6 @@ public class RightClickMenu extends JPopupMenu implements PopupMenuListener { private final JMenuItem groupAdd; private final JMenuItem groupRemove; private final JMenuItem groupMoveTo; - private final JCheckBoxMenuItem floatMarked = new JCheckBoxMenuItem(Localization.lang("Float marked entries"), - Globals.prefs.getBoolean(JabRefPreferences.FLOAT_MARKED_ENTRIES)); public RightClickMenu(JabRefFrame frame, BasePanel panel) { @@ -67,50 +66,46 @@ public RightClickMenu(JabRefFrame frame, BasePanel panel) { addPopupMenuListener(this); JMenu copySpecialMenu = new JMenu(Localization.lang("Copy") + "..."); - copySpecialMenu.add(new GeneralAction(Actions.COPY_KEY, Localization.lang("Copy BibTeX key"))); - copySpecialMenu.add(new GeneralAction(Actions.COPY_CITE_KEY, Localization.lang("Copy \\cite{BibTeX key}"))); - copySpecialMenu - .add(new GeneralAction(Actions.COPY_KEY_AND_TITLE, Localization.lang("Copy BibTeX key and title"))); - copySpecialMenu - .add(new GeneralAction(Actions.COPY_KEY_AND_LINK, Localization.lang("Copy BibTeX key and link"))); + copySpecialMenu.add(new GeneralAction(Actions.COPY_KEY, Localization.lang("Copy BibTeX key"), KeyBinding.COPY_BIBTEX_KEY)); + copySpecialMenu.add(new GeneralAction(Actions.COPY_CITE_KEY, Localization.lang("Copy \\cite{BibTeX key}"), KeyBinding.COPY_CITE_BIBTEX_KEY)); + copySpecialMenu.add(new GeneralAction(Actions.COPY_KEY_AND_TITLE, Localization.lang("Copy BibTeX key and title"), KeyBinding.COPY_BIBTEX_KEY_AND_TITLE)); + copySpecialMenu.add(new GeneralAction(Actions.COPY_KEY_AND_LINK, Localization.lang("Copy BibTeX key and link"), KeyBinding.COPY_BIBTEX_KEY_AND_LINK)); copySpecialMenu.add(new GeneralAction(Actions.EXPORT_TO_CLIPBOARD, Localization.lang("Export to clipboard"), IconTheme.JabRefIcon.EXPORT_TO_CLIPBOARD.getSmallIcon())); - add(new GeneralAction(Actions.COPY, Localization.lang("Copy"), IconTheme.JabRefIcon.COPY.getSmallIcon())); + add(new GeneralAction(Actions.COPY, Localization.lang("Copy"), IconTheme.JabRefIcon.COPY.getSmallIcon(), KeyBinding.COPY)); add(copySpecialMenu); - add(new GeneralAction(Actions.PASTE, Localization.lang("Paste"), IconTheme.JabRefIcon.PASTE.getSmallIcon())); - add(new GeneralAction(Actions.CUT, Localization.lang("Cut"), IconTheme.JabRefIcon.CUT.getSmallIcon())); - add(new GeneralAction(Actions.DELETE, Localization.lang("Delete"), IconTheme.JabRefIcon.DELETE_ENTRY.getSmallIcon())); - add(new GeneralAction(Actions.PRINT_PREVIEW, Localization.lang("Print entry preview"), IconTheme.JabRefIcon.PRINTED.getSmallIcon()) { - { - if (multiple) { - this.setEnabled(false); - } - } - }); + add(new GeneralAction(Actions.PASTE, Localization.lang("Paste"), IconTheme.JabRefIcon.PASTE.getSmallIcon(), KeyBinding.PASTE)); + add(new GeneralAction(Actions.CUT, Localization.lang("Cut"), IconTheme.JabRefIcon.CUT.getSmallIcon(), KeyBinding.CUT)); + add(new GeneralAction(Actions.DELETE, Localization.lang("Delete"), IconTheme.JabRefIcon.DELETE_ENTRY.getSmallIcon(), KeyBinding.DELETE_ENTRY)); + GeneralAction printPreviewAction = new GeneralAction(Actions.PRINT_PREVIEW, Localization.lang("Print entry preview"), IconTheme.JabRefIcon.PRINTED.getSmallIcon()); + printPreviewAction.setEnabled(!multiple); + add(printPreviewAction); + addSeparator(); add(new GeneralAction(Actions.SEND_AS_EMAIL, Localization.lang("Send as email"), IconTheme.JabRefIcon.EMAIL.getSmallIcon())); addSeparator(); JMenu markSpecific = JabRefFrame.subMenu(Localization.menuTitle("Mark specific color")); + markSpecific.setIcon(IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon()); for (int i = 0; i < EntryMarker.MAX_MARKING_LEVEL; i++) { markSpecific.add(new MarkEntriesAction(frame, i).getMenuItem()); } if (multiple) { - add(new GeneralAction(Actions.MARK_ENTRIES, Localization.lang("Mark entries"), IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon())); + add(new GeneralAction(Actions.MARK_ENTRIES, Localization.lang("Mark entries"), IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon(), KeyBinding.MARK_ENTRIES)); add(markSpecific); - add(new GeneralAction(Actions.UNMARK_ENTRIES, Localization.lang("Unmark entries"), IconTheme.JabRefIcon.UNMARK_ENTRIES.getSmallIcon())); + add(new GeneralAction(Actions.UNMARK_ENTRIES, Localization.lang("Unmark entries"), IconTheme.JabRefIcon.UNMARK_ENTRIES.getSmallIcon(), KeyBinding.UNMARK_ENTRIES)); } else if (be != null) { Optional marked = be.getField(FieldName.MARKED_INTERNAL); // We have to check for "" too as the marked field may be empty if ((!marked.isPresent()) || marked.get().isEmpty()) { - add(new GeneralAction(Actions.MARK_ENTRIES, Localization.lang("Mark entry"), IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon())); + add(new GeneralAction(Actions.MARK_ENTRIES, Localization.lang("Mark entry"), IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon(), KeyBinding.MARK_ENTRIES)); add(markSpecific); } else { add(markSpecific); - add(new GeneralAction(Actions.UNMARK_ENTRIES, Localization.lang("Unmark entry"), IconTheme.JabRefIcon.UNMARK_ENTRIES.getSmallIcon())); + add(new GeneralAction(Actions.UNMARK_ENTRIES, Localization.lang("Unmark entry"), IconTheme.JabRefIcon.UNMARK_ENTRIES.getSmallIcon(), KeyBinding.UNMARK_ENTRIES)); } } @@ -150,62 +145,43 @@ public RightClickMenu(JabRefFrame frame, BasePanel panel) { addSeparator(); - add(new GeneralAction(Actions.OPEN_FOLDER, Localization.lang("Open folder")) { - { - if (!isFieldSetForSelectedEntry(FieldName.FILE)) { - this.setEnabled(false); - } - } - }); + GeneralAction openFolderAction = new GeneralAction(Actions.OPEN_FOLDER, Localization.lang("Open folder"), + KeyBinding.OPEN_FOLDER); + openFolderAction.setEnabled(isFieldSetForSelectedEntry(FieldName.FILE)); + add(openFolderAction); - add(new GeneralAction(Actions.OPEN_EXTERNAL_FILE, Localization.lang("Open file"), getFileIconForSelectedEntry()) { - { - if (!isFieldSetForSelectedEntry(FieldName.FILE)) { - this.setEnabled(false); - } - } - }); + GeneralAction openFileAction = new GeneralAction(Actions.OPEN_EXTERNAL_FILE, Localization.lang("Open file"), + getFileIconForSelectedEntry(), KeyBinding.OPEN_FILE); + openFileAction.setEnabled(isFieldSetForSelectedEntry(FieldName.FILE)); + add(openFileAction); - add(new GeneralAction(Actions.OPEN_URL, Localization.lang("Open URL or DOI"), IconTheme.JabRefIcon.WWW.getSmallIcon()) { - { - if(!(isFieldSetForSelectedEntry(FieldName.URL) || isFieldSetForSelectedEntry(FieldName.DOI))) { - this.setEnabled(false); - } - } - }); + GeneralAction openUrlAction = new GeneralAction(Actions.OPEN_URL, Localization.lang("Open URL or DOI"), + IconTheme.JabRefIcon.WWW.getSmallIcon(), KeyBinding.OPEN_URL_OR_DOI); + openUrlAction.setEnabled(isFieldSetForSelectedEntry(FieldName.URL) || isFieldSetForSelectedEntry(FieldName.DOI)); + add(openUrlAction); addSeparator(); add(typeMenu); - add(new GeneralAction(Actions.MERGE_WITH_FETCHED_ENTRY, - Localization.lang("Get BibTeX data from %0", FetchAndMergeEntry.getDisplayNameOfSupportedFields())) { - { - if (!(isAnyFieldSetForSelectedEntry(FetchAndMergeEntry.SUPPORTED_FIELDS))) { - this.setEnabled(false); - } - } - }); + GeneralAction mergeFetchedEntryAction = new GeneralAction(Actions.MERGE_WITH_FETCHED_ENTRY, + Localization.lang("Get BibTeX data from %0", FetchAndMergeEntry.getDisplayNameOfSupportedFields())); + mergeFetchedEntryAction.setEnabled(isAnyFieldSetForSelectedEntry(FetchAndMergeEntry.SUPPORTED_FIELDS)); + add(mergeFetchedEntryAction); + add(frame.getMassSetField()); - add(new GeneralAction(Actions.ADD_FILE_LINK, Localization.lang("Attach file"), IconTheme.JabRefIcon.ATTACH_FILE.getSmallIcon()) { - { - if (multiple) { - this.setEnabled(false); - } - } - }); - add(frame.getManageKeywords()); - add(new GeneralAction(Actions.MERGE_ENTRIES, - Localization.lang("Merge entries") + "...", - IconTheme.JabRefIcon.MERGE_ENTRIES.getSmallIcon()) { - { - if (!(areExactlyTwoEntriesSelected())) { - this.setEnabled(false); - } - } + GeneralAction attachFileAction = new GeneralAction(Actions.ADD_FILE_LINK, Localization.lang("Attach file"), + IconTheme.JabRefIcon.ATTACH_FILE.getSmallIcon()); + attachFileAction.setEnabled(!multiple); + add(attachFileAction); + + add(frame.getManageKeywords()); - }); + GeneralAction mergeEntriesAction = new GeneralAction(Actions.MERGE_ENTRIES, + Localization.lang("Merge entries") + "...", IconTheme.JabRefIcon.MERGE_ENTRIES.getSmallIcon()); + mergeEntriesAction.setEnabled(areExactlyTwoEntriesSelected()); + add(mergeEntriesAction); addSeparator(); // for "add/move/remove to/from group" entries (appended here) @@ -247,15 +223,11 @@ public static void populateSpecialFieldMenu(JMenu menu, SpecialField field, JabR @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { panel.storeCurrentEdit(); - if (panel.getBibDatabaseContext().getMetaData().getGroups().isPresent()) { - groupAdd.setEnabled(true); - groupRemove.setEnabled(true); - groupMoveTo.setEnabled(true); - } else { - groupAdd.setEnabled(false); - groupRemove.setEnabled(false); - groupMoveTo.setEnabled(false); - } + + boolean groupsPresent = panel.getBibDatabaseContext().getMetaData().getGroups().isPresent(); + groupAdd.setEnabled(groupsPresent); + groupRemove.setEnabled(groupsPresent); + groupMoveTo.setEnabled(groupsPresent); } @@ -308,6 +280,18 @@ public GeneralAction(String command, String name, Icon icon) { this.command = command; } + public GeneralAction(String command, String name, KeyBinding key) { + super(name); + this.command = command; + putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(key)); + } + + public GeneralAction(String command, String name, Icon icon, KeyBinding key) { + super(name, icon); + this.command = command; + putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(key)); + } + @Override public void actionPerformed(ActionEvent e) { try { diff --git a/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficePanel.java index 5934c435587..45ab2217451 100644 --- a/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficePanel.java @@ -82,7 +82,7 @@ public class OpenOfficePanel extends AbstractWorker { private static final Log LOGGER = LogFactory.getLog(OpenOfficePanel.class); - private OOPanel comp; + private OpenOfficeSidePanel sidePane; private JDialog diag; private final JButton connect; private final JButton manualConnect; @@ -101,7 +101,6 @@ public class OpenOfficePanel extends AbstractWorker { HelpFile.OPENOFFICE_LIBREOFFICE).getHelpButton(); private OOBibBase ooBase; private JabRefFrame frame; - private SidePaneManager manager; private OOBibStyle style; private StyleSelectDialog styleDialog; private boolean dialogOkPressed; @@ -111,10 +110,8 @@ public class OpenOfficePanel extends AbstractWorker { private final OpenOfficePreferences preferences; private final StyleLoader loader; - private static OpenOfficePanel instance; - - private OpenOfficePanel() { + public OpenOfficePanel(JabRefFrame jabRefFrame, SidePaneManager spManager) { Icon connectImage = IconTheme.JabRefIcon.CONNECT_OPEN_OFFICE.getSmallIcon(); connect = new JButton(connectImage); @@ -134,36 +131,11 @@ private OpenOfficePanel() { loader = new StyleLoader(preferences, Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader), Globals.prefs.getDefaultEncoding()); - } - - public static OpenOfficePanel getInstance() { - if (OpenOfficePanel.instance == null) { - OpenOfficePanel.instance = new OpenOfficePanel(); - } - return OpenOfficePanel.instance; - } - - public SidePaneComponent getSidePaneComponent() { - return comp; - } - public void init(JabRefFrame jabRefFrame, SidePaneManager spManager) { this.frame = jabRefFrame; - this.manager = spManager; - comp = new OOPanel(spManager, IconTheme.getImage("openoffice"), "OpenOffice/LibreOffice", this); + sidePane = new OpenOfficeSidePanel(spManager, IconTheme.getImage("openoffice"), "OpenOffice/LibreOffice", preferences); initPanel(); - spManager.register(getName(), comp); - } - - public JMenuItem getMenuItem() { - if (preferences.showPanel()) { - manager.show(getName()); - } - JMenuItem item = new JMenuItem(Localization.lang("OpenOffice/LibreOffice connection"), - IconTheme.getImage("openoffice")); - item.addActionListener(event -> manager.show(getName())); - item.setAccelerator(Globals.getKeyPrefs().getKey(KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION)); - return item; + spManager.register(sidePane); } private void initPanel() { @@ -326,7 +298,7 @@ public void actionPerformed(ActionEvent e) { mainBuilder.add(settingsB).xy(1, 10); JPanel content = new JPanel(); - comp.setContentContainer(content); + sidePane.setContentContainer(content); content.setLayout(new BorderLayout()); content.add(mainBuilder.getPanel(), BorderLayout.CENTER); @@ -719,7 +691,7 @@ private boolean checkThatEntriesHaveKeys(List entries) { prefs); // Add undo change undoCompound.addEdit( - new UndoableKeyChange(panel.getDatabase(), entry, null, entry.getCiteKeyOptional().get())); + new UndoableKeyChange(entry, null, entry.getCiteKeyOptional().get())); } } undoCompound.end(); @@ -806,40 +778,8 @@ private void showSettingsPopup() { menu.show(settingsB, 0, settingsB.getHeight()); } - public String getName() { - return "OpenOffice/LibreOffice"; - } - - - private class OOPanel extends SidePaneComponent { - - private final OpenOfficePanel openOfficePanel; - - - public OOPanel(SidePaneManager sidePaneManager, Icon url, String s, OpenOfficePanel panel) { - super(sidePaneManager, url, s); - openOfficePanel = panel; - } - - @Override - public String getName() { - return openOfficePanel.getName(); - } - - @Override - public void componentClosing() { - preferences.setShowPanel(false); - } - - @Override - public void componentOpening() { - preferences.setShowPanel(true); - } - - @Override - public int getRescalingWeight() { - return 0; - } + public SidePaneComponent.ToggleAction getToggleAction() { + return sidePane.getToggleAction(); } } diff --git a/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficeSidePanel.java b/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficeSidePanel.java new file mode 100644 index 00000000000..5c3797cfef2 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/openoffice/OpenOfficeSidePanel.java @@ -0,0 +1,52 @@ +package net.sf.jabref.gui.openoffice; + +import javax.swing.Icon; + +import net.sf.jabref.Globals; +import net.sf.jabref.gui.SidePaneComponent; +import net.sf.jabref.gui.SidePaneManager; +import net.sf.jabref.gui.keyboard.KeyBinding; +import net.sf.jabref.logic.l10n.Localization; +import net.sf.jabref.logic.openoffice.OpenOfficePreferences; + +public class OpenOfficeSidePanel extends SidePaneComponent { + + private OpenOfficePreferences preferences; + private final ToggleAction toggleAction; + + + public OpenOfficeSidePanel(SidePaneManager sidePaneManager, Icon icon, String title, OpenOfficePreferences preferences) { + super(sidePaneManager, icon, title); + this.preferences = preferences; + sidePaneManager.register(this); + if (preferences.showPanel()) { + manager.show(OpenOfficeSidePanel.class); + } + + toggleAction = new ToggleAction(Localization.lang("OpenOffice/LibreOffice connection"), + Localization.lang("OpenOffice/LibreOffice connection"), + Globals.getKeyPrefs().getKey(KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), + icon); + } + + @Override + public void componentClosing() { + preferences.setShowPanel(false); + } + + @Override + public void componentOpening() { + preferences.setShowPanel(true); + } + + @Override + public int getRescalingWeight() { + return 0; + } + + @Override + public ToggleAction getToggleAction() { + return toggleAction; + } + +} diff --git a/src/main/java/net/sf/jabref/gui/preftabs/AppearancePrefsTab.java b/src/main/java/net/sf/jabref/gui/preftabs/AppearancePrefsTab.java index 135c07783cd..7547effefbd 100644 --- a/src/main/java/net/sf/jabref/gui/preftabs/AppearancePrefsTab.java +++ b/src/main/java/net/sf/jabref/gui/preftabs/AppearancePrefsTab.java @@ -182,7 +182,7 @@ public AppearancePrefsTab(JabRefPreferences prefs) { overrideFonts.addActionListener(e -> fontSize.setEnabled(overrideFonts.isSelected())); fontButton.addActionListener( - e -> new FontSelectorDialog(null, GUIGlobals.currentFont).getSelectedFont().ifPresent(x -> usedFont = x)); + e -> new FontSelectorDialog(null, usedFont).getSelectedFont().ifPresent(x -> usedFont = x)); JPanel pan = builder.getPanel(); pan.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); diff --git a/src/main/java/net/sf/jabref/gui/preftabs/PreferencesDialog.java b/src/main/java/net/sf/jabref/gui/preftabs/PreferencesDialog.java index ca8d5476c47..95b99510324 100644 --- a/src/main/java/net/sf/jabref/gui/preftabs/PreferencesDialog.java +++ b/src/main/java/net/sf/jabref/gui/preftabs/PreferencesDialog.java @@ -42,6 +42,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + /** * Preferences dialog. Contains a TabbedPane, and tabs will be defined in * separate classes. Tabs MUST implement the PrefsTab interface, since this @@ -153,21 +154,7 @@ public PreferencesDialog(JabRefFrame parent) { // Import and export actions: exportPreferences.setToolTipText(Localization.lang("Export preferences to file")); - exportPreferences.addActionListener(e -> { - FileDialog dialog = new FileDialog(frame).withExtension(FileExtensions.XML); - dialog.setDefaultExtension(FileExtensions.XML); - Optional path = dialog.saveNewFile(); - - path.ifPresent(exportFile -> { - try { - prefs.exportPreferences(exportFile.toString()); - } catch (JabRefException ex) { - LOGGER.warn(ex.getMessage(), ex); - JOptionPane.showMessageDialog(PreferencesDialog.this, ex.getLocalizedMessage(), - Localization.lang("Export preferences"), JOptionPane.ERROR_MESSAGE); - } - }); - }); + exportPreferences.addActionListener(new ExportAction()); importPreferences.setToolTipText(Localization.lang("Import preferences from file")); importPreferences.addActionListener(e -> { @@ -182,6 +169,7 @@ public PreferencesDialog(JabRefFrame parent) { JOptionPane.showMessageDialog(PreferencesDialog.this, Localization.lang("You must restart JabRef for this to come into effect."), Localization.lang("Import preferences"), JOptionPane.WARNING_MESSAGE); + this.dispose(); } catch (JabRefException ex) { LOGGER.warn(ex.getMessage(), ex); JOptionPane.showMessageDialog(PreferencesDialog.this, ex.getLocalizedMessage(), @@ -191,7 +179,7 @@ public PreferencesDialog(JabRefFrame parent) { }); showPreferences.addActionListener( - e -> new PreferencesFilterDialog(new JabRefPreferencesFilter(Globals.prefs), frame).setVisible(true)); + e -> new PreferencesFilterDialog(new JabRefPreferencesFilter(prefs), frame).setVisible(true)); resetPreferences.addActionListener(e -> { if (JOptionPane.showConfirmDialog(PreferencesDialog.this, Localization.lang("Are you sure you want to reset all settings to default values?"), @@ -229,6 +217,28 @@ private void updateAfterPreferenceChanges() { Globals.prefs.updateEntryEditorTabList(); } + private void storeAllSettings(){ + // First check that all tabs are ready to close: + Component[] preferenceTabs = main.getComponents(); + for (Component tab: preferenceTabs) { + if (!((PrefsTab) tab).validateSettings()) { + return; // If not, break off. + } + } + // Then store settings and close: + for (Component tab: preferenceTabs) { + ((PrefsTab) tab).storeSettings(); + } + Globals.prefs.flush(); + + setVisible(false); + MainTable.updateRenderers(); + GUIGlobals.updateEntryEditorColors(); + frame.setupAllTables(); + frame.getGroupSelector().revalidateGroups(); // icons may have changed + frame.output(Localization.lang("Preferences recorded.")); + } + class OkAction extends AbstractAction { @@ -238,27 +248,32 @@ public OkAction() { @Override public void actionPerformed(ActionEvent e) { + storeAllSettings(); + } + } - // First check that all tabs are ready to close: - int count = main.getComponentCount(); - Component[] comps = main.getComponents(); - for (int i = 0; i < count; i++) { - if (!((PrefsTab) comps[i]).validateSettings()) { - return; // If not, break off. - } - } - // Then store settings and close: - for (int i = 0; i < count; i++) { - ((PrefsTab) comps[i]).storeSettings(); - } - Globals.prefs.flush(); + class ExportAction extends AbstractAction { - setVisible(false); - MainTable.updateRenderers(); - GUIGlobals.updateEntryEditorColors(); - frame.setupAllTables(); - frame.getGroupSelector().revalidateGroups(); // icons may have changed - frame.output(Localization.lang("Preferences recorded.")); + public ExportAction() { + super("Export"); + } + + @Override + public void actionPerformed(ActionEvent e) { + FileDialog dialog = new FileDialog(frame).withExtension(FileExtensions.XML); + dialog.setDefaultExtension(FileExtensions.XML); + Optional path = dialog.saveNewFile(); + + path.ifPresent(exportFile -> { + try { + storeAllSettings(); + Globals.prefs.exportPreferences(exportFile.toString()); + } catch (JabRefException ex) { + LOGGER.warn(ex.getMessage(), ex); + JOptionPane.showMessageDialog(PreferencesDialog.this, ex.getLocalizedMessage(), + Localization.lang("Export preferences"), JOptionPane.WARNING_MESSAGE); + } + }); } } diff --git a/src/main/java/net/sf/jabref/gui/search/GlobalSearchBar.java b/src/main/java/net/sf/jabref/gui/search/GlobalSearchBar.java index 68324c3a0ba..9210ae4ae22 100644 --- a/src/main/java/net/sf/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/net/sf/jabref/gui/search/GlobalSearchBar.java @@ -16,6 +16,7 @@ import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.JToolBar; +import javax.swing.SwingUtilities; import net.sf.jabref.Globals; import net.sf.jabref.gui.BasePanel; @@ -26,8 +27,9 @@ import net.sf.jabref.gui.autocompleter.AutoCompleteSupport; import net.sf.jabref.gui.help.HelpAction; import net.sf.jabref.gui.keyboard.KeyBinding; +import net.sf.jabref.gui.maintable.MainTable; import net.sf.jabref.gui.maintable.MainTableDataModel; -import net.sf.jabref.gui.util.component.JTextFieldWithUnfocusedText; +import net.sf.jabref.gui.util.component.JTextFieldWithPlaceholder; import net.sf.jabref.logic.autocompleter.AutoCompleter; import net.sf.jabref.logic.help.HelpFile; import net.sf.jabref.logic.l10n.Localization; @@ -47,7 +49,7 @@ public class GlobalSearchBar extends JPanel { private final JabRefFrame frame; private final JLabel searchIcon = new JLabel(IconTheme.JabRefIcon.SEARCH.getSmallIcon()); - private final JTextFieldWithUnfocusedText searchField = new JTextFieldWithUnfocusedText(Localization.lang("Search") + "..."); + private final JTextFieldWithPlaceholder searchField = new JTextFieldWithPlaceholder(Localization.lang("Search") + "..."); private JButton openCurrentResultsInDialog = new JButton(IconTheme.JabRefIcon.OPEN_IN_NEW_WINDOW.getSmallIcon()); private final JToggleButton caseSensitive; @@ -136,7 +138,11 @@ public GlobalSearchBar(JabRefFrame frame) { searchField.getActionMap().put(endSearch, new AbstractAction() { @Override public void actionPerformed(ActionEvent event) { - endSearch(); + if (autoCompleteSupport.isVisible()) { + autoCompleteSupport.setVisible(false); + } else { + endSearch(); + } } }); @@ -249,12 +255,14 @@ private void updateSearchModeButtonText() { searchModeButton.setToolTipText(searchDisplayMode.getToolTipText()); } - private void endSearch() { + public void endSearch() { BasePanel currentBasePanel = frame.getCurrentBasePanel(); if (currentBasePanel != null) { clearSearch(currentBasePanel); - Globals.getFocusListener().setFocused(currentBasePanel.getMainTable()); - currentBasePanel.getMainTable().requestFocus(); + MainTable mainTable = frame.getCurrentBasePanel().getMainTable(); + Globals.getFocusListener().setFocused(mainTable); + mainTable.requestFocus(); + SwingUtilities.invokeLater(() -> mainTable.ensureVisible(mainTable.getSelectedRow())); } } @@ -264,6 +272,7 @@ private void endSearch() { public void focus() { if (!searchField.hasFocus()) { searchField.requestFocus(); + searchField.selectAll(); } } diff --git a/src/main/java/net/sf/jabref/gui/search/SearchWorker.java b/src/main/java/net/sf/jabref/gui/search/SearchWorker.java index 893a4f8bb9e..3c1291db866 100644 --- a/src/main/java/net/sf/jabref/gui/search/SearchWorker.java +++ b/src/main/java/net/sf/jabref/gui/search/SearchWorker.java @@ -9,6 +9,7 @@ import net.sf.jabref.JabRefGUI; import net.sf.jabref.gui.BasePanel; +import net.sf.jabref.gui.BasePanelMode; import net.sf.jabref.gui.maintable.MainTableDataModel; import net.sf.jabref.logic.search.SearchQuery; import net.sf.jabref.model.database.BibDatabase; @@ -92,14 +93,17 @@ private void updateUIWithSearchResult(List matchedEntries) { } // only selects the first match if the selected entries are no hits or no entry is selected - List selectedEntries = basePanel.getSelectedEntries(); - boolean isHitSelected = selectedEntries.stream().anyMatch(BibEntry::isSearchHit); - if (!isHitSelected && !matchedEntries.isEmpty()) { - for (int i = 0; i < basePanel.getMainTable().getRowCount(); i++) { - BibEntry entry = basePanel.getMainTable().getEntryAt(i); - if (entry.isSearchHit()) { - basePanel.getMainTable().setSelected(i); - break; + // and no editor is open (to avoid jumping around when editing an entry) + if (basePanel.getMode() != BasePanelMode.SHOWING_EDITOR && basePanel.getMode() != BasePanelMode.WILL_SHOW_EDITOR) { + List selectedEntries = basePanel.getSelectedEntries(); + boolean isHitSelected = selectedEntries.stream().anyMatch(BibEntry::isSearchHit); + if (!isHitSelected && !matchedEntries.isEmpty()) { + for (int i = 0; i < basePanel.getMainTable().getRowCount(); i++) { + BibEntry entry = basePanel.getMainTable().getEntryAt(i); + if (entry.isSearchHit()) { + basePanel.getMainTable().setSelected(i); + break; + } } } } diff --git a/src/main/java/net/sf/jabref/gui/shared/OpenSharedDatabaseDialog.java b/src/main/java/net/sf/jabref/gui/shared/OpenSharedDatabaseDialog.java index 87db077670d..dfdce9c80e9 100644 --- a/src/main/java/net/sf/jabref/gui/shared/OpenSharedDatabaseDialog.java +++ b/src/main/java/net/sf/jabref/gui/shared/OpenSharedDatabaseDialog.java @@ -36,6 +36,8 @@ import net.sf.jabref.gui.help.HelpAction; import net.sf.jabref.logic.help.HelpFile; import net.sf.jabref.logic.l10n.Localization; +import net.sf.jabref.model.database.BibDatabaseContext; +import net.sf.jabref.model.database.DatabaseLocation; import net.sf.jabref.shared.DBMSConnection; import net.sf.jabref.shared.DBMSConnectionProperties; import net.sf.jabref.shared.DBMSType; @@ -382,13 +384,11 @@ private void setLoadingConnectButtonText(boolean isLoading) { */ private boolean isSharedDatabaseAlreadyPresent() { List panels = JabRefGUI.getMainFrame().getBasePanelList(); - for (BasePanel panel : panels) { - DBMSConnectionProperties dbmsConnectionProperties = panel.getBibDatabaseContext().getDBMSSynchronizer() - .getDBProcessor().getDBMSConnectionProperties(); - if (this.connectionProperties.equals(dbmsConnectionProperties)) { - return true; - } - } - return false; + return panels.parallelStream().anyMatch(panel -> { + BibDatabaseContext context = panel.getBibDatabaseContext(); + return ((context.getLocation() == DatabaseLocation.SHARED) && + this.connectionProperties.equals(context.getDBMSSynchronizer() + .getDBProcessor().getDBMSConnectionProperties())); + }); } } diff --git a/src/main/java/net/sf/jabref/gui/undo/UndoableKeyChange.java b/src/main/java/net/sf/jabref/gui/undo/UndoableKeyChange.java index b135c9bbb20..861ef1ede65 100644 --- a/src/main/java/net/sf/jabref/gui/undo/UndoableKeyChange.java +++ b/src/main/java/net/sf/jabref/gui/undo/UndoableKeyChange.java @@ -1,7 +1,6 @@ package net.sf.jabref.gui.undo; import net.sf.jabref.logic.l10n.Localization; -import net.sf.jabref.model.database.BibDatabase; import net.sf.jabref.model.entry.BibEntry; import net.sf.jabref.model.strings.StringUtil; @@ -13,14 +12,11 @@ public class UndoableKeyChange extends AbstractUndoableJabRefEdit { private final BibEntry entry; - private final BibDatabase base; private final String oldValue; private final String newValue; - public UndoableKeyChange(BibDatabase base, BibEntry entry, - String oldValue, String newValue) { - this.base = base; + public UndoableKeyChange(BibEntry entry, String oldValue, String newValue) { this.entry = entry; this.oldValue = oldValue; this.newValue = newValue; @@ -31,27 +27,18 @@ public String getPresentationName() { return Localization.lang("change key from %0 to %1", StringUtil.boldHTML(oldValue, Localization.lang("undefined")), StringUtil.boldHTML(newValue, Localization.lang("undefined"))); - } @Override public void undo() { super.undo(); - - // Revert the change. - set(oldValue); + entry.setCiteKey(oldValue); } @Override public void redo() { super.redo(); - - // Redo the change. - set(newValue); - } - - private void set(String to) { - base.setCiteKeyForEntry(entry, to); + entry.setCiteKey(newValue); } } diff --git a/src/main/java/net/sf/jabref/gui/util/component/JTextAreaWithPlaceholder.java b/src/main/java/net/sf/jabref/gui/util/component/JTextAreaWithPlaceholder.java new file mode 100644 index 00000000000..d928ccc7f22 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/util/component/JTextAreaWithPlaceholder.java @@ -0,0 +1,90 @@ +package net.sf.jabref.gui.util.component; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JTextArea; +import javax.swing.UIManager; + +/** + * A text area which displays a predefined text the same way as {@link JTextFieldWithPlaceholder} does. + */ +public class JTextAreaWithPlaceholder extends JTextArea implements KeyListener { + + private final String textWhenNotFocused; + + public JTextAreaWithPlaceholder() { + this(""); + } + + /** + * Additionally to {@link JTextAreaWithPlaceholder#JTextAreaWithPlaceholder(String)} + * this also sets the initial text of the text field component. + * + * @param content as the text of the textfield + * @param placeholder as the placeholder of the textfield + */ + public JTextAreaWithPlaceholder(String content, String placeholder) { + this(placeholder); + setText(content); + } + + /** + * This will create a {@link JTextArea} with a placeholder text. The placeholder + * will always be displayed if the text of the {@link JTextArea} is empty. + * + * @param placeholder as the placeholder of the textarea + */ + public JTextAreaWithPlaceholder(String placeholder) { + super(); + this.setEditable(true); + this.setText(""); + this.textWhenNotFocused = placeholder; + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + if (this.getText().isEmpty()) { + Font prev = graphics.getFont(); + Color prevColor = graphics.getColor(); + graphics.setColor(UIManager.getColor("textInactiveText")); + int textHeight = graphics.getFontMetrics().getHeight(); + int x = this.getInsets().left; + Graphics2D g2d = (Graphics2D) graphics; + RenderingHints hints = g2d.getRenderingHints(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.drawString(textWhenNotFocused, x, textHeight + this.getInsets().top); + g2d.setRenderingHints(hints); + graphics.setFont(prev); + graphics.setColor(prevColor); + } + } + + @Override + public void keyTyped(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } + + @Override + public void keyPressed(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } +} diff --git a/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithPlaceholder.java b/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithPlaceholder.java new file mode 100644 index 00000000000..f867c9762a7 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithPlaceholder.java @@ -0,0 +1,91 @@ +package net.sf.jabref.gui.util.component; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JTextField; +import javax.swing.UIManager; + +/** + * A text field which displays a predefined text (e.g. "Search") if the text field is empty. + * This is similar to a html5 input element with a defined placeholder attribute. + * Implementation based on https://gmigdos.wordpress.com/2010/03/30/java-a-custom-jtextfield-for-searching/ + */ +public class JTextFieldWithPlaceholder extends JTextField implements KeyListener { + + private final String textWhenNotFocused; + + /** + * Additionally to {@link JTextFieldWithPlaceholder#JTextFieldWithPlaceholder(String)} + * this also sets the initial text of the text field component. + * + * @param content as the text of the textfield + * @param placeholder as the placeholder of the textfield + */ + public JTextFieldWithPlaceholder(String content, String placeholder) { + this(placeholder); + setText(content); + } + + /** + * This will create a {@link JTextField} with a placeholder text. The placeholder + * will always be displayed if the text of the {@link JTextField} is empty. + * + * @param placeholder as the placeholder of the textfield + */ + public JTextFieldWithPlaceholder(String placeholder) { + super(); + this.setEditable(true); + this.setText(""); + this.textWhenNotFocused = placeholder; + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + if (this.getText().isEmpty()) { + int height = this.getHeight(); + Font prev = graphics.getFont(); + Color prevColor = graphics.getColor(); + graphics.setColor(UIManager.getColor("textInactiveText")); + int textHeight = graphics.getFontMetrics().getHeight(); + int textBottom = (((height - textHeight) / 2) + textHeight) - 4; + int x = this.getInsets().left; + Graphics2D g2d = (Graphics2D) graphics; + RenderingHints hints = g2d.getRenderingHints(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.drawString(textWhenNotFocused, x, textBottom); + g2d.setRenderingHints(hints); + graphics.setFont(prev); + graphics.setColor(prevColor); + } + } + + @Override + public void keyTyped(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } + + @Override + public void keyPressed(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (this.getText().isEmpty()) { + this.repaint(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithUnfocusedText.java b/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithUnfocusedText.java deleted file mode 100644 index cb25794c848..00000000000 --- a/src/main/java/net/sf/jabref/gui/util/component/JTextFieldWithUnfocusedText.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.sf.jabref.gui.util.component; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; - -import javax.swing.JTextField; -import javax.swing.UIManager; - -/** - * A text field which displays a predefined text (e.g. "Search") if it has not the focus and no text is entered. - * Implementation based on https://gmigdos.wordpress.com/2010/03/30/java-a-custom-jtextfield-for-searching/ - */ -public class JTextFieldWithUnfocusedText extends JTextField implements FocusListener { - - private final String textWhenNotFocused; - - public JTextFieldWithUnfocusedText(String textWhenNotFocused) { - super(); - this.setEditable(true); - this.setText(""); - this.textWhenNotFocused = textWhenNotFocused; - this.addFocusListener(this); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - if (!this.hasFocus() && this.getText().isEmpty()) { - int height = this.getHeight(); - Font prev = g.getFont(); - Color prevColor = g.getColor(); - g.setColor(UIManager.getColor("textInactiveText")); - int h = g.getFontMetrics().getHeight(); - int textBottom = (((height - h) / 2) + h) - 4; - int x = this.getInsets().left; - Graphics2D g2d = (Graphics2D) g; - RenderingHints hints = g2d.getRenderingHints(); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2d.drawString(textWhenNotFocused, x, textBottom); - g2d.setRenderingHints(hints); - g.setFont(prev); - g.setColor(prevColor); - } - } - - @Override - public void focusGained(FocusEvent e) { - this.repaint(); - } - - @Override - public void focusLost(FocusEvent e) { - this.repaint(); - } - -} \ No newline at end of file diff --git a/src/main/java/net/sf/jabref/logic/bibtexkeypattern/BibtexKeyPatternUtil.java b/src/main/java/net/sf/jabref/logic/bibtexkeypattern/BibtexKeyPatternUtil.java index 81554f2c303..7e86d49abcf 100644 --- a/src/main/java/net/sf/jabref/logic/bibtexkeypattern/BibtexKeyPatternUtil.java +++ b/src/main/java/net/sf/jabref/logic/bibtexkeypattern/BibtexKeyPatternUtil.java @@ -429,7 +429,7 @@ public static void makeLabel(AbstractBibtexKeyPattern citeKeyPattern, BibDatabas } String oldKey = entry.getCiteKeyOptional().orElse(null); - int occurrences = database.getNumberOfKeyOccurrences(key); + int occurrences = database.getDuplicationChecker().getNumberOfKeyOccurrences(key); if (Objects.equals(oldKey, key)) { occurrences--; // No change, so we can accept one dupe. @@ -439,48 +439,24 @@ public static void makeLabel(AbstractBibtexKeyPattern citeKeyPattern, BibDatabas boolean firstLetterA = bibtexKeyPatternPreferences.isFirstLetterA(); if (!alwaysAddLetter && (occurrences == 0)) { - // No dupes found, so we can just go ahead. - if (!key.equals(oldKey)) { - if (database.containsEntryWithId(entry.getId())) { - database.setCiteKeyForEntry(entry, key); - } else { - // entry does not (yet) exist in the database, just update the entry - entry.setCiteKey(key); - } - } - + entry.setCiteKey(key); } else { // The key is already in use, so we must modify it. - int number = 0; - if (!alwaysAddLetter && !firstLetterA) { - number = 1; - } - - String moddedKey = key + getAddition(number); - occurrences = database.getNumberOfKeyOccurrences(moddedKey); - - if (Objects.equals(oldKey, moddedKey)) { - occurrences--; - } + int number = !alwaysAddLetter && !firstLetterA ? 1 : 0; + String moddedKey; - while (occurrences > 0) { - number++; + do { moddedKey = key + getAddition(number); + number++; - occurrences = database.getNumberOfKeyOccurrences(moddedKey); + occurrences = database.getDuplicationChecker().getNumberOfKeyOccurrences(moddedKey); + // only happens if #getAddition() is buggy if (Objects.equals(oldKey, moddedKey)) { occurrences--; } - } + } while (occurrences > 0); - if (!moddedKey.equals(oldKey)) { - if (database.containsEntryWithId(entry.getId())) { - database.setCiteKeyForEntry(entry, moddedKey); - } else { - // entry does not (yet) exist in the database, just update the entry - entry.setCiteKey(moddedKey); - } - } + entry.setCiteKey(moddedKey); } } diff --git a/src/main/java/net/sf/jabref/logic/importer/FulltextFetcher.java b/src/main/java/net/sf/jabref/logic/importer/FulltextFetcher.java index 0faa59ba516..ceda8b390d9 100644 --- a/src/main/java/net/sf/jabref/logic/importer/FulltextFetcher.java +++ b/src/main/java/net/sf/jabref/logic/importer/FulltextFetcher.java @@ -21,5 +21,5 @@ public interface FulltextFetcher { * @throws NullPointerException if no BibTex entry is given * @throws java.io.IOException */ - Optional findFullText(BibEntry entry) throws IOException; + Optional findFullText(BibEntry entry) throws IOException, FetcherException; } diff --git a/src/main/java/net/sf/jabref/logic/importer/FulltextFetchers.java b/src/main/java/net/sf/jabref/logic/importer/FulltextFetchers.java index 133f07a96bc..27865837d88 100644 --- a/src/main/java/net/sf/jabref/logic/importer/FulltextFetchers.java +++ b/src/main/java/net/sf/jabref/logic/importer/FulltextFetchers.java @@ -39,7 +39,7 @@ public FulltextFetchers(ImportFormatPreferences importFormatPreferences) { finders.add(new ArXiv(importFormatPreferences)); finders.add(new IEEE()); // Meta search - finders.add(new GoogleScholar()); + finders.add(new GoogleScholar(importFormatPreferences)); } public FulltextFetchers(List fetcher) { @@ -62,7 +62,7 @@ public Optional findFullTextPDF(BibEntry entry) { if (result.isPresent() && MimeTypeDetector.isPdfContentType(result.get().toString())) { return result; } - } catch (IOException e) { + } catch (IOException | FetcherException e) { LOGGER.debug("Failed to find fulltext PDF at given URL", e); } } diff --git a/src/main/java/net/sf/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/net/sf/jabref/logic/importer/fetcher/GoogleScholar.java index 61b7b5192e7..88031fdd16c 100644 --- a/src/main/java/net/sf/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/net/sf/jabref/logic/importer/fetcher/GoogleScholar.java @@ -1,18 +1,33 @@ package net.sf.jabref.logic.importer.fetcher; import java.io.IOException; +import java.io.StringReader; +import java.net.HttpCookie; +import java.net.URISyntaxException; import java.net.URL; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.sf.jabref.logic.help.HelpFile; +import net.sf.jabref.logic.importer.FetcherException; import net.sf.jabref.logic.importer.FulltextFetcher; +import net.sf.jabref.logic.importer.ImportFormatPreferences; +import net.sf.jabref.logic.importer.ParserResult; +import net.sf.jabref.logic.importer.SearchBasedFetcher; +import net.sf.jabref.logic.importer.fileformat.BibtexParser; +import net.sf.jabref.logic.net.URLDownload; import net.sf.jabref.model.entry.BibEntry; import net.sf.jabref.model.entry.FieldName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.http.client.utils.URIBuilder; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; @@ -20,14 +35,26 @@ /** * FulltextFetcher implementation that attempts to find a PDF URL at GoogleScholar. */ -public class GoogleScholar implements FulltextFetcher { +public class GoogleScholar implements FulltextFetcher, SearchBasedFetcher { private static final Log LOGGER = LogFactory.getLog(GoogleScholar.class); - private static final String SEARCH_URL = "https://scholar.google.com//scholar?as_q=&as_epq=%s&as_occt=title"; + private static final Pattern LINK_TO_BIB_PATTERN = Pattern.compile("(https:\\/\\/scholar.googleusercontent.com\\/scholar.bib[^\"]*)"); + + private static final String BASIC_SEARCH_URL = "https://scholar.google.com/scholar?"; + private static final String SEARCH_IN_TITLE_URL = "https://scholar.google.com//scholar?"; + private static final int NUM_RESULTS = 10; + private final ImportFormatPreferences importFormatPreferences; + + public GoogleScholar(ImportFormatPreferences importFormatPreferences) { + Objects.requireNonNull(importFormatPreferences); + + this.importFormatPreferences = importFormatPreferences; + } + @Override - public Optional findFullText(BibEntry entry) throws IOException { + public Optional findFullText(BibEntry entry) throws IOException, FetcherException { Objects.requireNonNull(entry); Optional pdfLink = Optional.empty(); @@ -36,30 +63,113 @@ public Optional findFullText(BibEntry entry) throws IOException { return pdfLink; } - String url = String.format(SEARCH_URL, - URLEncoder.encode(entry.getField(FieldName.TITLE).orElse(null), StandardCharsets.UTF_8.name())); - - Document doc = Jsoup.connect(url) - .userAgent("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0") // don't identify as a crawler - .get(); - // Check results for PDF link - // TODO: link always on first result or none? - for (int i = 0; i < NUM_RESULTS; i++) { - Elements link = doc.select(String.format("#gs_ggsW%s a", i)); - - if (link.first() != null) { - String s = link.first().attr("href"); - // link present? - if (!"".equals(s)) { - // TODO: check title inside pdf + length? - // TODO: report error function needed?! query -> result - LOGGER.info("Fulltext PDF found @ Google: " + s); - pdfLink = Optional.of(new URL(s)); - break; + try { + URIBuilder uriBuilder = new URIBuilder(SEARCH_IN_TITLE_URL); + uriBuilder.addParameter("as_q", ""); + uriBuilder.addParameter("as_epq", entry.getField(FieldName.TITLE).orElse(null)); + uriBuilder.addParameter("as_occt", "title"); + + Document doc = Jsoup.connect(uriBuilder.toString()).userAgent( + "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0") // don't identify as a crawler + .get(); + // Check results for PDF link + // TODO: link always on first result or none? + for (int i = 0; i < NUM_RESULTS; i++) { + Elements link = doc.select(String.format("#gs_ggsW%s a", i)); + + if (link.first() != null) { + String s = link.first().attr("href"); + // link present? + if (!"".equals(s)) { + // TODO: check title inside pdf + length? + // TODO: report error function needed?! query -> result + LOGGER.info("Fulltext PDF found @ Google: " + s); + pdfLink = Optional.of(new URL(s)); + break; + } } } + } catch (URISyntaxException e) { + throw new FetcherException("Building URI failed.", e); } return pdfLink; } + + @Override + public String getName() { + return "Google Scholar"; + } + + @Override + public HelpFile getHelpPage() { + return HelpFile.FETCHER_GOOGLE_SCHOLAR; + } + + @Override + public List performSearch(String query) throws FetcherException { + try { + obtainAndModifyCookie(); + List foundEntries = new ArrayList<>(10); + + URIBuilder uriBuilder = new URIBuilder(BASIC_SEARCH_URL); + uriBuilder.addParameter("hl", "en"); + uriBuilder.addParameter("btnG", "Search"); + uriBuilder.addParameter("q", query); + + addHitsFromQuery(foundEntries, uriBuilder.toString()); + + if(foundEntries.size()==10) { + uriBuilder.addParameter("start", "10"); + addHitsFromQuery(foundEntries, uriBuilder.toString()); + } + + return foundEntries; + } catch (IOException | URISyntaxException e) { + throw new FetcherException("Error while fetching from "+getName(), e); + } + } + + private void addHitsFromQuery(List entryList, String queryURL) throws IOException, FetcherException { + String content = URLDownload.createURLDownloadWithBrowserUserAgent(queryURL) + .downloadToString(StandardCharsets.UTF_8); + + Matcher matcher = LINK_TO_BIB_PATTERN.matcher(content); + while (matcher.find()) { + String citationsPageURL = matcher.group().replace("&", "&"); + BibEntry newEntry = downloadEntry(citationsPageURL); + entryList.add(newEntry); + } + } + + private BibEntry downloadEntry(String link) throws IOException, FetcherException { + String downloadedContent = URLDownload.createURLDownloadWithBrowserUserAgent(link).downloadToString(StandardCharsets.UTF_8); + BibtexParser parser = new BibtexParser(importFormatPreferences); + ParserResult result = parser.parse(new StringReader(downloadedContent)); + if ((result == null) || (result.getDatabase() == null)) { + throw new FetcherException("Parsing entries from Google Scholar bib file failed."); + } else { + Collection entries = result.getDatabase().getEntries(); + if (entries.size() != 1) { + LOGGER.debug(entries.size() + " entries found! (" + link + ")"); + throw new FetcherException("Parsing entries from Google Scholar bib file failed."); + } else { + BibEntry entry = entries.iterator().next(); + return entry; + } + } + } + + private void obtainAndModifyCookie() throws FetcherException { + try { + URLDownload downloader = URLDownload.createURLDownloadWithBrowserUserAgent("https://scholar.google.com"); + List cookies = downloader.getCookieFromUrl(); + for (HttpCookie cookie : cookies) { + // append "CF=4" which represents "Citation format bibtex" + cookie.setValue(cookie.getValue() + ":CF=4"); + } + } catch (IOException e) { + throw new FetcherException("Cookie configuration for Google Scholar failed.", e); + } + } } diff --git a/src/main/java/net/sf/jabref/logic/importer/fileformat/ModsImporter.java b/src/main/java/net/sf/jabref/logic/importer/fileformat/ModsImporter.java index c232339403a..9210fb90119 100644 --- a/src/main/java/net/sf/jabref/logic/importer/fileformat/ModsImporter.java +++ b/src/main/java/net/sf/jabref/logic/importer/fileformat/ModsImporter.java @@ -57,11 +57,9 @@ import org.apache.commons.logging.LogFactory; /** - * * Importer for the MODS format.
* More details about the format can be found here
http://www.loc.gov/standards/mods/.
* The newest xml schema can also be found here www.loc.gov/standards/mods/mods-schemas.html.. - * */ public class ModsImporter extends Importer { @@ -188,7 +186,7 @@ private void parseModsGroup(Map fields, List modsGroup, } //The element subject can appear more than one time, that's why the keywords has to be put out of the for loop - putIfListIsNotEmpty(fields, keywords, FieldName.KEYWORDS, KEYWORD_SEPARATOR.toString()); + putIfListIsNotEmpty(fields, keywords, FieldName.KEYWORDS, KEYWORD_SEPARATOR); //same goes for authors and notes putIfListIsNotEmpty(fields, authors, FieldName.AUTHOR, " and "); putIfListIsNotEmpty(fields, notes, FieldName.NOTE, ", "); @@ -219,7 +217,7 @@ private void parseIdentifier(Map fields, IdentifierDefinition id } private void parseTopic(Map fields, List> topicOrGeographicOrTemporal, - List keywords) { + List keywords) { for (JAXBElement jaxbElement : topicOrGeographicOrTemporal) { Object value = jaxbElement.getValue(); String elementName = jaxbElement.getName().getLocalPart(); @@ -238,9 +236,9 @@ private void parseTopic(Map fields, List> topicOr * If the element can not be cast to the given class, then an empty optional will be returned. * * @param groupElement The element that should be cast - * @param clazz The class to which groupElement should be cast + * @param clazz The class to which groupElement should be cast * @return An Optional, that contains the groupElement as instance of clazz, if groupElement can be cast to clazz. - * An empty Optional, if groupElement can not be cast to clazz + * An empty Optional, if groupElement can not be cast to clazz */ private Optional getElement(Object groupElement, Class clazz) { if (clazz.isAssignableFrom(groupElement.getClass())) { @@ -250,7 +248,7 @@ private Optional getElement(Object groupElement, Class clazz) { } private void parseGeographicInformation(Map fields, - HierarchicalGeographicDefinition hierarchichalGeographic) { + HierarchicalGeographicDefinition hierarchichalGeographic) { List> areaOrContinentOrCountry = hierarchichalGeographic .getExtraTerrestrialAreaOrContinentOrCountry(); for (JAXBElement element : areaOrContinentOrCountry) { @@ -372,7 +370,7 @@ private void putPlaceOrPublisherOrDate(Map fields, String elemen } private void putPublisherOrEdition(Map fields, String elementName, - StringPlusLanguagePlusSupplied pubOrEd) { + StringPlusLanguagePlusSupplied pubOrEd) { if ("publisher".equals(elementName)) { putIfValueNotNull(fields, FieldName.PUBLISHER, pubOrEd.getValue()); } else if ("edition".equals(elementName)) { diff --git a/src/main/java/net/sf/jabref/logic/net/URLDownload.java b/src/main/java/net/sf/jabref/logic/net/URLDownload.java index b9d49f56785..3e11fbb97b0 100644 --- a/src/main/java/net/sf/jabref/logic/net/URLDownload.java +++ b/src/main/java/net/sf/jabref/logic/net/URLDownload.java @@ -13,11 +13,18 @@ import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.HttpCookie; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -136,6 +143,23 @@ public String downloadToString(Charset encoding) throws IOException { } } + public List getCookieFromUrl() throws IOException { + CookieManager cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); + + URLConnection con = openConnection(); + con.getHeaderFields(); // must be read to store the cookie + + try { + return cookieManager.getCookieStore().get(source.toURI()); + } catch (URISyntaxException e) { + LOGGER.error("Unable to convert download URL to URI", e); + return Collections.emptyList(); + } + + } + private void copy(InputStream in, Writer out, Charset encoding) throws IOException { InputStream monitoredInputStream = monitorInputStream(in); Reader r = new InputStreamReader(monitoredInputStream, encoding); diff --git a/src/main/java/net/sf/jabref/logic/util/Version.java b/src/main/java/net/sf/jabref/logic/util/Version.java index 4e13af77db1..6d78e8a29b1 100644 --- a/src/main/java/net/sf/jabref/logic/util/Version.java +++ b/src/main/java/net/sf/jabref/logic/util/Version.java @@ -17,7 +17,7 @@ */ public class Version { - public static final String JABREF_DOWNLOAD_URL = "http://www.fosshub.com/JabRef.html"; + public static final String JABREF_DOWNLOAD_URL = "https://downloads.jabref.org"; private static final Log LOGGER = LogFactory.getLog(Version.class); private static final String JABREF_GITHUB_URL = "https://api.github.com/repos/JabRef/jabref/releases/latest"; diff --git a/src/main/java/net/sf/jabref/model/database/BibDatabase.java b/src/main/java/net/sf/jabref/model/database/BibDatabase.java index fa6182f8191..bb0ba1e3226 100644 --- a/src/main/java/net/sf/jabref/model/database/BibDatabase.java +++ b/src/main/java/net/sf/jabref/model/database/BibDatabase.java @@ -65,6 +65,7 @@ public class BibDatabase { public BibDatabase() { + this.eventBus.register(duplicationChecker); this.registerListener(new KeyChangeListener(this)); } @@ -181,7 +182,7 @@ public synchronized boolean insertEntry(BibEntry entry, EntryEventSource eventSo entry.registerListener(this); eventBus.post(new EntryAddedEvent(entry, eventSource)); - return duplicationChecker.checkForDuplicateKeyAndAdd(null, entry.getCiteKey()); + return duplicationChecker.isDuplicateCiteKeyExisting(entry); } /** @@ -206,31 +207,10 @@ public synchronized void removeEntry(BibEntry toBeDeleted, EntryEventSource even boolean anyRemoved = entries.removeIf(entry -> entry.getId().equals(toBeDeleted.getId())); if (anyRemoved) { internalIDs.remove(toBeDeleted.getId()); - toBeDeleted.getCiteKeyOptional().ifPresent(duplicationChecker::removeKeyFromSet); eventBus.post(new EntryRemovedEvent(toBeDeleted, eventSource)); } } - public int getNumberOfKeyOccurrences(String key) { - return duplicationChecker.getNumberOfKeyOccurrences(key); - } - - /** - * Sets the given key to the given entry. - * If the key is null, the entry field will be cleared. - * - * @return true, if the entry contains the key, false if not - */ - public synchronized boolean setCiteKeyForEntry(BibEntry entry, String key) { - String oldKey = entry.getCiteKey(); - if (key == null) { - entry.clearCiteKey(); - } else { - entry.setCiteKey(key); - } - return duplicationChecker.checkForDuplicateKeyAndAdd(oldKey, key); - } - /** * Sets the database's preamble. */ @@ -603,4 +583,9 @@ private void relayEntryChangeEvent(FieldChangedEvent event) { public Optional getReferencedEntry(BibEntry entry) { return entry.getField(FieldName.CROSSREF).flatMap(this::getEntryByKey); } + + public DuplicationChecker getDuplicationChecker() { + return duplicationChecker; + } + } diff --git a/src/main/java/net/sf/jabref/model/database/DuplicationChecker.java b/src/main/java/net/sf/jabref/model/database/DuplicationChecker.java index 52c61f3b071..a7f429d4962 100644 --- a/src/main/java/net/sf/jabref/model/database/DuplicationChecker.java +++ b/src/main/java/net/sf/jabref/model/database/DuplicationChecker.java @@ -2,62 +2,43 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import net.sf.jabref.model.database.event.EntryAddedEvent; +import net.sf.jabref.model.database.event.EntryRemovedEvent; +import net.sf.jabref.model.entry.BibEntry; +import net.sf.jabref.model.entry.event.FieldChangedEvent; + +import com.google.common.eventbus.Subscribe; /** * Determines which bibtex cite keys are duplicates in a single {@link BibDatabase}. */ -class DuplicationChecker { - - private static final Log LOGGER = LogFactory.getLog(DuplicationChecker.class); +public class DuplicationChecker { - // use a map instead of a set since i need to know how many of each key is in there + /** use a map instead of a set since I need to know how many of each key is in there */ private final Map allKeys = new HashMap<>(); + /** - * Usage: - *
- * isDuplicate=checkForDuplicateKeyAndAdd( null, b.getKey() , issueDuplicateWarning); - * - * If the newkey already exists and is not the same as oldkey it will give a warning - * else it will add the newkey to the to set and remove the oldkey - * - * @return true, if there is a duplicate key, else false + * Checks if there is more than one occurrence of this key */ - public boolean checkForDuplicateKeyAndAdd(String oldKey, String newKey) { + public boolean isDuplicateCiteKeyExisting(String citeKey) { + return getNumberOfKeyOccurrences(citeKey) > 1; + } - boolean duplicate = false; - if (oldKey == null) { // No old key - duplicate = addKeyToSet(newKey); - } else { - if (oldKey.equals(newKey)) {// were OK because the user did not change keys - duplicate = false; - } else { - removeKeyFromSet(oldKey); // Get rid of old key - if (newKey != null) { // Add new key if any - duplicate = addKeyToSet(newKey); - } - } - } - if (duplicate) { - LOGGER.warn("Warning there is a duplicate key: " + newKey); - } - return duplicate; + /** + * Checks if there is more than one occurrence of the cite key + */ + public boolean isDuplicateCiteKeyExisting(BibEntry entry) { + return isDuplicateCiteKeyExisting(entry.getCiteKeyOptional().orElse(null)); } /** * Returns the number of occurrences of the given key in this database. */ - public int getNumberOfKeyOccurrences(String key) { - Integer numberOfOccurrences = allKeys.get(key); - if (numberOfOccurrences == null) { - return 0; - } else { - return numberOfOccurrences; - } - + public int getNumberOfKeyOccurrences(String citeKey) { + return allKeys.getOrDefault(citeKey, 0); } /** @@ -74,19 +55,12 @@ public int getNumberOfKeyOccurrences(String key) { * Thus, I need a way to count the number of keys of each type. * Solution: hashmap=>int (increment each time at add and decrement each time at remove) */ - protected boolean addKeyToSet(String key) { - if ((key == null) || key.isEmpty()) { - return false;//don't put empty key - } - boolean exists = false; - if (allKeys.containsKey(key)) { - // warning - exists = true; - allKeys.put(key, allKeys.get(key) + 1); - } else { - allKeys.put(key, 1); + private void addKeyToSet(String key) { + if (key == null || key.isEmpty()) { + return; } - return exists; + + allKeys.put(key, getNumberOfKeyOccurrences(key) + 1); } /** @@ -96,17 +70,41 @@ protected boolean addKeyToSet(String key) { *
* Special case: If a null or empty key is passed, it is not counted and thus not removed. */ - protected void removeKeyFromSet(String key) { - if ((key == null) || key.isEmpty()) { + private void removeKeyFromSet(String key) { + if (key == null || key.isEmpty()) { return; } - if (allKeys.containsKey(key)) { - Integer tI = allKeys.get(key); - if (tI == 1) { - allKeys.remove(key); - } else { - allKeys.put(key, tI - 1); - } + + int numberOfKeyOccurrences = getNumberOfKeyOccurrences(key); + if (numberOfKeyOccurrences > 1) { + allKeys.put(key, numberOfKeyOccurrences - 1); + } else { + allKeys.remove(key); + } + } + + @Subscribe + public void listen(FieldChangedEvent fieldChangedEvent) { + if (fieldChangedEvent.getFieldName().equals(BibEntry.KEY_FIELD)) { + removeKeyFromSet(fieldChangedEvent.getOldValue()); + addKeyToSet(fieldChangedEvent.getNewValue()); } } + + @Subscribe + public void listen(EntryRemovedEvent entryRemovedEvent) { + Optional citeKey = entryRemovedEvent.getBibEntry().getCiteKeyOptional(); + if (citeKey.isPresent()) { + removeKeyFromSet(citeKey.get()); + } + } + + @Subscribe + public void listen(EntryAddedEvent entryAddedEvent) { + Optional citekey = entryAddedEvent.getBibEntry().getCiteKeyOptional(); + if (citekey.isPresent()) { + addKeyToSet(citekey.get()); + } + } + } diff --git a/src/main/java/net/sf/jabref/pdfimport/ImportDialog.java b/src/main/java/net/sf/jabref/pdfimport/ImportDialog.java index 79b2ef15331..9a025a1a0a5 100644 --- a/src/main/java/net/sf/jabref/pdfimport/ImportDialog.java +++ b/src/main/java/net/sf/jabref/pdfimport/ImportDialog.java @@ -8,8 +8,12 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import javax.swing.BorderFactory; +import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -19,11 +23,14 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; +import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.WindowConstants; import net.sf.jabref.Globals; import net.sf.jabref.logic.l10n.Localization; +import net.sf.jabref.logic.xmp.XMPUtil; +import net.sf.jabref.model.entry.BibEntry; import net.sf.jabref.model.strings.StringUtil; import net.sf.jabref.preferences.JabRefPreferences; @@ -80,6 +87,15 @@ public ImportDialog(boolean targetIsARow, String fileName) { checkBoxDoNotShowAgain = new JCheckBox(Localization.lang("Do not show this box again for this import")); useDefaultPDFImportStyle = new JCheckBox(Localization.lang("Always use this PDF import style (and do not ask for each import)")); DefaultFormBuilder b = new DefaultFormBuilder(new FormLayout("left:pref, 5dlu, left:pref:grow", "")); + List foundEntries = getEntriesFromXMP(fileName); + JPanel entriesPanel = new JPanel(); + entriesPanel.setLayout(new BoxLayout(entriesPanel, BoxLayout.Y_AXIS)); + foundEntries.forEach(entry -> { + JTextArea entryArea = new JTextArea(entry.toString()); + entryArea.setEditable(false); + entriesPanel.add(entryArea); + }); + b.appendSeparator(Localization.lang("Create new entry")); b.append(radioButtonNoMeta, 3); b.append(radioButtonXmp, 3); @@ -89,6 +105,10 @@ public ImportDialog(boolean targetIsARow, String fileName) { b.nextLine(); b.append(checkBoxDoNotShowAgain); b.append(useDefaultPDFImportStyle); + if (!foundEntries.isEmpty()) { + b.appendSeparator(Localization.lang("XMP-metadata")); + b.append(entriesPanel, 3); + } b.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); @@ -155,6 +175,16 @@ public void windowClosing(WindowEvent e) { this.setSize(555, 371); } + private List getEntriesFromXMP(String fileName) { + List foundEntries = new ArrayList<>(); + try { + foundEntries = XMPUtil.readXMP(fileName, Globals.prefs.getXMPPreferences()); + } catch (IOException e) { + e.printStackTrace(); + } + return foundEntries; + } + private void onOK() { this.result = JOptionPane.OK_OPTION; Globals.prefs.putInt(JabRefPreferences.IMPORT_DEFAULT_PDF_IMPORT_STYLE, this.getChoice()); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 04c70468675..fc51f3e38ce 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index ad31648d85b..39ae3986ed6 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.=Die_übergebene_Such-ID_ist_leer. Copy_BibTeX_key_and_link=BibTeX-Key_und_Link_kopieren empty_BibTeX_key=Leerer_BibTeX-Key BibLaTeX_field_only=Nur_ein_BibLaTeX-Feld +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 4ff6b67b3c3..0e957de96dc 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.=The_given_search_ID_was_empty. Copy_BibTeX_key_and_link=Copy_BibTeX_key_and_link empty_BibTeX_key=empty_BibTeX_key BibLaTeX_field_only=BibLaTeX_field_only +Firstname_Lastname=Firstname_Lastname diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 1ccadb3a5ce..0b3c89d69ac 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 579ecddb780..11cebdecf46 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index fc9d3dda11f..672a4b13c3e 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.=L'Identifiant_de_recherche_entré_était_vide. Copy_BibTeX_key_and_link=Copier_la_clef_BibTeX_et_le_lien empty_BibTeX_key=Clef_BibTeX_vide BibLaTeX_field_only=Champ_BibLaTeX_uniquement +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 0d9ab80d81b..a01a5d81728 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 1fdd6700bc0..245f99da489 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.=L'ID_della_ricerca_usato_era_vuoto. Copy_BibTeX_key_and_link=Copia_la_chiave_e_il_link_BibTeX empty_BibTeX_key=chiave_BibTeX_vuota BibLaTeX_field_only=campo_solo_BibLaTeX +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index bba42f918a9..25be0f178fa 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -131,7 +131,7 @@ Automatically_create_groups_for_database.=データベースのグループを Automatically_created_groups=グループを自動生成しました -Automatically_remove_exact_duplicates=完全な重複を自動的に削除する +Automatically_remove_exact_duplicates=完全に同一な重複を自動削除 Allow_overwriting_existing_links.=既存リンクの上書きを許可する。 @@ -294,9 +294,9 @@ Custom_entry_types=ユーザー項目型 Custom_entry_types_found_in_file=ユーザー項目型がファイル中に見つかりました -Customize_entry_types=項目型を調整 +Customize_entry_types=項目型の調整 -Customize_key_bindings=キー割当を調整 +Customize_key_bindings=キー割当の調整 Cut=切り取り @@ -713,7 +713,7 @@ Journal_name=学術誌名 Keep=維持 -Keep_both=両者を維持 +Keep_both=両側を維持 Key_bindings=キー割当 @@ -1060,7 +1060,7 @@ Renamed_string=文字列を改名しました Replace_(regular_expression)=置換対象(正規表現) -Replace_string=文字列を置換 +Replace_string=文字列の置換 Replace_with=置換文字列 @@ -1216,13 +1216,13 @@ Strings=文字列 Strings_for_database=右のデータベースで用いる文字列 -Subdatabase_from_AUX=AUXから副データベース +Subdatabase_from_AUX=AUXからの部分データベース Switches_between_full_and_abbreviated_journal_name_if_the_journal_name_is_known.=学術誌名が既知の場合は、完全な学術誌名と短縮形を切り替える。 -Synchronize_file_links=ファイルリンクを同期 +Synchronize_file_links=ファイルリンクの同期 -Synchronizing_file_links...=ファイルリンクを同期... +Synchronizing_file_links...=ファイルリンクの同期... Table_appearance=表の外観 @@ -1581,7 +1581,7 @@ Journals=学術誌 Cite=引用 Cite_in-text=文中への引用 Insert_empty_citation=空の引用を挿入 -Merge_citations=引用を統合 +Merge_citations=引用の統合 Manual_connect=手動接続 Select_Writer_document=Writer文書を選択 Sync_OpenOffice/LibreOffice_bibliography=OpenOffice/LibreOffice書誌情報を同期 @@ -1617,7 +1617,7 @@ Edit_group_membership=グループ参加状態を編集 HTML_list=HTML一覧 Click_group_to_toggle_membership_of_selected_entries=グループをクリックして、選択した項目の所属を入切してください Use_EMACS_23_insertion_string=EMACS_23型文字列挿入を行う -If_possible,_normalize_this_list_of_names_to_conform_to_standard_BibTeX_name_formatting=可能ならば、名称一覧を標準的なBibTeX名書式に合致するように規準化する +If_possible,_normalize_this_list_of_names_to_conform_to_standard_BibTeX_name_formatting=可能ならば、名称一覧を標準的なBibTeX名書式に合致するように標準化する Could_not_open_%0=%0を開くことができませんでした Unknown_import_format=未知の読み込み型です Web_search=ウェブ検索 @@ -1637,7 +1637,7 @@ This_feature_generates_a_new_database_based_on_which_entries_are_needed_in_an_ex You_need_to_select_one_of_your_open_databases_from_which_to_choose_entries,_as_well_as_the_AUX_file_produced_by_LaTeX_when_compiling_your_document.=項目を選ぶための既に開かれているデータベースと、文書をコンパイルする際にLaTeXが生成したAUXファイルを選択する必要があります。 First_select_entries_to_clean_up.=まず消去する項目を選択してください。 -Cleanup_entry=項目を消去 +Cleanup_entry=項目の整理 Autogenerate_PDF_Names=PDF名を自動生成 Auto-generating_PDF-Names_does_not_support_undo._Continue?=PDF名の自動生成は取り消せません。続けますか? @@ -1648,7 +1648,7 @@ Autocompletion_options=自動補完オプション Autocomplete_after_following_number_of_characters=右記の文字数以上で自動補完 Name_format_used_for_autocompletion=自動補完に使用される氏名の書式 Treatment_of_first_names=名(first_name)の取り扱い -Cleanup_entries=項目を消去 +Cleanup_entries=項目の整理 Automatically_assign_new_entry_to_selected_groups=選択したグループに新規項目を自動割り当て %0_mode=%0モード Move_DOIs_from_note_and_URL_field_to_DOI_field_and_remove_http_prefix=DOIをnote及びURLフィールドからDOIフィールドに移動して、http前置句を削除する @@ -1656,7 +1656,7 @@ Make_paths_of_linked_files_relative_(if_possible)=リンクファイルのパス Rename_PDFs_to_given_filename_format_pattern=PDFを指定したファイル名書式パターンに改名する Rename_only_PDFs_having_a_relative_path=相対パスを持つPDFのみを改名 What_would_you_like_to_clean_up?=どれを消去しますか? -Doing_a_cleanup_for_%0_entries...=%0個の項目を消去しています... +Doing_a_cleanup_for_%0_entries...=%0個の項目を整理しています... No_entry_needed_a_clean_up=消去の必要がある項目はありませんでした One_entry_needed_a_clean_up=1つの項目を消去する必要がありました %0_entries_needed_a_clean_up=%0個の項目を消去する必要がありました @@ -1674,13 +1674,13 @@ Unknown_preference_key_'%0'=キー「%0」という設定は知りません Unable_to_clear_preferences.=設定を消去することができませんでした。 Reset_preferences_(key1,key2,..._or_'all')=設定をリセット(キー1・キー2…あるいは「all」) -Find_unlinked_files=リンクしていないファイルを検索 +Find_unlinked_files=リンクされていないファイルを検索 Unselect_all=すべての選択を解除 Expand_all=すべて展開表示 Collapse_all=すべて畳んで表示 Opens_the_file_browser.=ファイルブラウザを開きます Scan_directory=ディレクトリを走査 -Searches_the_selected_directory_for_unlinked_files.=選択したディレクトリで非リンクファイルを検索 +Searches_the_selected_directory_for_unlinked_files.=選択したディレクトリでリンクされていないファイルを検索 Starts_the_import_of_BibTeX_entries.=BibTeX項目の読み込みを開始します。 Leave_this_dialog.=このダイアログを閉じます。 Create_directory_based_keywords=ディレクトリに基づいたキーワードを生成 @@ -1771,7 +1771,7 @@ Log=ログ Canceled_merging_entries=項目の統合を取り消しました Format_units_by_adding_non-breaking_separators_and_keeping_the_correct_case_on_search=非改行区切りを付して検索で大文字小文字が正しくなるよう単位を整形 -Merge_entries=項目を統合 +Merge_entries=項目の統合 Merged_entries=項目を統合しました Merged_entry=統合後の項目 None=なし @@ -2052,7 +2052,7 @@ Converts_Unicode_characters_to_LaTeX_encoding.=Unicode文字をLaTeXエンコー Converts_ordinals_to_LaTeX_superscripts.=序数をLaTeX上付き文字に変換します。 Converts_units_to_LaTeX_formatting.=単位をLaTeX形式に変換します。 HTML_to_LaTeX=HTMLからLaTeXへ -LaTeX_cleanup=LaTeX除去 +LaTeX_cleanup=LaTeXの整理 LaTeX_to_Unicode=LaTeXからUnicodeへ Lower_case=小文字 Minify_list_of_person_names=人名一覧の圧縮 @@ -2290,12 +2290,13 @@ Fetcher_'%0'_did_not_find_an_entry_for_id_'%1'.=取得子「%0」は,IDが「% Select_first_entry=最初の項目を選択 Select_last_entry=最後の項目を選択 -Invalid_ISBN\:_'%0'.= -should_be_an_integer_or_normalized= -should_be_normalized= +Invalid_ISBN\:_'%0'.=ISBN「%0」は無効です. +should_be_an_integer_or_normalized=標準化されたものか整数でなくてはなりません +should_be_normalized=標準化されたものでなくてはなりません -Empty_search_ID= -The_given_search_ID_was_empty.= -Copy_BibTeX_key_and_link= -empty_BibTeX_key= -BibLaTeX_field_only= +Empty_search_ID=検索IDが空 +The_given_search_ID_was_empty.=提示された検索IDが空です. +Copy_BibTeX_key_and_link=BibTeX鍵とリンクをコピー +empty_BibTeX_key=空のBibTeX鍵 +BibLaTeX_field_only=BibLaTeXフィールドのみ +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 61c66a01230..5794491da0c 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 7e1b78cb53c..efb66041663 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index d8f63b73b88..b4348b1c876 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 566269fe4f1..6f13dd8e392 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index dd6b6e71f97..34fe7a9d5f7 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 0f40f82ba7b..ae5ae6613c4 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.=Verilen_arama_IDsi_boştu. Copy_BibTeX_key_and_link=BibTeX_anahtarı_ve_bağlantısını_kopyala empty_BibTeX_key=boş_BibTeX_anahtarı BibLaTeX_field_only=Yalnızca_BibLaTeX_alanı +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index d7ccefcd97a..b5575f21738 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 2386e5742c4..06de2af8cb6 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -2299,3 +2299,4 @@ The_given_search_ID_was_empty.= Copy_BibTeX_key_and_link= empty_BibTeX_key= BibLaTeX_field_only= +Firstname_Lastname= diff --git a/src/main/resources/l10n/Menu_ja.properties b/src/main/resources/l10n/Menu_ja.properties index 8bd1ac9dac0..120022e9554 100644 --- a/src/main/resources/l10n/Menu_ja.properties +++ b/src/main/resources/l10n/Menu_ja.properties @@ -43,7 +43,7 @@ New_%0_database=新しい%0データベース New_entry=新規項目(&E) New_entry_by_type...=新規項目(&N)… New_entry_from_plain_text=平文から新規項目(&W) -New_subdatabase_based_on_AUX_file=AU&Xファイルに基づく新規副データベース +New_subdatabase_based_on_AUX_file=AU&Xファイルからの新規部分データベース # View Next_tab=次のタブ(&N) Open_database=データベースを開く(&O) @@ -58,7 +58,7 @@ Previous_tab=前のタブ(&P) Quit=終了(&Q) Recent_databases=最近開いたファイル(&R) Redo=再実行(&R) -Replace_string=文字列を置換(&R) +Replace_string=文字列の置換(&R) Save_database=データベースを保存(&S) Save_database_as...=データベースに名前を付けて保存(&A)… Save_selected_as...=選択部に名前を付けて保存(&L)… @@ -69,8 +69,8 @@ Select_all=全て選択(&A) Set_up_general_fields=汎用フィールドを設定(&G) Show_error_console=エラーコンソールを表示 Sort_tabs=タブを整序(&S) -Next_preview_layout= -Previous_preview_layout= +Next_preview_layout=次のプレビューレイアウト(&N) +Previous_preview_layout=前のプレビューレイアウト(&P) # Export menu Toggle_entry_preview=項目プレビューを入切(&T) Toggle_groups_interface=グループ操作面を入切(&G) @@ -111,11 +111,11 @@ Set/clear/rename_fields=フィールドを設定/クリア/名称変更 Resolve_duplicate_BibTeX_keys=重複したBibTeX鍵を解消する Copy_BibTeX_key_and_title=BibTeX鍵とタイトルをコピー -Cleanup_entries=項目を消去 +Cleanup_entries=項目の消去 Manage_keywords=キーワードを管理 -Merge_entries=項目を統合 +Merge_entries=項目の統合 Open_folder=フォルダを開く -Find_unlinked_files...=リンクしていないファイルを検索 +Find_unlinked_files...=リンクしていないファイルを検索... Hide/show_toolbar=ツールバーを表示/非表示 Fork_me_on_GitHub=私をGitHubにフォークして @@ -133,5 +133,5 @@ Blog=ブログ JabRef_resources=JabRefリソース Development_version=開発版 View_change_log=変更履歴を閲覧 -Copy_BibTeX_key_and_link= -Copy_DOI_url= +Copy_BibTeX_key_and_link=BibTeX鍵とリンクをコピー +Copy_DOI_url=DOIのURLをコピー diff --git a/src/test/java/net/sf/jabref/logic/exporter/ModsExportFormatTestFiles.java b/src/test/java/net/sf/jabref/logic/exporter/ModsExportFormatTestFiles.java index fc901d070b7..9ef9282a2f9 100644 --- a/src/test/java/net/sf/jabref/logic/exporter/ModsExportFormatTestFiles.java +++ b/src/test/java/net/sf/jabref/logic/exporter/ModsExportFormatTestFiles.java @@ -90,13 +90,10 @@ public final void testPerformExport() throws Exception { @Test public final void testExportAsModsAndThenImportAsMods() throws Exception { - String xmlFileName = filename.replace(".bib", ".xml"); - Path xmlFile = Paths.get(ModsExportFormatTestFiles.class.getResource(xmlFileName).toURI()); List entries = bibtexImporter.importDatabase(importFile, charset).getDatabase().getEntries(); - Path modsFile = importFile.resolve(xmlFile); - modsExportFormat.performExport(databaseContext, modsFile, charset, entries); - BibEntryAssert.assertEquals(entries, modsFile, modsImporter); + modsExportFormat.performExport(databaseContext, tempFile.getPath(), charset, entries); + BibEntryAssert.assertEquals(entries, Paths.get(tempFile.getPath()), modsImporter); } @Test diff --git a/src/test/java/net/sf/jabref/logic/importer/fetcher/DoiResolutionTest.java b/src/test/java/net/sf/jabref/logic/importer/fetcher/DoiResolutionTest.java index 65247a219fa..2c473ecda19 100644 --- a/src/test/java/net/sf/jabref/logic/importer/fetcher/DoiResolutionTest.java +++ b/src/test/java/net/sf/jabref/logic/importer/fetcher/DoiResolutionTest.java @@ -10,7 +10,6 @@ import org.junit.Assert; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class DoiResolutionTest { @@ -48,10 +47,10 @@ public void findByDOI() throws IOException { ); } - @Ignore @Test public void notReturnAnythingWhenMultipleLinksAreFound() throws IOException { - // To be implemented + entry.setField("doi", "10.1051/0004-6361/201527330; 10.1051/0004-6361/20152711233"); + Assert.assertEquals(Optional.empty(), finder.findFullText(entry)); } @Test diff --git a/src/test/java/net/sf/jabref/logic/importer/fetcher/GoogleScholarTest.java b/src/test/java/net/sf/jabref/logic/importer/fetcher/GoogleScholarTest.java index 2f6ac2cca2d..96bb1b3615f 100644 --- a/src/test/java/net/sf/jabref/logic/importer/fetcher/GoogleScholarTest.java +++ b/src/test/java/net/sf/jabref/logic/importer/fetcher/GoogleScholarTest.java @@ -2,9 +2,16 @@ import java.io.IOException; import java.net.URL; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import net.sf.jabref.logic.bibtex.FieldContentParserPreferences; +import net.sf.jabref.logic.importer.FetcherException; +import net.sf.jabref.logic.importer.ImportFormatPreferences; import net.sf.jabref.model.entry.BibEntry; +import net.sf.jabref.model.entry.BibtexEntryTypes; +import net.sf.jabref.model.entry.FieldName; import net.sf.jabref.support.DevEnvironment; import org.junit.Assert; @@ -12,6 +19,9 @@ import org.junit.Before; import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class GoogleScholarTest { private GoogleScholar finder; @@ -19,23 +29,26 @@ public class GoogleScholarTest { @Before public void setUp() { - finder = new GoogleScholar(); + ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class); + when(importFormatPreferences.getFieldContentParserPreferences()).thenReturn( + mock(FieldContentParserPreferences.class)); + finder = new GoogleScholar(importFormatPreferences); entry = new BibEntry(); } @Test(expected = NullPointerException.class) - public void rejectNullParameter() throws IOException { + public void rejectNullParameter() throws IOException, FetcherException { finder.findFullText(null); Assert.fail(); } @Test - public void requiresEntryTitle() throws IOException { + public void requiresEntryTitle() throws IOException, FetcherException { Assert.assertEquals(Optional.empty(), finder.findFullText(entry)); } @Test - public void linkFound() throws IOException { + public void linkFound() throws IOException, FetcherException { // CI server is blocked by Google Assume.assumeFalse(DevEnvironment.isCIServer()); @@ -48,7 +61,7 @@ public void linkFound() throws IOException { } @Test - public void noLinkFound() throws IOException { + public void noLinkFound() throws IOException, FetcherException { // CI server is blocked by Google Assume.assumeFalse(DevEnvironment.isCIServer()); @@ -56,4 +69,26 @@ public void noLinkFound() throws IOException { Assert.assertEquals(Optional.empty(), finder.findFullText(entry)); } + + @Test + public void findSingleEntry() throws FetcherException { + entry.setType(BibtexEntryTypes.INPROCEEDINGS.getName()); + entry.setCiteKey("geiger2013detecting"); + entry.setField(FieldName.TITLE, "Detecting Interoperability and Correctness Issues in BPMN 2.0 Process Models."); + entry.setField(FieldName.AUTHOR, "Geiger, Matthias and Wirtz, Guido"); + entry.setField(FieldName.BOOKTITLE, "ZEUS"); + entry.setField(FieldName.YEAR, "2013"); + entry.setField(FieldName.PAGES, "41--44"); + + List foundEntries = finder.performSearch("info:RExzBa3OlkQJ:scholar.google.com"); + + Assert.assertEquals(Collections.singletonList(entry), foundEntries); + } + + @Test + public void find20Entries() throws FetcherException { + List foundEntries = finder.performSearch("random test string"); + + Assert.assertEquals(20, foundEntries.size()); + } } diff --git a/src/test/java/net/sf/jabref/model/database/BibDatabaseTest.java b/src/test/java/net/sf/jabref/model/database/BibDatabaseTest.java index 3688e4cd36c..002f7146b5e 100644 --- a/src/test/java/net/sf/jabref/model/database/BibDatabaseTest.java +++ b/src/test/java/net/sf/jabref/model/database/BibDatabaseTest.java @@ -178,7 +178,7 @@ public void correctKeyCountOne() { BibEntry entry = new BibEntry(); entry.setCiteKey("AAA"); database.insertEntry(entry); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 1); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); } @Test @@ -189,51 +189,7 @@ public void correctKeyCountTwo() { entry = new BibEntry(); entry.setCiteKey("AAA"); database.insertEntry(entry); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 2); - } - - @Test - public void setCiteKeySameKeySameEntry() { - BibEntry entry = new BibEntry(); - entry.setCiteKey("AAA"); - database.insertEntry(entry); - assertFalse(database.setCiteKeyForEntry(entry, "AAA")); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 1); - } - - @Test - public void setCiteKeyRemoveKey() { - BibEntry entry = new BibEntry(); - entry.setCiteKey("AAA"); - database.insertEntry(entry); - assertFalse(database.setCiteKeyForEntry(entry, null)); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 0); - assertEquals(Optional.empty(), entry.getCiteKeyOptional()); - } - - @Test - public void setCiteKeyDifferentKeySameEntry() { - BibEntry entry = new BibEntry(); - entry.setCiteKey("AAA"); - database.insertEntry(entry); - assertFalse(database.setCiteKeyForEntry(entry, "BBB")); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 0); - assertEquals(database.getNumberOfKeyOccurrences("BBB"), 1); - } - - - @Test - public void setCiteKeySameKeyDifferentEntries() { - BibEntry entry = new BibEntry(); - entry.setCiteKey("AAA"); - database.insertEntry(entry); - entry = new BibEntry(); - entry.setCiteKey("BBB"); - database.insertEntry(entry); - assertTrue(database.setCiteKeyForEntry(entry, "AAA")); - assertEquals(entry.getCiteKeyOptional(), Optional.of("AAA")); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 2); - assertEquals(database.getNumberOfKeyOccurrences("BBB"), 0); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 2); } @Test @@ -245,7 +201,7 @@ public void correctKeyCountAfterRemoving() { entry.setCiteKey("AAA"); database.insertEntry(entry); database.removeEntry(entry); - assertEquals(database.getNumberOfKeyOccurrences("AAA"), 1); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); } @Test diff --git a/src/test/java/net/sf/jabref/model/database/DuplicationCheckerTest.java b/src/test/java/net/sf/jabref/model/database/DuplicationCheckerTest.java new file mode 100644 index 00000000000..c2c4487b35c --- /dev/null +++ b/src/test/java/net/sf/jabref/model/database/DuplicationCheckerTest.java @@ -0,0 +1,111 @@ +package net.sf.jabref.model.database; + +import net.sf.jabref.model.entry.BibEntry; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class DuplicationCheckerTest { + + private BibDatabase database; + + + @Before + public void setUp() { + database = new BibDatabase(); + } + + @Test + public void addEntry() { + BibEntry entry = new BibEntry(); + entry.setCiteKey("AAA"); + database.insertEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + } + + @Test + public void addAndRemoveEntry() { + BibEntry entry = new BibEntry(); + entry.setCiteKey("AAA"); + database.insertEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + database.removeEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 0); + } + + @Test + public void changeCiteKey() { + BibEntry entry = new BibEntry(); + entry.setCiteKey("AAA"); + database.insertEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + entry.setCiteKey("BBB"); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 0); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("BBB"), 1); + } + + + @Test + public void setCiteKeySameKeyDifferentEntries() { + BibEntry entry0 = new BibEntry(); + entry0.setCiteKey("AAA"); + database.insertEntry(entry0); + BibEntry entry1 = new BibEntry(); + entry1.setCiteKey("BBB"); + database.insertEntry(entry1); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("BBB"), 1); + + entry1.setCiteKey("AAA"); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 2); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("BBB"), 0); + } + + @Test + public void removeMultipleCiteKeys(){ + BibEntry entry0 = new BibEntry(); + entry0.setCiteKey("AAA"); + database.insertEntry(entry0); + BibEntry entry1 = new BibEntry(); + entry1.setCiteKey("AAA"); + database.insertEntry(entry1); + BibEntry entry2 = new BibEntry(); + entry2.setCiteKey("AAA"); + database.insertEntry(entry2); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 3); + + database.removeEntry(entry2); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 2); + + database.removeEntry(entry1); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + + database.removeEntry(entry0); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 0); + } + + @Test + public void addEmptyCiteKey(){ + BibEntry entry = new BibEntry(); + entry.setCiteKey(""); + database.insertEntry(entry); + + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences(""), 0); + } + + @Test + public void removeEmptyCiteKey(){ + BibEntry entry = new BibEntry(); + entry.setCiteKey("AAA"); + database.insertEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 1); + + entry.setCiteKey(""); + database.removeEntry(entry); + assertEquals(database.getDuplicationChecker().getNumberOfKeyOccurrences("AAA"), 0); + } + +} diff --git a/src/test/resources/net/sf/jabref/logic/exporter/BibTeXMLExporterTestInbook.xml b/src/test/resources/net/sf/jabref/logic/exporter/BibTeXMLExporterTestInBook.xml similarity index 100% rename from src/test/resources/net/sf/jabref/logic/exporter/BibTeXMLExporterTestInbook.xml rename to src/test/resources/net/sf/jabref/logic/exporter/BibTeXMLExporterTestInBook.xml