changeset 5:dc1f4359659d

Got it compiling.
author David Barts <n5jrn@me.com>
date Thu, 09 Apr 2020 18:20:34 -0700
parents ba5dc14652da
children aafc9c127c7b
files build.xml src/name/blackcap/exifwasher/Files.kt src/name/blackcap/exifwasher/Main.kt src/name/blackcap/exifwasher/MainFrame.kt src/name/blackcap/exifwasher/Menus.kt src/name/blackcap/exifwasher/Misc.kt src/name/blackcap/exifwasher/SettingsDialog.kt src/name/blackcap/exifwasher/ShowDialog.kt src/name/blackcap/exifwasher/WashDialog.kt src/name/blackcap/exifwasher/Whitelist.kt
diffstat 10 files changed, 144 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Wed Apr 08 21:31:30 2020 -0700
+++ b/build.xml	Thu Apr 09 18:20:34 2020 -0700
@@ -87,7 +87,9 @@
   <target name="compile" depends="classpath"
           description="Compile Java sources to ${work.home}">
     <kotlinc src="${src.home}" output="${work.jar}"
-             classpathref="compile.classpath"/>
+             classpathref="compile.classpath">
+      <compilerarg line="-jvm-target 1.8"/>
+    </kotlinc>
   </target>
 
   <!-- make .jar file -->
--- a/src/name/blackcap/exifwasher/Files.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/Files.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -76,9 +76,9 @@
     }
 }
 
-System.setProperty("java.util.logging.SimpleFormatter.format",
-    "%1$tFT%1$tT%1$tz %2$s%n%4$s: %5$s%6$s%n")
 val LOGGER = run {
+    System.setProperty("java.util.logging.SimpleFormatter.format",
+        "%1\$tFT%1\$tT%1\$tz %2\$s%n%4\$s: %5\$s%6\$s%n")
     LF_DIR.makeIfNeeded()
     Logger.getLogger(LONGNAME).apply {
         addHandler(FileHandler(LOG_FILE.toString()).apply {
--- a/src/name/blackcap/exifwasher/Main.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/Main.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -4,6 +4,8 @@
 package name.blackcap.exifwasher
 
 import javax.swing.UIManager
+import java.util.logging.Level
+import java.util.logging.Logger
 
 object Application {
     /* name we call ourselves */
--- a/src/name/blackcap/exifwasher/MainFrame.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/MainFrame.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -7,12 +7,13 @@
 import java.awt.datatransfer.DataFlavor
 import java.awt.datatransfer.Transferable
 import java.awt.datatransfer.UnsupportedFlavorException
+import java.awt.event.WindowEvent
+import java.awt.event.WindowListener
 import java.io.File
 import java.io.IOException
-import javax.swing.JFrame
-import javax.swing.TransferHandler
-
-class MainFrame: JFrame {
+import java.util.logging.Level
+import java.util.logging.Logger
+import javax.swing.*
 
 /* the main frame itself */
 class MainFrame : JFrame(Application.MYNAME) {
@@ -38,7 +39,7 @@
     }
 
     /* acts on dragged files */
-    private class MyTransferHandler : TransferHandler {
+    private class MyTransferHandler : TransferHandler() {
         override fun canImport(support: TransferHandler.TransferSupport): Boolean {
             return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
         }
@@ -48,7 +49,7 @@
                 return false
             }
             val files = try {
-                support.transferable.getTransferData(DataFlavor.javaFileListFlavor) as java.util.List<File>
+                support.transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<File>
             } catch (e: UnsupportedFlavorException) {
                 return false
             } catch (e: IOException) {
--- a/src/name/blackcap/exifwasher/Menus.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/Menus.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -68,7 +68,7 @@
  * Show an About dialog.
  */
 fun showAboutDialog() {
-    JOptionPane.showMessageDialog(frame.v,
+    JOptionPane.showMessageDialog(Application.mainFrame,
         "ExifWasher—Privacy for your photos.\n"
         + "© MMXX, David W. Barts",
         "About ExifWasher",
--- a/src/name/blackcap/exifwasher/Misc.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/Misc.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -9,7 +9,7 @@
 import javax.swing.*
 import kotlin.annotation.*
 import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
+import kotlin.reflect.*
 
 /**
  * Delegate that makes a var that can only be set once. This is commonly
@@ -17,22 +17,22 @@
  * outer levels but initialized in the Swing event dispatch thread.
  */
 class SetOnce<T: Any>: ReadWriteProperty<Any?,T> {
-    private var value: T? = null
+    private var setOnceValue: T? = null
 
     override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
-        if (value == null) {
+        if (setOnceValue == null) {
             throw RuntimeException("${property.name} has not been initialized")
         } else {
-            return value!!
+            return setOnceValue!!
         }
     }
 
     @Synchronized
-    override operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T): Unit {
-        if (value != null) {
+    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T): Unit {
+        if (setOnceValue != null) {
             throw RuntimeException("${property.name} has already been initialized")
         }
-        value = newValue
+        setOnceValue = value
     }
 }
 
@@ -82,14 +82,14 @@
  * Change to the standard wait cursor.
  */
 fun Component.useWaitCursor() {
-    this.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR))
+    this.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)
 }
 
 /**
  * Return back to the normal cursor().
  */
 fun Component.useNormalCursor() {
-    this.cursor = Cursor.defaultCursor
+    this.cursor = Cursor.getDefaultCursor()
 }
 
 /**
@@ -105,10 +105,10 @@
  * @param T Type returned by inBackground (Java doInBackground) task.
  */
 class SwingWorkerBuilder<T>: SwingWorker<T,Unit>() {
-    private var inBackgroundLambda: (SwingWorkerBuilder.() -> T)? = null
-    private var whenDoneLambda: (SwingWorkerBuilder.() -> Unit)? = null
+    private var inBackgroundLambda: (SwingWorkerBuilder<T>.() -> T)? = null
+    private var whenDoneLambda: (SwingWorkerBuilder<T>.() -> Unit)? = null
 
-    private fun setOnce<U>(prop: KMutableProperty<(SwingWorkerBuilder.() -> U)?>, value: SwingWorkerBuilder.() -> U) {
+    private fun <U> setOnce(prop: KMutableProperty0<(SwingWorkerBuilder<T>.() -> U)?>, value: SwingWorkerBuilder<T>.() -> U) {
         if (prop.get() != null) {
             throw SwingWorkerException(prop.name.removeSuffix("Lambda") + " already defined!")
         }
@@ -118,38 +118,40 @@
     /**
      * Define the inBackground task.
      */
-    fun inBackground(lambda: SwingWorkerBuilder.() -> T): Unit {
+    fun inBackground(lambda: SwingWorkerBuilder<T>.() -> T): Unit {
         setOnce<T>(::inBackgroundLambda, lambda)
     }
 
     /**
      * Define the whenDone task.
      */
-    fun whenDone(lambda: SwingWorkerBuilder.() -> Unit): Unit {
+    fun whenDone(lambda: SwingWorkerBuilder<T>.() -> Unit): Unit {
         setOnce<Unit>(::whenDoneLambda, lambda)
     }
 
+    /**
+     * Validates we've been properly initialized.
+     */
+    fun validate(): Unit {
+        if (inBackgroundLambda == null) {
+            throw SwingWorkerException("inBackground not defined!")
+        }
+    }
+
     /* standard overrides for SwingWorker follow */
 
-    override fun doInBackground(): T = inBackgroundLambda?.invoke(this)
-
-    override fun done(): Unit = whenDoneLambda?.invoke(this)
+    override fun doInBackground(): T = inBackgroundLambda!!.invoke(this)
 
-    override fun execute(): Unit {
-        if (inBackgroundLambda == null) {
-            throw SwingWorkerException("inBackground not defined!")
-        } else {
-            super.execute()
-        }
-    }
+    override fun done(): Unit = whenDoneLambda?.invoke(this) ?: Unit
 }
 
 /**
  * Provides for an outer swingWorker block to contain the DSL.
  */
-fun swingWorker<T>(initializer: SwingWorkerBuilder.() -> Unit): Unit {
+fun <T> swingWorker(initializer: SwingWorkerBuilder<T>.() -> Unit): Unit {
     SwingWorkerBuilder<T>().run {
         initializer()
+        validate()
         execute()
     }
 }
--- a/src/name/blackcap/exifwasher/SettingsDialog.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/SettingsDialog.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -6,71 +6,79 @@
 import java.awt.Toolkit
 import java.awt.event.ActionEvent
 import java.awt.event.ActionListener
+import java.io.BufferedWriter
 import java.io.File
+import java.io.FileOutputStream
 import java.io.IOException
+import java.io.OutputStreamWriter
+import java.util.Properties
 import java.util.logging.Level
 import java.util.logging.Logger
 import javax.swing.*
 import javax.swing.event.ListDataEvent
 import javax.swing.event.ListDataListener
+import javax.swing.event.ListSelectionListener
 import kotlin.text.toBoolean
 
 import name.blackcap.exifwasher.exiv2.*
 
+/* work around name shadowing */
+private val _PROPS = PROPERTIES
+
 class SettingsDialog : JDialog(Application.mainFrame) {
-    private val BW = 9
-    private val BW2 = BW * 2
+    protected val BW = 9
+    protected val BW2 = BW * 2
 
     /* where to send output, if not outputToInputDir */
-    private var _outputTo = PROPERTIES.getProperty("outputTo")
-    private var _dOutputTo = DPROPERTIES.getProperty("outputTo")
+    protected var _outputTo = _PROPS.getProperty("outputTo")
+    protected var _dOutputTo = DPROPERTIES.getProperty("outputTo")
     val outputTo: String
     get() = _outputTo
 
     /* make output where input was found */
-    private var _outputToInputDir = (PROPERTIES.getProperty("outputToInputDir") ?: "false").toBoolean()
-    private var _dOutputToInputDir = (DPROPERTIES.getProperty("outputToInputDir") ?: "false").toBoolean()
+    protected var _outputToInputDir = _PROPS.getProperty("outputToInputDir", "false").toBoolean()
+    protected var _dOutputToInputDir = DPROPERTIES.getProperty("outputToInputDir", "false").toBoolean()
     val outputToInputDir: Boolean
     get() = _outputToInputDir
 
     /* the whitelist of allowed Exif tags */
-    private var _whitelist = Whitelist.parse(PROPERTIES.getProperty("whitelist") :? "")
+    protected var _whitelist = Whitelist.parse(_PROPS.getProperty("whitelist", ""))
     val whitelist: Whitelist
     get() = _whitelist
 
     /* the default whitelist, for factory resets */
-    private val _oWhitelist = _whitelist.clone()
-    private val _dWhitelist = Whitelist.parse(DPROPERTIES.getProperty("whitelist") :? "")
+    protected val _oWhitelist = _whitelist.clone()
+    protected val _dWhitelist = Whitelist.parse(DPROPERTIES.getProperty("whitelist", ""))
 
     /* radio buttons to choose output directory policy */
-    private val outputToButton = JRadioButton("Output to:", !outputToInputDir).apply {
+    protected val outputToButton = JRadioButton("Output to:", !outputToInputDir).apply {
         addActionListener(ActionListener { setOutputOpts(isSelected()) })
     }
-    private val outputToInputButton = JRadioButton(
+    protected val outputToInputButton = JRadioButton(
       "Output to directory containing input file.", outputToInputDir).apply {
         addActionListener(ActionListener { setOutputOpts(!isSelected()) })
     }
-    private val buttonGroup = ButtonGroup().apply {
+    protected val buttonGroup = ButtonGroup().apply {
         add(outputToButton)
         add(outputToInputButton)
     }
-    private fun setOutputOpts(toSpecific: Boolean) {
+    protected fun setOutputOpts(toSpecific: Boolean) {
         _outputToInputDir = !toSpecific
         changeOutputTo.setEnabled(toSpecific)
     }
 
     /* displays the OutputTo directory */
-    private val outputToText = JTextField(outputTo, 50).apply {
+    protected val outputToText = JTextField(outputTo, 50).apply {
         setEditable(false)
     }
 
     /* pops up to change the above directory */
-    private val outputToChooser = JFileChooser(outputToText.text).apply {
-        fileSelectionMode = DIRECTORIES_ONLY
+    protected val outputToChooser = JFileChooser(outputToText.text).apply {
+        fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
     }
 
     /* requests the OutputTo directory be changed */
-    private val changeOutputTo = JButton("Change").also {
+    protected val changeOutputTo = JButton("Change").also {
         it.addActionListener(ActionListener {
             val status = outputToChooser.showOpenDialog(this)
             if (status == JFileChooser.APPROVE_OPTION) {
@@ -82,33 +90,33 @@
     }
 
     /* bottom buttons to restore defaults, cancel, save */
-    private val restoreButton = JButton("Restore All Defaults").also {
+    protected val restoreButton = JButton("Restore All Defaults").apply {
         addActionListener(ActionListener {
             restore(_dOutputToInputDir, _dOutputTo, _dWhitelist)
-        }
+        })
     }
-    private val cancelButton = JButton("Cancel").also {
+    protected val cancelButton = JButton("Cancel").apply {
         addActionListener(ActionListener {
             setVisible(false)
             restore(outputToInputDir, outputTo, whitelist)
         })
     }
-    private val saveButton = JButton("Save").also {
+    protected val saveButton = JButton("Save").apply {
         addActionListener(ActionListener {
             setVisible(false)
             writeProperties()
         })
     }
 
-    private fun writeProperties() {
-        PROPERTIES.run {
+    protected fun writeProperties() {
+        _PROPS.run {
             setProperty("outputTo", outputTo)
             setProperty("outputToInputDir", outputToInputDir.toString())
             setProperty("whitelist", whitelist.toString())
         }
         try {
             BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use {
-                PROPERTIES.store(it, null)
+                _PROPS.store(it, null)
             }
         } catch (e: IOException) {
             LOGGER.log(Level.SEVERE, "unable to write settings", e)
@@ -119,7 +127,7 @@
         }
     }
 
-    private fun restore(outputToInput: Boolean, output: String, wl: Whitelist) {
+    protected fun restore(outputToInput: Boolean, output: String, wl: Whitelist) {
         outputToButton.setSelected(!outputToInput)
         changeOutputTo.setEnabled(!outputToInput)
         outputToText.text = output
@@ -128,7 +136,7 @@
     }
 
     /* so we can present a list of strings that is always sorted */
-    private class WhiteListModel(basedOn: Collection<String>): ListModel<String> {
+    protected class WhiteListModel(basedOn: Collection<String>): ListModel<String> {
         private val storage = ArrayList<String>(basedOn).apply { sort() }
         private val listeners = mutableListOf<ListDataListener>()
 
@@ -140,50 +148,46 @@
                 index = -(index + 1)
             }
             storage.add(index, newItem)
-            val event = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)
-            listeners.forEach { it.intervalAdded(event) }
+            notifyAll(ListDataEvent.INTERVAL_ADDED, index, index)
         }
 
         fun removeAt(index: Int): Unit {
-            if (storage.removeAt(index)) {
-                val event = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)
-                listeners.forEach { it.intervalRemoved(event) }
-            }
+            storage.removeAt(index)
+            notifyAll(ListDataEvent.INTERVAL_REMOVED, index, index)
         }
 
         fun remove(oldItem: String): Unit {
-            val index = basedOn.binarySearch(oldItem)
+            val index: Int = storage.binarySearch(oldItem)
             if (index < 0) {
                 return
             }
-            var start = index
-            while (start > 0 && storage[start] == oldItem) {
-                start -= 1
-            }
-            var end = index
-            var max = storage.size - 1
-            while (end < max && storage[end] == oldItem) {
-                end += 1
-            }
-            storage.removeRange(start, end+1)
-            val event = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, start, end)
-            listeners.forEach { it.intervalRemoved(event) }
+            storage.removeAt(index)
+            notifyAll(ListDataEvent.INTERVAL_REMOVED, index, index)
         }
 
         fun reset(basedOn: Collection<String>): Unit {
-            val removeEvent = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, storage.size)
+            val oldSize = storage.size
             storage.clear()
+            notifyAll(ListDataEvent.INTERVAL_REMOVED, 0, oldSize)
             storage.addAll(basedOn)
             storage.sort()
-            val addEvent = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, 0, storage.size)
-            listeners.forEach {
-                it.contentsChanged(removeEvent)
-                it.contentsChanged(addEvent)
+            notifyAll(ListDataEvent.INTERVAL_ADDED, 0, storage.size)
+        }
+
+        /* misc. */
+
+        fun toList(): List<String> = storage
+
+        private fun notifyAll(eType: Int, index0: Int, index1: Int): Unit {
+            val event = ListDataEvent(this, eType, index0, index1)
+            when (eType) {
+                ListDataEvent.CONTENTS_CHANGED -> listeners.forEach { it.contentsChanged(event) }
+                ListDataEvent.INTERVAL_ADDED -> listeners.forEach { it.intervalAdded(event) }
+                ListDataEvent.INTERVAL_REMOVED -> listeners.forEach { it.intervalRemoved(event) }
+                else -> throw RuntimeException("unexpected event type!")
             }
         }
 
-        fun toList(): List<String> = storage
-
         /* so we are a proper ListModel */
 
         override fun addListDataListener(l: ListDataListener): Unit {
@@ -199,30 +203,32 @@
         override fun getSize(): Int = storage.size
     }
 
-    private class WLAddDialog(parent: SettingsDialog): JDialog(parent) {
-        JTextField text = JTextField(40).apply {
+    protected class WLAddDialog(parent: SettingsDialog): JDialog(parent) {
+        private val BW = parent.BW
+        private val BW2 = parent.BW2
+
+        private val toAdd = JTextField(40).apply {
             alignmentX = CENTER_ALIGNMENT
             border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
         }
 
-        JButton cancelButton = JButton("Cancel").apply {
+        private val cancelButton = JButton("Cancel").apply {
             addActionListener(ActionListener {
-                text.text = ""
+                toAdd.text = ""
                 setVisible(false)
             })
         }
 
-        JButton addButton = JButton("Add").apply {
+        private val addButton = JButton("Add").apply {
             addActionListener(ActionListener {
-                val newItem = text.text?.trim()
+                val newItem = toAdd.text?.trim()
                 if (newItem.isNullOrEmpty()) {
                     Toolkit.getDefaultToolkit().beep()
                 } else {
-                    wlSelectorModel.add(newItem)
+                    parent.wlSelectorModel.add(newItem)
                 }
-                text.text = ""
+                toAdd.text = ""
                 setVisible(false)
-                }
             })
         }
 
@@ -230,7 +236,7 @@
             title = "Add Item to Whitelist"
             contentPane.apply {
                 layout = BoxLayout(this, BoxLayout.Y_AXIS)
-                add(text)
+                add(toAdd)
                 add(Box(BoxLayout.X_AXIS).apply {
                     alignmentX = CENTER_ALIGNMENT
                     border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
@@ -247,33 +253,38 @@
     }
 
     /* the JList that holds our whitelist */
-    private val wlSelectorModel = WhiteListModel(whitelist.toList())
-    private val wlSelector = JList().apply {
+    protected val wlSelectorModel = WhiteListModel(whitelist.toList())
+    protected val wlSelector: JList<String> = JList<String>().apply {
         visibleRowCount = -1
         model = wlSelectorModel
         clearSelection()
         addListSelectionListener(ListSelectionListener {
             wlDeleteButton.setEnabled(!isSelectionEmpty())
-        }
+        })
     }
 
     /* buttons for managing the whitelist */
-    private val wlAddButton = JButton("Add").apply {
+    protected val wlAddButton = JButton("Add").apply {
         addActionListener(ActionListener { wlAddDialog.setVisible(true) })
     }
-    private val wlDeleteButton = JButton("Delete").apply {
+    protected val wlDeleteButton = JButton("Delete").apply {
         addActionListener(ActionListener {
             wlSelector.selectedIndices.forEach { wlSelectorModel.removeAt(it) }
+            setEnabled(false)
         })
         setEnabled(false)
     }
 
     /* the dialog that the Add button pops up */
-    private val wlAddDialog = WLAddDialog(this)
+    protected val wlAddDialog = WLAddDialog(this)
 
     init {
+        val home = System.getProperty("user.dir")
         if (_outputTo.isNullOrEmpty()) {
-            _outputTo = System.getProperty("user.dir")
+            _outputTo = home
+        }
+        if (_dOutputTo.isNullOrEmpty()) {
+            _dOutputTo = home
         }
         title = "Settings"
         contentPane.apply {
--- a/src/name/blackcap/exifwasher/ShowDialog.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/ShowDialog.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -4,6 +4,7 @@
  */
 package name.blackcap.exifwasher
 
+import java.awt.Dimension
 import java.awt.event.ActionEvent
 import java.awt.event.ActionListener
 import java.io.File
@@ -46,8 +47,8 @@
         swingWorker<Array<Array<String>>?> {
             inBackground {
                 try {
-                    val image = Image(image.absolutePath)
-                    val meta = image.meta
+                    val openedImage = Image(image.absolutePath)
+                    val meta = openedImage.metadata
                     val keys = meta.keys
                     keys.sort()
                     Array<Array<String>>(keys.size) {
@@ -79,9 +80,9 @@
         }
     }
 
-    private class MyTableModel : DefaultTableModel {
+    private class MyTableModel(tData: Array<Array<String>>, cNames: Array<String>) : DefaultTableModel(tData, cNames) {
         override fun isCellEditable(row: Int, col: Int) = false
-        override fun getColumnClass(col: Int) = java.lang.String::class.java
+        override fun getColumnClass(col: Int) = String::class.java
     }
 
     init {
--- a/src/name/blackcap/exifwasher/WashDialog.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/WashDialog.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -3,6 +3,7 @@
  */
 package name.blackcap.exifwasher
 
+import java.awt.Dimension
 import java.awt.event.ActionEvent
 import java.awt.event.ActionListener
 import java.io.File
@@ -57,7 +58,7 @@
 
     /* initiates the washing of the Exif data */
     fun wash(dirty: File) {
-        title = "Washing: ${image.name}"
+        title = "Washing: ${dirty.name}"
         selectAll.setSelected(false)
         washing = dirty
         useWaitCursor()
@@ -65,13 +66,15 @@
             inBackground {
                 try {
                     val image = Image(dirty.canonicalPath)
-                    val meta = image.meta
+                    val meta = image.metadata
                     val keys = meta.keys
                     keys.sort()
-                    Array<Array<String>>(keys.size) {
+                    Array<Array<Any>>(keys.size) {
                         val key = keys[it]
                         val value = meta[key]
-                        arrayOf(!settingsDialog.whitelist.contains(key), key, value.type, value.value)
+                        arrayOf(
+                            !Application.settingsDialog.whitelist.contains(key),
+                            key, value.type, value.value)
                     }
                 } catch (e: Exiv2Exception) {
                     LOGGER.log(Level.SEVERE, "unable to read metadata", e)
@@ -97,12 +100,12 @@
         }
     }
 
-    private class MyTableModel : DefaultTableModel {
+    private class MyTableModel(tData: Array<Array<Any>>, cNames: Array<String>) : DefaultTableModel(tData, cNames) {
         override fun isCellEditable(row: Int, col: Int) = col == 0
         override fun getColumnClass(col: Int) = if (col == 0) {
-            Boolean
+            Boolean::class.java
         } else {
-            String
+            String::class.java
         }
     }
 
@@ -110,7 +113,7 @@
         myTable.model.run {
             for (i in 0 .. rowCount - 1) {
                 val key = getValueAt(i, 1) as String
-                setValueAt(!settingsDialog.whitelist.contains(key), i, 0)
+                setValueAt(!Application.settingsDialog.whitelist.contains(key), i, 0)
             }
         }
         myTable.validate()
@@ -120,10 +123,10 @@
         setVisible(false)
 
         /* get path to the directory we create */
-        val outDir = if (settingsDialog.outputToInputDir) {
+        val outDir = if (Application.settingsDialog.outputToInputDir) {
             washing.canonicalFile.parent
         } else {
-            settingsDialog.outputTo
+            Application.settingsDialog.outputTo
         }
 
         /* get new file name */
@@ -141,9 +144,9 @@
                         }
                     }
                     val image = Image(newFile.canonicalPath)
-                    val meta = image.meta
+                    val meta = image.metadata
                     meta.keys.forEach {
-                        if (!settingsDialog.whitelist.contains(it)) {
+                        if (!Application.settingsDialog.whitelist.contains(it)) {
                             meta.erase(it)
                         }
                     }
--- a/src/name/blackcap/exifwasher/Whitelist.kt	Wed Apr 08 21:31:30 2020 -0700
+++ b/src/name/blackcap/exifwasher/Whitelist.kt	Thu Apr 09 18:20:34 2020 -0700
@@ -33,14 +33,14 @@
 
     override fun toString(): String = toList().joinToString(",")
 
-    override public fun clone() = this().also { new ->
+    override public fun clone() = Whitelist().also { new ->
         entire.forEach { new.addEntire(it) }
         prefixes.forEach { new.addPrefix(it) }
     }
 
     companion object {
         private val SPLITTER = Pattern.compile(",\\s*")
-        fun parse(raw: String) = this().also {
+        fun parse(raw: String) = Whitelist().also {
             for (s in raw.split(SPLITTER)) {
                 it.add(s)
             }