diff --git a/.gitignore b/.gitignore index 9e3e952..c82f584 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /Belegscanner /bin/ /target/ +.idea \ No newline at end of file diff --git a/BelegScanner.iml b/BelegScanner.iml new file mode 100644 index 0000000..ec4b066 --- /dev/null +++ b/BelegScanner.iml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/srsoftware/belegscanner/Constants.java b/src/main/java/de/srsoftware/belegscanner/Constants.java index 09ceebb..39ecae6 100644 --- a/src/main/java/de/srsoftware/belegscanner/Constants.java +++ b/src/main/java/de/srsoftware/belegscanner/Constants.java @@ -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"; } diff --git a/src/main/java/de/srsoftware/belegscanner/Worker.java b/src/main/java/de/srsoftware/belegscanner/Worker.java new file mode 100644 index 0000000..1da6e30 --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/Worker.java @@ -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 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 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; + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/gui/DocTable.java b/src/main/java/de/srsoftware/belegscanner/gui/DocTable.java index 8d4e119..ced46b4 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/DocTable.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/DocTable.java @@ -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); - } - - /** - * represents on previously scanned document - * @author Stephan Richter - * - */ - 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 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 listeners = new ArrayList<>(); private static final long serialVersionUID = 1073955198529023744L; - private final HashMap rows = new HashMap<>(); - private HashSet 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); - } - - /** - * 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(); - for (var file : folder.list()){ - Optional 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 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 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 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 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; - } } diff --git a/src/main/java/de/srsoftware/belegscanner/gui/FormatSelector.java b/src/main/java/de/srsoftware/belegscanner/gui/FormatSelector.java new file mode 100644 index 0000000..89eceff --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/gui/FormatSelector.java @@ -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); + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/gui/ImproveSelector.java b/src/main/java/de/srsoftware/belegscanner/gui/ImproveSelector.java new file mode 100644 index 0000000..cec36bf --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/gui/ImproveSelector.java @@ -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; + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java b/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java index 07c628e..530ec5c 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java @@ -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 { 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 = ""; 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 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 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 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 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 entry : fields.entrySet()) { - String key = entry.getKey(); - String val = entry.getValue(); - JSONArray arr = config.getOrCreateArray(prefix+"fields."+key); - HashSet 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 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(); - } } diff --git a/src/main/java/de/srsoftware/belegscanner/gui/Preview.java b/src/main/java/de/srsoftware/belegscanner/gui/Preview.java new file mode 100644 index 0000000..fcc256f --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/gui/Preview.java @@ -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); + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/gui/ResolutionSelector.java b/src/main/java/de/srsoftware/belegscanner/gui/ResolutionSelector.java new file mode 100644 index 0000000..e3320da --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/gui/ResolutionSelector.java @@ -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; + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java b/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java index 352b300..d4b62cb 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java @@ -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()); diff --git a/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java b/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java index 2b3fd70..3f66234 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java @@ -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 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 additionalInputs = new HashMap<>(); private Vector paths = new Vector<>(); - private HashMap additonalComponents = new HashMap<>(); - private HashSet categoryListeners = new HashSet<>(); - private HashSet dateListeners = new HashSet<>(); - private HashSet fieldListeners = new HashSet<>(); - private HashSet pathListeners = new HashSet<>(); - private HashSet scanListeners = new HashSet<>(); - private HashSet 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 marks) { - LOG.debug("addFieldsFor({})",marks); - - List 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 deleteValue(JSONArray arr, String tx) { - for (int i = 0; i1) { - 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 { 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; } } diff --git a/src/main/java/de/srsoftware/belegscanner/gui/TypeSelector.java b/src/main/java/de/srsoftware/belegscanner/gui/TypeSelector.java new file mode 100644 index 0000000..e2665ba --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/gui/TypeSelector.java @@ -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; + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/listeners/ProjectListener.java b/src/main/java/de/srsoftware/belegscanner/listeners/ProjectListener.java new file mode 100644 index 0000000..761703f --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/listeners/ProjectListener.java @@ -0,0 +1,7 @@ +package de.srsoftware.belegscanner.listeners; + +import de.srsoftware.belegscanner.model.api.Project; + +public interface ProjectListener { + void preview(Project project); +} diff --git a/src/main/java/de/srsoftware/belegscanner/model/ScanProject.java b/src/main/java/de/srsoftware/belegscanner/model/ScanProject.java new file mode 100644 index 0000000..f140d7d --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/model/ScanProject.java @@ -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 pageList = List.of(); + + public ScanProject(String resolvedPath){ + this.pattern = resolvedPath; + } + @Override + public String pattern() { + return null; + } + + @Override + public List 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() { + + } +} diff --git a/src/main/java/de/srsoftware/belegscanner/model/api/Page.java b/src/main/java/de/srsoftware/belegscanner/model/api/Page.java new file mode 100644 index 0000000..5106150 --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/model/api/Page.java @@ -0,0 +1,7 @@ +package de.srsoftware.belegscanner.model.api; + +import java.io.File; + +public interface Page { + public File file(); +} diff --git a/src/main/java/de/srsoftware/belegscanner/model/api/Project.java b/src/main/java/de/srsoftware/belegscanner/model/api/Project.java new file mode 100644 index 0000000..f0536f6 --- /dev/null +++ b/src/main/java/de/srsoftware/belegscanner/model/api/Project.java @@ -0,0 +1,19 @@ +package de.srsoftware.belegscanner.model.api; + +import java.util.List; + +public interface Project { + String pattern(); + + List pages(); + + int nextPage(); + + void openDir(); + + void previewLastPage(); + + void join(); + + void dropLastPage(); +}