Browse Source

started remodeling application

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
main
Stephan Richter 10 months ago
parent
commit
0f16ad3bd0
  1. 1
      .gitignore
  2. 32
      BelegScanner.iml
  3. 2
      src/main/java/de/srsoftware/belegscanner/Constants.java
  4. 166
      src/main/java/de/srsoftware/belegscanner/Worker.java
  5. 274
      src/main/java/de/srsoftware/belegscanner/gui/DocTable.java
  6. 59
      src/main/java/de/srsoftware/belegscanner/gui/FormatSelector.java
  7. 29
      src/main/java/de/srsoftware/belegscanner/gui/ImproveSelector.java
  8. 472
      src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java
  9. 34
      src/main/java/de/srsoftware/belegscanner/gui/Preview.java
  10. 47
      src/main/java/de/srsoftware/belegscanner/gui/ResolutionSelector.java
  11. 6
      src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java
  12. 547
      src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java
  13. 49
      src/main/java/de/srsoftware/belegscanner/gui/TypeSelector.java
  14. 7
      src/main/java/de/srsoftware/belegscanner/listeners/ProjectListener.java
  15. 50
      src/main/java/de/srsoftware/belegscanner/model/ScanProject.java
  16. 7
      src/main/java/de/srsoftware/belegscanner/model/api/Page.java
  17. 19
      src/main/java/de/srsoftware/belegscanner/model/api/Project.java

1
.gitignore vendored

@ -2,3 +2,4 @@ @@ -2,3 +2,4 @@
/Belegscanner
/bin/
/target/
.idea

32
BelegScanner.iml

@ -0,0 +1,32 @@ @@ -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>

2
src/main/java/de/srsoftware/belegscanner/Constants.java

@ -11,4 +11,6 @@ public class Constants { @@ -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

@ -0,0 +1,166 @@ @@ -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;
}
}

274
src/main/java/de/srsoftware/belegscanner/gui/DocTable.java

@ -3,36 +3,27 @@ package de.srsoftware.belegscanner.gui; @@ -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{ @@ -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);
}
/**
* 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);
}
private List<Project> projects = new ArrayList<>();
/**
* 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{ @@ -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);
}
/**
* 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));
public Optional<Project> getProject(String resolvedPath) {
return projects.stream().filter(p -> resolvedPath.equals(p.pattern())).findAny();
}
/**
* 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;
}
}

59
src/main/java/de/srsoftware/belegscanner/gui/FormatSelector.java

@ -0,0 +1,59 @@ @@ -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);
}
}

29
src/main/java/de/srsoftware/belegscanner/gui/ImproveSelector.java

@ -0,0 +1,29 @@ @@ -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;
}
}

472
src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java

@ -1,53 +1,27 @@ @@ -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 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 de.srsoftware.belegscanner.Configuration;
import de.srsoftware.belegscanner.Worker;
import org.icepdf.ri.common.SwingController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

@ -0,0 +1,34 @@ @@ -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);
}
}

47
src/main/java/de/srsoftware/belegscanner/gui/ResolutionSelector.java

@ -0,0 +1,47 @@ @@ -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;
}
}

6
src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java

@ -11,6 +11,7 @@ import javax.swing.JLabel; @@ -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 { @@ -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 { @@ -40,8 +41,9 @@ public class StatusBar extends JPanel {
openDir();
}
});
worker.setStatusBar(this);
}
protected void openDir() {
try {
File dir = new File(path.getText());

547
src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java

@ -1,36 +1,28 @@ @@ -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.*; @@ -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 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 final int OFFSET = 2;
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 { @@ -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 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;
public void dropUnusedAdditionalFields() {
additionalInputs.entrySet()
.stream()
.filter(entry -> !entry.getValue().required)
.map(Map.Entry::getValue)
.map(ai -> ai.panel)
.forEach(this::remove);
}
/**
* 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 { @@ -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;
}
/**
* 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;
}
/**
* create scan button
* @return
*/
private Component scanButton() {
scanButton = new JButton(t("scan!"));
scanButton.addActionListener(this::scanPressed);
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;
return worker.setPathPicker(new SelectComboBox(paths));
}
/**
* 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)));
}
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);
}
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!");
}
return additionalInput.input.getText();
}
/**
* 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));
public void resetAdditionalFields() {
additionalInputs.values().forEach(AdditionalInput::notRequired);
}
/**
* notify listeners of changes to the path field
* @param newPath
* create scan button
* @return
*/
protected void updatePath(String newPath) {
LOG.debug("updatePath({})",newPath);
resetPage();
pathListeners.forEach(l -> l.setPath(newPath));
private Component scanButton() {
scanButton = new JButton(t("scan!"));
scanButton.addActionListener(ev -> worker.scan());
return scanButton;
}
}

49
src/main/java/de/srsoftware/belegscanner/gui/TypeSelector.java

@ -0,0 +1,49 @@ @@ -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;
}
}

7
src/main/java/de/srsoftware/belegscanner/listeners/ProjectListener.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
package de.srsoftware.belegscanner.listeners;
import de.srsoftware.belegscanner.model.api.Project;
public interface ProjectListener {
void preview(Project project);
}

50
src/main/java/de/srsoftware/belegscanner/model/ScanProject.java

@ -0,0 +1,50 @@ @@ -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() {
}
}

7
src/main/java/de/srsoftware/belegscanner/model/api/Page.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
package de.srsoftware.belegscanner.model.api;
import java.io.File;
public interface Page {
public File file();
}

19
src/main/java/de/srsoftware/belegscanner/model/api/Project.java

@ -0,0 +1,19 @@ @@ -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();
}
Loading…
Cancel
Save