started remodeling application
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
/Belegscanner
|
||||
/bin/
|
||||
/target/
|
||||
.idea
|
||||
32
BelegScanner.iml
Normal file
32
BelegScanner.iml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: de.srsoftware:tools:1.1.18" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: de.srsoftware:tools.translations:1.1.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: de.srsoftware:tools.gui.selectcombobox:0.1.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.json:json:20200518" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.icepdf.os:icepdf-core:6.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.54" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-ext-jdk15on:1.54" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.bouncycastle:bcpkix-jdk15on:1.54" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.icepdf.os:icepdf-viewer:6.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-awt-util:1.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-dom:1.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-svg-dom:1.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-svggen:1.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-util:1.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: batik:batik-xml:1.6" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -11,4 +11,6 @@ public class Constants {
|
||||
public static final String OCR = "pdf+ocr";
|
||||
public static final String PDF = "pdf";
|
||||
|
||||
public static final String PATH = "path";
|
||||
public static final String PAGE = "PAGE";
|
||||
}
|
||||
|
||||
166
src/main/java/de/srsoftware/belegscanner/Worker.java
Normal file
166
src/main/java/de/srsoftware/belegscanner/Worker.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package de.srsoftware.belegscanner;
|
||||
|
||||
import de.srsoftware.belegscanner.gui.DocTable;
|
||||
import de.srsoftware.belegscanner.gui.StatusBar;
|
||||
import de.srsoftware.belegscanner.gui.Toolbar;
|
||||
import de.srsoftware.belegscanner.model.ScanProject;
|
||||
import de.srsoftware.belegscanner.model.api.Project;
|
||||
import de.srsoftware.tools.gui.DateChooser;
|
||||
import de.srsoftware.tools.gui.SelectComboBox;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static de.srsoftware.belegscanner.Constants.CONFIG_TARGET;
|
||||
import static de.srsoftware.belegscanner.Constants.JPG;
|
||||
import static de.srsoftware.belegscanner.Constants.OCR;
|
||||
import static de.srsoftware.belegscanner.Constants.PDF;
|
||||
|
||||
public class Worker {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Worker.class);
|
||||
private static final String HOME = System.getProperty("user.home");
|
||||
private static final String YEAR = t("YEAR");
|
||||
private final Configuration config;
|
||||
private SelectComboBox pathPicker;
|
||||
private StatusBar statusBar;
|
||||
private String category;
|
||||
private DateChooser datePicker;
|
||||
private String rawPath, resolvedPath;
|
||||
private Toolbar toolbar;
|
||||
private DocTable docTable;
|
||||
|
||||
public Worker(Configuration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void dropCategory(String droppedCat) {
|
||||
LOG.debug("Worker.dropCategory({})",droppedCat);
|
||||
}
|
||||
public JSONObject getCategories() {
|
||||
return config.getOrCreate("app.categories",new JSONObject());
|
||||
}
|
||||
|
||||
private Optional<JSONObject> getCategory(String name){
|
||||
var cats = getCategories();
|
||||
return cats.has(name) ? (cats.get(name) instanceof JSONObject cat ? Optional.of(cat) : Optional.empty()) : Optional.empty();
|
||||
}
|
||||
public String getType() {
|
||||
return config.getOrCreate(CONFIG_TARGET, PDF);
|
||||
}
|
||||
|
||||
public void resolvePath(){
|
||||
if (rawPath == null) return;
|
||||
LOG.debug("resolve({})",rawPath);
|
||||
var date = datePicker.getSelectedDate();
|
||||
int year = 1900+date.getYear();
|
||||
int month = date.getMonth()+1;
|
||||
int day = date.getDate();
|
||||
resolvedPath = rawPath.replace("$"+t("HOME"),HOME)
|
||||
.replace("$"+t("CATEGORY"), category)
|
||||
.replace("$"+t("YEAR"), year+"")
|
||||
.replace("$"+t("MONTH"), month<10 ? "0"+month : ""+month)
|
||||
.replace("$"+t("DAY"), day < 10 ? "0"+day : ""+day);
|
||||
|
||||
var start = resolvedPath.indexOf("$");
|
||||
toolbar.resetAdditionalFields();
|
||||
while (start>-1) {
|
||||
var end = endOfVar(resolvedPath, start+1);
|
||||
if (end>start+1){
|
||||
var variable = resolvedPath.substring(start+1, end);
|
||||
LOG.debug("…unresolved '{}'",variable);
|
||||
String value = toolbar.requireField(variable);
|
||||
variable = "$"+variable;
|
||||
LOG.debug("replacing '{}' in '{}' by '{}'",variable,resolvedPath,value);
|
||||
resolvedPath = resolvedPath.replace(variable,value);
|
||||
start = resolvedPath.indexOf("$",start+1);
|
||||
} else {
|
||||
start = resolvedPath.indexOf("$",start+2);
|
||||
}
|
||||
}
|
||||
toolbar.dropUnusedAdditionalFields();
|
||||
resolvedPath+="."+extension();
|
||||
statusBar.setPath(resolvedPath);
|
||||
}
|
||||
|
||||
private String extension() {
|
||||
switch (getType()){
|
||||
case PDF:
|
||||
case OCR:
|
||||
return "pdf";
|
||||
case JPG:
|
||||
return "jpg";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private int endOfVar(String path, int index) {
|
||||
while(index<path.length()){
|
||||
if (!Character.isAlphabetic(path.codePointAt(index))) return index;
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
public void scan() {
|
||||
LOG.debug("scan()");
|
||||
var project = docTable.getProject(resolvedPath).orElseGet(this::createProject);
|
||||
}
|
||||
|
||||
private Project createProject() {
|
||||
return new ScanProject(resolvedPath);
|
||||
}
|
||||
|
||||
public void setDimension(Dimension dimension) {
|
||||
LOG.debug("setDimension({})",dimension);
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
LOG.debug("setPath({})",path);
|
||||
this.rawPath = path;
|
||||
resolvePath();
|
||||
|
||||
}
|
||||
public SelectComboBox setPathPicker(SelectComboBox select) {
|
||||
pathPicker = select;
|
||||
return pathPicker.onUpdateText(this::setPath);
|
||||
}
|
||||
|
||||
public void setStatusBar(StatusBar statusBar) {
|
||||
this.statusBar = statusBar;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
LOG.debug("setType({})",type);
|
||||
config.set(CONFIG_TARGET, type);
|
||||
resolvePath();
|
||||
}
|
||||
|
||||
public void setCat(String newCat) {
|
||||
LOG.debug("setCat({}})",newCat);
|
||||
this.category = newCat;
|
||||
getCategory(newCat).map(cat -> cat.get(Constants.PATH))
|
||||
.map(o -> o instanceof String path ? path : null)
|
||||
.ifPresent(path -> {
|
||||
pathPicker.setText(path);
|
||||
setPath(path);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void setDatePicker(DateChooser pd) {
|
||||
datePicker = pd;
|
||||
pd.addActionListener(ev -> this.resolvePath());
|
||||
}
|
||||
|
||||
public void setToolbar(Toolbar toolbar) {
|
||||
this.toolbar = toolbar;
|
||||
}
|
||||
|
||||
public void setDocTable(DocTable docTable) {
|
||||
this.docTable = docTable;
|
||||
}
|
||||
}
|
||||
@@ -3,36 +3,27 @@ package de.srsoftware.belegscanner.gui;
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.border.MatteBorder;
|
||||
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import de.srsoftware.belegscanner.listeners.ProjectListener;
|
||||
import de.srsoftware.belegscanner.model.api.Project;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.srsoftware.belegscanner.Constants;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a table of previously scanned documents.
|
||||
@@ -46,89 +37,20 @@ public class DocTable extends JPanel{
|
||||
private static int rowCounter = 0;
|
||||
private static Font btnFont = new Font("Arial", Font.PLAIN, 28);
|
||||
|
||||
public interface PreviewListener{
|
||||
public void show(Path filename);
|
||||
}
|
||||
private List<Project> projects = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* represents on previously scanned document
|
||||
* @author Stephan Richter <s.richter@srsoftware.de>
|
||||
*
|
||||
*/
|
||||
private class Row{
|
||||
|
||||
private JLabel status;
|
||||
private JLabel pathLabel;
|
||||
private JPanel buttons;
|
||||
private String path;
|
||||
private JButton joinButton;
|
||||
|
||||
public Row(String project) {
|
||||
this.path = project;
|
||||
rows.put(project, this);
|
||||
constraints.gridy = ++rowCounter;
|
||||
constraints.gridx = 0;
|
||||
add(pathLabel = new JLabel(project), constraints);
|
||||
constraints.gridx = 1;
|
||||
add(status = new JLabel("neu"),constraints);
|
||||
|
||||
buttons = new JPanel();
|
||||
buttons.setLayout(new FlowLayout());
|
||||
|
||||
buttons.add(button("⚁",t("open folder"),ev -> openFolder(project)));
|
||||
buttons.add(joinButton = button("⎗",t("join PDFs"),ev -> joinDocs(project)));
|
||||
buttons.add(button("✉",t("display preview"),ev -> preview(project)));
|
||||
buttons.add(button("✓",t("done"),ev -> drop(this)));
|
||||
buttons.add(button("❌",t("drop file"),ev -> dropFile(project)));
|
||||
|
||||
constraints.gridx = 2;
|
||||
add(buttons,constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* drop the selected document from the table
|
||||
* @param row
|
||||
*/
|
||||
private void drop(Row row) {
|
||||
if (row.joinButton.isEnabled()) joinDocs(row.path);
|
||||
remove(pathLabel);
|
||||
remove(status);
|
||||
remove(buttons);
|
||||
invalidate();
|
||||
repaint();
|
||||
rows.remove(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* update status for selected document
|
||||
* @param status
|
||||
* @return
|
||||
*/
|
||||
public Row status(String status) {
|
||||
this.status.setText(status);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the join button assigned with the selected document
|
||||
*/
|
||||
public void setJoinButton(boolean enabled) {
|
||||
joinButton.setEnabled(enabled);
|
||||
joinButton.setVisible(enabled);
|
||||
}
|
||||
}
|
||||
private List<ProjectListener> listeners = new ArrayList<>();
|
||||
|
||||
private static final long serialVersionUID = 1073955198529023744L;
|
||||
|
||||
private final HashMap<String,Row> rows = new HashMap<>();
|
||||
private HashSet<PreviewListener> previewListeners;
|
||||
private GridBagConstraints constraints;
|
||||
|
||||
/**
|
||||
* create a new DocTable
|
||||
*/
|
||||
public DocTable() {
|
||||
public DocTable(Worker worker) {
|
||||
setLayout(new GridBagLayout());
|
||||
worker.setDocTable(this);
|
||||
constraints = new GridBagConstraints();
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
constraints.insets = new Insets(3, 10, 3, 10);
|
||||
@@ -146,185 +68,9 @@ public class DocTable extends JPanel{
|
||||
JLabel actions = new JLabel(t("Actions"),SwingConstants.CENTER);
|
||||
actions.setBorder(border);
|
||||
add(actions,constraints);
|
||||
previewListeners = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ad a table entry for the directory represented by path
|
||||
* @param project
|
||||
*/
|
||||
public void add(String project) {
|
||||
var row = rows.get(project);
|
||||
if (row == null) {
|
||||
new Row(project).status("neu");
|
||||
} else row.setJoinButton(true);
|
||||
public Optional<Project> getProject(String resolvedPath) {
|
||||
return projects.stream().filter(p -> resolvedPath.equals(p.pattern())).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* register a listener for changes of the preview area
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public DocTable addPreviewListener(PreviewListener listener) {
|
||||
previewListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method to create a button with given label, tooltip and action listener
|
||||
* @param label
|
||||
* @param tooltip
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
private static JButton button(String label, String tooltip, ActionListener listener) {
|
||||
JButton btn = new JButton(label);
|
||||
btn.setFont(btnFont);
|
||||
btn.setToolTipText(tooltip);
|
||||
btn.addActionListener(listener);
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static int compareFiles(String file1, String file2) {
|
||||
return timestampFrom(file1) < timestampFrom(file2) ? 1 : -1;
|
||||
}
|
||||
|
||||
private void dropFile(String path) {
|
||||
Path latest = latestFile(new File(path));
|
||||
if (latest != null) {
|
||||
LOG.debug("Removing {}",latest);
|
||||
latest.toFile().delete();
|
||||
preview(latest.toFile().getParent()); // show lates of remaining files
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* join documents in given path
|
||||
* @param path
|
||||
*/
|
||||
public void joinDocs(String path) {
|
||||
LOG.debug("joinFiles({})",path);
|
||||
rows.get(path).setJoinButton(false);
|
||||
File folder = new File(path).getParentFile();
|
||||
if (!folder.exists()) return;
|
||||
|
||||
String pattern = new File(path).getName();
|
||||
var parts = pattern.split("\\$"+t("PAGE"));
|
||||
var prefix = parts[0];
|
||||
var suffix = parts.length>1 ? parts[1] : "";
|
||||
if (parts.length<2) return;
|
||||
|
||||
var pages = new TreeMap<Integer,String>();
|
||||
for (var file : folder.list()){
|
||||
Optional<Integer> opt = getPage(file, prefix, suffix);
|
||||
if (opt.isPresent()){
|
||||
pages.put(opt.get(),file);
|
||||
}
|
||||
}
|
||||
if (pages.size()<2) return;
|
||||
int first = pages.firstKey();
|
||||
int last = pages.lastKey();
|
||||
|
||||
var range = first + (last != first ? "…"+last:"");
|
||||
var target = pattern.replace("$"+t("PAGE"),range);
|
||||
|
||||
var pdfs = pages.values();
|
||||
Vector<String> cmd = new Vector<>();
|
||||
cmd.add("pdftk");
|
||||
cmd.addAll(pdfs);
|
||||
cmd.add("cat");
|
||||
cmd.add("output");
|
||||
cmd.add(target);
|
||||
LOG.debug("executing {}",cmd);
|
||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.directory(folder);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
setState(path,t("Joining PDFs…"));
|
||||
int errorCode = process.waitFor();
|
||||
if (errorCode != 0) {
|
||||
LOG.error("error code: {} for {}",errorCode,cmd);
|
||||
} else LOG.debug("error code: {}",errorCode);
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error("{} terminated: ",builder,e);
|
||||
}
|
||||
|
||||
pdfs.stream()
|
||||
.peek(page -> LOG.debug("removing {}",page))
|
||||
.map(page -> folder.toPath().resolve(page))
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
setState(path,t("PDFs joined."));
|
||||
preview(folder);
|
||||
}
|
||||
|
||||
private Optional<Integer> getPage(String file, String prefix, String suffix) {
|
||||
if (file.startsWith(prefix) && file.endsWith(suffix)) {
|
||||
file = file.substring(prefix.length(), file.length() - suffix.length());
|
||||
try {
|
||||
return Optional.of(Integer.parseInt(file));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
|
||||
private Path latestFile(File folder) {
|
||||
if (!folder.exists()) return null;
|
||||
if (!folder.isDirectory()) folder = folder.getParentFile();
|
||||
List<String> files = Arrays.asList(folder.list());
|
||||
String latest = files.stream().sorted(DocTable::compareFiles).findFirst().orElse(null);
|
||||
return latest == null ? null : folder.toPath().resolve(latest);
|
||||
}
|
||||
|
||||
/**
|
||||
* open given path in external file browser
|
||||
* @param path
|
||||
*/
|
||||
public void openFolder(String path) {
|
||||
Process process = null;
|
||||
try {
|
||||
process = new ProcessBuilder(List.of("killall",Constants.FILE_BROWSER)).start();
|
||||
process.waitFor();
|
||||
Thread.sleep(100);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
LOG.error(t("{} terminated: "),process,e);
|
||||
}
|
||||
|
||||
try {
|
||||
process = new ProcessBuilder(List.of(Constants.FILE_BROWSER,new File(path).getParent())).start();
|
||||
} catch (IOException e) {
|
||||
LOG.error(t("{} terminated: "),process,e);
|
||||
}
|
||||
}
|
||||
|
||||
public void preview(String path){
|
||||
preview(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* show lates document of the given directory in preview area
|
||||
* @param path
|
||||
*/
|
||||
public void preview(File path) {
|
||||
LOG.debug("preview({})",path);
|
||||
previewListeners.forEach(l -> l.show(latestFile(path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* update state column text for given path
|
||||
* @param path
|
||||
* @param state
|
||||
*/
|
||||
public void setState(String path, String state) {
|
||||
Row row = rows.get(path);
|
||||
if (row != null) row.status(state);
|
||||
}
|
||||
|
||||
private static long timestampFrom(String filename) {
|
||||
try {
|
||||
return Long.parseLong(filename.split("\\.",2)[0]);
|
||||
} catch (Exception e) {}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static java.awt.BorderLayout.CENTER;
|
||||
import static java.awt.BorderLayout.NORTH;
|
||||
import static java.awt.BorderLayout.SOUTH;
|
||||
import static java.awt.BorderLayout.WEST;
|
||||
|
||||
public class FormatSelector extends JPanel {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FormatSelector.class);
|
||||
private final JTextField height, width;
|
||||
|
||||
public FormatSelector(Worker worker){
|
||||
setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
|
||||
JPanel widthSelector = new JPanel();
|
||||
widthSelector.setLayout(new BorderLayout());
|
||||
widthSelector.setMaximumSize(new Dimension(600, 40));
|
||||
|
||||
widthSelector.add(new JLabel(t("Width")+": "), WEST);
|
||||
widthSelector.add(width = new JTextField(209+""), CENTER);
|
||||
widthSelector.add(new JLabel("px"),BorderLayout.EAST);
|
||||
add(widthSelector,NORTH);
|
||||
|
||||
var heightSelector = new JPanel();
|
||||
heightSelector.setLayout(new BorderLayout());
|
||||
heightSelector.setMaximumSize(new Dimension(600, 40));
|
||||
|
||||
heightSelector.add(new JLabel(t("Height")+": "), WEST);
|
||||
heightSelector.add(height = new JTextField(297+""), CENTER);
|
||||
heightSelector.add(new JLabel("px"),BorderLayout.EAST);
|
||||
add(heightSelector,SOUTH);
|
||||
|
||||
KeyAdapter dimensionListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
LOG.debug("updateDimensions()");
|
||||
try {
|
||||
int w = Integer.parseInt(width.getText().trim());
|
||||
int h = Integer.parseInt(height.getText().trim());
|
||||
worker.setDimension(new Dimension(w < 0 ? 0 : w, h < 0 ? 0 : h));
|
||||
} catch (NumberFormatException nfe) {
|
||||
LOG.warn("Invalid dimensions!");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
width.addKeyListener(dimensionListener);
|
||||
height.addKeyListener(dimensionListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static java.awt.BorderLayout.NORTH;
|
||||
import static java.awt.BorderLayout.WEST;
|
||||
|
||||
public class ImproveSelector extends JPanel {
|
||||
|
||||
private boolean improveBrightness;
|
||||
|
||||
public ImproveSelector() {
|
||||
super(new BorderLayout());
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
add(new JLabel(t("Adjustments")), NORTH);
|
||||
var checkbox = new JCheckBox(t("Improve brightness"));
|
||||
add(checkbox,WEST);
|
||||
setMaximumSize(new Dimension(600,200));
|
||||
checkbox.addActionListener(e -> {
|
||||
improveBrightness = checkbox.isSelected();
|
||||
});
|
||||
}
|
||||
|
||||
public boolean improveBrightness(){
|
||||
return improveBrightness;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,27 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static de.srsoftware.belegscanner.Constants.CONFIG_TARGET;
|
||||
import static de.srsoftware.belegscanner.Constants.FILE_BROWSER;
|
||||
import static de.srsoftware.belegscanner.Constants.JPG;
|
||||
import static de.srsoftware.belegscanner.Constants.PDF;
|
||||
import de.srsoftware.belegscanner.Configuration;
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import org.icepdf.ri.common.SwingController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import org.icepdf.ri.common.ComponentKeyBinding;
|
||||
import org.icepdf.ri.common.SwingController;
|
||||
import org.icepdf.ri.common.SwingViewBuilder;
|
||||
import org.icepdf.ri.common.views.DocumentViewController;
|
||||
import org.json.JSONArray;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.srsoftware.belegscanner.Configuration;
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static de.srsoftware.belegscanner.Constants.FILE_BROWSER;
|
||||
|
||||
/**
|
||||
* The "guts" of the application:
|
||||
@@ -60,7 +34,6 @@ public class MainFrame extends JFrame {
|
||||
|
||||
private static final long serialVersionUID = 210283601541223645L;
|
||||
|
||||
private static final String HOME = System.getProperty("user.home");
|
||||
private static final Pattern MARKEN = Pattern.compile("\\$([a-zäöüA-ZÄÖÜ]+)");
|
||||
|
||||
private static final String APP_WIDTH = "app.main.dimension.w";
|
||||
@@ -74,6 +47,7 @@ public class MainFrame extends JFrame {
|
||||
private static final String BAD_POLICY = "<policy domain=\"coder\" rights=\"none\" pattern=\"PDF\" />";
|
||||
|
||||
private static final String POLICY_FILE = "/etc/ImageMagick-6/policy.xml";
|
||||
private final Worker worker;
|
||||
|
||||
private StatusBar statusBar;
|
||||
private Toolbar toolbar;
|
||||
@@ -89,21 +63,12 @@ public class MainFrame extends JFrame {
|
||||
private String patchedPath = "";
|
||||
private String project = "";
|
||||
|
||||
private Configuration config;
|
||||
|
||||
private String mode = "Color";
|
||||
|
||||
private DocTable docTable;
|
||||
|
||||
private Dimension dimension = new Dimension(209, 297);
|
||||
|
||||
private SwingController pdfViewer;
|
||||
|
||||
private JLabel fileName;
|
||||
|
||||
private boolean isScanning = false;
|
||||
|
||||
private JComponent previewComponent = null;
|
||||
|
||||
private JPanel pdfPreview;
|
||||
|
||||
@@ -113,55 +78,28 @@ public class MainFrame extends JFrame {
|
||||
*/
|
||||
public MainFrame(Configuration config) {
|
||||
super("BelegScanner");
|
||||
this.config = config;
|
||||
int width = config.getOrCreate(APP_WIDTH,800);
|
||||
int height = config.getOrCreate(APP_HEIGHT,600);
|
||||
|
||||
BorderLayout layout = new BorderLayout();
|
||||
setLayout(layout);
|
||||
|
||||
toolbar = new Toolbar(config);
|
||||
statusBar = new StatusBar();
|
||||
docTable = new DocTable();
|
||||
|
||||
add(toolbar,BorderLayout.EAST);
|
||||
add(statusBar,BorderLayout.SOUTH);
|
||||
add(docTable,BorderLayout.NORTH);
|
||||
pdfPreview = preview();
|
||||
add(previewComponent = pdfPreview,BorderLayout.CENTER);
|
||||
|
||||
toolbar.addCategoryListener(this::setCategory)
|
||||
.addDateListener(this::setDate)
|
||||
.addDimensionListener(this::setDimension)
|
||||
.addFieldListener(this::setField)
|
||||
.addPathListener(this::setPath)
|
||||
.addScanListener(this::scan);
|
||||
|
||||
docTable.addPreviewListener(this::preview);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
worker = new Worker(config);
|
||||
|
||||
add(toolbar = new Toolbar(worker),BorderLayout.EAST);
|
||||
add(statusBar = new StatusBar(worker),BorderLayout.SOUTH);
|
||||
add(docTable = new DocTable(worker),BorderLayout.NORTH);
|
||||
add(pdfPreview = new Preview(worker),BorderLayout.CENTER);
|
||||
|
||||
int x = config.getOrCreate(APP_X, 20);
|
||||
int y = config.getOrCreate(APP_Y, 20);
|
||||
setLocation(new Point(x, y));
|
||||
|
||||
checkScanButton();
|
||||
|
||||
|
||||
int width = config.getOrCreate(APP_WIDTH,800);
|
||||
int height = config.getOrCreate(APP_HEIGHT,600);
|
||||
setPreferredSize(new Dimension(width,height));
|
||||
|
||||
pack();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Scans the given path for occurences of '$VARIABLES' and creates input fields for the found variables.
|
||||
* @param path
|
||||
*/
|
||||
private void addFieldsForPath(String path) {
|
||||
Vector<String> marks = new Vector<>();
|
||||
Matcher matches = MARKEN.matcher(path);
|
||||
while (matches.find()) marks.add(matches.group(1));
|
||||
toolbar.addFieldsFor(marks);
|
||||
};
|
||||
|
||||
public String appMissing(String appTest){
|
||||
List<String> cmd = Arrays.asList(appTest.split(" "));
|
||||
@@ -242,17 +180,6 @@ public class MainFrame extends JFrame {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* checks, whether the scan button may be enabled.
|
||||
*/
|
||||
private void checkScanButton() {
|
||||
toolbar.readyToScan(!(isScanning || patchedPath==null || patchedPath.isBlank() || patchedPath.contains("$")));
|
||||
}
|
||||
|
||||
private String extension(String path) {
|
||||
var target = config.get(CONFIG_TARGET);
|
||||
return path +"."+ (JPG.equals(target) ? "jpg":"pdf");
|
||||
}
|
||||
|
||||
|
||||
private void notifyAppsMissing(List<String> missingApps) {
|
||||
@@ -269,357 +196,4 @@ public class MainFrame extends JFrame {
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* scan a new document, convert it to pdf and perform text recognition
|
||||
* @param path
|
||||
* @param dimension
|
||||
*/
|
||||
private void performScan(String project, String path, Dimension dimension) {
|
||||
LOG.debug("performScan({})",path);
|
||||
/* We need to save all required config values before starting to scan, because they might be changed during scanning */
|
||||
String target = config.getOrCreate(CONFIG_TARGET, "pdf");
|
||||
setScanning(true);
|
||||
|
||||
var targetFile = new File(path);
|
||||
File folder = targetFile.getParentFile();
|
||||
if (!folder.exists()) {
|
||||
LOG.warn(t("Path '{}' does not exist!"),path);
|
||||
folder.mkdirs();
|
||||
}
|
||||
|
||||
docTable.add(project);
|
||||
|
||||
int resolution = toolbar.getResolution();
|
||||
boolean improbeBrightness = toolbar.getImproveBrightness();
|
||||
//long timestamp = new Date().getTime();
|
||||
//String raw = timestamp+".jpg";
|
||||
var raw = targetFile.getName()+".jpg";
|
||||
|
||||
Vector<String> cmd = new Vector<>();
|
||||
cmd.add("scanimage");
|
||||
cmd.add("-x");
|
||||
cmd.add(dimension.width+"");
|
||||
cmd.add("-y");
|
||||
cmd.add(dimension.height+"");
|
||||
cmd.add("--mode");
|
||||
cmd.add(mode);
|
||||
cmd.add("--resolution");
|
||||
cmd.add(resolution+"");
|
||||
cmd.add("-o");
|
||||
cmd.add(raw);
|
||||
LOG.debug("executing {}",cmd);
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.directory(folder);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
docTable.setState(project,t("Scanning…"));
|
||||
int errorCode = process.waitFor();
|
||||
if (errorCode != 0) {
|
||||
LOG.error(t("Error code: {} for {}"),errorCode,cmd);
|
||||
docTable.setState(project, t("Scan failed."));
|
||||
setScanning(false);
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error(t("{} terminated: "),builder,e);
|
||||
}
|
||||
setScanning(false);
|
||||
toolbar.nextPage();
|
||||
|
||||
if (improbeBrightness) {
|
||||
cmd = new Vector<>();
|
||||
cmd.add("mogrify");
|
||||
cmd.add("-contrast-stretch");
|
||||
cmd.add("0x60%");
|
||||
cmd.add(raw);
|
||||
builder = new ProcessBuilder(cmd);
|
||||
builder.directory(folder);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
docTable.setState(project,t("Adjusting brightness…"));
|
||||
int errorCode = process.waitFor();
|
||||
if (errorCode != 0) {
|
||||
LOG.error(t("Error code: {} for {}"),errorCode,cmd);
|
||||
docTable.setState(project, t("Adjustment failed."));
|
||||
setScanning(false);
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error(t("{} terminated: "),builder,e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (JPG.equals(target)) {
|
||||
docTable.setState(project, t("Image scanned"));
|
||||
docTable.preview(folder);
|
||||
return;
|
||||
};
|
||||
|
||||
String pdf = targetFile.getName() + (PDF.equals(target) ? ".pdf" : ".tmp.pdf"); //timestamp+".page.pdf";
|
||||
cmd = new Vector<>();
|
||||
cmd.add("convert");
|
||||
cmd.add(raw);
|
||||
cmd.add(pdf);
|
||||
builder = new ProcessBuilder(cmd);
|
||||
builder.directory(folder);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
docTable.setState(project,t("Converting to PDF…"));
|
||||
int errorCode = process.waitFor();
|
||||
if (errorCode != 0) {
|
||||
LOG.error("Error code: {} for {}",errorCode,cmd);
|
||||
docTable.setState(project, t("Conversion failed."));
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error("{} terminated: ",builder,e);
|
||||
}
|
||||
LOG.debug("removing {}",raw);
|
||||
folder.toPath().resolve(raw).toFile().delete();
|
||||
//Path.of(folder,raw).toFile().delete();
|
||||
if (PDF.equals(target)) {
|
||||
docTable.setState(project, t("PDF created"));
|
||||
docTable.preview(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
String ocr = targetFile.getName() + ".pdf";//timestamp+".page.ocr.pdf";
|
||||
|
||||
cmd = new Vector<>();
|
||||
cmd.add("pdfsandwich");
|
||||
cmd.add("-lang");
|
||||
cmd.add("deu");
|
||||
cmd.add("-rgb");
|
||||
if (resolution>300) {
|
||||
cmd.add("-resolution");
|
||||
cmd.add(""+resolution);
|
||||
}
|
||||
cmd.add("-o");
|
||||
cmd.add(ocr);
|
||||
cmd.add(pdf);
|
||||
LOG.debug("executing {}",cmd);
|
||||
builder = new ProcessBuilder(cmd);
|
||||
builder.directory(folder);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
docTable.setState(project,t("Text recognition…"));
|
||||
int errorCode = process.waitFor();
|
||||
if (errorCode != 0) {
|
||||
LOG.error("error code: {} for {}",errorCode,cmd);
|
||||
docTable.setState(project, t("OCR failed."));
|
||||
return;
|
||||
}
|
||||
if (!folder.toPath().resolve(ocr).toFile().exists()){
|
||||
docTable.setState(project,t("OCR failed."));
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error("{} terminated: ",builder,e);
|
||||
}
|
||||
|
||||
LOG.debug("removing {}",pdf);
|
||||
folder.toPath().resolve(pdf).toFile().delete();
|
||||
docTable.setState(project,t("Finished text recognition."));
|
||||
docTable.preview(folder);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* create preview panel
|
||||
* @return
|
||||
*/
|
||||
private JPanel preview() {
|
||||
// https://stackoverflow.com/a/48248739/1285585
|
||||
JPanel pane = new JPanel(new BorderLayout());
|
||||
|
||||
pdfViewer = new SwingController();
|
||||
SwingViewBuilder factory = new SwingViewBuilder(pdfViewer);
|
||||
|
||||
JPanel viewerComponentPanel = factory.buildViewerPanel();
|
||||
|
||||
ComponentKeyBinding.install(pdfViewer, viewerComponentPanel);
|
||||
// add interactive mouse link annotation support via callback
|
||||
pdfViewer.getDocumentViewController().setAnnotationCallback(new org.icepdf.ri.common.MyAnnotationCallback(pdfViewer.getDocumentViewController()));
|
||||
|
||||
pane.add(viewerComponentPanel,BorderLayout.CENTER);
|
||||
pane.setBorder(BorderFactory.createLineBorder(Color.black));
|
||||
|
||||
pane.add(fileName = new JLabel(),BorderLayout.SOUTH);
|
||||
fileName.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
/**
|
||||
* open file in preview panel
|
||||
* @param filePath
|
||||
*/
|
||||
private void preview(Path filePath) {
|
||||
if (filePath == null) return;
|
||||
String file = filePath.toString();
|
||||
if (file.toLowerCase().endsWith("pdf")) {
|
||||
if (previewComponent != pdfPreview) {
|
||||
remove(previewComponent);
|
||||
previewComponent = pdfPreview;
|
||||
add(previewComponent,BorderLayout.CENTER);
|
||||
pack();
|
||||
}
|
||||
pdfViewer.openDocument(file);
|
||||
pdfViewer.setPageFitMode(DocumentViewController.PAGE_FIT_WINDOW_HEIGHT, false);
|
||||
}
|
||||
if (file.toLowerCase().endsWith("jpg")) {
|
||||
try {
|
||||
Image img = ImageIO.read(new File(file));
|
||||
Dimension dim = previewComponent.getSize();
|
||||
double w = img.getWidth(null);
|
||||
double h = img.getHeight(null);
|
||||
double factor = Math.min(dim.width/w, dim.height/h);
|
||||
if (factor != 1.0) img = img.getScaledInstance((int)(w*factor), (int)(h*factor), Image.SCALE_DEFAULT);
|
||||
ImageIcon icon = new ImageIcon(img);
|
||||
JLabel label = new JLabel(icon);
|
||||
this.remove(previewComponent);
|
||||
previewComponent = label;
|
||||
this.add(previewComponent, BorderLayout.CENTER);
|
||||
pack();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Was not able to preview {}:",file,e);
|
||||
}
|
||||
}
|
||||
fileName.setText(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* save config, then start scannin in separate thread
|
||||
* @param ev
|
||||
*/
|
||||
private void scan(ActionEvent ev) {
|
||||
updateConfig();
|
||||
new Thread(() -> performScan(project,patchedPath,dimension)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* update the category currently used
|
||||
* @param category
|
||||
*/
|
||||
private void setCategory(String category) {
|
||||
this.category = category;
|
||||
updatePath();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* update the date currently used
|
||||
* @param date
|
||||
*/
|
||||
private void setDate(Date date) {
|
||||
this.date = date;
|
||||
updatePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* update the scan dimensions currently used
|
||||
* @param dim
|
||||
*/
|
||||
private void setDimension(Dimension dim) {
|
||||
this.dimension = dim;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* update the value of a variable field
|
||||
* @param key
|
||||
* @param val
|
||||
*/
|
||||
private void setField(String key, String val) {
|
||||
if (val == null || val.isEmpty()) {
|
||||
fields.remove(key);
|
||||
} else fields.put(key, val);
|
||||
updatePath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* update the path currently used as scan target directory
|
||||
* @param path
|
||||
*/
|
||||
private void setPath(String path) {
|
||||
this.path = path;
|
||||
updatePath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* toggle scanning state
|
||||
* @param scanning
|
||||
*/
|
||||
private void setScanning(boolean scanning) {
|
||||
isScanning = scanning;
|
||||
checkScanButton();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* push current settings to the configuration, then save it.
|
||||
*/
|
||||
private void updateConfig() {
|
||||
|
||||
String prefix = "app.categories."+category+".";
|
||||
for (Entry<String, String> entry : fields.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String val = entry.getValue();
|
||||
JSONArray arr = config.getOrCreateArray(prefix+"fields."+key);
|
||||
HashSet<Object> existing = new HashSet<>();
|
||||
arr.forEach(existing::add);
|
||||
if (!existing.contains(val)) arr.put(val);
|
||||
}
|
||||
config.set(prefix+"path",path);
|
||||
config.set(prefix+"size.w", dimension.width);
|
||||
config.set(prefix+"size.h", dimension.height);
|
||||
|
||||
|
||||
Point loc = getLocation();
|
||||
config.set(APP_X, loc.x);
|
||||
config.set(APP_Y, loc.y);
|
||||
Dimension dim = getSize();
|
||||
config.set(APP_WIDTH, dim.width);
|
||||
config.set(APP_HEIGHT, dim.height);
|
||||
try {
|
||||
config.save();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Was not able to save config!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* compile path, variable values, date to a patched path
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updatePath() {
|
||||
LOG.debug("updatePath() [path = {}]",path);
|
||||
int year = 1900+date.getYear();
|
||||
int month = date.getMonth()+1;
|
||||
int day = date.getDate();
|
||||
patchedPath = path.replace("$"+t("HOME"),HOME)
|
||||
.replace("$"+t("CATEGORY"), category)
|
||||
.replace("$"+t("YEAR"), year+"")
|
||||
.replace("$"+t("MONTH"), month<10 ? "0"+month : ""+month)
|
||||
.replace("$"+t("DAY"), day < 10 ? "0"+day : ""+day);
|
||||
addFieldsForPath(patchedPath);
|
||||
project = extension(patchedPath);
|
||||
for (Entry<String, String> entry : fields.entrySet()) {
|
||||
var key = entry.getKey();
|
||||
patchedPath = patchedPath.replace("$"+entry.getKey(), entry.getValue());
|
||||
if (!t("PAGE").equals(key)){
|
||||
project = project.replace("$"+entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
statusBar.setPath(extension(patchedPath));
|
||||
checkScanButton();
|
||||
}
|
||||
}
|
||||
|
||||
34
src/main/java/de/srsoftware/belegscanner/gui/Preview.java
Normal file
34
src/main/java/de/srsoftware/belegscanner/gui/Preview.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import org.icepdf.ri.common.ComponentKeyBinding;
|
||||
import org.icepdf.ri.common.SwingController;
|
||||
import org.icepdf.ri.common.SwingViewBuilder;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Preview extends JPanel {
|
||||
private final SwingController pdfViewer;
|
||||
private final JLabel fileName;
|
||||
|
||||
Preview(Worker worker){
|
||||
// https://stackoverflow.com/a/48248739/1285585
|
||||
super(new BorderLayout());
|
||||
|
||||
pdfViewer = new SwingController();
|
||||
SwingViewBuilder factory = new SwingViewBuilder(pdfViewer);
|
||||
|
||||
JPanel viewerComponentPanel = factory.buildViewerPanel();
|
||||
|
||||
ComponentKeyBinding.install(pdfViewer, viewerComponentPanel);
|
||||
// add interactive mouse link annotation support via callback
|
||||
pdfViewer.getDocumentViewController().setAnnotationCallback(new org.icepdf.ri.common.MyAnnotationCallback(pdfViewer.getDocumentViewController()));
|
||||
|
||||
add(viewerComponentPanel,BorderLayout.CENTER);
|
||||
setBorder(BorderFactory.createLineBorder(Color.black));
|
||||
|
||||
add(fileName = new JLabel(),BorderLayout.SOUTH);
|
||||
fileName.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static java.awt.BorderLayout.CENTER;
|
||||
import static java.awt.BorderLayout.NORTH;
|
||||
import static java.awt.BorderLayout.WEST;
|
||||
|
||||
public class ResolutionSelector extends JPanel{
|
||||
|
||||
private int resolution = 150;
|
||||
|
||||
public ResolutionSelector() {
|
||||
super(new BorderLayout());
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
JLabel label = new JLabel(t("current resolution") + ": " + resolution + "px");
|
||||
add(label, NORTH);
|
||||
add(resolutionButton(group, label, 150), WEST);
|
||||
add(resolutionButton(group, label, 300), CENTER);
|
||||
add(resolutionButton(group, label, 600), BorderLayout.EAST);
|
||||
setMaximumSize(new Dimension(600, 200));
|
||||
}
|
||||
|
||||
public int resolutiion(){
|
||||
return resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* create one radio button for the resoultionSelector
|
||||
* @param group
|
||||
* @param label
|
||||
* @param i
|
||||
* @return
|
||||
*/
|
||||
private JRadioButton resolutionButton(ButtonGroup group, JLabel label, int i) {
|
||||
JRadioButton btn = new JRadioButton(i+" px");
|
||||
btn.addActionListener(ev -> {
|
||||
resolution = i;
|
||||
label.setText("aktuelle Auflösung: "+resolution+"px");
|
||||
});
|
||||
group.add(btn);
|
||||
if (i==150) btn.setSelected(true);
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,7 +30,7 @@ public class StatusBar extends JPanel {
|
||||
private static final long serialVersionUID = 8102800846089594705L;
|
||||
private JLabel path;
|
||||
|
||||
public StatusBar() {
|
||||
public StatusBar(Worker worker) {
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
add(path = new JLabel("Kein Pfad gewält."));
|
||||
path.setBorder(new EmptyBorder(5,5,5,5));
|
||||
@@ -40,8 +41,9 @@ public class StatusBar extends JPanel {
|
||||
openDir();
|
||||
}
|
||||
});
|
||||
worker.setStatusBar(this);
|
||||
}
|
||||
|
||||
|
||||
protected void openDir() {
|
||||
try {
|
||||
File dir = new File(path.getText());
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.sql.Struct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import de.srsoftware.belegscanner.Constants;
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.srsoftware.belegscanner.Configuration;
|
||||
import de.srsoftware.belegscanner.Constants;
|
||||
import de.srsoftware.tools.gui.DateChooser;
|
||||
import de.srsoftware.tools.gui.SelectComboBox;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.*;
|
||||
import static de.srsoftware.belegscanner.Constants.*;
|
||||
import static java.awt.BorderLayout.*;
|
||||
|
||||
/**
|
||||
* create the application's toolbar
|
||||
@@ -38,209 +30,59 @@ import static java.awt.BorderLayout.*;
|
||||
*
|
||||
*/
|
||||
public class Toolbar extends JPanel {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Toolbar.class);
|
||||
|
||||
public interface CategoryListener{
|
||||
public void setCategory(String category);
|
||||
}
|
||||
|
||||
public interface DateListener{
|
||||
public void setDate(Date date);
|
||||
}
|
||||
|
||||
public interface DimensionListener{
|
||||
public void setDimension(Dimension dimension);
|
||||
}
|
||||
|
||||
|
||||
public interface FieldListener{
|
||||
public void setField(String name, String value);
|
||||
}
|
||||
|
||||
public interface PathListener{
|
||||
public void setPath(String path);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -5834326573752788233L;
|
||||
private final SelectComboBox pathPicker, catPicker;
|
||||
private static final int OFFSET = 2;
|
||||
|
||||
|
||||
|
||||
private class AdditionalInput {
|
||||
private final JPanel panel;
|
||||
private final SelectComboBox input;
|
||||
private boolean required;
|
||||
|
||||
AdditionalInput(JPanel panel, SelectComboBox input, boolean required){
|
||||
this.panel = panel;
|
||||
this.input = input;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public void notRequired () {
|
||||
required = false;
|
||||
}
|
||||
}
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Toolbar.class);
|
||||
private final Worker worker;
|
||||
private static final long serialVersionUID = -5834326573752788233L;
|
||||
private static EmptyBorder BORDER = new EmptyBorder(0,5,5,5);
|
||||
|
||||
private static Map<String, AdditionalInput> additionalInputs = new HashMap<>();
|
||||
|
||||
private Vector<Object> paths = new Vector<>();
|
||||
|
||||
private HashMap<String,JPanel> additonalComponents = new HashMap<>();
|
||||
private HashSet<CategoryListener> categoryListeners = new HashSet<>();
|
||||
private HashSet<DateListener> dateListeners = new HashSet<>();
|
||||
private HashSet<FieldListener> fieldListeners = new HashSet<>();
|
||||
private HashSet<PathListener> pathListeners = new HashSet<>();
|
||||
private HashSet<ActionListener> scanListeners = new HashSet<>();
|
||||
private HashSet<DimensionListener> dimensionListeners = new HashSet<>();
|
||||
|
||||
private DateChooser date;
|
||||
|
||||
private Configuration config;
|
||||
|
||||
private SelectComboBox pathPicker;
|
||||
|
||||
private JButton scanButton;
|
||||
|
||||
private JTextField width;
|
||||
private JTextField height;
|
||||
private SelectComboBox catPicker;
|
||||
private int resolution = 150;
|
||||
private boolean improveBrightness = false;
|
||||
/**
|
||||
* create toolbar
|
||||
* @param config
|
||||
*/
|
||||
public Toolbar(Configuration config) {
|
||||
this.config = config;
|
||||
public Toolbar(Worker worker) {
|
||||
this.worker = worker;
|
||||
worker.setToolbar(this);
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
add(datePicker());
|
||||
add(input(t("Category"),catPicker = categoryPicker(config)));
|
||||
add(input(t("Category"),catPicker = categoryPicker()));
|
||||
add(input(t("Path"),pathPicker = pathPicker()));
|
||||
addFormatSelector();
|
||||
add(resolutionSelector());
|
||||
add(improveSelector());
|
||||
add(typeSelector());
|
||||
add(new FormatSelector(worker));
|
||||
add(new ResolutionSelector());
|
||||
add(new ImproveSelector());
|
||||
add(new TypeSelector(worker));
|
||||
add(scanButton());
|
||||
add(Box.createGlue());
|
||||
Arrays.stream(getComponents()).filter(c -> c instanceof JPanel).map(JPanel.class::cast).forEach(p -> p.setBorder(BORDER));
|
||||
}
|
||||
|
||||
/**
|
||||
* add a listener for updates of the categoriy field
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addCategoryListener(CategoryListener listener) {
|
||||
categoryListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a listener for updates of the date selector
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addDateListener(DateListener listener) {
|
||||
dateListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a listener for changes to the dimension form
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addDimensionListener(DimensionListener listener) {
|
||||
dimensionListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add an input field for a variable placeholder
|
||||
* @param marks
|
||||
*/
|
||||
public void addFieldsFor(Vector<String> marks) {
|
||||
LOG.debug("addFieldsFor({})",marks);
|
||||
|
||||
List<String> deleted = additonalComponents.keySet().stream().filter(key -> !marks.contains(key)).collect(Collectors.toList());
|
||||
deleted.forEach(name -> remove(additonalComponents.remove(name)));
|
||||
|
||||
for (String name : marks) {
|
||||
if (additonalComponents.containsKey(name)) continue;
|
||||
SelectComboBox valuePicker = new SelectComboBox(new Vector<>()).onUpdateText(newText -> updateField(name,newText));
|
||||
JPanel input = input(name, valuePicker);
|
||||
if (t("PAGE").equals(name)) {
|
||||
valuePicker.setText("1");
|
||||
SwingUtilities.invokeLater(() -> updateField(name,"1"));
|
||||
}
|
||||
input.setBorder(BORDER);
|
||||
add(input,getComponentCount()-OFFSET);
|
||||
additonalComponents.put(name, input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a form that enables the user to set the size of the scanning area
|
||||
*/
|
||||
private void addFormatSelector() {
|
||||
JPanel dummy = new JPanel();
|
||||
dummy.setLayout(new BorderLayout());
|
||||
dummy.setMaximumSize(new Dimension(600, 40));
|
||||
|
||||
dummy.add(new JLabel(t("Width")+": "), WEST);
|
||||
dummy.add(width = new JTextField(209+""), CENTER);
|
||||
dummy.add(new JLabel("px"),BorderLayout.EAST);
|
||||
add(dummy);
|
||||
|
||||
dummy = new JPanel();
|
||||
dummy.setLayout(new BorderLayout());
|
||||
dummy.setMaximumSize(new Dimension(600, 40));
|
||||
|
||||
dummy.add(new JLabel(t("Height")+": "), WEST);
|
||||
dummy.add(height = new JTextField(297+""), CENTER);
|
||||
dummy.add(new JLabel("px"),BorderLayout.EAST);
|
||||
add(dummy);
|
||||
|
||||
KeyAdapter dimensionListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
updateDimensions();
|
||||
}
|
||||
};
|
||||
|
||||
width.addKeyListener(dimensionListener);
|
||||
height.addKeyListener(dimensionListener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* add a listener for updates of the variable fields
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addFieldListener(FieldListener listener) {
|
||||
fieldListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a listener for updates of the target path
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addPathListener(PathListener listener){
|
||||
pathListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a listener for the scan button
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public Toolbar addScanListener(ActionListener listener) {
|
||||
scanListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* create a auto complete text field for the category selection
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private SelectComboBox categoryPicker(Configuration config) {
|
||||
JSONObject elements = config.getOrCreate("app.categories",new JSONObject());
|
||||
return new SelectComboBox(elements.keySet())
|
||||
.onUpdateText(this::updateCat)
|
||||
.onDelete(this::dropCategory);
|
||||
private SelectComboBox categoryPicker() {
|
||||
return new SelectComboBox(worker.getCategories().keySet()).onUpdateText(worker::setCat).onDelete(worker::dropCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,95 +93,20 @@ public class Toolbar extends JPanel {
|
||||
date = new DateChooser();
|
||||
Dimension dim = new Dimension(400, 250);
|
||||
date.setMaximumSize(dim);
|
||||
date.addActionListener(ev -> {
|
||||
resetPage();
|
||||
dateListeners.forEach(listener -> listener.setDate(date.getSelectedDate()));
|
||||
});
|
||||
worker.setDatePicker(date);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a value (by key) from a JSONArray
|
||||
* @param arr
|
||||
* @param tx
|
||||
* @return
|
||||
*/
|
||||
private List<Object> deleteValue(JSONArray arr, String tx) {
|
||||
for (int i = 0; i<arr.length(); i++) {
|
||||
if (arr.get(i).equals(tx)) {
|
||||
arr.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return arr.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* drop a category from the category pickers list of known categories
|
||||
* @param catName
|
||||
*/
|
||||
private void dropCategory(String catName) {
|
||||
LOG.debug("dropCategory({})",catName);
|
||||
Object o = config.get("app.categories");
|
||||
if (o instanceof JSONObject) {
|
||||
JSONObject json = (JSONObject)o;
|
||||
json.remove(catName);
|
||||
catPicker.setElements(json.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get date from the date picker
|
||||
* @return
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date.getSelectedDate();
|
||||
|
||||
public void dropUnusedAdditionalFields() {
|
||||
additionalInputs.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !entry.getValue().required)
|
||||
.map(Map.Entry::getValue)
|
||||
.map(ai -> ai.panel)
|
||||
.forEach(this::remove);
|
||||
}
|
||||
|
||||
|
||||
public boolean getImproveBrightness(){
|
||||
return improveBrightness;
|
||||
}
|
||||
/**
|
||||
* get resolution (set by resoultion form)
|
||||
* @return
|
||||
*/
|
||||
public int getResolution() {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* helper: returns the selector added as the second component of a JPanel
|
||||
* @param panel
|
||||
* @return null, of the panel has less than two children or the second child is no selector
|
||||
*/
|
||||
public SelectComboBox getSelector(JPanel panel) {
|
||||
if (panel != null && panel.getComponentCount()>1) {
|
||||
Component child = panel.getComponent(1);
|
||||
if (child instanceof SelectComboBox) return (SelectComboBox) child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set of checkboxes for adjustments
|
||||
* @return
|
||||
*/
|
||||
private JPanel improveSelector() {
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
JPanel adjustmentSelector = new JPanel(new BorderLayout());
|
||||
adjustmentSelector.add(new JLabel(t("Adjustments")), NORTH);
|
||||
var checkbox = new JCheckBox(t("Improve brightness"));
|
||||
adjustmentSelector.add(checkbox,WEST);
|
||||
adjustmentSelector.setMaximumSize(new Dimension(600,200));
|
||||
checkbox.addActionListener(e -> {
|
||||
improveBrightness = checkbox.isSelected();
|
||||
});
|
||||
return adjustmentSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a labeled input field
|
||||
* @param caption
|
||||
@@ -355,222 +122,58 @@ public class Toolbar extends JPanel {
|
||||
panel.add(component);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public Toolbar resetPage(){
|
||||
var page = t("PAGE");
|
||||
var panel = additonalComponents.get(page);
|
||||
if (panel == null) return this;
|
||||
for (var child : panel.getComponents()){
|
||||
if (child instanceof SelectComboBox scb){
|
||||
scb.setText("1");
|
||||
fieldListeners.forEach(l -> l.setField(page,"1"));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Toolbar nextPage(){
|
||||
var page = t("PAGE");
|
||||
var panel = additonalComponents.get(page);
|
||||
if (panel == null) return this;
|
||||
for (var child : panel.getComponents()){
|
||||
if (child instanceof SelectComboBox scb){
|
||||
var val = scb.getText();
|
||||
try {
|
||||
var newVal = ""+(Integer.parseInt(val)+1);
|
||||
scb.setText(newVal);
|
||||
fieldListeners.forEach(l -> l.setField(page,newVal));
|
||||
} catch (NumberFormatException nfe){}
|
||||
}
|
||||
LOG.debug("component: {}",child);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates an auto-complete text field for setting the scan target path
|
||||
* @return
|
||||
*/
|
||||
private SelectComboBox pathPicker() {
|
||||
JSONObject cats = config.getOrCreate("app.categories",new JSONObject());
|
||||
JSONObject cats = worker.getCategories();
|
||||
for (String catName : cats.keySet()) {
|
||||
JSONObject catInfo = cats.getJSONObject(catName);
|
||||
if (catInfo.has("path")) paths.add(catInfo.get("path"));
|
||||
}
|
||||
paths.add("$"+t("HOME")+"/$"+t("YEAR")+"/$"+t("MONTH")+"-$"+t("DAY")+" - $"+t("CATEGORY")+"/$"+t("PAGE"));
|
||||
return new SelectComboBox(paths).onUpdateText(this::updatePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* enables/disables the toolbars scan button
|
||||
* @param val
|
||||
*/
|
||||
public void readyToScan(boolean val) {
|
||||
scanButton.setEnabled(val);
|
||||
}
|
||||
/**
|
||||
* create one radio button for the resoultionSelector
|
||||
* @param group
|
||||
* @param label
|
||||
* @param i
|
||||
* @return
|
||||
*/
|
||||
private JRadioButton resolutionButton(ButtonGroup group, JLabel label, int i) {
|
||||
JRadioButton btn = new JRadioButton(i+" px");
|
||||
btn.addActionListener(ev -> {
|
||||
resolution = i;
|
||||
label.setText("aktuelle Auflösung: "+resolution+"px");
|
||||
});
|
||||
group.add(btn);
|
||||
if (i==150) btn.setSelected(true);
|
||||
return btn;
|
||||
return worker.setPathPicker(new SelectComboBox(paths));
|
||||
}
|
||||
|
||||
/**
|
||||
* create a radio group to select the resolution for scanning
|
||||
* @return
|
||||
*/
|
||||
private JPanel resolutionSelector() {
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
JPanel resolutionSelector = new JPanel(new BorderLayout());
|
||||
JLabel label = new JLabel(t("current resolution")+": "+resolution+"px");
|
||||
resolutionSelector.add(label, NORTH);
|
||||
resolutionSelector.add(resolutionButton(group,label,150), WEST);
|
||||
resolutionSelector.add(resolutionButton(group,label,300), CENTER);
|
||||
resolutionSelector.add(resolutionButton(group,label,600),BorderLayout.EAST);
|
||||
resolutionSelector.setMaximumSize(new Dimension(600,200));
|
||||
return resolutionSelector;
|
||||
|
||||
public String requireField(String varName){
|
||||
LOG.debug("requireField({})",varName);
|
||||
var additionalInput = additionalInputs.get(varName);
|
||||
if (additionalInput == null) {
|
||||
LOG.debug("Field missing, creating…");
|
||||
var input = new SelectComboBox(new Vector<>());
|
||||
if (varName.equals(t(Constants.PAGE))){
|
||||
return "["+varName+"]";
|
||||
}
|
||||
input.onUpdateText(newText -> worker.resolvePath());
|
||||
JPanel panel = input(varName,input);
|
||||
additionalInputs.put(varName, additionalInput = new AdditionalInput(panel,input,true));
|
||||
input.setBorder(BORDER);
|
||||
add(panel,getComponentCount()-OFFSET);
|
||||
} else {
|
||||
LOG.debug("Field preset, flagging as required");
|
||||
additionalInput.required = true;
|
||||
if (!Arrays.asList(getComponents()).contains(additionalInput.panel)){
|
||||
add(additionalInput.panel,getComponentCount()-OFFSET);
|
||||
}
|
||||
}
|
||||
return additionalInput.input.getText();
|
||||
}
|
||||
|
||||
|
||||
public void resetAdditionalFields() {
|
||||
additionalInputs.values().forEach(AdditionalInput::notRequired);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create scan button
|
||||
* @return
|
||||
*/
|
||||
private Component scanButton() {
|
||||
scanButton = new JButton(t("scan!"));
|
||||
scanButton.addActionListener(this::scanPressed);
|
||||
scanButton.addActionListener(ev -> worker.scan());
|
||||
return scanButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* notify listeners when scan button clicked
|
||||
* @param evt
|
||||
*/
|
||||
private void scanPressed(ActionEvent evt) {
|
||||
scanListeners.forEach(l->l.actionPerformed(evt));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create one radio button for the typeSelector
|
||||
* @param group
|
||||
* @param label
|
||||
* @param selection
|
||||
* @return
|
||||
*/
|
||||
private JRadioButton typeButton(ButtonGroup group, String label,String selection) {
|
||||
JRadioButton btn = new JRadioButton(label);
|
||||
group.add(btn);
|
||||
btn.addActionListener(ev -> config.set(CONFIG_TARGET, label));
|
||||
if (label.equals(selection)) btn.setSelected(true);
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a radio group to select the target type for scanning
|
||||
* @return
|
||||
*/
|
||||
private JPanel typeSelector() {
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
JPanel typeSelector = new JPanel(new BorderLayout());
|
||||
JLabel label = new JLabel(t("Target type"));
|
||||
String target = config.getOrCreate(CONFIG_TARGET, PDF);
|
||||
typeSelector.add(label, NORTH);
|
||||
typeSelector.add(typeButton(group,t(JPG),target), WEST);
|
||||
typeSelector.add(typeButton(group,t(PDF),target), CENTER);
|
||||
typeSelector.add(typeButton(group,t(OCR),target),BorderLayout.EAST);
|
||||
typeSelector.setMaximumSize(new Dimension(600,200));
|
||||
return typeSelector;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* category updated: notify listeners and update dependent fields
|
||||
* @param newCat
|
||||
*/
|
||||
private void updateCat(String newCat) {
|
||||
LOG.debug("updateCat({})",newCat);
|
||||
resetPage();
|
||||
categoryListeners.forEach(l -> l.setCategory(newCat));
|
||||
LOG.debug("Listeners notified.");
|
||||
if (!newCat.isEmpty()) {
|
||||
String prefix = "app.categories."+newCat+".";
|
||||
String path = config.get(prefix+"path");
|
||||
if (path != null) {
|
||||
pathPicker.setSelectedItem(path);
|
||||
updatePath(path);
|
||||
}
|
||||
|
||||
JSONObject fields = config.get(prefix+"fields");
|
||||
if (fields != null) {
|
||||
LOG.debug("fields: {}",fields);
|
||||
for (String fieldName : fields.keySet()) {
|
||||
path = prefix+"fields."+fieldName;
|
||||
|
||||
JSONArray arr = config.getOrCreateArray(path);
|
||||
SelectComboBox selector = getSelector(additonalComponents.get(fieldName));
|
||||
if (selector != null) selector.setElements(arr.toList()).onDelete(tx -> selector.setElements(deleteValue(arr,tx)));
|
||||
}
|
||||
}
|
||||
LOG.debug("Trying to read size");
|
||||
Integer w = config.get(prefix+"size.w");
|
||||
LOG.debug("w: {}",w);
|
||||
width.setText(w == null ? Constants.DEFAULT_WIDTH : w+"");
|
||||
Integer h = config.get(prefix+"size.h");
|
||||
LOG.debug("h: {}",h);
|
||||
height.setText(h == null ? Constants.DEFAULT_HEIGHT : h+"");
|
||||
updateDimensions();
|
||||
} else LOG.debug("newCat is empty!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* read new dimension from dimension form, notify listeners
|
||||
*/
|
||||
protected void updateDimensions() {
|
||||
LOG.debug("updateDimensions()");
|
||||
try {
|
||||
int w = Integer.parseInt(width.getText().trim());
|
||||
int h = Integer.parseInt(height.getText().trim());
|
||||
dimensionListeners.forEach(listener -> listener.setDimension(new Dimension(w < 0 ? 0 : w, h < 0 ? 0 : h)));
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warn("Invalid dimensions!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* notify listeners of changes to the variable field's values
|
||||
* @param name
|
||||
* @param val
|
||||
*/
|
||||
private void updateField(String name, String val) {
|
||||
LOG.debug("updateField({},{})",name,val);
|
||||
fieldListeners.forEach(l -> l.setField(name,val));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* notify listeners of changes to the path field
|
||||
* @param newPath
|
||||
*/
|
||||
protected void updatePath(String newPath) {
|
||||
LOG.debug("updatePath({})",newPath);
|
||||
resetPage();
|
||||
pathListeners.forEach(l -> l.setPath(newPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.srsoftware.belegscanner.gui;
|
||||
|
||||
import de.srsoftware.belegscanner.Configuration;
|
||||
import de.srsoftware.belegscanner.Worker;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import static de.srsoftware.belegscanner.Application.t;
|
||||
import static de.srsoftware.belegscanner.Constants.CONFIG_TARGET;
|
||||
import static de.srsoftware.belegscanner.Constants.JPG;
|
||||
import static de.srsoftware.belegscanner.Constants.OCR;
|
||||
import static de.srsoftware.belegscanner.Constants.PDF;
|
||||
import static java.awt.BorderLayout.CENTER;
|
||||
import static java.awt.BorderLayout.NORTH;
|
||||
import static java.awt.BorderLayout.WEST;
|
||||
|
||||
public class TypeSelector extends JPanel {
|
||||
|
||||
private final Worker worker;
|
||||
|
||||
public TypeSelector(Worker worker){
|
||||
super(new BorderLayout());
|
||||
this.worker = worker;
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
JLabel label = new JLabel(t("Target type"));
|
||||
String target = worker.getType();
|
||||
add(label, NORTH);
|
||||
add(typeButton(group,t(JPG),target), WEST);
|
||||
add(typeButton(group,t(PDF),target), CENTER);
|
||||
add(typeButton(group,t(OCR),target),BorderLayout.EAST);
|
||||
setMaximumSize(new Dimension(600,200));
|
||||
}
|
||||
|
||||
/**
|
||||
* create one radio button for the typeSelector
|
||||
* @param group
|
||||
* @param label
|
||||
* @param selection
|
||||
* @return
|
||||
*/
|
||||
private JRadioButton typeButton(ButtonGroup group, String label,String selection) {
|
||||
JRadioButton btn = new JRadioButton(label);
|
||||
group.add(btn);
|
||||
btn.addActionListener(ev -> worker.setType(label));
|
||||
if (label.equals(selection)) btn.setSelected(true);
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.srsoftware.belegscanner.listeners;
|
||||
|
||||
import de.srsoftware.belegscanner.model.api.Project;
|
||||
|
||||
public interface ProjectListener {
|
||||
void preview(Project project);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.srsoftware.belegscanner.model;
|
||||
|
||||
import de.srsoftware.belegscanner.model.api.Page;
|
||||
import de.srsoftware.belegscanner.model.api.Project;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ScanProject implements Project {
|
||||
|
||||
private final String pattern;
|
||||
private List<Page> pageList = List.of();
|
||||
|
||||
public ScanProject(String resolvedPath){
|
||||
this.pattern = resolvedPath;
|
||||
}
|
||||
@Override
|
||||
public String pattern() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> pages() {
|
||||
return pageList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextPage() {
|
||||
return pageList.size()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openDir() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void previewLastPage() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void join() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropLastPage() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.srsoftware.belegscanner.model.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface Page {
|
||||
public File file();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.srsoftware.belegscanner.model.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Project {
|
||||
String pattern();
|
||||
|
||||
List<Page> pages();
|
||||
|
||||
int nextPage();
|
||||
|
||||
void openDir();
|
||||
|
||||
void previewLastPage();
|
||||
|
||||
void join();
|
||||
|
||||
void dropLastPage();
|
||||
}
|
||||
Reference in New Issue
Block a user