From 5f4c338fdb7bb3367a4c71edee892c121e0d1b64 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 5 Jun 2016 14:00:46 +0200 Subject: [PATCH 01/18] First working prototype --- build.gradle | 3 + .../java/net/sf/jabref/gui/BasePanel.java | 1 - src/main/java/net/sf/jabref/gui/FXAlert.java | 22 +-- .../java/net/sf/jabref/gui/JabRefFrame.java | 7 +- .../sf/jabref/gui/help/AboutDialogView.java | 8 +- .../sf/jabref/gui/pdfviewer/PdfViewer.fxml | 11 ++ .../jabref/gui/pdfviewer/PdfViewerView.java | 43 +++++ .../gui/pdfviewer/PdfViewerViewModel.java | 162 ++++++++++++++++++ .../gui/pdfviewer/TogglePdfViewerAction.java | 41 +++++ .../net/sf/jabref/logic/TypedBibEntry.java | 4 + 10 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml create mode 100644 src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java create mode 100644 src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java create mode 100644 src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java diff --git a/build.gradle b/build.gradle index c838d0fdb66..95ec98c99f7 100644 --- a/build.gradle +++ b/build.gradle @@ -121,6 +121,9 @@ sourceSets { java { srcDirs = ["src/main/java", "src/main/gen"] } + resources { + srcDirs = ["src/main/java", "src/main/resources"] + } } integrationTest { java { diff --git a/src/main/java/net/sf/jabref/gui/BasePanel.java b/src/main/java/net/sf/jabref/gui/BasePanel.java index ed59ef18988..bfd246ee00f 100644 --- a/src/main/java/net/sf/jabref/gui/BasePanel.java +++ b/src/main/java/net/sf/jabref/gui/BasePanel.java @@ -1251,7 +1251,6 @@ public SearchBar getSearchBar() { return searchBar; } - /** * This listener is used to add a new entry to a group (or a set of groups) in case the Group View is selected and * one or more groups are marked diff --git a/src/main/java/net/sf/jabref/gui/FXAlert.java b/src/main/java/net/sf/jabref/gui/FXAlert.java index bdcf2df5cde..b31a55c19a4 100644 --- a/src/main/java/net/sf/jabref/gui/FXAlert.java +++ b/src/main/java/net/sf/jabref/gui/FXAlert.java @@ -15,15 +15,15 @@ */ package net.sf.jabref.gui; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.image.Image; import javafx.stage.Stage; -import java.awt.Window; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - import net.sf.jabref.JabRefGUI; /** @@ -38,7 +38,7 @@ * {@link FXMLLoader}. * */ -public class FXAlert extends Alert { +public class FXAlert extends Stage { /** * The WindowAdapter will be added to all swing windows once an instance @@ -67,18 +67,18 @@ public void windowGainedFocus(WindowEvent e) { } }; - public FXAlert(AlertType type, String title, Image image) { + public FXAlert(Alert.AlertType type, String title, Image image) { this(type, title); setDialogIcon(image); } - public FXAlert(AlertType type, String title) { + public FXAlert(Alert.AlertType type, String title) { this(type); setTitle(title); } - public FXAlert(AlertType type) { - super(type); + public FXAlert(Alert.AlertType type) { + //super(type); Stage fxDialogWindow = getDialogWindow(); fxDialogWindow.setOnShown(evt -> { @@ -92,7 +92,7 @@ public FXAlert(AlertType type) { } public void setDialogStyle(String pathToStyleSheet) { - getDialogPane().getScene().getStylesheets().add(pathToStyleSheet); + this.getScene().getStylesheets().add(pathToStyleSheet); } public void setDialogIcon(Image image) { @@ -101,7 +101,7 @@ public void setDialogIcon(Image image) { } private Stage getDialogWindow() { - return (Stage) getDialogPane().getScene().getWindow(); + return this; } private void setSwingWindowsEnabledAndFocusable(boolean enabled) { diff --git a/src/main/java/net/sf/jabref/gui/JabRefFrame.java b/src/main/java/net/sf/jabref/gui/JabRefFrame.java index 1dc616bd874..d8cdfdbb224 100644 --- a/src/main/java/net/sf/jabref/gui/JabRefFrame.java +++ b/src/main/java/net/sf/jabref/gui/JabRefFrame.java @@ -70,6 +70,8 @@ import javax.swing.UIManager; import javax.swing.WindowConstants; +import javafx.application.Platform; + import net.sf.jabref.BibDatabaseContext; import net.sf.jabref.Globals; import net.sf.jabref.HighlightMatchingGroupPreferences; @@ -110,6 +112,7 @@ import net.sf.jabref.gui.menus.help.DonateAction; import net.sf.jabref.gui.menus.help.ForkMeOnGitHubAction; import net.sf.jabref.gui.openoffice.OpenOfficePanel; +import net.sf.jabref.gui.pdfviewer.TogglePdfViewerAction; import net.sf.jabref.gui.preftabs.PreferencesDialog; import net.sf.jabref.gui.util.FocusRequester; import net.sf.jabref.gui.util.PositionWindow; @@ -141,7 +144,6 @@ import com.jgoodies.looks.HeaderStyle; import com.jgoodies.looks.Options; -import javafx.application.Platform; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import osx.macadapter.MacAdapter; @@ -329,6 +331,8 @@ public void actionPerformed(ActionEvent e) { Localization.lang("Toggle groups interface"), Globals.getKeyPrefs().getKey(KeyBinding.TOGGLE_GROUPS_INTERFACE), IconTheme.JabRefIcon.TOGGLE_GROUPS.getIcon())); + private final AbstractAction showPdvViewer = new TogglePdfViewerAction(Localization.menuTitle("Show PDF Viewer"), Localization.lang("Show PDF Viewer"), + IconTheme.JabRefIcon.PDF_FILE.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); @@ -1278,6 +1282,7 @@ private void fillMenu() { view.add(new JCheckBoxMenuItem(enableToggle(generalFetcher.getAction()))); view.add(new JCheckBoxMenuItem(toggleGroups)); view.add(new JCheckBoxMenuItem(togglePreview)); + view.add(showPdvViewer); view.add(getSwitchPreviewAction()); mb.add(view); diff --git a/src/main/java/net/sf/jabref/gui/help/AboutDialogView.java b/src/main/java/net/sf/jabref/gui/help/AboutDialogView.java index 3bc0df15593..e64badc533d 100644 --- a/src/main/java/net/sf/jabref/gui/help/AboutDialogView.java +++ b/src/main/java/net/sf/jabref/gui/help/AboutDialogView.java @@ -15,12 +15,13 @@ */ package net.sf.jabref.gui.help; +import javafx.scene.Scene; +import javafx.scene.control.Alert.AlertType; + import net.sf.jabref.gui.FXAlert; import net.sf.jabref.logic.l10n.Localization; import com.airhacks.afterburner.views.FXMLView; -import javafx.scene.control.DialogPane; -import javafx.scene.control.Alert.AlertType; public class AboutDialogView extends FXMLView { @@ -31,7 +32,8 @@ public AboutDialogView() { public void show() { FXAlert aboutDialog = new FXAlert(AlertType.INFORMATION, Localization.lang("About JabRef")); - aboutDialog.setDialogPane((DialogPane) this.getView()); + aboutDialog.setScene(new Scene(this.getView())); + //aboutDialog.setDialogPane((DialogPane) this.getView()); aboutDialog.show(); } diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml new file mode 100644 index 00000000000..8d362ca5507 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java new file mode 100644 index 00000000000..e49ae26a269 --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2016 JabRef contributors. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package net.sf.jabref.gui.pdfviewer; + +import javafx.scene.Scene; +import javafx.scene.control.Alert.AlertType; + +import net.sf.jabref.gui.FXAlert; +import net.sf.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.FXMLView; + +public class PdfViewerView extends FXMLView { + + public PdfViewerView() { + super(); + bundle = Localization.getMessages(); + } + + public void show() { + FXAlert dialog = new FXAlert(AlertType.INFORMATION, Localization.lang("PDF Preview")); + //dialog.setDialogPane((DialogPane) this.getView()); + dialog.setScene(new Scene(this.getView())); + //dialog.getDialogPane().setContent(this.getView()); + dialog.setResizable(true); + dialog.show(); + } +} diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java new file mode 100644 index 00000000000..edbbb1790da --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2016 JabRef contributors. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package net.sf.jabref.gui.pdfviewer; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.IntegerBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.embed.swing.SwingFXUtils; +import javafx.fxml.FXML; +import javafx.scene.control.Pagination; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +import net.sf.jabref.JabRefGUI; +import net.sf.jabref.logic.TypedBibEntry; +import net.sf.jabref.logic.util.io.FileUtil; +import net.sf.jabref.model.entry.BibEntry; +import net.sf.jabref.model.entry.ParsedFileField; + +import ca.odell.glazedlists.event.ListEvent; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; + +public class PdfViewerViewModel { + + @FXML + private Pagination pagination; + + private ObjectProperty currentDocument; + + @FXML + private void initialize() { + currentDocument = new SimpleObjectProperty<>(); + currentDocument.addListener((observable, oldDocument, newDocument) -> { + if (newDocument != null) { + pagination.setCurrentPageIndex(0); + } + }); + pagination.pageCountProperty().bind(new IntegerBinding() { + { + super.bind(currentDocument); + } + @Override + protected int computeValue() { + return currentDocument.get() == null ? 0 : currentDocument.get().getNumberOfPages(); + } + }); + pagination.disableProperty().bind(Bindings.isNull(currentDocument)); + + pagination.setPageFactory(pageNumber -> currentDocument.get() == null ? null : new ImageView(currentDocument.get().getImage(pageNumber))); + + // TODO: Find a better way to subscribe to the currently selected entries + JabRefGUI.getMainFrame().getCurrentBasePanel().mainTable.getSelected().addListEventListener(listChanges -> setCurrentEntries(listChanges)); + setCurrentEntries(JabRefGUI.getMainFrame().getCurrentBasePanel().mainTable.getSelectedEntries()); + } + + private void setCurrentEntries(ListEvent listChanges) { + List newlySelectedEntries = new ArrayList<>(); + while (listChanges.next()) { + if (listChanges.getType() == ListEvent.INSERT) { + newlySelectedEntries.add(listChanges.getNewValue()); + } + } + setCurrentEntries(newlySelectedEntries); + + } + + private void setCurrentEntries(List entries) { + if (!entries.isEmpty()) { + BibEntry firstSelectedEntry = entries.get(0); + setCurrentEntry(firstSelectedEntry); + } + } + + private void setCurrentEntry(BibEntry rawEntry) { + TypedBibEntry entry = new TypedBibEntry(rawEntry); + List linkedFiles = entry.getFiles(); + for (ParsedFileField linkedFile : linkedFiles) { + // TODO: Find a better way to get the open database + // TODO: It should be possible to simply write linkedFile.getFile() + Optional file = FileUtil.expandFilename( + JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), linkedFile.getLink()); + if (file.isPresent()) { + setCurrentDocument(file.get().toPath()); + } + } + } + + private void setCurrentDocument(Path path) { + try { + currentDocument.set(new PdfDocumentViewModel(PDDocument.load(path.toFile()))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private class PdfDocumentViewModel { + + private final PDDocument document; + + public PdfDocumentViewModel(PDDocument document) { + this.document = Objects.requireNonNull(document); + } + + public int getNumberOfPages() { + return document.getNumberOfPages(); + } + + public Image getImage(int pageNumber) { + if (pageNumber <= 0 || pageNumber > document.getNumberOfPages()) { + return null; + } + + PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(pageNumber - 1); + try { + BufferedImage image = page.convertToImage(); + return SwingFXUtils.toFXImage(resize(image, 600, 800), null); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + // Taken from http://stackoverflow.com/a/9417836/873661 + private BufferedImage resize(BufferedImage img, int newW, int newH) { + java.awt.Image tmp = img.getScaledInstance(newW, newH, java.awt.Image.SCALE_SMOOTH); + BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = dimg.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + + return dimg; + } + } +} diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java b/src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java new file mode 100644 index 00000000000..0c8bc84bf0f --- /dev/null +++ b/src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2016 JabRef contributors. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package net.sf.jabref.gui.pdfviewer; + +import java.awt.event.ActionEvent; + +import javax.swing.Action; +import javax.swing.Icon; + +import javafx.application.Platform; + +import net.sf.jabref.gui.actions.MnemonicAwareAction; + +public class TogglePdfViewerAction extends MnemonicAwareAction { + + public TogglePdfViewerAction(String title, String tooltip, Icon iconFile) { + super(iconFile); + putValue(Action.NAME, title); + putValue(Action.SHORT_DESCRIPTION, tooltip); + } + + @Override + public void actionPerformed(ActionEvent e) { + Platform.runLater(() -> new PdfViewerView().show()); + } + +} diff --git a/src/main/java/net/sf/jabref/logic/TypedBibEntry.java b/src/main/java/net/sf/jabref/logic/TypedBibEntry.java index 080c06f2f3c..a097bf563ba 100644 --- a/src/main/java/net/sf/jabref/logic/TypedBibEntry.java +++ b/src/main/java/net/sf/jabref/logic/TypedBibEntry.java @@ -37,6 +37,10 @@ public class TypedBibEntry { private final Optional database; private final BibDatabaseMode mode; + public TypedBibEntry(BibEntry entry) { + this(entry, BibDatabaseMode.BIBLATEX); + } + public TypedBibEntry(BibEntry entry, BibDatabaseMode mode) { this(entry, Optional.empty(), mode); } From d0ff7730fcbe07a9748f53931b78032fbe1e9cba Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 23 Mar 2017 18:33:26 +0100 Subject: [PATCH 02/18] Fix compile errors --- src/main/java/org/jabref/gui/JabRefFrame.java | 1 + .../gui/pdfviewer/PdfViewerController.java} | 51 +++++++++---------- .../jabref/gui/pdfviewer/PdfViewerView.java | 25 ++++----- .../gui/pdfviewer/TogglePdfViewerAction.java | 5 +- 4 files changed, 37 insertions(+), 45 deletions(-) rename src/main/java/{net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java => org/jabref/gui/pdfviewer/PdfViewerController.java} (77%) rename src/main/java/{net/sf => org}/jabref/gui/pdfviewer/PdfViewerView.java (64%) rename src/main/java/{net/sf => org}/jabref/gui/pdfviewer/TogglePdfViewerAction.java (93%) diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 50079f8165e..6cd84e848af 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -99,6 +99,7 @@ import org.jabref.gui.menus.RightClickMenu; import org.jabref.gui.openoffice.OpenOfficePanel; import org.jabref.gui.openoffice.OpenOfficeSidePanel; +import org.jabref.gui.pdfviewer.TogglePdfViewerAction; import org.jabref.gui.preftabs.PreferencesDialog; import org.jabref.gui.protectedterms.ProtectedTermsDialog; import org.jabref.gui.push.PushToApplicationButton; diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java similarity index 77% rename from src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java rename to src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java index edbbb1790da..9222537ea6a 100644 --- a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerViewModel.java +++ b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java @@ -15,42 +15,48 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package net.sf.jabref.gui.pdfviewer; +package org.jabref.gui.pdfviewer; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; +import javax.inject.Inject; + import javafx.beans.binding.Bindings; import javafx.beans.binding.IntegerBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; import javafx.embed.swing.SwingFXUtils; import javafx.fxml.FXML; import javafx.scene.control.Pagination; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import net.sf.jabref.JabRefGUI; -import net.sf.jabref.logic.TypedBibEntry; -import net.sf.jabref.logic.util.io.FileUtil; -import net.sf.jabref.model.entry.BibEntry; -import net.sf.jabref.model.entry.ParsedFileField; +import org.jabref.Globals; +import org.jabref.gui.AbstractController; +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.StateManager; +import org.jabref.logic.TypedBibEntry; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.ParsedFileField; -import ca.odell.glazedlists.event.ListEvent; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; -public class PdfViewerViewModel { +public class PdfViewerController extends AbstractController { - @FXML - private Pagination pagination; + @FXML private Pagination pagination; + + @Inject private StateManager stateManager; private ObjectProperty currentDocument; @@ -75,20 +81,8 @@ protected int computeValue() { pagination.setPageFactory(pageNumber -> currentDocument.get() == null ? null : new ImageView(currentDocument.get().getImage(pageNumber))); - // TODO: Find a better way to subscribe to the currently selected entries - JabRefGUI.getMainFrame().getCurrentBasePanel().mainTable.getSelected().addListEventListener(listChanges -> setCurrentEntries(listChanges)); - setCurrentEntries(JabRefGUI.getMainFrame().getCurrentBasePanel().mainTable.getSelectedEntries()); - } - - private void setCurrentEntries(ListEvent listChanges) { - List newlySelectedEntries = new ArrayList<>(); - while (listChanges.next()) { - if (listChanges.getType() == ListEvent.INSERT) { - newlySelectedEntries.add(listChanges.getNewValue()); - } - } - setCurrentEntries(newlySelectedEntries); - + stateManager.getSelectedEntries().addListener((ListChangeListener) c -> setCurrentEntries(stateManager.getSelectedEntries())); + setCurrentEntries(stateManager.getSelectedEntries()); } private void setCurrentEntries(List entries) { @@ -99,13 +93,14 @@ private void setCurrentEntries(List entries) { } private void setCurrentEntry(BibEntry rawEntry) { - TypedBibEntry entry = new TypedBibEntry(rawEntry); + BibDatabaseContext databaseContext = stateManager.activeDatabaseProperty().get().get(); + TypedBibEntry entry = new TypedBibEntry(rawEntry, databaseContext); List linkedFiles = entry.getFiles(); for (ParsedFileField linkedFile : linkedFiles) { // TODO: Find a better way to get the open database // TODO: It should be possible to simply write linkedFile.getFile() Optional file = FileUtil.expandFilename( - JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), linkedFile.getLink()); + databaseContext, linkedFile.getLink(), Globals.prefs.getFileDirectoryPreferences()); if (file.isPresent()) { setCurrentDocument(file.get().toPath()); } @@ -120,7 +115,7 @@ private void setCurrentDocument(Path path) { } } - private class PdfDocumentViewModel { + public class PdfDocumentViewModel extends AbstractViewModel { private final PDDocument document; diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java similarity index 64% rename from src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java rename to src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java index e49ae26a269..e99ed8eb3c6 100644 --- a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewerView.java +++ b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java @@ -15,27 +15,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package net.sf.jabref.gui.pdfviewer; +package org.jabref.gui.pdfviewer; -import javafx.scene.Scene; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.DialogPane; -import net.sf.jabref.gui.FXAlert; -import net.sf.jabref.logic.l10n.Localization; +import org.jabref.gui.AbstractDialogView; +import org.jabref.gui.FXDialog; +import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.FXMLView; - -public class PdfViewerView extends FXMLView { - - public PdfViewerView() { - super(); - bundle = Localization.getMessages(); - } +public class PdfViewerView extends AbstractDialogView { + @Override public void show() { - FXAlert dialog = new FXAlert(AlertType.INFORMATION, Localization.lang("PDF Preview")); - //dialog.setDialogPane((DialogPane) this.getView()); - dialog.setScene(new Scene(this.getView())); + FXDialog dialog = new FXDialog(AlertType.INFORMATION, Localization.lang("PDF Preview")); + dialog.setDialogPane((DialogPane) this.getView()); + //dialog.setScene(new Scene(this.getView())); //dialog.getDialogPane().setContent(this.getView()); dialog.setResizable(true); dialog.show(); diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java b/src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java similarity index 93% rename from src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java rename to src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java index 0c8bc84bf0f..1c19eafd015 100644 --- a/src/main/java/net/sf/jabref/gui/pdfviewer/TogglePdfViewerAction.java +++ b/src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java @@ -14,7 +14,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package net.sf.jabref.gui.pdfviewer; +package org.jabref.gui.pdfviewer; import java.awt.event.ActionEvent; @@ -23,7 +23,8 @@ import javafx.application.Platform; -import net.sf.jabref.gui.actions.MnemonicAwareAction; +import org.jabref.gui.actions.MnemonicAwareAction; + public class TogglePdfViewerAction extends MnemonicAwareAction { From cc13ea51dca01e27a360d6bc65559afdc4cfa3f6 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 25 Mar 2017 19:43:55 +0100 Subject: [PATCH 03/18] First really working prototype --- build.gradle | 2 + .../sf/jabref/gui/pdfviewer/PdfViewer.fxml | 11 -- src/main/java/org/jabref/gui/BasePanel.java | 45 ++++--- src/main/java/org/jabref/gui/IconTheme.java | 33 ++--- src/main/java/org/jabref/gui/Main.css | 16 +++ .../java/org/jabref/gui/groups/GroupTree.fxml | 4 +- .../org/jabref/gui/pdfviewer/PdfViewer.css | 11 ++ .../org/jabref/gui/pdfviewer/PdfViewer.fxml | 95 +++++++++++++ .../gui/pdfviewer/PdfViewerController.java | 126 +++++++++++++++--- .../jabref/gui/pdfviewer/PdfViewerView.java | 12 +- src/main/resources/l10n/JabRef_en.properties | 6 + 11 files changed, 289 insertions(+), 72 deletions(-) delete mode 100644 src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml create mode 100644 src/main/java/org/jabref/gui/pdfviewer/PdfViewer.css create mode 100644 src/main/java/org/jabref/gui/pdfviewer/PdfViewer.fxml diff --git a/build.gradle b/build.gradle index ad12127b7a8..36c3cb8fecc 100644 --- a/build.gradle +++ b/build.gradle @@ -114,6 +114,8 @@ dependencies { compile 'de.codecentric.centerdevice:javafxsvg:1.2.1' compile 'org.controlsfx:controlsfx:8.40.12' compile 'org.fxmisc.easybind:easybind:1.0.3' + compile 'org.fxmisc.flowless:flowless:0.5.2' + compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' compile 'commons-logging:commons-logging:1.2' diff --git a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml b/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml deleted file mode 100644 index 8d362ca5507..00000000000 --- a/src/main/java/net/sf/jabref/gui/pdfviewer/PdfViewer.fxml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 1f15d1902d0..a7d5f5dfd69 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -41,6 +41,8 @@ import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; +import javafx.application.Platform; + import org.jabref.Globals; import org.jabref.JabRefExecutorService; import org.jabref.collab.ChangeScanner; @@ -237,6 +239,26 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { } } + public static void runWorker(AbstractWorker worker) throws Exception { + // This part uses Spin's features: + Runnable wrk = worker.getWorker(); + // The Worker returned by getWorker() has been wrapped + // by Spin.off(), which makes its methods be run in + // a different thread from the EDT. + CallBack clb = worker.getCallBack(); + + worker.init(); // This method runs in this same thread, the EDT. + // Useful for initial GUI actions, like printing a message. + + // The CallBack returned by getCallBack() has been wrapped + // by Spin.over(), which makes its methods be run on + // the EDT. + wrk.run(); // Runs the potentially time-consuming action + // without freezing the GUI. The magic is that THIS line + // of execution will not continue until run() is finished. + clb.update(); // Runs the update() method on the EDT. + } + // Returns a collection of AutoCompleters, which are populated from the current library public ContentAutoCompleters getAutoCompleters() { return autoCompleters; @@ -999,26 +1021,6 @@ public void runCommand(final String _command) { } } - public static void runWorker(AbstractWorker worker) throws Exception { - // This part uses Spin's features: - Runnable wrk = worker.getWorker(); - // The Worker returned by getWorker() has been wrapped - // by Spin.off(), which makes its methods be run in - // a different thread from the EDT. - CallBack clb = worker.getCallBack(); - - worker.init(); // This method runs in this same thread, the EDT. - // Useful for initial GUI actions, like printing a message. - - // The CallBack returned by getCallBack() has been wrapped - // by Spin.over(), which makes its methods be run on - // the EDT. - wrk.run(); // Runs the potentially time-consuming action - // without freezing the GUI. The magic is that THIS line - // of execution will not continue until run() is finished. - clb.update(); // Runs the update() method on the EDT. - } - private boolean saveDatabase(File file, boolean selectedOnly, Charset enc, SavePreferences.DatabaseSaveType saveType) throws SaveException { SaveSession session; @@ -1230,7 +1232,8 @@ private void createMainTable() { mainTable.addFocusListener(selectionListener); // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) - mainTable.addSelectionListener(listEvent -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries())); + mainTable.addSelectionListener(listEvent -> + Platform.runLater(() -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries()))); String clearSearch = "clearSearch"; mainTable.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.CLEAR_SEARCH), clearSearch); diff --git a/src/main/java/org/jabref/gui/IconTheme.java b/src/main/java/org/jabref/gui/IconTheme.java index dc7a3e8d0a7..7e73a70bd1a 100644 --- a/src/main/java/org/jabref/gui/IconTheme.java +++ b/src/main/java/org/jabref/gui/IconTheme.java @@ -52,10 +52,10 @@ public class IconTheme { private static javafx.scene.text.Font FX_FONT; static { - try (InputStream stream = FontBasedIcon.class.getResourceAsStream("/fonts/materialdesignicons-webfont.ttf")) { + try (InputStream stream = getMaterialDesignIconsStream()) { FONT = Font.createFont(Font.TRUETYPE_FONT, stream); FONT_16 = FONT.deriveFont(Font.PLAIN, 16f); - try (InputStream stream2 = FontBasedIcon.class.getResourceAsStream("/fonts/materialdesignicons-webfont.ttf")) { + try (InputStream stream2 = getMaterialDesignIconsStream()) { FX_FONT = javafx.scene.text.Font.loadFont(stream2, JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_LARGE)); } } catch (FontFormatException | IOException e) { @@ -63,6 +63,10 @@ public class IconTheme { } } + private static InputStream getMaterialDesignIconsStream() { + return FontBasedIcon.class.getResourceAsStream("/fonts/materialdesignicons-webfont.ttf"); + } + public static javafx.scene.paint.Color getDefaultColor() { return javafx.scene.paint.Color.rgb(DEFAULT_COLOR.getRed(), DEFAULT_COLOR.getGreen(), DEFAULT_COLOR.getBlue(), DEFAULT_COLOR.getAlpha() / 255.0); } @@ -146,6 +150,18 @@ private static Map readIconThemeFile(URL url, String prefix) { return result; } + public static List getLogoSet() { + List jabrefLogos = new ArrayList<>(); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon16")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon20")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon32")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon40")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon48")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon64")).getImage()); + jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon128")).getImage()); + + return jabrefLogos; + } public enum JabRefIcon { @@ -344,17 +360,4 @@ public FontBasedIcon createWithNewColor(Color newColor) { return new FontBasedIcon(this.iconCode, newColor, this.size); } } - - public static List getLogoSet() { - List jabrefLogos = new ArrayList<>(); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon16")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon20")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon32")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon40")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon48")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon64")).getImage()); - jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon128")).getImage()); - - return jabrefLogos; - } } diff --git a/src/main/java/org/jabref/gui/Main.css b/src/main/java/org/jabref/gui/Main.css index 735592b4c0b..da6d8e94bec 100644 --- a/src/main/java/org/jabref/gui/Main.css +++ b/src/main/java/org/jabref/gui/Main.css @@ -48,9 +48,25 @@ .icon { -fx-font-family: 'Material Design Icons'; -fx-font-size: 16.0; + } + +.glyph-icon { + -fx-font-size: 16.0; + -glyph-size: 16.0; } .tooltip { -fx-background-color: #757575; /* For some reason it does not work if we use -fx-dark-background here */ -fx-effect:none; } + +.flatButton { + -fx-shadow-highlight-color: transparent; + -fx-outer-border: transparent; + -fx-inner-border: transparent; + -fx-focus-color: #6A9FCD; + -fx-faint-focus-color: transparent; + -fx-background-color: transparent; + -fx-text-background-color: dimgray; + -fx-padding: 0.5em; +} diff --git a/src/main/java/org/jabref/gui/groups/GroupTree.fxml b/src/main/java/org/jabref/gui/groups/GroupTree.fxml index f0ce56026e6..c465159d028 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTree.fxml +++ b/src/main/java/org/jabref/gui/groups/GroupTree.fxml @@ -7,8 +7,8 @@ + -
@@ -32,7 +32,7 @@ + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java index 9222537ea6a..6ccd5fba4e9 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java +++ b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java @@ -25,24 +25,32 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import javax.inject.Inject; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.IntegerBinding; +import javafx.animation.FadeTransition; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.embed.swing.SwingFXUtils; import javafx.fxml.FXML; -import javafx.scene.control.Pagination; +import javafx.geometry.Pos; +import javafx.scene.control.ChoiceBox; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; import org.jabref.Globals; import org.jabref.gui.AbstractController; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.StateManager; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.TypedBibEntry; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; @@ -51,24 +59,68 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; +import org.fxmisc.flowless.Cell; +import org.fxmisc.flowless.VirtualFlow; public class PdfViewerController extends AbstractController { - @FXML private Pagination pagination; + @FXML private ChoiceBox fileChoice; + @FXML private BorderPane mainPane; + + //@FXML private Pagination pagination; @Inject private StateManager stateManager; + @Inject private TaskExecutor taskExecutor; private ObjectProperty currentDocument; + private static Image renderPage(PDPage page) { + try { + int resolution = 96; + BufferedImage image = page.convertToImage(BufferedImage.TYPE_INT_RGB, 2 * resolution); + return SwingFXUtils.toFXImage(resize(image, 600, 800), null); + } catch (IOException e) { + // TODO: LOG + return null; + } + } + + // Taken from http://stackoverflow.com/a/9417836/873661 + private static BufferedImage resize(BufferedImage img, int newW, int newH) { + java.awt.Image tmp = img.getScaledInstance(newW, newH, java.awt.Image.SCALE_SMOOTH); + BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = dimg.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + + return dimg; + } + @FXML private void initialize() { currentDocument = new SimpleObjectProperty<>(); currentDocument.addListener((observable, oldDocument, newDocument) -> { if (newDocument != null) { - pagination.setCurrentPageIndex(0); + ObservableList pages = FXCollections.observableArrayList(newDocument.getPages()); + VirtualFlow> flow = VirtualFlow.createVertical(pages, this::createPageCell); + + + //flow.visibleProperty().bind(isNotEmpty(pages)); + //flow.managedProperty().bind(isNotEmpty(pages)); + + mainPane.setCenter(flow); + StackPane.setAlignment(flow, Pos.BOTTOM_RIGHT); + + //flow.resize(1000, 1000); + //flow.layout(); + //flow.setMinWidth(1000); + //flow.setPrefWidth(1000); + //pagination.setCurrentPageIndex(0); } }); - pagination.pageCountProperty().bind(new IntegerBinding() { + + /*pagination.pageCountProperty().bind(new IntegerBinding() { { super.bind(currentDocument); } @@ -80,11 +132,50 @@ protected int computeValue() { pagination.disableProperty().bind(Bindings.isNull(currentDocument)); pagination.setPageFactory(pageNumber -> currentDocument.get() == null ? null : new ImageView(currentDocument.get().getImage(pageNumber))); - +*/ stateManager.getSelectedEntries().addListener((ListChangeListener) c -> setCurrentEntries(stateManager.getSelectedEntries())); setCurrentEntries(stateManager.getSelectedEntries()); } + private Cell createPageCell(PDPage pageNew) { + return new Cell() { + ImageView imageView = new ImageView(renderPage(pageNew)); + StackPane imageHolder = new StackPane(); + + @Override + public StackPane getNode() { + imageHolder.getStyleClass().setAll("image-holder"); + imageHolder.getChildren().setAll(imageView); + //imageHolder.setPadding(new Insets(100)); + return imageHolder; + } + + @Override + public boolean isReusable() { + return true; + } + + @Override + public void updateItem(PDPage page) { + // First hide old page + imageView.setOpacity(0); + + BackgroundTask generateImage = BackgroundTask + .run(() -> renderPage(page)) + .onSuccess(image -> { + imageView.setImage(image); + + // Fade new page in for smoother transition + FadeTransition fadeIn = new FadeTransition(Duration.millis(100), imageView); + fadeIn.setFromValue(0); + fadeIn.setToValue(1); + fadeIn.play(); + }); + taskExecutor.execute(generateImage); + } + }; + } + private void setCurrentEntries(List entries) { if (!entries.isEmpty()) { BibEntry firstSelectedEntry = entries.get(0); @@ -105,6 +196,11 @@ private void setCurrentEntry(BibEntry rawEntry) { setCurrentDocument(file.get().toPath()); } } + + fileChoice.setItems( + FXCollections.observableArrayList( + linkedFiles.stream().map(ParsedFileField::getLink).collect(Collectors.toList()))); + fileChoice.getSelectionModel().selectFirst(); } private void setCurrentDocument(Path path) { @@ -127,6 +223,10 @@ public int getNumberOfPages() { return document.getNumberOfPages(); } + public List getPages() { + return document.getDocumentCatalog().getAllPages(); + } + public Image getImage(int pageNumber) { if (pageNumber <= 0 || pageNumber > document.getNumberOfPages()) { return null; @@ -141,17 +241,5 @@ public Image getImage(int pageNumber) { return null; } } - - // Taken from http://stackoverflow.com/a/9417836/873661 - private BufferedImage resize(BufferedImage img, int newW, int newH) { - java.awt.Image tmp = img.getScaledInstance(newW, newH, java.awt.Image.SCALE_SMOOTH); - BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2d = dimg.createGraphics(); - g2d.drawImage(tmp, 0, 0, null); - g2d.dispose(); - - return dimg; - } } } diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java index e99ed8eb3c6..6111e7e110d 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java +++ b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java @@ -18,6 +18,7 @@ package org.jabref.gui.pdfviewer; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar; import javafx.scene.control.DialogPane; import org.jabref.gui.AbstractDialogView; @@ -28,10 +29,13 @@ public class PdfViewerView extends AbstractDialogView { @Override public void show() { - FXDialog dialog = new FXDialog(AlertType.INFORMATION, Localization.lang("PDF Preview")); - dialog.setDialogPane((DialogPane) this.getView()); - //dialog.setScene(new Scene(this.getView())); - //dialog.getDialogPane().setContent(this.getView()); + FXDialog dialog = new FXDialog(AlertType.INFORMATION, Localization.lang("PDF Preview"), false); + DialogPane dialogPane = (DialogPane) this.getView(); + + // Remove button bar at bottom + dialogPane.getChildren().removeIf(node -> node instanceof ButtonBar); + + dialog.setDialogPane(dialogPane); dialog.setResizable(true); dialog.show(); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 9654b903c09..4bae1d0f2af 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2374,3 +2374,9 @@ Default_table_font_size=Default_table_font_size Escape_underscores=Escape_underscores Color=Color Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.=Please_also_add_all_steps_to_reproduce_this_issue,_if_possible. +Fit_width=Fit_width +Fit_a_single_page=Fit_a_single_page +Zoom_in=Zoom_in +Zoom_out=Zoom_out +Previous_page=Previous_page +Next_page=Next_page From a527d4224def377fcd13c9b8d09d62dcf8ad58ea Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 26 Mar 2017 16:56:29 +0200 Subject: [PATCH 04/18] Refactor code --- .../documentviewer/DocumentPageViewModel.java | 14 + .../gui/documentviewer/DocumentViewModel.java | 7 + .../DocumentViewer.css} | 2 +- .../DocumentViewer.fxml} | 3 +- .../documentviewer/DocumentViewerControl.java | 100 +++++++ .../DocumentViewerController.java | 58 +++++ .../DocumentViewerView.java} | 6 +- .../DocumentViewerViewModel.java | 85 ++++++ .../PdfDocumentPageViewModel.java | 47 ++++ .../documentviewer/PdfDocumentViewModel.java | 33 +++ .../ShowDocumentViewerAction.java} | 15 +- .../gui/pdfviewer/PdfViewerController.java | 245 ------------------ 12 files changed, 361 insertions(+), 254 deletions(-) create mode 100644 src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java create mode 100644 src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java rename src/main/java/org/jabref/gui/{pdfviewer/PdfViewer.css => documentviewer/DocumentViewer.css} (87%) rename src/main/java/org/jabref/gui/{pdfviewer/PdfViewer.fxml => documentviewer/DocumentViewer.fxml} (96%) create mode 100644 src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java create mode 100644 src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java rename src/main/java/org/jabref/gui/{pdfviewer/PdfViewerView.java => documentviewer/DocumentViewerView.java} (90%) create mode 100644 src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java create mode 100644 src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java create mode 100644 src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java rename src/main/java/org/jabref/gui/{pdfviewer/TogglePdfViewerAction.java => documentviewer/ShowDocumentViewerAction.java} (68%) delete mode 100644 src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java new file mode 100644 index 00000000000..d8c2926a737 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java @@ -0,0 +1,14 @@ +package org.jabref.gui.documentviewer; + +import javafx.scene.image.Image; + +/** + * Represents the view model for a page in the document viewer. + */ +public abstract class DocumentPageViewModel { + + /** + * Renders this page and returns an image representation of itself. + */ + public abstract Image render(); +} diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java new file mode 100644 index 00000000000..05035d8ddd4 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java @@ -0,0 +1,7 @@ +package org.jabref.gui.documentviewer; + +import javafx.collections.ObservableList; + +public abstract class DocumentViewModel { + public abstract ObservableList getPages(); +} diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewer.css b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.css similarity index 87% rename from src/main/java/org/jabref/gui/pdfviewer/PdfViewer.css rename to src/main/java/org/jabref/gui/documentviewer/DocumentViewer.css index 760181dc64c..00ce6d65f3c 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewer.css +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.css @@ -3,7 +3,7 @@ -fx-padding: 0; } -.virtual-flow .image-holder { +.document-viewer .page { -fx-padding: 0em 0em 1em 0em; -fx-background-color: -fx-accented-background; -fx-background-insets: 0; diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewer.fxml b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml similarity index 96% rename from src/main/java/org/jabref/gui/pdfviewer/PdfViewer.fxml rename to src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml index bc9777bd61b..d93cd6b04d0 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewer.fxml +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml @@ -13,7 +13,8 @@ + xmlns="http://javafx.com/javafx/8.0.112" + fx:controller="org.jabref.gui.documentviewer.DocumentViewerController"> diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java new file mode 100644 index 00000000000..1f663f18252 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java @@ -0,0 +1,100 @@ +package org.jabref.gui.documentviewer; + +import java.util.Objects; + +import javafx.animation.FadeTransition; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; + +import org.fxmisc.flowless.Cell; +import org.fxmisc.flowless.VirtualFlow; + +public class DocumentViewerControl extends StackPane { + + private TaskExecutor taskExecutor; + + public DocumentViewerControl(TaskExecutor taskExecutor) { + this.taskExecutor = Objects.requireNonNull(taskExecutor); + + this.getStyleClass().setAll("document-viewer"); + } + + public void show(DocumentViewModel document) { + VirtualFlow> flow + = VirtualFlow.createVertical(document.getPages(), DocumentViewerPage::new); + getChildren().setAll(flow); + } + + /** + * Represents the viewport for a page. Note: the instances of {@link DocumentViewerPage} are reused, i.e., not every + * page is rendered in a new instance but instead {@link DocumentViewerPage#updateItem(Object)} is called. + */ + private class DocumentViewerPage implements Cell { + private final ImageView imageView; + private final StackPane imageHolder; + private final Rectangle background; + + public DocumentViewerPage(DocumentPageViewModel initialPage) { + imageView = new ImageView(); + imageHolder = new StackPane(); + imageHolder.getStyleClass().setAll("page"); + + // Show progress indicator + ProgressIndicator progress = new ProgressIndicator(); + progress.setMaxSize(50, 50); + + // Set empty background and create proper rendering in background (for smoother loading) + background = new Rectangle(600, 800); + background.setStyle("-fx-fill: WHITE"); + imageView.setImage(new WritableImage(600, 800)); + BackgroundTask generateImage = BackgroundTask + .run(initialPage::render) + .onSuccess(image -> { + imageView.setImage(image); + progress.setVisible(false); + background.setVisible(false); + }); + taskExecutor.execute(generateImage); + + imageHolder.getChildren().setAll(background, progress, imageView); + } + + @Override + public StackPane getNode() { + return imageHolder; + } + + @Override + public boolean isReusable() { + return true; + } + + @Override + public void updateItem(DocumentPageViewModel page) { + // First hide old page and show background instead + background.setVisible(true); + imageView.setOpacity(0); + + BackgroundTask generateImage = BackgroundTask + .run(page::render) + .onSuccess(image -> { + imageView.setImage(image); + + // Fade new page in for smoother transition + FadeTransition fadeIn = new FadeTransition(Duration.millis(100), imageView); + fadeIn.setFromValue(0); + fadeIn.setToValue(1); + fadeIn.play(); + }); + taskExecutor.execute(generateImage); + } + } +} diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java new file mode 100644 index 00000000000..a9a364251d8 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2016 JabRef contributors. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package org.jabref.gui.documentviewer; + +import javax.inject.Inject; + +import javafx.fxml.FXML; +import javafx.scene.control.ChoiceBox; +import javafx.scene.layout.BorderPane; + +import org.jabref.gui.AbstractController; +import org.jabref.gui.StateManager; +import org.jabref.gui.util.TaskExecutor; + +public class DocumentViewerController extends AbstractController { + + @FXML private ChoiceBox fileChoice; + @FXML private BorderPane mainPane; + + @Inject private StateManager stateManager; + @Inject private TaskExecutor taskExecutor; + + @FXML + private void initialize() { + viewModel = new DocumentViewerViewModel(stateManager); + + setupViewer(); + + fileChoice.itemsProperty().bind(viewModel.filesProperty()); + fileChoice.getSelectionModel().selectFirst(); + } + + private void setupViewer() { + DocumentViewerControl viewer = new DocumentViewerControl(taskExecutor); + viewModel.currentDocumentProperty().addListener((observable, oldDocument, newDocument) -> { + if (newDocument != null) { + viewer.show(newDocument); + } + }); + viewer.show(viewModel.currentDocumentProperty().get()); + mainPane.setCenter(viewer); + } +} diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java similarity index 90% rename from src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java rename to src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java index 6111e7e110d..7dad995f85b 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerView.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java @@ -15,7 +15,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package org.jabref.gui.pdfviewer; +package org.jabref.gui.documentviewer; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonBar; @@ -25,11 +25,11 @@ import org.jabref.gui.FXDialog; import org.jabref.logic.l10n.Localization; -public class PdfViewerView extends AbstractDialogView { +public class DocumentViewerView extends AbstractDialogView { @Override public void show() { - FXDialog dialog = new FXDialog(AlertType.INFORMATION, Localization.lang("PDF Preview"), false); + FXDialog dialog = new FXDialog(AlertType.INFORMATION, Localization.lang("Document viewer"), false); DialogPane dialogPane = (DialogPane) this.getView(); // Remove button bar at bottom diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java new file mode 100644 index 00000000000..d8aea189c14 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -0,0 +1,85 @@ +package org.jabref.gui.documentviewer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; + +import org.jabref.Globals; +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.StateManager; +import org.jabref.logic.TypedBibEntry; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.ParsedFileField; + +import org.apache.pdfbox.pdmodel.PDDocument; + +public class DocumentViewerViewModel extends AbstractViewModel { + + private StateManager stateManager; + private ObjectProperty currentDocument = new SimpleObjectProperty<>(); + private ListProperty files = new SimpleListProperty<>(); + + public DocumentViewerViewModel(StateManager stateManager) { + this.stateManager = Objects.requireNonNull(stateManager); + + this.stateManager.getSelectedEntries().addListener((ListChangeListener) c -> setCurrentEntries(this.stateManager.getSelectedEntries())); + setCurrentEntries(this.stateManager.getSelectedEntries()); + } + + ; + + public ObjectProperty currentDocumentProperty() { + return currentDocument; + } + + public ListProperty filesProperty() { + return files; + } + + private void setCurrentEntries(List entries) { + if (!entries.isEmpty()) { + BibEntry firstSelectedEntry = entries.get(0); + setCurrentEntry(firstSelectedEntry); + } + } + + private void setCurrentEntry(BibEntry rawEntry) { + BibDatabaseContext databaseContext = stateManager.activeDatabaseProperty().get().get(); + TypedBibEntry entry = new TypedBibEntry(rawEntry, databaseContext); + List linkedFiles = entry.getFiles(); + for (ParsedFileField linkedFile : linkedFiles) { + // TODO: Find a better way to get the open database + // TODO: It should be possible to simply write linkedFile.getFile() + Optional file = FileUtil.expandFilename( + databaseContext, linkedFile.getLink(), Globals.prefs.getFileDirectoryPreferences()); + if (file.isPresent()) { + setCurrentDocument(file.get().toPath()); + } + } + + files.setValue( + FXCollections.observableArrayList( + linkedFiles.stream().map(ParsedFileField::getLink).collect(Collectors.toList()))); + } + + private void setCurrentDocument(Path path) { + try { + currentDocument.set(new PdfDocumentViewModel(PDDocument.load(path.toFile()))); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java new file mode 100644 index 00000000000..f6f95415037 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java @@ -0,0 +1,47 @@ +package org.jabref.gui.documentviewer; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Objects; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; + +import org.apache.pdfbox.pdmodel.PDPage; + +/** + * Represents the view model of a pdf page backed by a {@link PDPage}. + */ +public class PdfDocumentPageViewModel extends DocumentPageViewModel { + + private final PDPage page; + + public PdfDocumentPageViewModel(PDPage page) { + this.page = Objects.requireNonNull(page); + } + + // Taken from http://stackoverflow.com/a/9417836/873661 + private static BufferedImage resize(BufferedImage img, int newWidth, int newHeight) { + java.awt.Image tmp = img.getScaledInstance(newWidth, newHeight, java.awt.Image.SCALE_SMOOTH); + BufferedImage dimg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = dimg.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + + return dimg; + } + + @Override + public Image render() { + try { + int resolution = 96; + BufferedImage image = page.convertToImage(BufferedImage.TYPE_INT_RGB, 2 * resolution); + return SwingFXUtils.toFXImage(resize(image, 600, 800), null); + } catch (IOException e) { + // TODO: LOG + return null; + } + } +} diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java new file mode 100644 index 00000000000..7d762e8f145 --- /dev/null +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java @@ -0,0 +1,33 @@ +package org.jabref.gui.documentviewer; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; + +public class PdfDocumentViewModel extends DocumentViewModel { + + private final PDDocument document; + + public PdfDocumentViewModel(PDDocument document) { + this.document = Objects.requireNonNull(document); + } + + @Override + public ObservableList getPages() { + @SuppressWarnings("unchecked") + List pages = document.getDocumentCatalog().getAllPages(); + + return FXCollections.observableArrayList( + pages.stream().map(PdfDocumentPageViewModel::new).collect(Collectors.toList())); + } + + public int getNumberOfPages() { + return document.getNumberOfPages(); + } +} diff --git a/src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java similarity index 68% rename from src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java rename to src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java index 1c19eafd015..1ca9f1c5620 100644 --- a/src/main/java/org/jabref/gui/pdfviewer/TogglePdfViewerAction.java +++ b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java @@ -14,7 +14,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package org.jabref.gui.pdfviewer; +package org.jabref.gui.documentviewer; import java.awt.event.ActionEvent; @@ -23,20 +23,27 @@ import javafx.application.Platform; +import org.jabref.gui.IconTheme; import org.jabref.gui.actions.MnemonicAwareAction; +import org.jabref.logic.l10n.Localization; -public class TogglePdfViewerAction extends MnemonicAwareAction { +public class ShowDocumentViewerAction extends MnemonicAwareAction { - public TogglePdfViewerAction(String title, String tooltip, Icon iconFile) { + public ShowDocumentViewerAction(String title, String tooltip, Icon iconFile) { super(iconFile); putValue(Action.NAME, title); putValue(Action.SHORT_DESCRIPTION, tooltip); } + public ShowDocumentViewerAction() { + this(Localization.menuTitle("Show document viewer"), Localization.lang("Show document viewer"), + IconTheme.JabRefIcon.PDF_FILE.getIcon()); + } + @Override public void actionPerformed(ActionEvent e) { - Platform.runLater(() -> new PdfViewerView().show()); + Platform.runLater(() -> new DocumentViewerView().show()); } } diff --git a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java b/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java deleted file mode 100644 index 6ccd5fba4e9..00000000000 --- a/src/main/java/org/jabref/gui/pdfviewer/PdfViewerController.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2016 JabRef contributors. - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -package org.jabref.gui.pdfviewer; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.inject.Inject; - -import javafx.animation.FadeTransition; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.embed.swing.SwingFXUtils; -import javafx.fxml.FXML; -import javafx.geometry.Pos; -import javafx.scene.control.ChoiceBox; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; -import javafx.util.Duration; - -import org.jabref.Globals; -import org.jabref.gui.AbstractController; -import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.StateManager; -import org.jabref.gui.util.BackgroundTask; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.TypedBibEntry; -import org.jabref.logic.util.io.FileUtil; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.ParsedFileField; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.fxmisc.flowless.Cell; -import org.fxmisc.flowless.VirtualFlow; - -public class PdfViewerController extends AbstractController { - - @FXML private ChoiceBox fileChoice; - @FXML private BorderPane mainPane; - - //@FXML private Pagination pagination; - - @Inject private StateManager stateManager; - @Inject private TaskExecutor taskExecutor; - - private ObjectProperty currentDocument; - - private static Image renderPage(PDPage page) { - try { - int resolution = 96; - BufferedImage image = page.convertToImage(BufferedImage.TYPE_INT_RGB, 2 * resolution); - return SwingFXUtils.toFXImage(resize(image, 600, 800), null); - } catch (IOException e) { - // TODO: LOG - return null; - } - } - - // Taken from http://stackoverflow.com/a/9417836/873661 - private static BufferedImage resize(BufferedImage img, int newW, int newH) { - java.awt.Image tmp = img.getScaledInstance(newW, newH, java.awt.Image.SCALE_SMOOTH); - BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2d = dimg.createGraphics(); - g2d.drawImage(tmp, 0, 0, null); - g2d.dispose(); - - return dimg; - } - - @FXML - private void initialize() { - currentDocument = new SimpleObjectProperty<>(); - currentDocument.addListener((observable, oldDocument, newDocument) -> { - if (newDocument != null) { - ObservableList pages = FXCollections.observableArrayList(newDocument.getPages()); - VirtualFlow> flow = VirtualFlow.createVertical(pages, this::createPageCell); - - - //flow.visibleProperty().bind(isNotEmpty(pages)); - //flow.managedProperty().bind(isNotEmpty(pages)); - - mainPane.setCenter(flow); - StackPane.setAlignment(flow, Pos.BOTTOM_RIGHT); - - //flow.resize(1000, 1000); - //flow.layout(); - //flow.setMinWidth(1000); - //flow.setPrefWidth(1000); - //pagination.setCurrentPageIndex(0); - } - }); - - /*pagination.pageCountProperty().bind(new IntegerBinding() { - { - super.bind(currentDocument); - } - @Override - protected int computeValue() { - return currentDocument.get() == null ? 0 : currentDocument.get().getNumberOfPages(); - } - }); - pagination.disableProperty().bind(Bindings.isNull(currentDocument)); - - pagination.setPageFactory(pageNumber -> currentDocument.get() == null ? null : new ImageView(currentDocument.get().getImage(pageNumber))); -*/ - stateManager.getSelectedEntries().addListener((ListChangeListener) c -> setCurrentEntries(stateManager.getSelectedEntries())); - setCurrentEntries(stateManager.getSelectedEntries()); - } - - private Cell createPageCell(PDPage pageNew) { - return new Cell() { - ImageView imageView = new ImageView(renderPage(pageNew)); - StackPane imageHolder = new StackPane(); - - @Override - public StackPane getNode() { - imageHolder.getStyleClass().setAll("image-holder"); - imageHolder.getChildren().setAll(imageView); - //imageHolder.setPadding(new Insets(100)); - return imageHolder; - } - - @Override - public boolean isReusable() { - return true; - } - - @Override - public void updateItem(PDPage page) { - // First hide old page - imageView.setOpacity(0); - - BackgroundTask generateImage = BackgroundTask - .run(() -> renderPage(page)) - .onSuccess(image -> { - imageView.setImage(image); - - // Fade new page in for smoother transition - FadeTransition fadeIn = new FadeTransition(Duration.millis(100), imageView); - fadeIn.setFromValue(0); - fadeIn.setToValue(1); - fadeIn.play(); - }); - taskExecutor.execute(generateImage); - } - }; - } - - private void setCurrentEntries(List entries) { - if (!entries.isEmpty()) { - BibEntry firstSelectedEntry = entries.get(0); - setCurrentEntry(firstSelectedEntry); - } - } - - private void setCurrentEntry(BibEntry rawEntry) { - BibDatabaseContext databaseContext = stateManager.activeDatabaseProperty().get().get(); - TypedBibEntry entry = new TypedBibEntry(rawEntry, databaseContext); - List linkedFiles = entry.getFiles(); - for (ParsedFileField linkedFile : linkedFiles) { - // TODO: Find a better way to get the open database - // TODO: It should be possible to simply write linkedFile.getFile() - Optional file = FileUtil.expandFilename( - databaseContext, linkedFile.getLink(), Globals.prefs.getFileDirectoryPreferences()); - if (file.isPresent()) { - setCurrentDocument(file.get().toPath()); - } - } - - fileChoice.setItems( - FXCollections.observableArrayList( - linkedFiles.stream().map(ParsedFileField::getLink).collect(Collectors.toList()))); - fileChoice.getSelectionModel().selectFirst(); - } - - private void setCurrentDocument(Path path) { - try { - currentDocument.set(new PdfDocumentViewModel(PDDocument.load(path.toFile()))); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public class PdfDocumentViewModel extends AbstractViewModel { - - private final PDDocument document; - - public PdfDocumentViewModel(PDDocument document) { - this.document = Objects.requireNonNull(document); - } - - public int getNumberOfPages() { - return document.getNumberOfPages(); - } - - public List getPages() { - return document.getDocumentCatalog().getAllPages(); - } - - public Image getImage(int pageNumber) { - if (pageNumber <= 0 || pageNumber > document.getNumberOfPages()) { - return null; - } - - PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(pageNumber - 1); - try { - BufferedImage image = page.convertToImage(); - return SwingFXUtils.toFXImage(resize(image, 600, 800), null); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - } -} From 50ef8e43793a4d57181e52097c21bde523d6524a Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 26 Mar 2017 16:56:43 +0200 Subject: [PATCH 05/18] Refactor code --- src/main/java/org/jabref/gui/JabRefFrame.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 6cd84e848af..f5aa7f281ba 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -77,6 +77,7 @@ import org.jabref.gui.bibtexkeypattern.BibtexKeyPatternDialog; import org.jabref.gui.customentrytypes.EntryCustomizationDialog; import org.jabref.gui.dbproperties.DatabasePropertiesDialog; +import org.jabref.gui.documentviewer.ShowDocumentViewerAction; import org.jabref.gui.exporter.ExportAction; import org.jabref.gui.exporter.ExportCustomizationDialog; import org.jabref.gui.exporter.SaveAllAction; @@ -99,7 +100,6 @@ import org.jabref.gui.menus.RightClickMenu; import org.jabref.gui.openoffice.OpenOfficePanel; import org.jabref.gui.openoffice.OpenOfficeSidePanel; -import org.jabref.gui.pdfviewer.TogglePdfViewerAction; import org.jabref.gui.preftabs.PreferencesDialog; import org.jabref.gui.protectedterms.ProtectedTermsDialog; import org.jabref.gui.push.PushToApplicationButton; @@ -320,8 +320,7 @@ public void actionPerformed(ActionEvent e) { tlb.setVisible(!tlb.isVisible()); } }); - private final AbstractAction showPdvViewer = new TogglePdfViewerAction(Localization.menuTitle("Show PDF Viewer"), Localization.lang("Show PDF Viewer"), - IconTheme.JabRefIcon.PDF_FILE.getIcon()); + private final AbstractAction showPdvViewer = new ShowDocumentViewerAction(); 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); From b1e07a3a1fbfa4b800d3d5da8f8920bfd62ec90f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 26 Mar 2017 22:01:59 +0200 Subject: [PATCH 06/18] Add function to file choice box --- .../java/org/jabref/gui/StateManager.java | 9 +++-- .../gui/documentviewer/DocumentViewer.fxml | 4 +- .../DocumentViewerController.java | 18 +++++++-- .../DocumentViewerViewModel.java | 39 ++++++++----------- .../jabref/model/entry/ParsedFileField.java | 12 ++++++ 5 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 83495d868d9..6325cc623f2 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -49,12 +49,11 @@ public ReadOnlyObjectProperty> activeGroupProperty() { } public ObservableList getSelectedEntries() { - return FXCollections.unmodifiableObservableList(selectedEntries); + return selectedEntries; } public void setSelectedEntries(List newSelectedEntries) { - selectedEntries.clear(); - selectedEntries.addAll(newSelectedEntries); + selectedEntries.setAll(newSelectedEntries); } public void setSelectedGroup(BibDatabaseContext database, GroupTreeNode newSelectedGroup) { @@ -69,4 +68,8 @@ public Optional getSelectedGroup(BibDatabaseContext database) { public void clearSelectedGroup(BibDatabaseContext database) { selectedGroups.remove(database); } + + public Optional getActiveDatabase() { + return activeDatabase.get(); + } } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml index d93cd6b04d0..df57a9a37d0 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml @@ -1,7 +1,7 @@ - + @@ -22,7 +22,7 @@ - + diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java index a9a364251d8..6a30256e50c 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java @@ -20,16 +20,18 @@ import javax.inject.Inject; import javafx.fxml.FXML; -import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ComboBox; import javafx.scene.layout.BorderPane; import org.jabref.gui.AbstractController; import org.jabref.gui.StateManager; import org.jabref.gui.util.TaskExecutor; +import org.jabref.gui.util.ViewModelListCellFactory; +import org.jabref.model.entry.ParsedFileField; public class DocumentViewerController extends AbstractController { - @FXML private ChoiceBox fileChoice; + @FXML private ComboBox fileChoice; @FXML private BorderPane mainPane; @Inject private StateManager stateManager; @@ -41,8 +43,17 @@ private void initialize() { setupViewer(); + ViewModelListCellFactory cellFactory = new ViewModelListCellFactory() + .withText(ParsedFileField::getLink); + fileChoice.setButtonCell(cellFactory.call(null)); + fileChoice.setCellFactory(cellFactory); + fileChoice.getSelectionModel().selectedItemProperty().addListener( + (observable, oldValue, newValue) -> viewModel.switchToFile(newValue)); + // We always want that the first item is selected after a change + // This also automatically selects the first file on the initial load + fileChoice.itemsProperty().addListener( + (observable, oldValue, newValue) -> fileChoice.getSelectionModel().selectFirst()); fileChoice.itemsProperty().bind(viewModel.filesProperty()); - fileChoice.getSelectionModel().selectFirst(); } private void setupViewer() { @@ -52,7 +63,6 @@ private void setupViewer() { viewer.show(newDocument); } }); - viewer.show(viewModel.currentDocumentProperty().get()); mainPane.setCenter(viewer); } } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index d8aea189c14..ad5c6c6d1a7 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -1,12 +1,9 @@ package org.jabref.gui.documentviewer; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -19,8 +16,6 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.StateManager; import org.jabref.logic.TypedBibEntry; -import org.jabref.logic.util.io.FileUtil; -import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.ParsedFileField; @@ -30,7 +25,7 @@ public class DocumentViewerViewModel extends AbstractViewModel { private StateManager stateManager; private ObjectProperty currentDocument = new SimpleObjectProperty<>(); - private ListProperty files = new SimpleListProperty<>(); + private ListProperty files = new SimpleListProperty<>(); public DocumentViewerViewModel(StateManager stateManager) { this.stateManager = Objects.requireNonNull(stateManager); @@ -45,7 +40,7 @@ public ObjectProperty currentDocumentProperty() { return currentDocument; } - public ListProperty filesProperty() { + public ListProperty filesProperty() { return files; } @@ -57,22 +52,12 @@ private void setCurrentEntries(List entries) { } private void setCurrentEntry(BibEntry rawEntry) { - BibDatabaseContext databaseContext = stateManager.activeDatabaseProperty().get().get(); - TypedBibEntry entry = new TypedBibEntry(rawEntry, databaseContext); - List linkedFiles = entry.getFiles(); - for (ParsedFileField linkedFile : linkedFiles) { - // TODO: Find a better way to get the open database - // TODO: It should be possible to simply write linkedFile.getFile() - Optional file = FileUtil.expandFilename( - databaseContext, linkedFile.getLink(), Globals.prefs.getFileDirectoryPreferences()); - if (file.isPresent()) { - setCurrentDocument(file.get().toPath()); - } - } - - files.setValue( - FXCollections.observableArrayList( - linkedFiles.stream().map(ParsedFileField::getLink).collect(Collectors.toList()))); + stateManager.getActiveDatabase().ifPresent(database -> { + TypedBibEntry entry = new TypedBibEntry(rawEntry, database); + List linkedFiles = entry.getFiles(); + // We don't need to switch to the first file, this is done automatically in the UI part + files.setValue(FXCollections.observableArrayList(linkedFiles)); + }); } private void setCurrentDocument(Path path) { @@ -82,4 +67,12 @@ private void setCurrentDocument(Path path) { e.printStackTrace(); } } + + public void switchToFile(ParsedFileField file) { + if (file != null) { + stateManager.getActiveDatabase().ifPresent(database -> + file.toPath(database, Globals.prefs.getFileDirectoryPreferences()) + .ifPresent(this::setCurrentDocument)); + } + } } diff --git a/src/main/java/org/jabref/model/entry/ParsedFileField.java b/src/main/java/org/jabref/model/entry/ParsedFileField.java index e20a816d8d3..a069cd0c486 100644 --- a/src/main/java/org/jabref/model/entry/ParsedFileField.java +++ b/src/main/java/org/jabref/model/entry/ParsedFileField.java @@ -1,7 +1,14 @@ package org.jabref.model.entry; +import java.io.File; import java.net.URL; +import java.nio.file.Path; import java.util.Objects; +import java.util.Optional; + +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.metadata.FileDirectoryPreferences; public class ParsedFileField { @@ -70,4 +77,9 @@ public String toString() { public boolean isEmpty() { return NULL_OBJECT.equals(this); } + + public Optional toPath(BibDatabaseContext database, FileDirectoryPreferences directoryPreferences) { + Optional path = FileUtil.expandFilename(database, getLink(), directoryPreferences); + return path.map(File::toPath); + } } From 2ab7b36287bdbe069bc2f6575975d65c8c19618d Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 28 Mar 2017 21:22:35 +0200 Subject: [PATCH 07/18] Add function to modus buttons --- src/main/java/org/jabref/gui/Main.css | 12 +++++++++ .../gui/documentviewer/DocumentViewer.fxml | 18 +++++++++---- .../DocumentViewerController.java | 7 +++++ .../DocumentViewerViewModel.java | 26 +++++++++++++++++-- src/main/resources/l10n/JabRef_en.properties | 7 +++++ src/main/resources/l10n/Menu_en.properties | 1 + 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/gui/Main.css b/src/main/java/org/jabref/gui/Main.css index da6d8e94bec..329e6f4afb5 100644 --- a/src/main/java/org/jabref/gui/Main.css +++ b/src/main/java/org/jabref/gui/Main.css @@ -32,6 +32,12 @@ * A dark gray as background */ -fx-dark-background: #757575; + + /* + * A strong blue for backgrounds of active items (toggle button, selected list item) + */ + -fx-active-background: #6A9FCD; + } .hyperlink { @@ -70,3 +76,9 @@ -fx-text-background-color: dimgray; -fx-padding: 0.5em; } + +.flatButton:selected { + -fx-background-color: -fx-active-background; + -fx-text-fill: white; + -fx-fill: white; +} diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml index df57a9a37d0..ebbfff7e347 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml @@ -22,12 +22,20 @@ - + - - + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java index 6a30256e50c..30a2a7d2d19 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerController.java @@ -21,6 +21,7 @@ import javafx.fxml.FXML; import javafx.scene.control.ComboBox; +import javafx.scene.control.ToggleButton; import javafx.scene.layout.BorderPane; import org.jabref.gui.AbstractController; @@ -33,6 +34,7 @@ public class DocumentViewerController extends AbstractController fileChoice; @FXML private BorderPane mainPane; + @FXML private ToggleButton modeLive; @Inject private StateManager stateManager; @Inject private TaskExecutor taskExecutor; @@ -42,7 +44,12 @@ private void initialize() { viewModel = new DocumentViewerViewModel(stateManager); setupViewer(); + setupFileChoice(); + viewModel.liveModeProperty().bind(modeLive.selectedProperty()); + } + + private void setupFileChoice() { ViewModelListCellFactory cellFactory = new ViewModelListCellFactory() .withText(ParsedFileField::getLink); fileChoice.setButtonCell(cellFactory.call(null)); diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index ad5c6c6d1a7..943e9412e24 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.Objects; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -26,15 +28,31 @@ public class DocumentViewerViewModel extends AbstractViewModel { private StateManager stateManager; private ObjectProperty currentDocument = new SimpleObjectProperty<>(); private ListProperty files = new SimpleListProperty<>(); + private BooleanProperty liveMode = new SimpleBooleanProperty(); public DocumentViewerViewModel(StateManager stateManager) { this.stateManager = Objects.requireNonNull(stateManager); - this.stateManager.getSelectedEntries().addListener((ListChangeListener) c -> setCurrentEntries(this.stateManager.getSelectedEntries())); + this.stateManager.getSelectedEntries().addListener((ListChangeListener) c -> { + // Switch to currently selected entry in live mode + if (isLiveMode()) { + setCurrentEntries(this.stateManager.getSelectedEntries()); + } + }); + + this.liveMode.addListener((observable, oldValue, newValue) -> { + // Switch to currently selected entry if mode is changed to live + if (newValue) { + setCurrentEntries(this.stateManager.getSelectedEntries()); + } + }); + setCurrentEntries(this.stateManager.getSelectedEntries()); } - ; + public boolean isLiveMode() { + return liveMode.get(); + } public ObjectProperty currentDocumentProperty() { return currentDocument; @@ -75,4 +93,8 @@ public void switchToFile(ParsedFileField file) { .ifPresent(this::setCurrentDocument)); } } + + public BooleanProperty liveModeProperty() { + return liveMode; + } } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 4bae1d0f2af..a8dbce528d9 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2380,3 +2380,10 @@ Zoom_in=Zoom_in Zoom_out=Zoom_out Previous_page=Previous_page Next_page=Next_page +Document_viewer=Document_viewer +Live=Live +Locked=Locked +Show_document_viewer=Show_document_viewer +Show_the_document_of_the_currently_selected_entry.=Show_the_document_of_the_currently_selected_entry. +Show_this_document_until_unlocked.=Show_this_document_until_unlocked. + diff --git a/src/main/resources/l10n/Menu_en.properties b/src/main/resources/l10n/Menu_en.properties index 93cfbfb9331..3a8258c10d7 100644 --- a/src/main/resources/l10n/Menu_en.properties +++ b/src/main/resources/l10n/Menu_en.properties @@ -137,3 +137,4 @@ Copy_DOI_url=Copy_DOI_url Copy_citation=Copy_citation Default_table_font_size=Default_table_font_size +Show_document_viewer=Show_document_viewer From 42af93a2a42c7e6bf03ea492011c2a06f06d7aa9 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 28 Mar 2017 23:16:39 +0200 Subject: [PATCH 08/18] Add function to page control buttons and text field --- .../documentviewer/DocumentPageViewModel.java | 5 ++ .../gui/documentviewer/DocumentViewModel.java | 12 ++++ .../gui/documentviewer/DocumentViewer.fxml | 13 ++-- .../documentviewer/DocumentViewerControl.java | 68 ++++++++++++++++++- .../DocumentViewerController.java | 23 +++++++ .../DocumentViewerViewModel.java | 32 ++++++++- .../PdfDocumentPageViewModel.java | 9 ++- .../documentviewer/PdfDocumentViewModel.java | 15 ++-- .../jabref/gui/util/OnlyIntegerFormatter.java | 32 +++++++++ 9 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/jabref/gui/util/OnlyIntegerFormatter.java diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java index d8c2926a737..42411ebee3d 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java @@ -11,4 +11,9 @@ public abstract class DocumentPageViewModel { * Renders this page and returns an image representation of itself. */ public abstract Image render(); + + /** + * Get the page number of the current page in the document. + */ + public abstract int getPageNumber(); } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java index 05035d8ddd4..48bf3157521 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewModel.java @@ -1,7 +1,19 @@ package org.jabref.gui.documentviewer; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.ObservableList; public abstract class DocumentViewModel { + private IntegerProperty maxPages = new SimpleIntegerProperty(); + public abstract ObservableList getPages(); + + public int getMaxPages() { + return maxPages.get(); + } + + public IntegerProperty maxPagesProperty() { + return maxPages; + } } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml index ebbfff7e347..322bb7f07c6 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewer.fxml @@ -3,6 +3,7 @@ + @@ -25,7 +26,7 @@ + text="%Live" toggleGroup="$toggleGroupMode"> @@ -44,8 +45,8 @@ - - - - - - - -