Added more to the bookbuilder, not done yet though.

This commit is contained in:
Andrew Lalis 2020-11-07 15:05:16 +01:00
parent cb6841cdac
commit c9e7211323
14 changed files with 388 additions and 21 deletions

View File

@ -21,5 +21,12 @@
<version>1.18.14</version> <version>1.18.14</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
<dependency>
<groupId>com.1stleg</groupId>
<artifactId>jnativehook</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,8 +1,11 @@
package nl.andrewlalis.blockbookbinder; package nl.andrewlalis.blockbookbinder;
import nl.andrewlalis.blockbookbinder.view.MainFrame; import nl.andrewlalis.blockbookbinder.view.MainFrame;
import org.jnativehook.GlobalScreen;
import javax.swing.*; import javax.swing.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* The main class for the application. * The main class for the application.

View File

@ -0,0 +1,41 @@
package nl.andrewlalis.blockbookbinder.control;
import nl.andrewlalis.blockbookbinder.model.Book;
import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
public class BookExportActionListener implements ActionListener {
private final BookPreviewPanel bookPreviewPanel;
private final Clipboard clipboard;
public BookExportActionListener(BookPreviewPanel bookPreviewPanel) {
this.bookPreviewPanel = bookPreviewPanel;
this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Starting export.");
final Book book = this.bookPreviewPanel.getBook();
BookPagePasteListener pasteListener = new BookPagePasteListener(book, clipboard);
try {
// For catching native events, set logging here.
Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
logger.setLevel(Level.WARNING);
logger.setUseParentHandlers(false);
GlobalScreen.registerNativeHook();
GlobalScreen.addNativeKeyListener(pasteListener);
} catch (NativeHookException nativeHookException) {
System.err.println("Could not register native hook.");
nativeHookException.printStackTrace();
}
}
}

View File

@ -0,0 +1,51 @@
package nl.andrewlalis.blockbookbinder.control;
import nl.andrewlalis.blockbookbinder.model.Book;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
public class BookPagePasteListener implements NativeKeyListener {
private final Book book;
private final Clipboard clipboard;
private int nextPage;
public BookPagePasteListener(Book book, Clipboard clipboard) {
this.book = book;
this.clipboard = clipboard;
this.nextPage = 0;
}
@Override
public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {}
@Override
public void nativeKeyPressed(NativeKeyEvent nativeKeyEvent) {
if (nativeKeyEvent.getKeyCode() == NativeKeyEvent.VC_V && (nativeKeyEvent.getModifiers() & NativeKeyEvent.CTRL_MASK) > 0) {
System.out.println("CTRL + V -> Paste!!!");
clipboard.setContents(
new StringSelection(book.getPages().get(this.nextPage).toString()),
null
);
this.nextPage++;
System.out.println("Incremented page.");
if (this.nextPage >= this.book.getPageCount()) {
try {
GlobalScreen.removeNativeKeyListener(this);
GlobalScreen.unregisterNativeHook();
System.out.println("Done pasting.");
} catch (NativeHookException nativeHookException) {
System.err.println("Could not unregister a native hook.");
nativeHookException.printStackTrace();
}
}
}
}
@Override
public void nativeKeyReleased(NativeKeyEvent nativeKeyEvent) {}
}

View File

@ -0,0 +1,23 @@
package nl.andrewlalis.blockbookbinder.control;
import nl.andrewlalis.blockbookbinder.model.BookBuilder;
import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel;
import nl.andrewlalis.blockbookbinder.view.SourceTextPanel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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()));
}
}

View File

@ -16,4 +16,14 @@ public class Book {
public int getPageCount() { public int getPageCount() {
return this.pages.size(); return this.pages.size();
} }
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Book of " + this.getPageCount() + " pages:\n");
for (int i = 0; i < this.getPageCount(); i++) {
BookPage page = this.pages.get(i);
sb.append("Page ").append(i + 1).append(":\n").append(page).append('\n');
}
return sb.toString();
}
} }

View File

@ -0,0 +1,73 @@
package nl.andrewlalis.blockbookbinder.model;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
/**
* Class which helps construct formatted book pages from a source text.
*/
public class BookBuilder {
public Book build(String source) {
Book book = new Book();
char[] sourceChars = source.trim().toCharArray();
final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines");
final int maxLineWidth = ApplicationProperties.getIntProp("book.page_max_width");
final CharWidthMapper charWidthMapper = new CharWidthMapper();
BookPage currentPage = new BookPage();
StringBuilder lineStringBuilder = new StringBuilder(64);
int pageLineCount = 1; // Current line on the page we're on.
int lineCharWidth = 0; // Total pixel width of the current line so far.
int i = 0;
while (i < sourceChars.length) {
final char c = sourceChars[i];
if (c == '\n') {
i++;
continue;
}
final int cWidth = charWidthMapper.getWidth(c);
boolean newLineNeeded = lineCharWidth + cWidth + 1 > maxLineWidth;
boolean newPageNeeded = pageLineCount == maxLines && newLineNeeded;
System.out.println("Current char: " + c + ", Current Line: " + pageLineCount + ", Current Line Char Width: " + lineCharWidth + ", New line needed: " + newLineNeeded + ", New page needed: " + newPageNeeded);
// Check if the page is full, and append it to the book, and refresh.
if (newPageNeeded) {
// If necessary, append whatever is left in the last line to the page.
if (lineStringBuilder.length() > 0) {
currentPage.addLine(lineStringBuilder.toString());
}
book.getPages().add(currentPage);
currentPage = new BookPage();
// Reset all buffers and counters for the next page.
lineStringBuilder.setLength(0);
newLineNeeded = false;
pageLineCount = 1;
lineCharWidth = 0;
}
// Check if the line is full, and append it to the page and refresh.
if (newLineNeeded) {
currentPage.addLine(lineStringBuilder.toString());
// Reset line status info.
lineStringBuilder.setLength(0);
pageLineCount++;
lineCharWidth = 0;
}
// Finally, append the char to the current line.
lineStringBuilder.append(c);
lineCharWidth += cWidth + 1;
i++;
}
// Append a final page with the remainder of the text.
if (currentPage.hasContent()) {
if (lineStringBuilder.length() > 0) {
currentPage.addLine(lineStringBuilder.toString());
}
book.getPages().add(currentPage);
}
return book;
}
}

View File

@ -1,14 +1,31 @@
package nl.andrewlalis.blockbookbinder.model; package nl.andrewlalis.blockbookbinder.model;
import lombok.Getter; import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
public class BookPage { public class BookPage {
@Getter private final List<String> lines;
@Setter
private String content;
public BookPage() { public BookPage() {
this.content = ""; this.lines = new ArrayList<>(ApplicationProperties.getIntProp("book.page_max_lines"));
}
public boolean addLine(String line) {
if (this.lines.size() == ApplicationProperties.getIntProp("book.page_max_lines")) {
return false;
}
this.lines.add(line);
return true;
}
public boolean hasContent() {
return !this.lines.isEmpty();
}
@Override
public String toString() {
return String.join("\n", this.lines);
} }
} }

View File

@ -0,0 +1,56 @@
package nl.andrewlalis.blockbookbinder.model;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
import java.util.HashMap;
import java.util.Map;
public class CharWidthMapper {
private final Map<Character, Integer> charWidthMap;
public CharWidthMapper() {
this.charWidthMap = new HashMap<>();
this.initCharWidthMap();
}
public int getWidth(char c) {
return this.charWidthMap.getOrDefault(c, 0);
}
private void initCharWidthMap() {
this.charWidthMap.put(' ', 3);
this.charWidthMap.put('!', 1);
this.charWidthMap.put('"', 3);
this.charWidthMap.put('\'', 1);
this.charWidthMap.put('(', 3);
this.charWidthMap.put(')', 3);
this.charWidthMap.put('*', 3);
this.charWidthMap.put(',', 1);
this.charWidthMap.put('.', 1);
this.charWidthMap.put(':', 1);
this.charWidthMap.put(';', 1);
this.charWidthMap.put('<', 4);
this.charWidthMap.put('>', 4);
this.charWidthMap.put('@', 6);
this.charWidthMap.put('I', 3);
this.charWidthMap.put('[', 3);
this.charWidthMap.put(']', 3);
this.charWidthMap.put('`', 2);
this.charWidthMap.put('f', 4);
this.charWidthMap.put('i', 1);
this.charWidthMap.put('k', 4);
this.charWidthMap.put('l', 2);
this.charWidthMap.put('t', 3);
this.charWidthMap.put('{', 3);
this.charWidthMap.put('|', 1);
this.charWidthMap.put('}', 3);
this.charWidthMap.put('~', 6);
final int defaultWidth = ApplicationProperties.getIntProp("book.default_char_width");
for (char c = 32; c < 127; c++) {
if (!this.charWidthMap.containsKey(c)) {
this.charWidthMap.put(c, defaultWidth);
}
}
}
}

View File

@ -3,6 +3,8 @@ package nl.andrewlalis.blockbookbinder.util;
import lombok.Getter; import lombok.Getter;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
/** /**
@ -14,6 +16,8 @@ public class ApplicationProperties {
@Getter @Getter
private final Properties properties; private final Properties properties;
private final Map<String, Integer> intPropCache;
public static ApplicationProperties getInstance() { public static ApplicationProperties getInstance() {
if (instance == null) { if (instance == null) {
try { try {
@ -35,8 +39,18 @@ public class ApplicationProperties {
return getInstance().getProperties().getProperty(key); return getInstance().getProperties().getProperty(key);
} }
public static Integer getIntProp(String key) {
Integer value = getInstance().intPropCache.get(key);
if (value == null) {
value = Integer.parseInt(getProp(key));
getInstance().intPropCache.put(key, value);
}
return value;
}
private ApplicationProperties() throws IOException { private ApplicationProperties() throws IOException {
this.properties = new Properties(); this.properties = new Properties();
this.properties.load(this.getClass().getClassLoader().getResourceAsStream("application.properties")); this.properties.load(this.getClass().getClassLoader().getResourceAsStream("application.properties"));
this.intPropCache = new HashMap<>();
} }
} }

View File

@ -1,30 +1,87 @@
package nl.andrewlalis.blockbookbinder.view; package nl.andrewlalis.blockbookbinder.view;
import lombok.Getter;
import nl.andrewlalis.blockbookbinder.model.Book; import nl.andrewlalis.blockbookbinder.model.Book;
import nl.andrewlalis.blockbookbinder.model.BookPage;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.io.IOException;
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 final JTextArea previewPageTextArea;
private final JLabel titleLabel;
public BookPreviewPanel() { public BookPreviewPanel() {
super(new BorderLayout()); super(new BorderLayout());
this.add(new JLabel("Book Preview"), BorderLayout.NORTH); this.titleLabel = new JLabel("Book Preview");
this.add(this.titleLabel, BorderLayout.NORTH);
this.setBorder(new EmptyBorder(5, 5, 5, 5)); this.setBorder(new EmptyBorder(5, 5, 5, 5));
JTextArea previewPageTextArea = new JTextArea(); this.previewPageTextArea = new JTextArea();
JScrollPane previewPageScrollPane = new JScrollPane(previewPageTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); this.previewPageTextArea.setEditable(false);
try {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("fonts/1_Minecraft-Regular.otf");
if (is == null) {
throw new IOException("Could not read minecraft font.");
}
Font mcFont = Font.createFont(Font.TRUETYPE_FONT, is);
mcFont = mcFont.deriveFont(24.0f);
this.previewPageTextArea.setFont(mcFont);
} catch (FontFormatException | IOException e) {
e.printStackTrace();
}
JScrollPane previewPageScrollPane = new JScrollPane(this.previewPageTextArea, 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));
previewButtonPanel.add(new JButton("<")); JButton previousPageButton = new JButton("Previous Page");
previewButtonPanel.add(new JButton(">")); previousPageButton.addActionListener(e -> {
if (currentPage > 0) {
currentPage--;
displayCurrentPage();
}
});
JButton nextPageButton = new JButton("Next Page");
nextPageButton.addActionListener(e -> {
if (currentPage < book.getPageCount() - 1) {
currentPage++;
displayCurrentPage();
}
});
previewButtonPanel.add(previousPageButton);
previewButtonPanel.add(nextPageButton);
this.add(previewButtonPanel, BorderLayout.SOUTH); this.add(previewButtonPanel, BorderLayout.SOUTH);
this.setBook(new Book());
}
private void displayCurrentPage() {
if (this.book.getPageCount() == 0) {
return;
}
BookPage currentPage = this.book.getPages().get(this.currentPage);
this.previewPageTextArea.setText(currentPage.toString());
this.titleLabel.setText("Book Preview (Page " + (this.currentPage + 1) + " of " + this.book.getPageCount() + ")");
}
public void setBook(Book book) {
this.book = book;
this.currentPage = 0;
this.displayCurrentPage();
}
public void setCurrentPage(int page) {
this.currentPage = page;
this.displayCurrentPage();
} }
} }

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.blockbookbinder.view; package nl.andrewlalis.blockbookbinder.view;
import nl.andrewlalis.blockbookbinder.control.BookExportActionListener;
import nl.andrewlalis.blockbookbinder.control.ImportAction; import nl.andrewlalis.blockbookbinder.control.ImportAction;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
@ -56,17 +57,15 @@ public class MainFrame extends JFrame {
JPanel mainPanel = new JPanel(new BorderLayout()); JPanel mainPanel = new JPanel(new BorderLayout());
JPanel doublePanel = new JPanel(new GridLayout(1, 2)); JPanel doublePanel = new JPanel(new GridLayout(1, 2));
doublePanel.add(new BookPreviewPanel()); BookPreviewPanel bookPreviewPanel = new BookPreviewPanel();
doublePanel.add(new SourceTextPanel()); doublePanel.add(bookPreviewPanel);
SourceTextPanel sourceTextPanel = new SourceTextPanel(bookPreviewPanel);
doublePanel.add(sourceTextPanel);
mainPanel.add(doublePanel, BorderLayout.CENTER); 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");
exportButton.addActionListener(e -> { exportButton.addActionListener(new BookExportActionListener(bookPreviewPanel));
System.out.println("Starting export.");
// final String fullText = mainTextArea.getText().trim();
// Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fullText.substring(0, 200)), null);
});
bottomPanel.add(exportButton); bottomPanel.add(exportButton);
mainPanel.add(bottomPanel, BorderLayout.SOUTH); mainPanel.add(bottomPanel, BorderLayout.SOUTH);

View File

@ -1,5 +1,7 @@
package nl.andrewlalis.blockbookbinder.view; package nl.andrewlalis.blockbookbinder.view;
import nl.andrewlalis.blockbookbinder.control.ConvertToBookActionListener;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
@ -9,14 +11,18 @@ import java.awt.*;
* a book. * a book.
*/ */
public class SourceTextPanel extends JPanel { public class SourceTextPanel extends JPanel {
public SourceTextPanel() { private final JTextArea textArea;
public SourceTextPanel(BookPreviewPanel bookPreviewPanel) {
super(new BorderLayout()); super(new BorderLayout());
this.add(new JLabel("Source Text"), BorderLayout.NORTH); this.add(new JLabel("Source Text"), BorderLayout.NORTH);
this.setBorder(new EmptyBorder(5, 5, 5, 5)); this.setBorder(new EmptyBorder(5, 5, 5, 5));
JTextArea mainTextArea = new JTextArea(); this.textArea = new JTextArea();
JScrollPane scrollWrappedMainTextArea = new JScrollPane(mainTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 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); this.add(scrollWrappedMainTextArea, BorderLayout.CENTER);
JPanel rightPanelButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel rightPanelButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
@ -24,5 +30,13 @@ public class SourceTextPanel extends JPanel {
JButton importButton = new JButton("Import"); JButton importButton = new JButton("Import");
importButton.setActionCommand("importSource"); importButton.setActionCommand("importSource");
rightPanelButtonPanel.add(importButton); rightPanelButtonPanel.add(importButton);
JButton convertButton = new JButton("Convert to Book");
convertButton.addActionListener(new ConvertToBookActionListener(this, bookPreviewPanel));
rightPanelButtonPanel.add(convertButton);
}
public String getSourceText() {
return this.textArea.getText();
} }
} }

View File

@ -7,3 +7,5 @@ frame.default_height=600
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=255
book.default_char_width=5