diff src/name/blackcap/exifwasher/WashDialog.kt @ 3:19c381c536ec

Code to make it a proper Mac GUI app. Untested!
author David Barts <n5jrn@me.com>
date Wed, 08 Apr 2020 20:29:12 -0700
parents
children dc1f4359659d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/name/blackcap/exifwasher/WashDialog.kt	Wed Apr 08 20:29:12 2020 -0700
@@ -0,0 +1,220 @@
+/*
+ * The dialog that controls washing a single file.
+ */
+package name.blackcap.exifwasher
+
+import java.awt.event.ActionEvent
+import java.awt.event.ActionListener
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.logging.Level
+import java.util.logging.Logger
+import javax.swing.*
+import javax.swing.table.DefaultTableModel
+import javax.swing.table.TableColumn
+import javax.swing.table.TableColumnModel
+
+import name.blackcap.exifwasher.exiv2.*
+
+class WashDialog : JDialog(Application.mainFrame) {
+    private val BW = 9
+    private val BW2 = BW * 2
+    private val WIDTH = 640
+    private val HEIGHT = 480
+
+    private val myTable = JTable().apply {
+        autoCreateRowSorter = false
+        rowSorter = null
+        columnModel.run {
+            getColumn(0).preferredWidth = 10  /* checkbox */
+            getColumn(1).preferredWidth = 25  /* key name */
+            getColumn(2).preferredWidth = 15  /* type */
+            getColumn(3).preferredWidth = 100  /* value */
+        }
+    }
+
+    private val selectAll = JCheckBox("Select all for deletion", false)
+
+    private val resetButton = JButton("Reset").also {
+        it.addActionListener(ActionListener { doReset() })
+        it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW)
+    }
+
+    private val cancelButton = JButton("Cancel").also {
+        it.addActionListener(ActionListener { close() })
+        it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW)
+    }
+
+    /* deliberately not the default action, because it changes a file */
+    private val washButton = JButton("Wash").also {
+        it.addActionListener(ActionListener { doWash() })
+        it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW)
+    }
+
+    private lateinit var washing: File
+
+    /* initiates the washing of the Exif data */
+    fun wash(dirty: File) {
+        title = "Washing: ${image.name}"
+        selectAll.setSelected(false)
+        washing = dirty
+        useWaitCursor()
+        swingWorker<Array<Array<Any>>?> {
+            inBackground {
+                try {
+                    val image = Image(dirty.canonicalPath)
+                    val meta = image.meta
+                    val keys = meta.keys
+                    keys.sort()
+                    Array<Array<String>>(keys.size) {
+                        val key = keys[it]
+                        val value = meta[key]
+                        arrayOf(!settingsDialog.whitelist.contains(key), key, value.type, value.value)
+                    }
+                } catch (e: Exiv2Exception) {
+                    LOGGER.log(Level.SEVERE, "unable to read metadata", e)
+                    null
+                }
+            }
+            whenDone {
+                useNormalCursor()
+                val tableData = get()
+                if (tableData == null) {
+                    JOptionPane.showMessageDialog(Application.mainFrame,
+                        "Unable to read metadata.",
+                        "Error", JOptionPane.ERROR_MESSAGE)
+                } else {
+                    val colNames = arrayOf("Delete?", "Key", "Type", "Value")
+                    myTable.apply {
+                        model = MyTableModel(tableData, colNames)
+                        validate()
+                    }
+                    setVisible(true)
+                }
+            }
+        }
+    }
+
+    private class MyTableModel : DefaultTableModel {
+        override fun isCellEditable(row: Int, col: Int) = col == 0
+        override fun getColumnClass(col: Int) = if (col == 0) {
+            Boolean
+        } else {
+            String
+        }
+    }
+
+    private fun doReset() {
+        myTable.model.run {
+            for (i in 0 .. rowCount - 1) {
+                val key = getValueAt(i, 1) as String
+                setValueAt(!settingsDialog.whitelist.contains(key), i, 0)
+            }
+        }
+        myTable.validate()
+    }
+
+    private fun doWash() {
+        setVisible(false)
+
+        /* get path to the directory we create */
+        val outDir = if (settingsDialog.outputToInputDir) {
+            washing.canonicalFile.parent
+        } else {
+            settingsDialog.outputTo
+        }
+
+        /* get new file name */
+        val (name, ext) = splitext(washing.name)
+        var newFile = File(outDir, "${name}_washed${ext}")
+
+        /* copy the file, then edit the Exif in the copy */
+        useWaitCursor()
+        swingWorker<Boolean> {
+            inBackground {
+                try {
+                    FileInputStream(washing).use { source ->
+                        FileOutputStream(newFile).use { target ->
+                            source.copyTo(target)
+                        }
+                    }
+                    val image = Image(newFile.canonicalPath)
+                    val meta = image.meta
+                    meta.keys.forEach {
+                        if (!settingsDialog.whitelist.contains(it)) {
+                            meta.erase(it)
+                        }
+                    }
+                    image.store()
+                    true
+                } catch (e: IOException) {
+                    LOGGER.log(Level.SEVERE, "unable to copy input", e)
+                    false
+                } catch (e: Exiv2Exception) {
+                    LOGGER.log(Level.SEVERE, "unable to edit metadata", e)
+                    false
+                }
+            }
+            whenDone {
+                useNormalCursor()
+                close()
+                /* if all went well, show the Exif in the new file */
+                if (get()) {
+                    ShowDialog().show(newFile)
+                } else {
+                    try {
+                        if (newFile.exists()) { newFile.delete() }
+                    } catch (e: IOException) {
+                        LOGGER.log(Level.SEVERE, "unable to delete", e)
+                    }
+                    JOptionPane.showMessageDialog(Application.mainFrame,
+                        "Error\nUnable to wash: ${washing.canonicalPath}\nTo: ${newFile.canonicalPath}",
+                        "Error", JOptionPane.ERROR_MESSAGE)
+                }
+            }
+        }
+    }
+
+    private fun splitext(s: String): Pair<String, String> {
+        val pos = s.lastIndexOf('.')
+        if (pos == -1) {
+            return Pair(s, "")
+        }
+        return Pair(s.substring(0, pos), s.substring(pos))
+    }
+
+    init {
+        defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE /* delete if reusing */
+        title = "Untitled"
+        contentPane.apply {
+            layout = BoxLayout(this, BoxLayout.Y_AXIS)
+            add(Box(BoxLayout.Y_AXIS).apply {
+                alignmentX = Box.CENTER_ALIGNMENT
+                border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                add(JScrollPane(myTable).apply {
+                    alignmentX = JScrollPane.LEFT_ALIGNMENT
+                    border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                    verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
+                    horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
+                    preferredSize = Dimension(WIDTH, HEIGHT)
+                    background = Application.mainFrame.background
+                })
+                add(selectAll.apply {
+                    alignmentX = JCheckBox.LEFT_ALIGNMENT
+                    border = BorderFactory.createEmptyBorder(BW, BW, 0, BW)
+                })
+            })
+            add(Box(BoxLayout.X_AXIS).apply {
+                alignmentX = Box.CENTER_ALIGNMENT
+                border = BorderFactory.createEmptyBorder(BW, BW, BW2, BW)
+                add(resetButton)
+                add(Box.createHorizontalGlue())
+                add(cancelButton)
+                add(washButton)
+            })
+        }
+        pack()
+    }
+}