diff --git a/pom.xml b/pom.xml
index 46ab53d..b097caf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
nl.andrewlalis
BlockBookBinder
- 1.0.0
+ 1.1.0
12
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java
deleted file mode 100644
index 14315f8..0000000
--- a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package nl.andrewlalis.blockbookbinder.control;
-
-import nl.andrewlalis.blockbookbinder.model.Book;
-import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel;
-import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
-import org.jnativehook.GlobalScreen;
-import org.jnativehook.NativeHookException;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.datatransfer.Clipboard;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Action listener that is used for when a user decides to begin exporting a
- * book to minecraft.
- */
-public class BookExportActionListener implements ActionListener {
- private final BookPreviewPanel bookPreviewPanel;
- private final Clipboard clipboard;
- private final JButton cancelExportButton;
-
- public BookExportActionListener(BookPreviewPanel bookPreviewPanel, JButton cancelExportButton) {
- this.bookPreviewPanel = bookPreviewPanel;
- this.cancelExportButton = cancelExportButton;
- this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- System.out.println("Starting export.");
- final Book book = this.bookPreviewPanel.getBook();
- int choice = JOptionPane.showConfirmDialog(
- this.bookPreviewPanel.getRootPane(),
- "Press OK to initialize export.",
- "Confirm Export",
- JOptionPane.OK_CANCEL_OPTION
- );
- if (choice == JOptionPane.CANCEL_OPTION) {
- return;
- }
- this.cancelExportButton.setEnabled(true);
- BookPagePasteListener pasteListener = new BookPagePasteListener(book, clipboard, this.bookPreviewPanel, this.cancelExportButton);
- this.bookPreviewPanel.enableNavigation(false);
- pasteListener.exportNextPage(); // Start by exporting the first page right away.
- try {
- // For catching native events, set logging here.
- Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
- logger.setLevel(Level.WARNING);
- logger.setUseParentHandlers(false);
- GlobalScreen.registerNativeHook();
- GlobalScreen.addNativeKeyListener(pasteListener);
- } catch (NativeHookException nativeHookException) {
- System.err.println("Could not register native hook.");
- nativeHookException.printStackTrace();
- }
- }
-}
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java
deleted file mode 100644
index 78b845f..0000000
--- a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package nl.andrewlalis.blockbookbinder.control;
-
-import nl.andrewlalis.blockbookbinder.model.Book;
-import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel;
-import org.jnativehook.GlobalScreen;
-import org.jnativehook.NativeHookException;
-import org.jnativehook.keyboard.NativeKeyEvent;
-import org.jnativehook.keyboard.NativeKeyListener;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.StringSelection;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-
-/**
- * Listener that listens for native key-presses that indicate the user has
- * pasted something into a book.
- */
-public class BookPagePasteListener implements NativeKeyListener {
- private final Book book;
- private final Clipboard clipboard;
- private final BookPreviewPanel bookPreviewPanel;
- private final JButton cancelExportButton;
- private final ActionListener cancelExportActionListener;
- private Robot robot;
- private int nextPage;
-
- public BookPagePasteListener(Book book, Clipboard clipboard, BookPreviewPanel bookPreviewPanel, JButton cancelExportButton) {
- this.book = book;
- this.clipboard = clipboard;
- this.bookPreviewPanel = bookPreviewPanel;
- this.cancelExportButton = cancelExportButton;
- this.nextPage = 0;
- this.cancelExportActionListener = (e) -> this.cancelExport();
- this.cancelExportButton.addActionListener(this.cancelExportActionListener);
- try {
- this.robot = new Robot();
- } catch (AWTException e) {
- e.printStackTrace();
- }
- }
-
- public void exportNextPage() {
- // Sleep a little bit to avoid rapid repeats.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.bookPreviewPanel.setCurrentPage(this.nextPage);
- boolean clipboardSuccess = false;
- while (!clipboardSuccess) {
- try {
- clipboard.setContents(
- new StringSelection(book.getPages().get(this.nextPage).toString()),
- null
- );
- clipboardSuccess = true;
- } catch (IllegalStateException e) {
- System.err.println("Could not open and set contents of system clipboard.");
- }
- if (!clipboardSuccess) {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- System.out.println("Copied page " + this.nextPage + " into clipboard.");
- this.nextPage++;
-
- // If we've reached the end of the book, unregister this listener and remove native hooks.
- if (this.nextPage >= this.book.getPageCount()) {
- this.cancelExport();
- return;
- }
-
- // Automatically do a CTRL+V and click the mouse to go to the next page.
- this.robot.keyPress(KeyEvent.VK_CONTROL);
- this.robot.keyPress(KeyEvent.VK_V);
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.robot.keyRelease(KeyEvent.VK_V);
- this.robot.keyRelease(KeyEvent.VK_CONTROL);
-
- this.robot.mousePress(MouseEvent.BUTTON1_DOWN_MASK);
- this.robot.mouseRelease(MouseEvent.BUTTON1_DOWN_MASK);
- }
-
- public void cancelExport() {
- try {
- this.bookPreviewPanel.enableNavigation(true);
- this.cancelExportButton.setEnabled(false);
- this.cancelExportButton.removeActionListener(this.cancelExportActionListener);
- GlobalScreen.removeNativeKeyListener(this);
- GlobalScreen.unregisterNativeHook();
- System.out.println("Done pasting.");
- } catch (NativeHookException nativeHookException) {
- System.err.println("Could not unregister a native hook.");
- nativeHookException.printStackTrace();
- }
- }
-
- @Override
- public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {
- }
-
- @Override
- public void nativeKeyPressed(NativeKeyEvent nativeKeyEvent) {
- if (nativeKeyEvent.getKeyCode() == NativeKeyEvent.VC_V && (nativeKeyEvent.getModifiers() & NativeKeyEvent.CTRL_MASK) > 0) {
- this.exportNextPage();
- }
- }
-
- @Override
- public void nativeKeyReleased(NativeKeyEvent nativeKeyEvent) {}
-}
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/export/BookExporter.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/export/BookExporter.java
index 432cd03..c6c859d 100644
--- a/src/main/java/nl/andrewlalis/blockbookbinder/control/export/BookExporter.java
+++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/export/BookExporter.java
@@ -3,10 +3,13 @@ package nl.andrewlalis.blockbookbinder.control.export;
import lombok.Setter;
import nl.andrewlalis.blockbookbinder.model.Book;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
+import nl.andrewlalis.blockbookbinder.view.export.ExportStatusPanel;
+import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import javax.sound.sampled.*;
+import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
@@ -24,10 +27,10 @@ import java.util.logging.Logger;
public class BookExporter implements Runnable {
private final static int START_DELAY = 10;
private final static int CLIPBOARD_RETRY_DELAY_MS = 100;
- private final static int ROBOT_ACTION_DELAY_MS = 100;
private final Book book;
private final boolean autoPaste;
+ private final int autoPasteDelay;
@Setter
private volatile boolean running;
@@ -39,13 +42,19 @@ public class BookExporter implements Runnable {
private final Clipboard clipboard;
private Robot robot;
+ private final ExportStatusPanel statusPanel;
+ private final ExportToBookDialog dialog;
+
// Some sound clips to play as user feedback.
private final Clip beepClip;
private final Clip beginningExportClip;
- public BookExporter(Book book, boolean autoPaste) {
+ public BookExporter(ExportToBookDialog dialog, ExportStatusPanel exportStatusPanel, Book book, boolean autoPaste, int autoPasteDelay) {
+ this.dialog = dialog;
+ this.statusPanel = exportStatusPanel;
this.book = book;
this.autoPaste = autoPaste;
+ this.autoPasteDelay = autoPasteDelay;
this.beepClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beep_sound"));
this.beginningExportClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beginning_export"));
this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
@@ -77,26 +86,30 @@ public class BookExporter implements Runnable {
}
// Otherwise, export one page.
if (!inStartPhase && this.nextPageRequested) {
- System.out.println("Page requested: " + nextPageToExport);
this.nextPageRequested = false; // Reset the flag so that some other process has to set it before the next page is exported.
// If this is the first time we're exporting, play a sound.
if (lastPageExportedAt == 0) {
+ this.initStatusPanel();
this.initNativeListener();
this.playAudioClip(this.beginningExportClip);
}
this.exportPageToClipboard(nextPageToExport);
if (this.autoPaste) {
this.pasteAndTurnPage();
+ } else {
+ this.addStatusMessage("Waiting to detect a CTRL+V keypress...");
}
- this.playAudioClip(this.beepClip);
nextPageToExport++;
+ this.updateStatusProgressBar(nextPageToExport);
// If we've reached the end of the book, stop the exporter.
if (nextPageToExport >= this.book.getPageCount()) {
- System.out.println("Export finished: " + this.book.getPageCount() + " pages exported.");
+ this.addStatusMessage("Export finished: " + this.book.getPageCount() + " pages exported.");
if (!this.autoPaste) {
this.stopNativeListener();
}
this.running = false;
+ this.updateStatusLabel("Export finished");
+ SwingUtilities.invokeLater(dialog::onExportFinished);
break;
}
// Since there may be significant delay, get a fresh timestamp.
@@ -132,7 +145,7 @@ public class BookExporter implements Runnable {
throw new RuntimeException("Could not insert page into clipboard after " + attempts + " attempts.");
}
}
- System.out.println("Exported page " + page + " to clipboard.");
+ this.addStatusMessage("Exported page " + (page + 1) + " to clipboard.");
}
/**
@@ -143,26 +156,26 @@ public class BookExporter implements Runnable {
this.robot.keyPress(KeyEvent.VK_CONTROL);
this.robot.keyPress(KeyEvent.VK_V);
try {
- Thread.sleep(ROBOT_ACTION_DELAY_MS);
+ Thread.sleep(this.autoPasteDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.robot.keyRelease(KeyEvent.VK_V);
this.robot.keyRelease(KeyEvent.VK_CONTROL);
- System.out.println("Pasted.");
+ this.addStatusMessage("Pasted page into book.");
this.robot.mousePress(MouseEvent.BUTTON1_DOWN_MASK);
try {
- Thread.sleep(ROBOT_ACTION_DELAY_MS);
+ Thread.sleep(this.autoPasteDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.robot.mouseRelease(MouseEvent.BUTTON1_DOWN_MASK);
try { // Wait for minecraft to turn the page.
- Thread.sleep(ROBOT_ACTION_DELAY_MS);
+ Thread.sleep(this.autoPasteDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
- System.out.println("Clicked mouse.");
+ this.addStatusMessage("Clicked to turn the page.");
this.nextPageRequested = true;
}
@@ -211,4 +224,34 @@ public class BookExporter implements Runnable {
return null;
}
}
+
+ private void initStatusPanel() {
+ SwingUtilities.invokeLater(() -> {
+ JProgressBar bar = this.statusPanel.getExportProgressBar();
+ bar.setMinimum(0);
+ bar.setMaximum(this.book.getPageCount());
+ bar.setStringPainted(true);
+ this.updateStatusProgressBar(0);
+ });
+ }
+
+ private void updateStatusLabel(String text) {
+ SwingUtilities.invokeLater(() -> {
+ this.statusPanel.getStatusLabel().setText(text);
+ });
+ }
+
+ private void updateStatusProgressBar(int nextPage) {
+ SwingUtilities.invokeLater(() -> {
+ JProgressBar bar = this.statusPanel.getExportProgressBar();
+ bar.setValue(nextPage);
+ bar.setString(String.format("%d of %d pages exported", nextPage, this.book.getPageCount()));
+ });
+ }
+
+ private void addStatusMessage(String message) {
+ SwingUtilities.invokeLater(() -> {
+ this.statusPanel.getOutputTextArea().append(message + "\n");
+ });
+ }
}
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java
index b749d26..0004f58 100644
--- a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java
+++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java
@@ -1,6 +1,5 @@
package nl.andrewlalis.blockbookbinder.view;
-import nl.andrewlalis.blockbookbinder.control.BookExportActionListener;
import nl.andrewlalis.blockbookbinder.control.ImportAction;
import nl.andrewlalis.blockbookbinder.model.Book;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportStatusPanel.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportStatusPanel.java
index c58202e..052e118 100644
--- a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportStatusPanel.java
+++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportStatusPanel.java
@@ -1,5 +1,7 @@
package nl.andrewlalis.blockbookbinder.view.export;
+import lombok.Getter;
+
import javax.swing.*;
import java.awt.*;
@@ -8,11 +10,29 @@ import java.awt.*;
* job.
*/
public class ExportStatusPanel extends JPanel {
- private JProgressBar exportProgressBar;
+ @Getter
+ private final JLabel statusLabel;
+ @Getter
+ private final JTextArea outputTextArea;
+ @Getter
+ private final JProgressBar exportProgressBar;
public ExportStatusPanel() {
this.setLayout(new BorderLayout());
+ this.statusLabel = new JLabel("Exporting...");
+ this.add(this.statusLabel, BorderLayout.NORTH);
+
+ this.outputTextArea = new JTextArea();
+ this.outputTextArea.setEditable(false);
+ this.outputTextArea.setLineWrap(true);
+ this.outputTextArea.setWrapStyleWord(true);
+ this.outputTextArea.setAutoscrolls(true);
+ JScrollPane scrollPane = new JScrollPane(this.outputTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scrollPane.setAutoscrolls(true);
+ this.add(scrollPane, BorderLayout.CENTER);
+
this.exportProgressBar = new JProgressBar();
+ this.add(this.exportProgressBar, BorderLayout.SOUTH);
}
}
diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java
index 325ea1d..998ddd8 100644
--- a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java
+++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java
@@ -21,6 +21,7 @@ public class ExportToBookDialog extends JDialog {
private JCheckBox autoCheckbox;
private JSpinner firstPageSpinner;
private JSpinner lastPageSpinner;
+ private JSpinner autoPasteDelaySpinner;
private JButton startButton;
private JButton stopButton;
@@ -54,10 +55,21 @@ public class ExportToBookDialog extends JDialog {
setupPanel.setLayout(new BoxLayout(setupPanel, BoxLayout.PAGE_AXIS));
this.autoCheckbox = new JCheckBox("Auto-paste", true);
this.firstPageSpinner = new JSpinner(new SpinnerNumberModel(1, 1, this.book.getPageCount(), 1));
+ JPanel firstPageSpinnerPanel = new JPanel(new BorderLayout());
+ firstPageSpinnerPanel.add(new JLabel("First Page:"), BorderLayout.WEST);
+ firstPageSpinnerPanel.add(this.firstPageSpinner, BorderLayout.CENTER);
this.lastPageSpinner = new JSpinner(new SpinnerNumberModel(this.book.getPageCount(), 1, this.book.getPageCount(), 1));
+ JPanel lastPageSpinnerPanel = new JPanel(new BorderLayout());
+ lastPageSpinnerPanel.add(new JLabel("Last Page:"), BorderLayout.WEST);
+ lastPageSpinnerPanel.add(this.lastPageSpinner, BorderLayout.CENTER);
+ this.autoPasteDelaySpinner = new JSpinner(new SpinnerNumberModel(0.2, 0.1, 5.0, 0.1));
+ JPanel autoPasteDelaySpinnerPanel = new JPanel(new BorderLayout());
+ autoPasteDelaySpinnerPanel.add(new JLabel("Auto-Paste Delay (s):"), BorderLayout.WEST);
+ autoPasteDelaySpinnerPanel.add(this.autoPasteDelaySpinner, BorderLayout.CENTER);
setupPanel.add(this.autoCheckbox);
- setupPanel.add(this.firstPageSpinner);
- setupPanel.add(this.lastPageSpinner);
+ setupPanel.add(firstPageSpinnerPanel);
+ setupPanel.add(lastPageSpinnerPanel);
+ setupPanel.add(autoPasteDelaySpinnerPanel);
this.exportStatusPanel = new ExportStatusPanel();
@@ -71,6 +83,9 @@ public class ExportToBookDialog extends JDialog {
JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
this.startButton = new JButton("Start");
this.startButton.addActionListener(e -> {
+ if (!this.checkSpinnerValues()){
+ return;
+ }
int choice = JOptionPane.showConfirmDialog(
this.rootPane,
"Exporting will begin after roughly 10 seconds.\n" +
@@ -97,21 +112,36 @@ public class ExportToBookDialog extends JDialog {
return mainPanel;
}
+ /**
+ * Starts up the exporter thread.
+ */
private void startExporter() {
final int firstPage = (int) this.firstPageSpinner.getValue();
final int lastPage = (int) this.lastPageSpinner.getValue();
final Book pagesRange = this.book.getPageRange(firstPage - 1, lastPage - firstPage + 1);
+ final double autoPasteDelay = (double) this.autoPasteDelaySpinner.getValue();
+ final int autoPasteDelayMillis = (int) (autoPasteDelay * 1000);
+
this.startButton.setEnabled(false);
this.startButton.setVisible(false);
this.stopButton.setEnabled(true);
this.stopButton.setVisible(true);
this.showCardByName(STATUS_CARD);
- this.exporterRunnable = new BookExporter(pagesRange, this.autoCheckbox.isSelected());
+ this.exporterRunnable = new BookExporter(
+ this,
+ this.exportStatusPanel,
+ pagesRange,
+ this.autoCheckbox.isSelected(),
+ autoPasteDelayMillis
+ );
this.exporterThread = new Thread(this.exporterRunnable);
this.exporterThread.start();
}
+ /**
+ * Shuts down the exporter thread.
+ */
private void stopExporter() {
this.exporterRunnable.setRunning(false);
try {
@@ -126,8 +156,49 @@ public class ExportToBookDialog extends JDialog {
this.startButton.setVisible(true);
}
+ /**
+ * This method is called by the exporter thread once it is done.
+ */
+ public void onExportFinished() {
+ JOptionPane.showMessageDialog(
+ this,
+ "Book export has finished.",
+ "Export Complete",
+ JOptionPane.INFORMATION_MESSAGE
+ );
+ this.stopExporter();
+ this.dispose();
+ }
+
private void showCardByName(String name) {
CardLayout cl = (CardLayout) this.centerCardPanel.getLayout();
cl.show(this.centerCardPanel, name);
}
+
+ /**
+ * Checks the values of the spinners that are used to select the first and
+ * last pages, and shows a popup warning if they're not correct.
+ */
+ private boolean checkSpinnerValues() {
+ final int firstPage = (int) this.firstPageSpinner.getValue();
+ final int lastPage = (int) this.lastPageSpinner.getValue();
+
+ if (
+ firstPage < 1
+ || lastPage > this.book.getPageCount()
+ || firstPage > lastPage
+ || (lastPage - firstPage + 1 > ApplicationProperties.getIntProp("book.max_pages"))
+ ) {
+ JOptionPane.showMessageDialog(
+ this,
+ "Invalid page range. Please follow the rules below:\n" +
+ "1. First page must be lower or equal to the last page.\n" +
+ "2. Number of pages to export cannot exceed 100.\n",
+ "Invalid Page Range",
+ JOptionPane.WARNING_MESSAGE
+ );
+ return false;
+ }
+ return true;
+ }
}