Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Andrew Lalis | 5450f69c3b | |
Andrew Lalis | 7735602a2c | |
Andrew Lalis | cfb70b14e8 | |
Andrew Lalis | 9ae48ec944 | |
Andrew Lalis | 2a9edf125e | |
Andrew Lalis | 605d050783 | |
Andrew Lalis | e32976fb8b |
Binary file not shown.
|
@ -5,9 +5,11 @@ This application was developed in order to make the production of written books
|
||||||
|
|
||||||
## Using Block Book Binder
|
## Using Block Book Binder
|
||||||
|
|
||||||
To get started, look for the [**Releases**](https://github.com/andrewlalis/BlockBookBinder/releases) section on this page, and find the latest release. Download the executable JAR file and you're ready to go. Simply paste some plain text into the _Source_ panel on the right-hand side, and press the button _Convert to Book_ to process that text into a series of pages which will appear on the left-hand side.
|
To get started, look for the [**Releases**](https://github.com/andrewlalis/BlockBookBinder/releases) section on this page, and find the latest release. Download the executable JAR file, and make sure you have Java version 17 or higher installed to run it.
|
||||||
|
|
||||||
Once you're happy with how the pages are formatted, you can click the _Export_ button to begin exporting pages to your clipboard. Once you give an affirmative response to the confirmation popup that appears, the first page will be loaded into your clipboard. You can then use `CTRL + V` to paste the page into your book. Each time you do, the program will take about a second to load the next page into your clipboard, so that you can paste it without even having to leave your game.
|
Start the program, and you'll be greeted with a window that has **Book Preview** and **Source Text** panels. Enter the text you'd like to work with into the **Source Text** panel. In the top menu under **Book**, you'll find a **Clean Source** button, which will remove extra whitespace from the text to make it more friendly for Minecraft's cramped style. You'll also find **Compile from Source**, which will compile your source text into a book in the **Book Preview** panel.
|
||||||
|
|
||||||
|
Once you're happy with how the pages are formatted, you can click the **Export to Minecraft** button to begin exporting pages to your clipboard.
|
||||||
|
|
||||||
## Demo Video on YouTube
|
## Demo Video on YouTube
|
||||||
https://youtu.be/Mu7Hv1na7Sw
|
https://youtu.be/Mu7Hv1na7Sw
|
||||||
|
|
19
pom.xml
19
pom.xml
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
<groupId>nl.andrewlalis</groupId>
|
<groupId>nl.andrewlalis</groupId>
|
||||||
<artifactId>BlockBookBinder</artifactId>
|
<artifactId>BlockBookBinder</artifactId>
|
||||||
<version>1.2.0</version>
|
<version>1.3.1</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>12</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>12</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -42,6 +43,14 @@
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.28</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
|
<!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.1stleg</groupId>
|
<groupId>com.1stleg</groupId>
|
||||||
|
@ -52,13 +61,13 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.formdev</groupId>
|
<groupId>com.formdev</groupId>
|
||||||
<artifactId>flatlaf</artifactId>
|
<artifactId>flatlaf</artifactId>
|
||||||
<version>1.0-rc3</version>
|
<version>3.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.maven</groupId>
|
<groupId>org.apache.maven</groupId>
|
||||||
<artifactId>maven-model</artifactId>
|
<artifactId>maven-model</artifactId>
|
||||||
<version>3.6.3</version>
|
<version>3.9.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
|
@ -1,7 +1,6 @@
|
||||||
package nl.andrewlalis.blockbookbinder;
|
package nl.andrewlalis.blockbookbinder;
|
||||||
|
|
||||||
import com.formdev.flatlaf.FlatDarkLaf;
|
import com.formdev.flatlaf.FlatDarkLaf;
|
||||||
import nl.andrewlalis.blockbookbinder.util.VersionReader;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.MainFrame;
|
import nl.andrewlalis.blockbookbinder.view.MainFrame;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
@ -10,11 +9,9 @@ import javax.swing.*;
|
||||||
* The main class for the application.
|
* The main class for the application.
|
||||||
*/
|
*/
|
||||||
public class BlockBookBinder {
|
public class BlockBookBinder {
|
||||||
public static final String VERSION = VersionReader.getVersion();
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
FlatDarkLaf.install();
|
FlatDarkLaf.setup();
|
||||||
var mainFrame = new MainFrame();
|
var mainFrame = new MainFrame();
|
||||||
mainFrame.setupAndShow();
|
mainFrame.setupAndShow();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.export;
|
package nl.andrewlalis.blockbookbinder.control.export;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
import nl.andrewlalis.blockbookbinder.model.Book;
|
import nl.andrewlalis.blockbookbinder.model.Book;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
import nl.andrewlalis.blockbookbinder.view.export.ExportStatusPanel;
|
import nl.andrewlalis.blockbookbinder.view.export.ExportStatusPanel;
|
||||||
|
@ -25,14 +26,17 @@ import java.util.logging.Logger;
|
||||||
* into one's clipboard and pasting the pages.
|
* into one's clipboard and pasting the pages.
|
||||||
*/
|
*/
|
||||||
public class BookExporter implements Runnable {
|
public class BookExporter implements Runnable {
|
||||||
private final static int START_DELAY = 5;
|
private final static int START_DELAY = 10;
|
||||||
private final static int CLIPBOARD_RETRY_DELAY_MS = 100;
|
private final static int CLIPBOARD_RETRY_DELAY_MS = 100;
|
||||||
|
|
||||||
private final Book book;
|
private final Book book;
|
||||||
private final boolean autoPaste;
|
private final boolean autoPaste;
|
||||||
private final int autoPasteDelay;
|
private final int autoPasteDelay;
|
||||||
|
|
||||||
|
@Setter
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
|
|
||||||
|
@Setter
|
||||||
private volatile boolean nextPageRequested;
|
private volatile boolean nextPageRequested;
|
||||||
|
|
||||||
private final ExporterKeyListener exporterKeyListener;
|
private final ExporterKeyListener exporterKeyListener;
|
||||||
|
@ -44,8 +48,8 @@ public class BookExporter implements Runnable {
|
||||||
|
|
||||||
// Some sound clips to play as user feedback.
|
// Some sound clips to play as user feedback.
|
||||||
private final Clip beepClip;
|
private final Clip beepClip;
|
||||||
// private final Clip beginningExportClip;
|
private final Clip beginningExportClip;
|
||||||
// private final Clip finishClip;
|
private final Clip finishClip;
|
||||||
|
|
||||||
public BookExporter(ExportToBookDialog dialog, ExportStatusPanel exportStatusPanel, Book book, boolean autoPaste, int autoPasteDelay) {
|
public BookExporter(ExportToBookDialog dialog, ExportStatusPanel exportStatusPanel, Book book, boolean autoPaste, int autoPasteDelay) {
|
||||||
this.dialog = dialog;
|
this.dialog = dialog;
|
||||||
|
@ -54,8 +58,8 @@ public class BookExporter implements Runnable {
|
||||||
this.autoPaste = autoPaste;
|
this.autoPaste = autoPaste;
|
||||||
this.autoPasteDelay = autoPasteDelay;
|
this.autoPasteDelay = autoPasteDelay;
|
||||||
this.beepClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beep_sound"));
|
this.beepClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beep_sound"));
|
||||||
// this.beginningExportClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beginning_export"));
|
this.beginningExportClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.beginning_export"));
|
||||||
// this.finishClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.finish_sound"));
|
this.finishClip = this.loadAudioClip(ApplicationProperties.getProp("export_dialog.finish_sound"));
|
||||||
this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
this.exporterKeyListener = new ExporterKeyListener(this);
|
this.exporterKeyListener = new ExporterKeyListener(this);
|
||||||
if (this.autoPaste) { // Only initialize the robot if we'll need it.
|
if (this.autoPaste) { // Only initialize the robot if we'll need it.
|
||||||
|
@ -93,7 +97,7 @@ public class BookExporter implements Runnable {
|
||||||
this.initStatusPanel();
|
this.initStatusPanel();
|
||||||
this.updateStatusLabel("Exporting.");
|
this.updateStatusLabel("Exporting.");
|
||||||
this.initNativeListener();
|
this.initNativeListener();
|
||||||
// this.playAudioClip(this.beginningExportClip);
|
this.playAudioClip(this.beginningExportClip);
|
||||||
}
|
}
|
||||||
this.exportPageToClipboard(nextPageToExport);
|
this.exportPageToClipboard(nextPageToExport);
|
||||||
if (this.autoPaste) {
|
if (this.autoPaste) {
|
||||||
|
@ -105,7 +109,7 @@ public class BookExporter implements Runnable {
|
||||||
this.updateStatusProgressBar(nextPageToExport);
|
this.updateStatusProgressBar(nextPageToExport);
|
||||||
// If we've reached the end of the book, stop the exporter.
|
// If we've reached the end of the book, stop the exporter.
|
||||||
if (nextPageToExport >= this.book.getPageCount()) {
|
if (nextPageToExport >= this.book.getPageCount()) {
|
||||||
// this.playAudioClip(this.finishClip);
|
this.playAudioClip(this.finishClip);
|
||||||
this.addStatusMessage("Export finished: " + this.book.getPageCount() + " pages exported.");
|
this.addStatusMessage("Export finished: " + this.book.getPageCount() + " pages exported.");
|
||||||
if (!this.autoPaste) {
|
if (!this.autoPaste) {
|
||||||
this.stopNativeListener();
|
this.stopNativeListener();
|
||||||
|
@ -121,22 +125,6 @@ public class BookExporter implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRunning(boolean running) {
|
|
||||||
this.running = running;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNextPageRequested() {
|
|
||||||
return nextPageRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextPageRequested(boolean nextPageRequested) {
|
|
||||||
this.nextPageRequested = nextPageRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the given page onto the system clipboard so either a user or this
|
* Loads the given page onto the system clipboard so either a user or this
|
||||||
* program can paste it into a minecraft book.
|
* program can paste it into a minecraft book.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.export;
|
package nl.andrewlalis.blockbookbinder.control.export;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import nl.andrewlalis.blockbookbinder.model.Book;
|
import nl.andrewlalis.blockbookbinder.model.Book;
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
||||||
import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
|
import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
|
||||||
|
@ -8,6 +10,7 @@ import javax.swing.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
public class ExportBookToMinecraftAction extends AbstractAction {
|
public class ExportBookToMinecraftAction extends AbstractAction {
|
||||||
|
@Getter
|
||||||
private static final ExportBookToMinecraftAction instance = new ExportBookToMinecraftAction();
|
private static final ExportBookToMinecraftAction instance = new ExportBookToMinecraftAction();
|
||||||
|
|
||||||
public ExportBookToMinecraftAction() {
|
public ExportBookToMinecraftAction() {
|
||||||
|
@ -15,6 +18,7 @@ public class ExportBookToMinecraftAction extends AbstractAction {
|
||||||
this.putValue(SHORT_DESCRIPTION, "Export the current book to Minecraft.");
|
this.putValue(SHORT_DESCRIPTION, "Export the current book to Minecraft.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Setter
|
||||||
private BookPreviewPanel bookPreviewPanel;
|
private BookPreviewPanel bookPreviewPanel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,7 +27,7 @@ public class ExportBookToMinecraftAction extends AbstractAction {
|
||||||
if (book == null || book.getPageCount() == 0) {
|
if (book == null || book.getPageCount() == 0) {
|
||||||
JOptionPane.showMessageDialog(
|
JOptionPane.showMessageDialog(
|
||||||
this.bookPreviewPanel.getRootPane(),
|
this.bookPreviewPanel.getRootPane(),
|
||||||
"Cannot export an empty book.",
|
"Cannot export an empty book.\nChoose \"Compile to Source\" first, and then export.",
|
||||||
"Empty Book",
|
"Empty Book",
|
||||||
JOptionPane.WARNING_MESSAGE
|
JOptionPane.WARNING_MESSAGE
|
||||||
);
|
);
|
||||||
|
@ -32,12 +36,4 @@ public class ExportBookToMinecraftAction extends AbstractAction {
|
||||||
ExportToBookDialog dialog = new ExportToBookDialog(SwingUtilities.getWindowAncestor(this.bookPreviewPanel), bookPreviewPanel.getBook());
|
ExportToBookDialog dialog = new ExportToBookDialog(SwingUtilities.getWindowAncestor(this.bookPreviewPanel), bookPreviewPanel.getBook());
|
||||||
dialog.setupAndShow();
|
dialog.setupAndShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBookPreviewPanel(BookPreviewPanel bookPreviewPanel) {
|
|
||||||
this.bookPreviewPanel = bookPreviewPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExportBookToMinecraftAction getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import org.jnativehook.keyboard.NativeKeyListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native key listener that's used during the export process, to detect when the
|
* Native key listener that's used during the export process, to detect when the
|
||||||
* user performs certain key actions outside of the focus of this program.
|
* user performs certain key actions outside the focus of this program.
|
||||||
*/
|
*/
|
||||||
public class ExporterKeyListener implements NativeKeyListener {
|
public class ExporterKeyListener implements NativeKeyListener {
|
||||||
private final BookExporter exporterRunnable;
|
private final BookExporter exporterRunnable;
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.source;
|
package nl.andrewlalis.blockbookbinder.control.source;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
public class CleanSourceAction extends AbstractAction {
|
public class CleanSourceAction extends AbstractAction {
|
||||||
|
@Getter
|
||||||
private final static CleanSourceAction instance = new CleanSourceAction();
|
private final static CleanSourceAction instance = new CleanSourceAction();
|
||||||
|
|
||||||
|
@Setter
|
||||||
private SourceTextPanel sourceTextPanel;
|
private SourceTextPanel sourceTextPanel;
|
||||||
|
|
||||||
public CleanSourceAction() {
|
public CleanSourceAction() {
|
||||||
|
@ -42,12 +46,4 @@ public class CleanSourceAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CleanSourceAction getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceTextPanel(SourceTextPanel sourceTextPanel) {
|
|
||||||
this.sourceTextPanel = sourceTextPanel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.source;
|
package nl.andrewlalis.blockbookbinder.control.source;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import nl.andrewlalis.blockbookbinder.model.build.BookBuilder;
|
import nl.andrewlalis.blockbookbinder.model.build.BookBuilder;
|
||||||
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
public class CompileFromSourceAction extends AbstractAction {
|
public class CompileFromSourceAction extends AbstractAction {
|
||||||
|
@Getter
|
||||||
private static final CompileFromSourceAction instance = new CompileFromSourceAction();
|
private static final CompileFromSourceAction instance = new CompileFromSourceAction();
|
||||||
|
|
||||||
|
@Setter
|
||||||
private SourceTextPanel sourceTextPanel;
|
private SourceTextPanel sourceTextPanel;
|
||||||
|
@Setter
|
||||||
private BookPreviewPanel bookPreviewPanel;
|
private BookPreviewPanel bookPreviewPanel;
|
||||||
|
|
||||||
public CompileFromSourceAction() {
|
public CompileFromSourceAction() {
|
||||||
|
@ -20,20 +27,22 @@ public class CompileFromSourceAction extends AbstractAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
String text = this.sourceTextPanel.getSourceText();
|
||||||
|
if (text.isBlank()) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
SwingUtilities.getWindowAncestor((Component) e.getSource()),
|
||||||
|
"No source text to compile.\nEnter some text into the \"Source Text\" panel first.",
|
||||||
|
"No Source Text",
|
||||||
|
JOptionPane.WARNING_MESSAGE
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.bookPreviewPanel.setBook(
|
this.bookPreviewPanel.setBook(
|
||||||
new BookBuilder().build(this.sourceTextPanel.getSourceText())
|
new BookBuilder(
|
||||||
|
ApplicationProperties.getIntProp("book.page_max_lines"),
|
||||||
|
ApplicationProperties.getIntProp("book.page_max_chars"),
|
||||||
|
ApplicationProperties.getIntProp("book.page_max_width")
|
||||||
|
).addText(this.sourceTextPanel.getSourceText()).build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompileFromSourceAction getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceTextPanel(SourceTextPanel sourceTextPanel) {
|
|
||||||
this.sourceTextPanel = sourceTextPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBookPreviewPanel(BookPreviewPanel bookPreviewPanel) {
|
|
||||||
this.bookPreviewPanel = bookPreviewPanel;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.source;
|
package nl.andrewlalis.blockbookbinder.control.source;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import nl.andrewlalis.blockbookbinder.BlockBookBinder;
|
||||||
|
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
public class ImportSourceAction extends AbstractAction {
|
public class ImportSourceAction extends AbstractAction {
|
||||||
|
@Getter
|
||||||
private static final ImportSourceAction instance = new ImportSourceAction();
|
private static final ImportSourceAction instance = new ImportSourceAction();
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private SourceTextPanel sourceTextPanel;
|
||||||
|
|
||||||
public ImportSourceAction() {
|
public ImportSourceAction() {
|
||||||
super("Import Source");
|
super("Import Source");
|
||||||
this.putValue(SHORT_DESCRIPTION, "Import source text from a file.");
|
this.putValue(SHORT_DESCRIPTION, "Import source text from a file.");
|
||||||
|
@ -13,10 +29,24 @@ public class ImportSourceAction extends AbstractAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
Preferences prefs = Preferences.userNodeForPackage(BlockBookBinder.class);
|
||||||
}
|
String dir = prefs.get("source-import-dir", ".");
|
||||||
|
JFileChooser fileChooser = new JFileChooser(dir);
|
||||||
public static ImportSourceAction getInstance() {
|
fileChooser.setFileFilter(new FileNameExtensionFilter("Text files", ".txt"));
|
||||||
return instance;
|
fileChooser.setAcceptAllFileFilterUsed(true);
|
||||||
|
fileChooser.setMultiSelectionEnabled(false);
|
||||||
|
final Component parent = SwingUtilities.getWindowAncestor((Component) e.getSource());
|
||||||
|
int result = fileChooser.showOpenDialog(parent);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File file = fileChooser.getSelectedFile();
|
||||||
|
try {
|
||||||
|
Path filePath = file.toPath();
|
||||||
|
sourceTextPanel.setSourceText(Files.readString(filePath));
|
||||||
|
prefs.put("source-import-dir", filePath.getParent().toAbsolutePath().toString());
|
||||||
|
} catch (IOException exc) {
|
||||||
|
exc.printStackTrace();
|
||||||
|
JOptionPane.showMessageDialog(parent, "Failed to read file:\n" + exc.getMessage(), "Read Failed", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package nl.andrewlalis.blockbookbinder.model;
|
package nl.andrewlalis.blockbookbinder.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Book {
|
public class Book {
|
||||||
|
@Getter
|
||||||
private final List<BookPage> pages;
|
private final List<BookPage> pages;
|
||||||
|
|
||||||
public Book() {
|
public Book() {
|
||||||
|
@ -16,10 +18,6 @@ public class Book {
|
||||||
return this.pages.size();
|
return this.pages.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BookPage> getPages() {
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPage(BookPage page) {
|
public void addPage(BookPage page) {
|
||||||
this.pages.add(page);
|
this.pages.add(page);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
package nl.andrewlalis.blockbookbinder.model;
|
package nl.andrewlalis.blockbookbinder.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CharWidthMapper {
|
public class CharWidthMapper {
|
||||||
|
@Getter
|
||||||
private static final CharWidthMapper instance = new CharWidthMapper();
|
private static final CharWidthMapper instance = new CharWidthMapper();
|
||||||
|
|
||||||
public static CharWidthMapper getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<Character, Integer> charWidthMap;
|
private final Map<Character, Integer> charWidthMap;
|
||||||
|
|
||||||
public CharWidthMapper() {
|
public CharWidthMapper() {
|
||||||
|
@ -19,8 +17,20 @@ public class CharWidthMapper {
|
||||||
this.initCharWidthMap();
|
this.initCharWidthMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(char c) {
|
public static int getWidth(char c) {
|
||||||
return this.charWidthMap.getOrDefault(c, 6);
|
return instance.charWidthMap.getOrDefault(c, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getWidth(String s) {
|
||||||
|
if (s.length() == 0) return 0;
|
||||||
|
int width = 0;
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
width += getWidth(s.charAt(i));
|
||||||
|
if (i < s.length() - 1) {
|
||||||
|
width++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCharWidthMap() {
|
private void initCharWidthMap() {
|
||||||
|
|
|
@ -3,112 +3,123 @@ package nl.andrewlalis.blockbookbinder.model.build;
|
||||||
import nl.andrewlalis.blockbookbinder.model.Book;
|
import nl.andrewlalis.blockbookbinder.model.Book;
|
||||||
import nl.andrewlalis.blockbookbinder.model.BookPage;
|
import nl.andrewlalis.blockbookbinder.model.BookPage;
|
||||||
import nl.andrewlalis.blockbookbinder.model.CharWidthMapper;
|
import nl.andrewlalis.blockbookbinder.model.CharWidthMapper;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class BookBuilder {
|
public class BookBuilder {
|
||||||
/**
|
private final int MAX_LINES_PER_PAGE;
|
||||||
* Builds a full book of pages from the given source text.
|
private final int MAX_CHARS_PER_PAGE;
|
||||||
* @param source The source text to convert.
|
private final int MAX_LINE_PIXEL_WIDTH;
|
||||||
* @return A book containing the source text formatted for a minecraft book.
|
|
||||||
*/
|
private final List<String> lines;
|
||||||
public Book build(String source) {
|
|
||||||
final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines");
|
private final StringBuilder lineBuilder;
|
||||||
List<String> lines = this.convertSourceToLines(source);
|
private final StringBuilder wordBuilder;
|
||||||
|
|
||||||
|
public BookBuilder(int maxLinesPerPage, int maxCharsPerPage, int maxLinePixelWidth) {
|
||||||
|
this.MAX_LINES_PER_PAGE = maxLinesPerPage;
|
||||||
|
this.MAX_CHARS_PER_PAGE = maxCharsPerPage;
|
||||||
|
this.MAX_LINE_PIXEL_WIDTH = maxLinePixelWidth;
|
||||||
|
this.lines = new ArrayList<>();
|
||||||
|
this.lineBuilder = new StringBuilder(64);
|
||||||
|
this.wordBuilder = new StringBuilder(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BookBuilder addText(String text) {
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < text.length()) {
|
||||||
|
final char c = text.charAt(idx++);
|
||||||
|
if (c == '\n') {
|
||||||
|
appendLine();
|
||||||
|
} else if (c == ' ' && lineBuilder.length() == 0) {
|
||||||
|
continue; // Skip spaces at the start of lines.
|
||||||
|
} else if (Character.isWhitespace(c)) {
|
||||||
|
if (CharWidthMapper.getWidth(lineBuilder.toString() + c) > MAX_LINE_PIXEL_WIDTH) {
|
||||||
|
appendLine();
|
||||||
|
if (c != ' ') {
|
||||||
|
lineBuilder.append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lineBuilder.append(c);
|
||||||
|
}
|
||||||
|
} else { // Read a continuous word.
|
||||||
|
String word = readWord(text, idx - 1);
|
||||||
|
idx += word.length() - 1;
|
||||||
|
if (CharWidthMapper.getWidth(lineBuilder + word) <= MAX_LINE_PIXEL_WIDTH) {
|
||||||
|
// Append the word if it'll fit completely.
|
||||||
|
lineBuilder.append(word);
|
||||||
|
} else if (CharWidthMapper.getWidth(word) <= MAX_LINE_PIXEL_WIDTH) {
|
||||||
|
// Go to the next line and put the word there, since it'll fit.
|
||||||
|
appendLine();
|
||||||
|
lineBuilder.append(word);
|
||||||
|
} else {
|
||||||
|
// The word is so large that it doesn't fit on a line on its own.
|
||||||
|
// Find the largest substring of the word that'll fit with a hyphen.
|
||||||
|
int subStringSize = word.length() - 2;
|
||||||
|
while (CharWidthMapper.getWidth(word.substring(0, subStringSize) + "-") > MAX_LINE_PIXEL_WIDTH) {
|
||||||
|
subStringSize--;
|
||||||
|
}
|
||||||
|
appendLine();
|
||||||
|
lineBuilder.append(word, 0, subStringSize).append('-');
|
||||||
|
appendLine();
|
||||||
|
lineBuilder.append(word.substring(subStringSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Book build() {
|
||||||
Book book = new Book();
|
Book book = new Book();
|
||||||
BookPage page = new BookPage();
|
BookPage page = new BookPage();
|
||||||
int currentPageLineCount = 0;
|
int currentPageLineCount = 0;
|
||||||
|
int currentPageCharCount = 0;
|
||||||
|
|
||||||
|
// Flush anything remaining in lineBuilder to a final line.
|
||||||
|
if (lineBuilder.length() > 0) {
|
||||||
|
appendLine();
|
||||||
|
}
|
||||||
|
|
||||||
for (String line : lines) {
|
for (String line : lines) {
|
||||||
page.addLine(line);
|
if (currentPageCharCount + line.length() > MAX_CHARS_PER_PAGE) {
|
||||||
currentPageLineCount++;
|
|
||||||
if (currentPageLineCount == maxLines) {
|
|
||||||
book.addPage(page);
|
book.addPage(page);
|
||||||
page = new BookPage();
|
page = new BookPage();
|
||||||
currentPageLineCount = 0;
|
currentPageLineCount = 0;
|
||||||
|
currentPageCharCount = 0;
|
||||||
|
}
|
||||||
|
page.addLine(line);
|
||||||
|
currentPageLineCount++;
|
||||||
|
currentPageCharCount += line.length();
|
||||||
|
if (currentPageLineCount == MAX_LINES_PER_PAGE) {
|
||||||
|
book.addPage(page);
|
||||||
|
page = new BookPage();
|
||||||
|
currentPageLineCount = 0;
|
||||||
|
currentPageCharCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.hasContent()) {
|
if (page.hasContent()) {
|
||||||
book.addPage(page);
|
book.addPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
return book;
|
return book;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String readWord(String text, int firstCharIdx) {
|
||||||
* Converts the given source string into a formatted list of lines that can
|
wordBuilder.setLength(0);
|
||||||
* be copied to a minecraft book.
|
int idx = firstCharIdx;
|
||||||
* @param source The source string.
|
while (idx < text.length()) {
|
||||||
* @return A list of lines.
|
char c = text.charAt(idx++);
|
||||||
*/
|
if (!Character.isWhitespace(c)) {
|
||||||
private List<String> convertSourceToLines(String source) {
|
wordBuilder.append(c);
|
||||||
List<String> lines = new ArrayList<>();
|
} else {
|
||||||
final char[] sourceChars = source.toCharArray();
|
break;
|
||||||
final int maxLinePixelWidth = ApplicationProperties.getIntProp("book.page_max_width");
|
}
|
||||||
int sourceIndex = 0;
|
}
|
||||||
StringBuilder lineBuilder = new StringBuilder(64);
|
return wordBuilder.toString();
|
||||||
int linePixelWidth = 0;
|
|
||||||
StringBuilder symbolBuilder = new StringBuilder(64);
|
|
||||||
|
|
||||||
while (sourceIndex < sourceChars.length) {
|
|
||||||
final char c = sourceChars[sourceIndex];
|
|
||||||
sourceIndex++;
|
|
||||||
symbolBuilder.setLength(0);
|
|
||||||
symbolBuilder.append(c);
|
|
||||||
int symbolWidth = CharWidthMapper.getInstance().getWidth(c);
|
|
||||||
|
|
||||||
// Since there's a 1-pixel gap between characters, add it to the width if this isn't the first char.
|
|
||||||
if (lineBuilder.length() > 0) {
|
|
||||||
symbolWidth++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we encounter a non-newline whitespace at the beginning of the line, skip it.
|
private void appendLine() {
|
||||||
if (c == ' ' && lineBuilder.length() == 0) {
|
this.lines.add(this.lineBuilder.toString());
|
||||||
continue;
|
this.lineBuilder.setLength(0);
|
||||||
}
|
|
||||||
|
|
||||||
// If we encounter a newline, immediately skip to a new line.
|
|
||||||
if (c == '\n') {
|
|
||||||
lines.add(lineBuilder.toString());
|
|
||||||
lineBuilder.setLength(0);
|
|
||||||
linePixelWidth = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we encounter a word, keep accepting characters until we reach the end.
|
|
||||||
if (Character.isLetterOrDigit(c)) {
|
|
||||||
while (
|
|
||||||
sourceIndex < sourceChars.length
|
|
||||||
&& Character.isLetterOrDigit(sourceChars[sourceIndex])
|
|
||||||
) {
|
|
||||||
char nextChar = sourceChars[sourceIndex];
|
|
||||||
symbolBuilder.append(nextChar);
|
|
||||||
symbolWidth += 1 + CharWidthMapper.getInstance().getWidth(nextChar);
|
|
||||||
sourceIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String symbol = symbolBuilder.toString();
|
|
||||||
// Check if we need to go to the next line to fit the symbol.
|
|
||||||
if (linePixelWidth + symbolWidth > maxLinePixelWidth) {
|
|
||||||
lines.add(lineBuilder.toString());
|
|
||||||
lineBuilder.setLength(0);
|
|
||||||
linePixelWidth = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, append the symbol.
|
|
||||||
lineBuilder.append(symbol);
|
|
||||||
linePixelWidth += symbolWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append any remaining text.
|
|
||||||
if (lineBuilder.length() > 0) {
|
|
||||||
lines.add(lineBuilder.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package nl.andrewlalis.blockbookbinder.util;
|
package nl.andrewlalis.blockbookbinder.util;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -11,6 +13,7 @@ import java.util.Properties;
|
||||||
*/
|
*/
|
||||||
public class ApplicationProperties {
|
public class ApplicationProperties {
|
||||||
private static ApplicationProperties instance;
|
private static ApplicationProperties instance;
|
||||||
|
@Getter
|
||||||
private final Properties properties;
|
private final Properties properties;
|
||||||
|
|
||||||
private final Map<String, Integer> intPropCache;
|
private final Map<String, Integer> intPropCache;
|
||||||
|
@ -27,10 +30,6 @@ public class ApplicationProperties {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Properties getProperties() {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for getting a property.
|
* Shortcut for getting a property.
|
||||||
* @param key The property's key.
|
* @param key The property's key.
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.util;
|
|
||||||
|
|
||||||
import org.apache.maven.model.Model;
|
|
||||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
|
||||||
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
public class VersionReader {
|
|
||||||
public static String getVersion() {
|
|
||||||
MavenXpp3Reader reader = new MavenXpp3Reader();
|
|
||||||
try {
|
|
||||||
Model model;
|
|
||||||
if ((new File("pom.xml")).exists()) {
|
|
||||||
model = reader.read(new FileReader("pom.xml"));
|
|
||||||
} else {
|
|
||||||
model = reader.read(new InputStreamReader(
|
|
||||||
VersionReader.class.getResourceAsStream("/META-INF/maven/nl.andrewlalis/BlockBookBinder/pom.xml")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return model.getVersion();
|
|
||||||
} catch (IOException | XmlPullParserException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view;
|
package nl.andrewlalis.blockbookbinder.view;
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.BlockBookBinder;
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.export.ExportBookToMinecraftAction;
|
import nl.andrewlalis.blockbookbinder.control.export.ExportBookToMinecraftAction;
|
||||||
import nl.andrewlalis.blockbookbinder.control.source.CleanSourceAction;
|
import nl.andrewlalis.blockbookbinder.control.source.CleanSourceAction;
|
||||||
import nl.andrewlalis.blockbookbinder.control.source.CompileFromSourceAction;
|
import nl.andrewlalis.blockbookbinder.control.source.CompileFromSourceAction;
|
||||||
|
@ -22,7 +21,7 @@ public class MainFrame extends JFrame {
|
||||||
ApplicationProperties.getIntProp("frame.default_width"),
|
ApplicationProperties.getIntProp("frame.default_width"),
|
||||||
ApplicationProperties.getIntProp("frame.default_height")
|
ApplicationProperties.getIntProp("frame.default_height")
|
||||||
));
|
));
|
||||||
this.setTitle(ApplicationProperties.getProp("frame.title") + " Version " + BlockBookBinder.VERSION);
|
this.setTitle(ApplicationProperties.getProp("frame.title") + " Version " + ApplicationProperties.getProp("version"));
|
||||||
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
final URL iconUrl = this.getClass().getClassLoader().getResource("images/book_and_quill.png");
|
final URL iconUrl = this.getClass().getClassLoader().getResource("images/book_and_quill.png");
|
||||||
if (iconUrl != null) {
|
if (iconUrl != null) {
|
||||||
|
@ -50,6 +49,7 @@ public class MainFrame extends JFrame {
|
||||||
doublePanel.add(sourceTextPanel);
|
doublePanel.add(sourceTextPanel);
|
||||||
CompileFromSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
CompileFromSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
||||||
CleanSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
CleanSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
||||||
|
ImportSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
||||||
|
|
||||||
mainPanel.add(doublePanel, BorderLayout.CENTER);
|
mainPanel.add(doublePanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view.book;
|
package nl.andrewlalis.blockbookbinder.view.book;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.blockbookbinder.model.Book;
|
import nl.andrewlalis.blockbookbinder.model.Book;
|
||||||
import nl.andrewlalis.blockbookbinder.model.BookPage;
|
import nl.andrewlalis.blockbookbinder.model.BookPage;
|
||||||
import nl.andrewlalis.blockbookbinder.util.IconLoader;
|
import nl.andrewlalis.blockbookbinder.util.IconLoader;
|
||||||
|
@ -14,6 +15,7 @@ import java.io.InputStream;
|
||||||
* A customized panel that's dedicated to showing a book's contents.
|
* A customized panel that's dedicated to showing a book's contents.
|
||||||
*/
|
*/
|
||||||
public class BookPreviewPanel extends JPanel {
|
public class BookPreviewPanel extends JPanel {
|
||||||
|
@Getter
|
||||||
private Book book;
|
private Book book;
|
||||||
private int currentPage = 0;
|
private int currentPage = 0;
|
||||||
|
|
||||||
|
@ -25,9 +27,6 @@ public class BookPreviewPanel extends JPanel {
|
||||||
private final JButton firstPageButton;
|
private final JButton firstPageButton;
|
||||||
private final JButton lastPageButton;
|
private final JButton lastPageButton;
|
||||||
|
|
||||||
private final SpinnerNumberModel currentPageNumberModel;
|
|
||||||
private boolean ignoreCurrentPageChange = false;
|
|
||||||
|
|
||||||
public BookPreviewPanel() {
|
public BookPreviewPanel() {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
|
|
||||||
|
@ -52,12 +51,10 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.add(previewPageScrollPane, BorderLayout.CENTER);
|
this.add(previewPageScrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel previewButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
JPanel previewButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||||
currentPageNumberModel = new SpinnerNumberModel(0, 0, 0, 1);
|
|
||||||
this.firstPageButton = new JButton();
|
this.firstPageButton = new JButton();
|
||||||
this.firstPageButton.setIcon(IconLoader.load("images/page_first.png", 16, 16));
|
this.firstPageButton.setIcon(IconLoader.load("images/page_first.png", 16, 16));
|
||||||
this.firstPageButton.addActionListener(e -> {
|
this.firstPageButton.addActionListener(e -> {
|
||||||
this.currentPage = 0;
|
this.currentPage = 0;
|
||||||
updateCurrentPageModel();
|
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,7 +63,6 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.previousPageButton.addActionListener(e -> {
|
this.previousPageButton.addActionListener(e -> {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 0) {
|
||||||
currentPage--;
|
currentPage--;
|
||||||
updateCurrentPageModel();
|
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -76,7 +72,6 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.nextPageButton.addActionListener(e -> {
|
this.nextPageButton.addActionListener(e -> {
|
||||||
if (currentPage < book.getPageCount() - 1) {
|
if (currentPage < book.getPageCount() - 1) {
|
||||||
currentPage++;
|
currentPage++;
|
||||||
updateCurrentPageModel();
|
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -84,21 +79,11 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.lastPageButton.setIcon(IconLoader.load("images/page_last.png", 16, 16));
|
this.lastPageButton.setIcon(IconLoader.load("images/page_last.png", 16, 16));
|
||||||
this.lastPageButton.addActionListener(e -> {
|
this.lastPageButton.addActionListener(e -> {
|
||||||
this.currentPage = Math.max(this.book.getPageCount() - 1, 0);
|
this.currentPage = Math.max(this.book.getPageCount() - 1, 0);
|
||||||
updateCurrentPageModel();
|
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
JSpinner currentPageSpinner = new JSpinner(currentPageNumberModel);
|
|
||||||
currentPageSpinner.addChangeListener(e -> {
|
|
||||||
if (!ignoreCurrentPageChange) {
|
|
||||||
this.currentPage = (int) currentPageNumberModel.getValue() - 1;
|
|
||||||
displayCurrentPage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
previewButtonPanel.add(this.firstPageButton);
|
previewButtonPanel.add(this.firstPageButton);
|
||||||
previewButtonPanel.add(this.previousPageButton);
|
previewButtonPanel.add(this.previousPageButton);
|
||||||
previewButtonPanel.add(currentPageSpinner);
|
|
||||||
previewButtonPanel.add(this.nextPageButton);
|
previewButtonPanel.add(this.nextPageButton);
|
||||||
previewButtonPanel.add(this.lastPageButton);
|
previewButtonPanel.add(this.lastPageButton);
|
||||||
this.add(previewButtonPanel, BorderLayout.SOUTH);
|
this.add(previewButtonPanel, BorderLayout.SOUTH);
|
||||||
|
@ -117,31 +102,10 @@ public class BookPreviewPanel extends JPanel {
|
||||||
|
|
||||||
public void setBook(Book book) {
|
public void setBook(Book book) {
|
||||||
this.book = book;
|
this.book = book;
|
||||||
ignoreCurrentPageChange = true;
|
|
||||||
if (book.getPageCount() == 0) {
|
|
||||||
currentPageNumberModel.setMinimum(0);
|
|
||||||
currentPageNumberModel.setMaximum(0);
|
|
||||||
currentPageNumberModel.setValue(0);
|
|
||||||
} else {
|
|
||||||
currentPageNumberModel.setMinimum(1);
|
|
||||||
currentPageNumberModel.setMaximum(book.getPageCount());
|
|
||||||
currentPageNumberModel.setValue(1);
|
|
||||||
}
|
|
||||||
ignoreCurrentPageChange = false;
|
|
||||||
this.currentPage = 0;
|
this.currentPage = 0;
|
||||||
this.displayCurrentPage();
|
this.displayCurrentPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Book getBook() {
|
|
||||||
return book;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateCurrentPageModel() {
|
|
||||||
ignoreCurrentPageChange = true;
|
|
||||||
currentPageNumberModel.setValue(currentPage + 1);
|
|
||||||
ignoreCurrentPageChange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentPage(int page) {
|
public void setCurrentPage(int page) {
|
||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
this.displayCurrentPage();
|
this.displayCurrentPage();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view.export;
|
package nl.andrewlalis.blockbookbinder.view.export;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
|
@ -8,8 +10,11 @@ import java.awt.*;
|
||||||
* job.
|
* job.
|
||||||
*/
|
*/
|
||||||
public class ExportStatusPanel extends JPanel {
|
public class ExportStatusPanel extends JPanel {
|
||||||
|
@Getter
|
||||||
private final JLabel statusLabel;
|
private final JLabel statusLabel;
|
||||||
|
@Getter
|
||||||
private final JTextArea outputTextArea;
|
private final JTextArea outputTextArea;
|
||||||
|
@Getter
|
||||||
private final JProgressBar exportProgressBar;
|
private final JProgressBar exportProgressBar;
|
||||||
|
|
||||||
public ExportStatusPanel() {
|
public ExportStatusPanel() {
|
||||||
|
@ -30,16 +35,4 @@ public class ExportStatusPanel extends JPanel {
|
||||||
this.exportProgressBar = new JProgressBar();
|
this.exportProgressBar = new JProgressBar();
|
||||||
this.add(this.exportProgressBar, BorderLayout.SOUTH);
|
this.add(this.exportProgressBar, BorderLayout.SOUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JLabel getStatusLabel() {
|
|
||||||
return statusLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextArea getOutputTextArea() {
|
|
||||||
return outputTextArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JProgressBar getExportProgressBar() {
|
|
||||||
return exportProgressBar;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,25 +51,32 @@ public class ExportToBookDialog extends JDialog {
|
||||||
private Container buildContentPane() {
|
private Container buildContentPane() {
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
JPanel setupPanel = new JPanel();
|
JPanel setupPanel = new JPanel(new GridBagLayout());
|
||||||
setupPanel.setLayout(new BoxLayout(setupPanel, BoxLayout.PAGE_AXIS));
|
String[] labels = {"", "First Page", "Last Page", "Auto-Paste Delay (Seconds)"};
|
||||||
this.autoCheckbox = new JCheckBox("Auto-paste", true);
|
this.autoCheckbox = new JCheckBox("Auto-paste", true);
|
||||||
this.firstPageSpinner = new JSpinner(new SpinnerNumberModel(1, 1, this.book.getPageCount(), 1));
|
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));
|
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));
|
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);
|
JComponent[] fields = {autoCheckbox, firstPageSpinner, lastPageSpinner, autoPasteDelaySpinner};
|
||||||
autoPasteDelaySpinnerPanel.add(this.autoPasteDelaySpinner, BorderLayout.CENTER);
|
GridBagConstraints c = new GridBagConstraints();
|
||||||
setupPanel.add(this.autoCheckbox);
|
c.gridx = 0;
|
||||||
setupPanel.add(firstPageSpinnerPanel);
|
c.gridy = 0;
|
||||||
setupPanel.add(lastPageSpinnerPanel);
|
c.anchor = GridBagConstraints.LINE_START;
|
||||||
setupPanel.add(autoPasteDelaySpinnerPanel);
|
c.weightx = 0.5;
|
||||||
|
c.fill = GridBagConstraints.NONE;
|
||||||
|
c.insets = new Insets(5, 5, 5, 5);
|
||||||
|
for (String label : labels) {
|
||||||
|
setupPanel.add(new JLabel(label), c);
|
||||||
|
c.gridy++;
|
||||||
|
}
|
||||||
|
c.gridx = 1;
|
||||||
|
c.gridy = 0;
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
for (JComponent field : fields) {
|
||||||
|
setupPanel.add(field, c);
|
||||||
|
c.gridy++;
|
||||||
|
}
|
||||||
|
|
||||||
this.exportStatusPanel = new ExportStatusPanel();
|
this.exportStatusPanel = new ExportStatusPanel();
|
||||||
|
|
||||||
|
@ -191,9 +198,11 @@ public class ExportToBookDialog extends JDialog {
|
||||||
) {
|
) {
|
||||||
JOptionPane.showMessageDialog(
|
JOptionPane.showMessageDialog(
|
||||||
this,
|
this,
|
||||||
"Invalid page range. Please follow the rules below:\n" +
|
"""
|
||||||
"1. First page must be lower or equal to the last page.\n" +
|
Invalid page range. Please follow the rules below:
|
||||||
"2. Number of pages to export cannot exceed 100.\n",
|
1. First page must be lower or equal to the last page.
|
||||||
|
2. Number of pages to export cannot exceed 100.
|
||||||
|
""",
|
||||||
"Invalid Page Range",
|
"Invalid Page Range",
|
||||||
JOptionPane.WARNING_MESSAGE
|
JOptionPane.WARNING_MESSAGE
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
version=1.2.0
|
version=1.3.1
|
||||||
|
|
||||||
# Settings for the application's GUI.
|
# Settings for the application's GUI.
|
||||||
frame.title=Block Book Binder
|
frame.title=Block Book Binder
|
||||||
|
@ -16,8 +16,8 @@ about_dialog.min_height=800
|
||||||
about_dialog.source=html/about.html
|
about_dialog.source=html/about.html
|
||||||
|
|
||||||
# Settings for Minecraft book interaction.
|
# Settings for Minecraft book interaction.
|
||||||
book.max_pages=50
|
book.max_pages=100
|
||||||
book.page_max_lines=11
|
book.page_max_lines=14
|
||||||
book.page_max_width=107
|
book.page_max_width=113
|
||||||
book.page_max_chars=205
|
book.page_max_chars=700
|
||||||
book.default_char_width=5
|
book.default_char_width=5
|
||||||
|
|
|
@ -9,15 +9,15 @@
|
||||||
<h1>About BlockBookBinder</h1>
|
<h1>About BlockBookBinder</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The <em>BlockBookBinder</em> is a simple application that gives you the power to write books faster than ever before! All you need to do is copy and paste some text into the <em>Source</em> panel on the right-hand side of the application, and click <strong>Convert to Book</strong>. Your text will be transformed so that it can fit onto written books in Minecraft, and you can preview the book in the <em>Book Preview</em> panel on the left-hand side of the application, using the navigation buttons below.
|
The <em>BlockBookBinder</em> is a simple application that gives you the power to write books faster than ever before! All you need to do is copy and paste some text into the <em>Source</em> panel on the right-hand side of the application, and click <strong>Compile from Source</strong>. Your text will be transformed so that it can fit onto written books in Minecraft, and you can preview the book in the <em>Book Preview</em> panel on the left-hand side of the application, using the navigation buttons below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p><em>
|
<p><em>
|
||||||
Note that your text might benefit from the <strong>Clean</strong> functionality, where extra new-line and other unnecessary whitespace characters are removed so that the text can be better compressed into Minecraft's limited format.
|
Note that your text might benefit from the <strong>Clean Source</strong> functionality, where extra new-line and other unnecessary whitespace characters are removed so that the text can be better compressed into Minecraft's limited format.
|
||||||
</em></p>
|
</em></p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Once you've prepared a book and you want to actually copy it into Minecraft, click on <strong>Export to Book</strong>. This will open up a popup with a few different buttons:
|
Once you've prepared a book and you want to actually copy it into Minecraft, click on <strong>Export to Minecraft</strong>. This will open up a popup with a few different buttons:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -38,6 +38,17 @@
|
||||||
When all pages have been exported, you'll be prompted with a little popup, after which the export popup closes and you have the option to export again, or simply close the application if you're done.
|
When all pages have been exported, you'll be prompted with a little popup, after which the export popup closes and you have the option to export again, or simply close the application if you're done.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3>Troubleshooting</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This program works by hijacking your PC's input system to automatically transfer text to the system's clipboard, and then paste it by pressing <em>CTRL+V</em> for you. Because of the nature of such applications, there's no guarantee that this process will work on every computer. Here are some tips to try and get things working.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Try to increase the auto-paste delay to 1 second or more, if you notice some pages being skipped during the export process.</li>
|
||||||
|
<li>If the automated approach fails, you can always select and copy each page from the book preview.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
Loading…
Reference in New Issue