Compare commits
4 Commits
main
...
editor_ref
Author | SHA1 | Date |
---|---|---|
Andrew Lalis | 259b7dae12 | |
Andrew Lalis | 86ede3789d | |
Andrew Lalis | e6694bed23 | |
Andrew Lalis | 5776fa7f94 |
|
@ -5,11 +5,6 @@ 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 make sure you have Java version 17 or higher installed to run it.
|
To get started, look for the **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.
|
||||||
|
|
||||||
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_ 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.
|
||||||
|
|
||||||
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
|
|
||||||
https://youtu.be/Mu7Hv1na7Sw
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
viewBox="0 0 8.4666667 8.4666667"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="page_first.svg"
|
|
||||||
inkscape:export-filename="D:\Programming\Projects\BlockBookBinder\src\main\resources\images\page_first.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="22.4"
|
|
||||||
inkscape:cx="7.7529167"
|
|
||||||
inkscape:cy="13.546332"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1017"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-288.53333)">
|
|
||||||
<g
|
|
||||||
id="g823"
|
|
||||||
transform="translate(0.82682292,0.09982333)">
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path815"
|
|
||||||
d="m 6.7794176,289.72688 -5.0921685,2.93996"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="M 6.7794176,295.6068 1.6872491,292.66684"
|
|
||||||
id="path817"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path819"
|
|
||||||
d="m 1.1316191,295.70662 4.3e-6,-5.87992"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.7 KiB |
|
@ -1,80 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
viewBox="0 0 8.4666667 8.4666667"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="page_last.svg"
|
|
||||||
inkscape:export-filename="D:\Programming\Projects\BlockBookBinder\src\main\resources\images\page_last.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="22.4"
|
|
||||||
inkscape:cx="15.007381"
|
|
||||||
inkscape:cy="13.546332"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1017"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-288.53333)">
|
|
||||||
<g
|
|
||||||
id="g823"
|
|
||||||
transform="matrix(-1,0,0,1,7.9175178,0.09982333)">
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path815"
|
|
||||||
d="m 6.7794176,289.72688 -5.0921685,2.93996"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="M 6.7794176,295.6068 1.6872491,292.66684"
|
|
||||||
id="path817"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path819"
|
|
||||||
d="m 7.6127216,295.70662 -4.3e-6,-5.87992"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.7 KiB |
|
@ -1,71 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
viewBox="0 0 8.4666667 8.4666667"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="page_left.svg"
|
|
||||||
inkscape:export-filename="D:\Programming\Projects\BlockBookBinder\src\main\resources\images\page_left.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="22.4"
|
|
||||||
inkscape:cx="7.5743453"
|
|
||||||
inkscape:cy="13.546332"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1017"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-288.53333)">
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 6.7794176,289.72688 -5.0921685,2.93996"
|
|
||||||
id="path815"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path817"
|
|
||||||
d="M 6.7794176,295.6068 1.6872491,292.66684"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.3 KiB |
|
@ -1,71 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
viewBox="0 0 8.4666667 8.4666667"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="page_right.svg"
|
|
||||||
inkscape:export-filename="D:\Programming\Projects\BlockBookBinder\src\main\resources\images\page_right.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="22.4"
|
|
||||||
inkscape:cx="7.5743453"
|
|
||||||
inkscape:cy="13.546332"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1017"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-288.53333)">
|
|
||||||
<path
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 1.6872491,289.72688 5.0921685,2.93996"
|
|
||||||
id="path815"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path817"
|
|
||||||
d="m 1.6872491,295.6068 5.0921685,-2.93996"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.3 KiB |
17
pom.xml
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
<groupId>nl.andrewlalis</groupId>
|
<groupId>nl.andrewlalis</groupId>
|
||||||
<artifactId>BlockBookBinder</artifactId>
|
<artifactId>BlockBookBinder</artifactId>
|
||||||
<version>1.3.1</version>
|
<version>1.1.0</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>12</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>12</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -47,7 +46,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.28</version>
|
<version>1.18.14</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@ -61,13 +60,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.formdev</groupId>
|
<groupId>com.formdev</groupId>
|
||||||
<artifactId>flatlaf</artifactId>
|
<artifactId>flatlaf</artifactId>
|
||||||
<version>3.1.1</version>
|
<version>1.0-rc3</version>
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.maven</groupId>
|
|
||||||
<artifactId>maven-model</artifactId>
|
|
||||||
<version>3.9.3</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
|
@ -10,8 +10,8 @@ import javax.swing.*;
|
||||||
*/
|
*/
|
||||||
public class BlockBookBinder {
|
public class BlockBookBinder {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
FlatDarkLaf.install();
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
FlatDarkLaf.setup();
|
|
||||||
var mainFrame = new MainFrame();
|
var mainFrame = new MainFrame();
|
||||||
mainFrame.setupAndShow();
|
mainFrame.setupAndShow();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package nl.andrewlalis.blockbookbinder.control;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
|
public class ImportAction extends AbstractAction {
|
||||||
|
public ImportAction(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportAction(String name, Icon icon) {
|
||||||
|
super(name, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.out.println("Import!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.export;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import nl.andrewlalis.blockbookbinder.model.Book;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
|
|
||||||
public class ExportBookToMinecraftAction extends AbstractAction {
|
|
||||||
@Getter
|
|
||||||
private static final ExportBookToMinecraftAction instance = new ExportBookToMinecraftAction();
|
|
||||||
|
|
||||||
public ExportBookToMinecraftAction() {
|
|
||||||
super("Export to Minecraft");
|
|
||||||
this.putValue(SHORT_DESCRIPTION, "Export the current book to Minecraft.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private BookPreviewPanel bookPreviewPanel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
final Book book = bookPreviewPanel.getBook();
|
|
||||||
if (book == null || book.getPageCount() == 0) {
|
|
||||||
JOptionPane.showMessageDialog(
|
|
||||||
this.bookPreviewPanel.getRootPane(),
|
|
||||||
"Cannot export an empty book.\nChoose \"Compile to Source\" first, and then export.",
|
|
||||||
"Empty Book",
|
|
||||||
JOptionPane.WARNING_MESSAGE
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ExportToBookDialog dialog = new ExportToBookDialog(SwingUtilities.getWindowAncestor(this.bookPreviewPanel), bookPreviewPanel.getBook());
|
|
||||||
dialog.setupAndShow();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 the focus of this program.
|
* user performs certain key actions outside of the focus of this program.
|
||||||
*/
|
*/
|
||||||
public class ExporterKeyListener implements NativeKeyListener {
|
public class ExporterKeyListener implements NativeKeyListener {
|
||||||
private final BookExporter exporterRunnable;
|
private final BookExporter exporterRunnable;
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.source;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
|
|
||||||
public class CleanSourceAction extends AbstractAction {
|
|
||||||
@Getter
|
|
||||||
private final static CleanSourceAction instance = new CleanSourceAction();
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private SourceTextPanel sourceTextPanel;
|
|
||||||
|
|
||||||
public CleanSourceAction() {
|
|
||||||
super("Clean Source");
|
|
||||||
this.putValue(SHORT_DESCRIPTION, "Clean up the source text.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
final String source = this.sourceTextPanel.getSourceText();
|
|
||||||
String updated = source.trim()
|
|
||||||
.replaceAll("(?>\\v)+(\\v)", "\n\n") // Replace large chunks of newline with just two.
|
|
||||||
.replaceAll("\\t", " ") // Replace tabs with single-spaces, for space savings.
|
|
||||||
.replaceAll(" +", " "); // Remove any double spaces.
|
|
||||||
updated = this.removeNewlineWrapping(updated);
|
|
||||||
this.sourceTextPanel.setSourceText(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String removeNewlineWrapping(String source) {
|
|
||||||
final StringBuilder sb = new StringBuilder(source.length());
|
|
||||||
final char[] sourceChars = source.toCharArray();
|
|
||||||
for (int i = 0; i < sourceChars.length; i++) {
|
|
||||||
char c = sourceChars[i];
|
|
||||||
if (
|
|
||||||
c == '\n'
|
|
||||||
&& (i - 1 >= 0 && !Character.isWhitespace(sourceChars[i - 1]))
|
|
||||||
&& (i + 1 < sourceChars.length && !Character.isWhitespace(sourceChars[i + 1]))
|
|
||||||
) {
|
|
||||||
c = ' ';
|
|
||||||
}
|
|
||||||
sb.append(c);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control.source;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import nl.andrewlalis.blockbookbinder.model.build.BookBuilder;
|
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
|
|
||||||
public class CompileFromSourceAction extends AbstractAction {
|
|
||||||
@Getter
|
|
||||||
private static final CompileFromSourceAction instance = new CompileFromSourceAction();
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private SourceTextPanel sourceTextPanel;
|
|
||||||
@Setter
|
|
||||||
private BookPreviewPanel bookPreviewPanel;
|
|
||||||
|
|
||||||
public CompileFromSourceAction() {
|
|
||||||
super("Compile From Source");
|
|
||||||
this.putValue(SHORT_DESCRIPTION, "Compile the current source text into a book.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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(
|
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
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.filechooser.FileNameExtensionFilter;
|
|
||||||
import java.awt.*;
|
|
||||||
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 {
|
|
||||||
@Getter
|
|
||||||
private static final ImportSourceAction instance = new ImportSourceAction();
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private SourceTextPanel sourceTextPanel;
|
|
||||||
|
|
||||||
public ImportSourceAction() {
|
|
||||||
super("Import Source");
|
|
||||||
this.putValue(SHORT_DESCRIPTION, "Import source text from a file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
Preferences prefs = Preferences.userNodeForPackage(BlockBookBinder.class);
|
|
||||||
String dir = prefs.get("source-import-dir", ".");
|
|
||||||
JFileChooser fileChooser = new JFileChooser(dir);
|
|
||||||
fileChooser.setFileFilter(new FileNameExtensionFilter("Text files", ".txt"));
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,6 +22,11 @@ public class Book {
|
||||||
this.pages.add(page);
|
this.pages.add(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BookPage getPage(int index) {
|
||||||
|
if (index < 0 || index >= this.pages.size()) return null;
|
||||||
|
return this.pages.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a book containing the pages specified by the range.
|
* Gets a book containing the pages specified by the range.
|
||||||
* @param firstIndex The index of the first page to include.
|
* @param firstIndex The index of the first page to include.
|
||||||
|
|
|
@ -2,46 +2,65 @@ package nl.andrewlalis.blockbookbinder.model;
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class BookPage {
|
public class BookPage {
|
||||||
private final List<String> lines;
|
public static final int MAX_LINES = ApplicationProperties.getIntProp("book.page_max_lines");
|
||||||
|
private final String[] lines;
|
||||||
|
|
||||||
public BookPage() {
|
public BookPage() {
|
||||||
this.lines = new ArrayList<>(ApplicationProperties.getIntProp("book.page_max_lines"));
|
this.lines = new String[MAX_LINES];
|
||||||
|
Arrays.fill(this.lines, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addLine(String line) {
|
private BookPage(String[] lines) {
|
||||||
if (this.lines.size() >= ApplicationProperties.getIntProp("book.page_max_lines")) {
|
this.lines = lines;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
public void setLine(int index, String line) {
|
||||||
|
if (index < 0 || index >= this.lines.length) {
|
||||||
|
throw new IndexOutOfBoundsException(index);
|
||||||
}
|
}
|
||||||
this.lines.add(line);
|
this.lines[index] = line;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
public String getLine(int index) {
|
||||||
|
if (index < 0 || index >= this.lines.length) {
|
||||||
|
throw new IndexOutOfBoundsException(index);
|
||||||
|
}
|
||||||
|
return this.lines[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the line at which this offset occurs.
|
||||||
|
* @param offset The offset, from the start of the page.
|
||||||
|
* @return The index of the line in which the given offset is placed.
|
||||||
|
*/
|
||||||
|
public int getLineIndexAtOffset(int offset) {
|
||||||
|
int lineIndex = 0;
|
||||||
|
String line = this.getLine(lineIndex);
|
||||||
|
while (offset - line.length() > 0) {
|
||||||
|
offset -= line.length();
|
||||||
|
line = this.getLine(lineIndex++);
|
||||||
|
}
|
||||||
|
return lineIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasContent() {
|
public boolean hasContent() {
|
||||||
return !this.lines.isEmpty();
|
for (String line : this.lines) {
|
||||||
|
if (!line.isBlank()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookPage copy() {
|
public BookPage copy() {
|
||||||
BookPage c = new BookPage();
|
return new BookPage(Arrays.copyOf(this.lines, MAX_LINES));
|
||||||
for (String line : this.lines) {
|
|
||||||
c.addLine(line);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.join("\n", this.lines);
|
return String.join("\n", this.lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BookPage fromString(String s) {
|
|
||||||
BookPage p = new BookPage();
|
|
||||||
for (String line : s.split("\n")) {
|
|
||||||
p.addLine(line);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,124 +2,128 @@ 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.util.ApplicationProperties;
|
||||||
|
import nl.andrewlalis.blockbookbinder.util.CharWidthMapper;
|
||||||
|
|
||||||
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;
|
/**
|
||||||
private final int MAX_CHARS_PER_PAGE;
|
* Builds a full book of pages from the given source text.
|
||||||
private final int MAX_LINE_PIXEL_WIDTH;
|
* @param source The source text to convert.
|
||||||
|
* @return A book containing the source text formatted for a minecraft book.
|
||||||
|
*/
|
||||||
|
public Book build(String source) {
|
||||||
|
List<String> lines = this.convertSourceToLines(this.cleanSource(source));
|
||||||
|
Book book = new Book();
|
||||||
|
BookPage page = new BookPage();
|
||||||
|
int currentPageLineCount = 0;
|
||||||
|
|
||||||
private final List<String> lines;
|
for (String line : lines) {
|
||||||
|
page.setLine(currentPageLineCount, line);
|
||||||
|
currentPageLineCount++;
|
||||||
|
if (currentPageLineCount == BookPage.MAX_LINES) {
|
||||||
|
book.addPage(page);
|
||||||
|
page = new BookPage();
|
||||||
|
currentPageLineCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final StringBuilder lineBuilder;
|
// Check the last page, and only add it if it contains content.
|
||||||
private final StringBuilder wordBuilder;
|
if (page.hasContent()) {
|
||||||
|
book.addPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
public BookBuilder(int maxLinesPerPage, int maxCharsPerPage, int maxLinePixelWidth) {
|
return book;
|
||||||
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;
|
* Cleans a given source text, by removing superfluous newlines that won't
|
||||||
while (idx < text.length()) {
|
* look too nice in minecraft text, and by erasing manually-imposed single
|
||||||
final char c = text.charAt(idx++);
|
* newlines that may be in the source text to format it in a normal editor.
|
||||||
if (c == '\n') {
|
* @param source The source text.
|
||||||
appendLine();
|
* @return The cleaned string.
|
||||||
} else if (c == ' ' && lineBuilder.length() == 0) {
|
*/
|
||||||
continue; // Skip spaces at the start of lines.
|
public String cleanSource(String source) {
|
||||||
} else if (Character.isWhitespace(c)) {
|
return source.trim()
|
||||||
if (CharWidthMapper.getWidth(lineBuilder.toString() + c) > MAX_LINE_PIXEL_WIDTH) {
|
.replaceAll("(?>\\v)+(\\v)", "\n\n") // Replace large chunks of newline with just two.
|
||||||
appendLine();
|
.replaceAll("\\S\n\\S", " ") // Unwrap previously-imposed single-line wrapping.
|
||||||
if (c != ' ') {
|
.replaceAll("\t", " ") // Replace tabs with single-spaces, due to space constraints.
|
||||||
lineBuilder.append(c);
|
.replaceAll(" [ ]+", " "); // Remove any superfluous spaces.
|
||||||
}
|
}
|
||||||
} 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();
|
* Converts the given source string into a formatted list of lines that can
|
||||||
BookPage page = new BookPage();
|
* be copied to a minecraft book.
|
||||||
int currentPageLineCount = 0;
|
* @param source The source string.
|
||||||
int currentPageCharCount = 0;
|
* @return A list of lines.
|
||||||
|
*/
|
||||||
|
private List<String> convertSourceToLines(String source) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
final char[] sourceChars = source.toCharArray();
|
||||||
|
final int maxLinePixelWidth = ApplicationProperties.getIntProp("book.page_max_width");
|
||||||
|
int sourceIndex = 0;
|
||||||
|
StringBuilder lineBuilder = new StringBuilder(64);
|
||||||
|
int linePixelWidth = 0;
|
||||||
|
StringBuilder symbolBuilder = new StringBuilder(64);
|
||||||
|
|
||||||
// Flush anything remaining in lineBuilder to a final line.
|
while (sourceIndex < sourceChars.length) {
|
||||||
if (lineBuilder.length() > 0) {
|
final char c = sourceChars[sourceIndex];
|
||||||
appendLine();
|
sourceIndex++;
|
||||||
}
|
symbolBuilder.setLength(0);
|
||||||
|
symbolBuilder.append(c);
|
||||||
|
int symbolWidth = CharWidthMapper.getInstance().getWidth(c);
|
||||||
|
|
||||||
for (String line : lines) {
|
// Since there's a 1-pixel gap between characters, add it to the width if this isn't the first char.
|
||||||
if (currentPageCharCount + line.length() > MAX_CHARS_PER_PAGE) {
|
if (lineBuilder.length() > 0) {
|
||||||
book.addPage(page);
|
symbolWidth++;
|
||||||
page = new BookPage();
|
}
|
||||||
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()) {
|
|
||||||
book.addPage(page);
|
|
||||||
}
|
|
||||||
return book;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readWord(String text, int firstCharIdx) {
|
// If we encounter a non-newline whitespace at the beginning of the line, skip it.
|
||||||
wordBuilder.setLength(0);
|
if (c == ' ' && lineBuilder.length() == 0) {
|
||||||
int idx = firstCharIdx;
|
continue;
|
||||||
while (idx < text.length()) {
|
}
|
||||||
char c = text.charAt(idx++);
|
|
||||||
if (!Character.isWhitespace(c)) {
|
|
||||||
wordBuilder.append(c);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wordBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendLine() {
|
// If we encounter a newline, immediately skip to a new line.
|
||||||
this.lines.add(this.lineBuilder.toString());
|
if (c == '\n') {
|
||||||
this.lineBuilder.setLength(0);
|
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,11 +1,14 @@
|
||||||
package nl.andrewlalis.blockbookbinder.model;
|
package nl.andrewlalis.blockbookbinder.util;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides the ability to determine the length of a string, in
|
||||||
|
* pixels, according to Minecraft's internal font.
|
||||||
|
*/
|
||||||
public class CharWidthMapper {
|
public class CharWidthMapper {
|
||||||
@Getter
|
@Getter
|
||||||
private static final CharWidthMapper instance = new CharWidthMapper();
|
private static final CharWidthMapper instance = new CharWidthMapper();
|
||||||
|
@ -17,18 +20,16 @@ public class CharWidthMapper {
|
||||||
this.initCharWidthMap();
|
this.initCharWidthMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getWidth(char c) {
|
public int getWidth(char c) {
|
||||||
return instance.charWidthMap.getOrDefault(c, 6);
|
return this.charWidthMap.getOrDefault(c, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getWidth(String s) {
|
public int getWidth(String s) {
|
||||||
if (s.length() == 0) return 0;
|
if (s.length() == 0) return 0;
|
||||||
int width = 0;
|
int width = getWidth(s.charAt(0));
|
||||||
for (int i = 0; i < s.length(); i++) {
|
for (int i = 1; i < s.length(); i++) {
|
||||||
width += getWidth(s.charAt(i));
|
final char c = s.charAt(i);
|
||||||
if (i < s.length() - 1) {
|
width += this.getWidth(c) + 1;
|
||||||
width++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +62,7 @@ public class CharWidthMapper {
|
||||||
this.charWidthMap.put('|', 1);
|
this.charWidthMap.put('|', 1);
|
||||||
this.charWidthMap.put('}', 3);
|
this.charWidthMap.put('}', 3);
|
||||||
this.charWidthMap.put('~', 6);
|
this.charWidthMap.put('~', 6);
|
||||||
|
this.charWidthMap.put('\n', 0);
|
||||||
|
|
||||||
final int defaultWidth = ApplicationProperties.getIntProp("book.default_char_width");
|
final int defaultWidth = ApplicationProperties.getIntProp("book.default_char_width");
|
||||||
for (char c = 32; c < 127; c++) {
|
for (char c = 32; c < 127; c++) {
|
|
@ -1,24 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.util;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class IconLoader {
|
|
||||||
|
|
||||||
public static Icon load(String resourceName, int width, int height) {
|
|
||||||
try {
|
|
||||||
InputStream is = IconLoader.class.getClassLoader().getResourceAsStream(resourceName);
|
|
||||||
if (is == null) {
|
|
||||||
throw new IOException("Could not open resource: " + resourceName);
|
|
||||||
}
|
|
||||||
Image img = ImageIO.read(is).getScaledInstance(width, height, Image.SCALE_SMOOTH);
|
|
||||||
return new ImageIcon(img);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,10 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view;
|
package nl.andrewlalis.blockbookbinder.view;
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.export.ExportBookToMinecraftAction;
|
import nl.andrewlalis.blockbookbinder.model.Book;
|
||||||
import nl.andrewlalis.blockbookbinder.control.source.CleanSourceAction;
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.source.CompileFromSourceAction;
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.source.ImportSourceAction;
|
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
import nl.andrewlalis.blockbookbinder.view.about.AboutDialog;
|
import nl.andrewlalis.blockbookbinder.view.about.AboutDialog;
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
||||||
|
import nl.andrewlalis.blockbookbinder.view.export.ExportToBookDialog;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
@ -21,7 +19,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 " + ApplicationProperties.getProp("version"));
|
this.setTitle(ApplicationProperties.getProp("frame.title"));
|
||||||
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) {
|
||||||
|
@ -36,42 +34,15 @@ public class MainFrame extends JFrame {
|
||||||
this.setVisible(true);
|
this.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Container buildContentPane() {
|
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
|
||||||
JPanel doublePanel = new JPanel(new GridLayout(1, 2));
|
|
||||||
|
|
||||||
BookPreviewPanel bookPreviewPanel = new BookPreviewPanel();
|
|
||||||
doublePanel.add(bookPreviewPanel);
|
|
||||||
CompileFromSourceAction.getInstance().setBookPreviewPanel(bookPreviewPanel);
|
|
||||||
ExportBookToMinecraftAction.getInstance().setBookPreviewPanel(bookPreviewPanel);
|
|
||||||
|
|
||||||
SourceTextPanel sourceTextPanel = new SourceTextPanel();
|
|
||||||
doublePanel.add(sourceTextPanel);
|
|
||||||
CompileFromSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
|
||||||
CleanSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
|
||||||
ImportSourceAction.getInstance().setSourceTextPanel(sourceTextPanel);
|
|
||||||
|
|
||||||
mainPanel.add(doublePanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
return mainPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JMenuBar buildMenuBar() {
|
private JMenuBar buildMenuBar() {
|
||||||
JMenuBar menuBar = new JMenuBar();
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
|
||||||
JMenu fileMenu = new JMenu("File");
|
JMenu fileMenu = new JMenu("File");
|
||||||
fileMenu.add(ImportSourceAction.getInstance());
|
|
||||||
JMenuItem exitItem = new JMenuItem("Exit");
|
JMenuItem exitItem = new JMenuItem("Exit");
|
||||||
exitItem.addActionListener(e -> this.dispose());
|
exitItem.addActionListener(e -> this.dispose());
|
||||||
fileMenu.add(exitItem);
|
fileMenu.add(exitItem);
|
||||||
menuBar.add(fileMenu);
|
menuBar.add(fileMenu);
|
||||||
|
|
||||||
JMenu bookMenu = new JMenu("Book");
|
|
||||||
bookMenu.add(CompileFromSourceAction.getInstance());
|
|
||||||
bookMenu.add(CleanSourceAction.getInstance());
|
|
||||||
bookMenu.add(ExportBookToMinecraftAction.getInstance());
|
|
||||||
menuBar.add(bookMenu);
|
|
||||||
|
|
||||||
JMenu helpMenu = new JMenu("Help");
|
JMenu helpMenu = new JMenu("Help");
|
||||||
JMenuItem aboutItem = new JMenuItem("About");
|
JMenuItem aboutItem = new JMenuItem("About");
|
||||||
aboutItem.addActionListener(e -> {
|
aboutItem.addActionListener(e -> {
|
||||||
|
@ -84,6 +55,31 @@ public class MainFrame extends JFrame {
|
||||||
return menuBar;
|
return menuBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Container buildContentPane() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
BookPreviewPanel bookPreviewPanel = new BookPreviewPanel();
|
||||||
|
mainPanel.add(bookPreviewPanel, BorderLayout.CENTER);
|
||||||
|
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
JButton exportButton = new JButton("Export to Book");
|
||||||
|
JButton cancelExportButton = new JButton("Cancel Export");
|
||||||
|
cancelExportButton.setEnabled(false);
|
||||||
|
exportButton.addActionListener(e -> {
|
||||||
|
final Book book = bookPreviewPanel.getBook();
|
||||||
|
if (book == null || book.getPageCount() == 0) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Cannot export an empty book.", "Empty Book", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExportToBookDialog dialog = new ExportToBookDialog(this, bookPreviewPanel.getBook());
|
||||||
|
dialog.setupAndShow();
|
||||||
|
});
|
||||||
|
bottomPanel.add(exportButton);
|
||||||
|
bottomPanel.add(cancelExportButton);
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
return mainPanel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A panel dedicated to displaying an interacting with a raw source of text for
|
|
||||||
* a book.
|
|
||||||
*/
|
|
||||||
public class SourceTextPanel extends JPanel {
|
|
||||||
private final JTextArea textArea;
|
|
||||||
|
|
||||||
public SourceTextPanel() {
|
|
||||||
super(new BorderLayout());
|
|
||||||
|
|
||||||
this.add(new JLabel("Source Text"), BorderLayout.NORTH);
|
|
||||||
this.setBorder(new EmptyBorder(5, 5, 5, 5));
|
|
||||||
|
|
||||||
this.textArea = new JTextArea();
|
|
||||||
this.textArea.setWrapStyleWord(true);
|
|
||||||
this.textArea.setLineWrap(true);
|
|
||||||
JScrollPane scrollWrappedMainTextArea = new JScrollPane(this.textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
|
||||||
this.add(scrollWrappedMainTextArea, BorderLayout.CENTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSourceText() {
|
|
||||||
return this.textArea.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceText(String text) {
|
|
||||||
this.textArea.setText(text);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,39 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view.book;
|
package nl.andrewlalis.blockbookbinder.view.book;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import nl.andrewlalis.blockbookbinder.model.BookPage;
|
||||||
|
|
||||||
import javax.swing.text.AttributeSet;
|
import javax.swing.text.AttributeSet;
|
||||||
import javax.swing.text.BadLocationException;
|
import javax.swing.text.BadLocationException;
|
||||||
import javax.swing.text.DocumentFilter;
|
import javax.swing.text.DocumentFilter;
|
||||||
|
|
||||||
public class BookPageDocumentFilter extends DocumentFilter {
|
public class BookPageDocumentFilter extends DocumentFilter {
|
||||||
|
@Setter
|
||||||
|
private BookPage page;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
|
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
|
||||||
|
System.out.printf("Remove: offset=%d, length=%d\n", offset, length);
|
||||||
|
if (page == null) return;
|
||||||
super.remove(fb, offset, length);
|
super.remove(fb, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
|
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
|
||||||
|
System.out.printf("Insert: offset=%d, string=%s\n", offset, string);
|
||||||
|
if (page == null) return;
|
||||||
|
int lineIndex = page.getLineIndexAtOffset(offset);
|
||||||
|
System.out.printf("Insert on line %d: %s\n", lineIndex, page.getLine(lineIndex));
|
||||||
|
fb.getDocument().getStartPosition().getOffset();
|
||||||
super.insertString(fb, offset, string, attr);
|
super.insertString(fb, offset, string, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
|
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
|
||||||
|
System.out.printf("Replace: offset=%d, length=%d, text=%s\n", offset, length, text);
|
||||||
|
if (page == null) return;
|
||||||
|
int lineIndex = page.getLineIndexAtOffset(offset);
|
||||||
|
System.out.printf("Replace on line %d: %s\n", lineIndex, page.getLine(lineIndex));
|
||||||
super.replace(fb, offset, length, text, attrs);
|
super.replace(fb, offset, length, text, attrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ package nl.andrewlalis.blockbookbinder.view.book;
|
||||||
import lombok.Getter;
|
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 javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.text.AbstractDocument;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -19,7 +19,8 @@ public class BookPreviewPanel extends JPanel {
|
||||||
private Book book;
|
private Book book;
|
||||||
private int currentPage = 0;
|
private int currentPage = 0;
|
||||||
|
|
||||||
private final JTextArea previewPageTextArea;
|
private final JEditorPane pageEditorPane;
|
||||||
|
private final BookPageDocumentFilter documentFilter;
|
||||||
private final JLabel titleLabel;
|
private final JLabel titleLabel;
|
||||||
|
|
||||||
private final JButton previousPageButton;
|
private final JButton previousPageButton;
|
||||||
|
@ -34,8 +35,11 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.add(this.titleLabel, BorderLayout.NORTH);
|
this.add(this.titleLabel, BorderLayout.NORTH);
|
||||||
this.setBorder(new EmptyBorder(5, 5, 5, 5));
|
this.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
|
||||||
this.previewPageTextArea = new JTextArea();
|
this.pageEditorPane = new JEditorPane();
|
||||||
this.previewPageTextArea.setEditable(false);
|
this.documentFilter = new BookPageDocumentFilter();
|
||||||
|
AbstractDocument doc = (AbstractDocument) this.pageEditorPane.getDocument();
|
||||||
|
doc.setDocumentFilter(this.documentFilter);
|
||||||
|
this.pageEditorPane.setEditable(true);
|
||||||
try {
|
try {
|
||||||
InputStream is = this.getClass().getClassLoader().getResourceAsStream("fonts/1_Minecraft-Regular.otf");
|
InputStream is = this.getClass().getClassLoader().getResourceAsStream("fonts/1_Minecraft-Regular.otf");
|
||||||
if (is == null) {
|
if (is == null) {
|
||||||
|
@ -43,23 +47,21 @@ public class BookPreviewPanel extends JPanel {
|
||||||
}
|
}
|
||||||
Font mcFont = Font.createFont(Font.TRUETYPE_FONT, is);
|
Font mcFont = Font.createFont(Font.TRUETYPE_FONT, is);
|
||||||
mcFont = mcFont.deriveFont(24.0f);
|
mcFont = mcFont.deriveFont(24.0f);
|
||||||
this.previewPageTextArea.setFont(mcFont);
|
this.pageEditorPane.setFont(mcFont);
|
||||||
} catch (FontFormatException | IOException e) {
|
} catch (FontFormatException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
JScrollPane previewPageScrollPane = new JScrollPane(this.previewPageTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
JScrollPane previewPageScrollPane = new JScrollPane(this.pageEditorPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
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));
|
||||||
this.firstPageButton = new JButton();
|
this.firstPageButton = new JButton("First");
|
||||||
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;
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.previousPageButton = new JButton();
|
this.previousPageButton = new JButton("Previous Page");
|
||||||
this.previousPageButton.setIcon(IconLoader.load("images/page_left.png", 16, 16));
|
|
||||||
this.previousPageButton.addActionListener(e -> {
|
this.previousPageButton.addActionListener(e -> {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 0) {
|
||||||
currentPage--;
|
currentPage--;
|
||||||
|
@ -67,16 +69,15 @@ public class BookPreviewPanel extends JPanel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nextPageButton = new JButton();
|
this.nextPageButton = new JButton("Next Page");
|
||||||
this.nextPageButton.setIcon(IconLoader.load("images/page_right.png", 16, 16));
|
|
||||||
this.nextPageButton.addActionListener(e -> {
|
this.nextPageButton.addActionListener(e -> {
|
||||||
if (currentPage < book.getPageCount() - 1) {
|
if (currentPage < book.getPageCount() - 1) {
|
||||||
currentPage++;
|
currentPage++;
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.lastPageButton = new JButton();
|
|
||||||
this.lastPageButton.setIcon(IconLoader.load("images/page_last.png", 16, 16));
|
this.lastPageButton = new JButton("Last");
|
||||||
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);
|
||||||
displayCurrentPage();
|
displayCurrentPage();
|
||||||
|
@ -87,8 +88,9 @@ public class BookPreviewPanel extends JPanel {
|
||||||
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);
|
||||||
|
Book starterBook = new Book();
|
||||||
this.setBook(new Book());
|
starterBook.addPage(new BookPage());
|
||||||
|
this.setBook(starterBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayCurrentPage() {
|
private void displayCurrentPage() {
|
||||||
|
@ -96,18 +98,18 @@ public class BookPreviewPanel extends JPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BookPage currentPage = this.book.getPages().get(this.currentPage);
|
BookPage currentPage = this.book.getPages().get(this.currentPage);
|
||||||
this.previewPageTextArea.setText(currentPage.toString());
|
this.pageEditorPane.setText(currentPage.toString());
|
||||||
this.titleLabel.setText("Book Preview (Page " + (this.currentPage + 1) + " of " + this.book.getPageCount() + ")");
|
this.titleLabel.setText("Book Preview (Page " + (this.currentPage + 1) + " of " + this.book.getPageCount() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBook(Book book) {
|
public void setBook(Book book) {
|
||||||
this.book = book;
|
this.book = book;
|
||||||
this.currentPage = 0;
|
this.setCurrentPage(0);
|
||||||
this.displayCurrentPage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentPage(int page) {
|
public void setCurrentPage(int page) {
|
||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
|
this.documentFilter.setPage(this.book.getPage(page));
|
||||||
this.displayCurrentPage();
|
this.displayCurrentPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ public class ExportToBookDialog extends JDialog {
|
||||||
private Thread exporterThread;
|
private Thread exporterThread;
|
||||||
private BookExporter exporterRunnable;
|
private BookExporter exporterRunnable;
|
||||||
|
|
||||||
public ExportToBookDialog(Window owner, Book book) {
|
public ExportToBookDialog(Frame owner, Book book) {
|
||||||
super(owner, "Export to Book");
|
super(owner, "Export to Book", true);
|
||||||
this.book = book;
|
this.book = book;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,32 +51,25 @@ 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(new GridBagLayout());
|
JPanel setupPanel = new JPanel();
|
||||||
String[] labels = {"", "First Page", "Last Page", "Auto-Paste Delay (Seconds)"};
|
setupPanel.setLayout(new BoxLayout(setupPanel, BoxLayout.PAGE_AXIS));
|
||||||
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());
|
||||||
JComponent[] fields = {autoCheckbox, firstPageSpinner, lastPageSpinner, autoPasteDelaySpinner};
|
autoPasteDelaySpinnerPanel.add(new JLabel("Auto-Paste Delay (s):"), BorderLayout.WEST);
|
||||||
GridBagConstraints c = new GridBagConstraints();
|
autoPasteDelaySpinnerPanel.add(this.autoPasteDelaySpinner, BorderLayout.CENTER);
|
||||||
c.gridx = 0;
|
setupPanel.add(this.autoCheckbox);
|
||||||
c.gridy = 0;
|
setupPanel.add(firstPageSpinnerPanel);
|
||||||
c.anchor = GridBagConstraints.LINE_START;
|
setupPanel.add(lastPageSpinnerPanel);
|
||||||
c.weightx = 0.5;
|
setupPanel.add(autoPasteDelaySpinnerPanel);
|
||||||
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();
|
||||||
|
|
||||||
|
@ -198,11 +191,9 @@ public class ExportToBookDialog extends JDialog {
|
||||||
) {
|
) {
|
||||||
JOptionPane.showMessageDialog(
|
JOptionPane.showMessageDialog(
|
||||||
this,
|
this,
|
||||||
"""
|
"Invalid page range. Please follow the rules below:\n" +
|
||||||
Invalid page range. Please follow the rules below:
|
"1. First page must be lower or equal to the last page.\n" +
|
||||||
1. First page must be lower or equal to the last page.
|
"2. Number of pages to export cannot exceed 100.\n",
|
||||||
2. Number of pages to export cannot exceed 100.
|
|
||||||
""",
|
|
||||||
"Invalid Page Range",
|
"Invalid Page Range",
|
||||||
JOptionPane.WARNING_MESSAGE
|
JOptionPane.WARNING_MESSAGE
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
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
|
||||||
frame.default_width=800
|
frame.default_width=500
|
||||||
frame.default_height=600
|
frame.default_height=600
|
||||||
|
|
||||||
export_dialog.min_width=400
|
export_dialog.min_width=400
|
||||||
|
@ -19,5 +17,5 @@ about_dialog.source=html/about.html
|
||||||
book.max_pages=100
|
book.max_pages=100
|
||||||
book.page_max_lines=14
|
book.page_max_lines=14
|
||||||
book.page_max_width=113
|
book.page_max_width=113
|
||||||
book.page_max_chars=700
|
book.page_max_chars=255
|
||||||
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>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.
|
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.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p><em>
|
<p><em>
|
||||||
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.
|
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.
|
||||||
</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 Minecraft</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 Book</strong>. This will open up a popup with a few different buttons:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -38,17 +38,6 @@
|
||||||
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>
|
||||||
|
|
Before Width: | Height: | Size: 505 B |
Before Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 515 B |
Before Width: | Height: | Size: 504 B |