Tried adding some sort of document filter.
This commit is contained in:
parent
ec06cf56cf
commit
5776fa7f94
|
@ -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) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
FlatDarkLaf.install();
|
FlatDarkLaf.install();
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
var mainFrame = new MainFrame();
|
var mainFrame = new MainFrame();
|
||||||
mainFrame.setupAndShow();
|
mainFrame.setupAndShow();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control;
|
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for an action where the source text is 'cleaned' by removing any
|
|
||||||
* trailing whitespace, and removing unnecessary newlines.
|
|
||||||
*/
|
|
||||||
public class CleanSourceActionListener implements ActionListener {
|
|
||||||
private final SourceTextPanel sourceTextPanel;
|
|
||||||
|
|
||||||
public CleanSourceActionListener(SourceTextPanel sourceTextPanel) {
|
|
||||||
this.sourceTextPanel = sourceTextPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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.
|
|
||||||
.replace(" ", " "); // 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,29 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.control;
|
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.model.build.BookBuilder;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action listener that, when activated, converts the text from the source panel
|
|
||||||
* into a formatted book.
|
|
||||||
*/
|
|
||||||
public class ConvertToBookActionListener implements ActionListener {
|
|
||||||
private final SourceTextPanel sourceTextPanel;
|
|
||||||
private final BookPreviewPanel bookPreviewPanel;
|
|
||||||
|
|
||||||
public ConvertToBookActionListener(SourceTextPanel sourceTextPanel, BookPreviewPanel bookPreviewPanel) {
|
|
||||||
this.sourceTextPanel = sourceTextPanel;
|
|
||||||
this.bookPreviewPanel = bookPreviewPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
this.bookPreviewPanel.setBook(
|
|
||||||
new BookBuilder().build(this.sourceTextPanel.getSourceText())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.
|
||||||
|
|
|
@ -20,6 +20,24 @@ public class BookPage {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLine(int index) {
|
||||||
|
if (index < 0 || index >= this.lines.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.lines.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineIndexAtOffset(int offset) {
|
||||||
|
int lineIndex = 0;
|
||||||
|
String line = this.getLine(lineIndex);
|
||||||
|
if (line == null) return -1;
|
||||||
|
while (offset - line.length() > 0) {
|
||||||
|
offset-= line.length();
|
||||||
|
line = this.getLine(++lineIndex);
|
||||||
|
}
|
||||||
|
return lineIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasContent() {
|
public boolean hasContent() {
|
||||||
return !this.lines.isEmpty();
|
return !this.lines.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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.CharWidthMapper;
|
||||||
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -16,7 +16,7 @@ public class BookBuilder {
|
||||||
*/
|
*/
|
||||||
public Book build(String source) {
|
public Book build(String source) {
|
||||||
final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines");
|
final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines");
|
||||||
List<String> lines = this.convertSourceToLines(source);
|
List<String> lines = this.convertSourceToLines(this.cleanSource(source));
|
||||||
Book book = new Book();
|
Book book = new Book();
|
||||||
BookPage page = new BookPage();
|
BookPage page = new BookPage();
|
||||||
int currentPageLineCount = 0;
|
int currentPageLineCount = 0;
|
||||||
|
@ -38,6 +38,21 @@ public class BookBuilder {
|
||||||
return book;
|
return book;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans a given source text, by removing superfluous newlines that won't
|
||||||
|
* look too nice in minecraft text, and by erasing manually-imposed single
|
||||||
|
* newlines that may be in the source text to format it in a normal editor.
|
||||||
|
* @param source The source text.
|
||||||
|
* @return The cleaned string.
|
||||||
|
*/
|
||||||
|
public String cleanSource(String source) {
|
||||||
|
return source.trim()
|
||||||
|
.replaceAll("(?>\\v)+(\\v)", "\n\n") // Replace large chunks of newline with just two.
|
||||||
|
.replaceAll("\\S\n\\S", " ") // Unwrap previously-imposed single-line wrapping.
|
||||||
|
.replaceAll("\t", " ") // Replace tabs with single-spaces, due to space constraints.
|
||||||
|
.replaceAll(" [ ]+", " "); // Remove any superfluous spaces.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given source string into a formatted list of lines that can
|
* Converts the given source string into a formatted list of lines that can
|
||||||
* be copied to a minecraft book.
|
* be copied to a minecraft book.
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -21,6 +24,16 @@ public class CharWidthMapper {
|
||||||
return this.charWidthMap.getOrDefault(c, 6);
|
return this.charWidthMap.getOrDefault(c, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getWidth(String s) {
|
||||||
|
if (s.length() == 0) return 0;
|
||||||
|
int width = getWidth(s.charAt(0));
|
||||||
|
for (int i = 1; i < s.length(); i++) {
|
||||||
|
final char c = s.charAt(i);
|
||||||
|
width += this.getWidth(c) + 1;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
private void initCharWidthMap() {
|
private void initCharWidthMap() {
|
||||||
this.charWidthMap.put(' ', 3);
|
this.charWidthMap.put(' ', 3);
|
||||||
this.charWidthMap.put('!', 1);
|
this.charWidthMap.put('!', 1);
|
||||||
|
@ -49,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++) {
|
|
@ -58,12 +58,8 @@ public class MainFrame extends JFrame {
|
||||||
private Container buildContentPane() {
|
private Container buildContentPane() {
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
JPanel doublePanel = new JPanel(new GridLayout(1, 2));
|
|
||||||
BookPreviewPanel bookPreviewPanel = new BookPreviewPanel();
|
BookPreviewPanel bookPreviewPanel = new BookPreviewPanel();
|
||||||
doublePanel.add(bookPreviewPanel);
|
mainPanel.add(bookPreviewPanel, BorderLayout.CENTER);
|
||||||
SourceTextPanel sourceTextPanel = new SourceTextPanel(bookPreviewPanel);
|
|
||||||
doublePanel.add(sourceTextPanel);
|
|
||||||
mainPanel.add(doublePanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
JButton exportButton = new JButton("Export to Book");
|
JButton exportButton = new JButton("Export to Book");
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
package nl.andrewlalis.blockbookbinder.view;
|
|
||||||
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.CleanSourceActionListener;
|
|
||||||
import nl.andrewlalis.blockbookbinder.control.ConvertToBookActionListener;
|
|
||||||
import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel;
|
|
||||||
|
|
||||||
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(BookPreviewPanel bookPreviewPanel) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
JPanel rightPanelButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
||||||
this.add(rightPanelButtonPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
JButton convertButton = new JButton("Convert to Book");
|
|
||||||
convertButton.addActionListener(new ConvertToBookActionListener(this, bookPreviewPanel));
|
|
||||||
rightPanelButtonPanel.add(convertButton);
|
|
||||||
|
|
||||||
JButton cleanButton = new JButton("Clean");
|
|
||||||
cleanButton.addActionListener(new CleanSourceActionListener(this));
|
|
||||||
rightPanelButtonPanel.add(cleanButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import nl.andrewlalis.blockbookbinder.model.BookPage;
|
||||||
|
|
||||||
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,6 +20,7 @@ public class BookPreviewPanel extends JPanel {
|
||||||
private int currentPage = 0;
|
private int currentPage = 0;
|
||||||
|
|
||||||
private final JTextArea previewPageTextArea;
|
private final JTextArea previewPageTextArea;
|
||||||
|
private final BookPageDocumentFilter documentFilter;
|
||||||
private final JLabel titleLabel;
|
private final JLabel titleLabel;
|
||||||
|
|
||||||
private final JButton previousPageButton;
|
private final JButton previousPageButton;
|
||||||
|
@ -34,7 +36,10 @@ public class BookPreviewPanel extends JPanel {
|
||||||
this.setBorder(new EmptyBorder(5, 5, 5, 5));
|
this.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
|
||||||
this.previewPageTextArea = new JTextArea();
|
this.previewPageTextArea = new JTextArea();
|
||||||
this.previewPageTextArea.setEditable(false);
|
this.documentFilter = new BookPageDocumentFilter();
|
||||||
|
AbstractDocument doc = (AbstractDocument) this.previewPageTextArea.getDocument();
|
||||||
|
doc.setDocumentFilter(this.documentFilter);
|
||||||
|
this.previewPageTextArea.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) {
|
||||||
|
@ -84,7 +89,9 @@ public class BookPreviewPanel extends JPanel {
|
||||||
previewButtonPanel.add(this.lastPageButton);
|
previewButtonPanel.add(this.lastPageButton);
|
||||||
this.add(previewButtonPanel, BorderLayout.SOUTH);
|
this.add(previewButtonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
this.setBook(new Book());
|
Book starterBook = new Book();
|
||||||
|
starterBook.addPage(new BookPage());
|
||||||
|
this.setBook(starterBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayCurrentPage() {
|
private void displayCurrentPage() {
|
||||||
|
@ -98,12 +105,12 @@ public class BookPreviewPanel extends JPanel {
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# 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
|
||||||
|
|
Loading…
Reference in New Issue