diff src/name/blackcap/exifwasher/SettingsDialog.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/SettingsDialog.kt	Wed Apr 08 20:29:12 2020 -0700
@@ -0,0 +1,336 @@
+/*
+ * The dialog that controls our settings.
+ */
+package name.blackcap.exifwasher
+
+import java.awt.Toolkit
+import java.awt.event.ActionEvent
+import java.awt.event.ActionListener
+import java.io.File
+import java.io.IOException
+import java.util.logging.Level
+import java.util.logging.Logger
+import javax.swing.*
+import javax.swing.event.ListDataEvent
+import javax.swing.event.ListDataListener
+import kotlin.text.toBoolean
+
+import name.blackcap.exifwasher.exiv2.*
+
+class SettingsDialog : JDialog(Application.mainFrame) {
+    private val BW = 9
+    private val BW2 = BW * 2
+
+    /* where to send output, if not outputToInputDir */
+    private var _outputTo = PROPERTIES.getProperty("outputTo")
+    private 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()
+    val outputToInputDir: Boolean
+    get() = _outputToInputDir
+
+    /* the whitelist of allowed Exif tags */
+    private var _whitelist = Whitelist.parse(PROPERTIES.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") :? "")
+
+    /* radio buttons to choose output directory policy */
+    private val outputToButton = JRadioButton("Output to:", !outputToInputDir).apply {
+        addActionListener(ActionListener { setOutputOpts(isSelected()) })
+    }
+    private val outputToInputButton = JRadioButton(
+      "Output to directory containing input file.", outputToInputDir).apply {
+        addActionListener(ActionListener { setOutputOpts(!isSelected()) })
+    }
+    private val buttonGroup = ButtonGroup().apply {
+        add(outputToButton)
+        add(outputToInputButton)
+    }
+    private fun setOutputOpts(toSpecific: Boolean) {
+        _outputToInputDir = !toSpecific
+        changeOutputTo.setEnabled(toSpecific)
+    }
+
+    /* displays the OutputTo directory */
+    private 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
+    }
+
+    /* requests the OutputTo directory be changed */
+    private val changeOutputTo = JButton("Change").also {
+        it.addActionListener(ActionListener {
+            val status = outputToChooser.showOpenDialog(this)
+            if (status == JFileChooser.APPROVE_OPTION) {
+                _outputTo = outputToChooser.selectedFile.canonicalPath
+                outputToText.text = _outputTo
+            }
+        })
+        it.setEnabled(!outputToInputDir)
+    }
+
+    /* bottom buttons to restore defaults, cancel, save */
+    private val restoreButton = JButton("Restore All Defaults").also {
+        addActionListener(ActionListener {
+            restore(_dOutputToInputDir, _dOutputTo, _dWhitelist)
+        }
+    }
+    private val cancelButton = JButton("Cancel").also {
+        addActionListener(ActionListener {
+            setVisible(false)
+            restore(outputToInputDir, outputTo, whitelist)
+        })
+    }
+    private val saveButton = JButton("Save").also {
+        addActionListener(ActionListener {
+            setVisible(false)
+            writeProperties()
+        })
+    }
+
+    private fun writeProperties() {
+        PROPERTIES.run {
+            setProperty("outputTo", outputTo)
+            setProperty("outputToInputDir", outputToInputDir.toString())
+            setProperty("whitelist", whitelist.toString())
+        }
+        try {
+            BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use {
+                PROPERTIES.store(it, null)
+            }
+        } catch (e: IOException) {
+            LOGGER.log(Level.SEVERE, "unable to write settings", e)
+            JOptionPane.showMessageDialog(Application.mainFrame,
+                "Unable to write settings.",
+                "Error",
+                JOptionPane.ERROR_MESSAGE)
+        }
+    }
+
+    private fun restore(outputToInput: Boolean, output: String, wl: Whitelist) {
+        outputToButton.setSelected(!outputToInput)
+        changeOutputTo.setEnabled(!outputToInput)
+        outputToText.text = output
+        outputToInputButton.setSelected(outputToInput)
+        wlSelectorModel.reset(wl.toList())
+    }
+
+    /* so we can present a list of strings that is always sorted */
+    private class WhiteListModel(basedOn: Collection<String>): ListModel<String> {
+        private val storage = ArrayList<String>(basedOn).apply { sort() }
+        private val listeners = mutableListOf<ListDataListener>()
+
+        /* so we can mutate the list */
+
+        fun add(newItem: String): Unit {
+            var index = storage.binarySearch(newItem)
+            if (index < 0) {
+                index = -(index + 1)
+            }
+            storage.add(index, newItem)
+            val event = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)
+            listeners.forEach { it.intervalAdded(event) }
+        }
+
+        fun removeAt(index: Int): Unit {
+            if (storage.removeAt(index)) {
+                val event = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)
+                listeners.forEach { it.intervalRemoved(event) }
+            }
+        }
+
+        fun remove(oldItem: String): Unit {
+            val index = basedOn.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) }
+        }
+
+        fun reset(basedOn: Collection<String>): Unit {
+            val removeEvent = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, storage.size)
+            storage.clear()
+            storage.addAll(basedOn)
+            storage.sort()
+            val addEvent = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, 0, storage.size)
+            listeners.forEach {
+                it.contentsChanged(removeEvent)
+                it.contentsChanged(addEvent)
+            }
+        }
+
+        fun toList(): List<String> = storage
+
+        /* so we are a proper ListModel */
+
+        override fun addListDataListener(l: ListDataListener): Unit {
+            listeners.add(l)
+        }
+
+        override fun removeListDataListener(l: ListDataListener): Unit {
+            listeners.remove(l)
+        }
+
+        override fun getElementAt(index: Int): String = storage[index]
+
+        override fun getSize(): Int = storage.size
+    }
+
+    private class WLAddDialog(parent: SettingsDialog): JDialog(parent) {
+        JTextField text = JTextField(40).apply {
+            alignmentX = CENTER_ALIGNMENT
+            border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+        }
+
+        JButton cancelButton = JButton("Cancel").apply {
+            addActionListener(ActionListener {
+                text.text = ""
+                setVisible(false)
+            })
+        }
+
+        JButton addButton = JButton("Add").apply {
+            addActionListener(ActionListener {
+                val newItem = text.text?.trim()
+                if (newItem.isNullOrEmpty()) {
+                    Toolkit.getDefaultToolkit().beep()
+                } else {
+                    wlSelectorModel.add(newItem)
+                }
+                text.text = ""
+                setVisible(false)
+                }
+            })
+        }
+
+        init {
+            title = "Add Item to Whitelist"
+            contentPane.apply {
+                layout = BoxLayout(this, BoxLayout.Y_AXIS)
+                add(text)
+                add(Box(BoxLayout.X_AXIS).apply {
+                    alignmentX = CENTER_ALIGNMENT
+                    border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                    add(Box.createHorizontalGlue())
+                    add(cancelButton)
+                    add(Box.createHorizontalGlue())
+                    add(addButton)
+                    add(Box.createHorizontalGlue())
+                })
+            }
+            pack()
+            setResizable(false)
+        }
+    }
+
+    /* the JList that holds our whitelist */
+    private val wlSelectorModel = WhiteListModel(whitelist.toList())
+    private val wlSelector = JList().apply {
+        visibleRowCount = -1
+        model = wlSelectorModel
+        clearSelection()
+        addListSelectionListener(ListSelectionListener {
+            wlDeleteButton.setEnabled(!isSelectionEmpty())
+        }
+    }
+
+    /* buttons for managing the whitelist */
+    private val wlAddButton = JButton("Add").apply {
+        addActionListener(ActionListener { wlAddDialog.setVisible(true) })
+    }
+    private val wlDeleteButton = JButton("Delete").apply {
+        addActionListener(ActionListener {
+            wlSelector.selectedIndices.forEach { wlSelectorModel.removeAt(it) }
+        })
+        setEnabled(false)
+    }
+
+    /* the dialog that the Add button pops up */
+    private val wlAddDialog = WLAddDialog(this)
+
+    init {
+        if (_outputTo.isNullOrEmpty()) {
+            _outputTo = System.getProperty("user.dir")
+        }
+        title = "Settings"
+        contentPane.apply {
+            layout = BoxLayout(this, BoxLayout.Y_AXIS)
+            add(JTabbedPane().apply {
+                addTab("Folders", JPanel().apply {
+                    border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                    layout = BoxLayout(this, BoxLayout.Y_AXIS)
+                    add(outputToInputButton.apply {
+                        alignmentX = LEFT_ALIGNMENT
+                        border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                    })
+                    add(Box(BoxLayout.X_AXIS).apply {
+                        alignmentX = LEFT_ALIGNMENT
+                        border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                        add(outputToButton)
+                        add(Box.createHorizontalStrut(BW2))
+                        add(outputToText)
+                        add(Box.createHorizontalGlue())
+                    })
+                    add(Box(BoxLayout.X_AXIS).apply {
+                        alignmentX = LEFT_ALIGNMENT
+                        border = BorderFactory.createEmptyBorder(0, BW, BW, BW)
+                        add(Box.createHorizontalGlue())
+                        add(changeOutputTo)
+                    })
+                })
+                addTab("Whitelist", JPanel().apply {
+                    border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                    layout = BoxLayout(this, BoxLayout.Y_AXIS)
+                    add(JScrollPane(wlSelector).apply {
+                        alignmentX = CENTER_ALIGNMENT
+                        border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                        verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
+                        horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
+                    })
+                    add(Box(BoxLayout.X_AXIS).apply {
+                        alignmentX = CENTER_ALIGNMENT
+                        border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
+                        add(Box.createHorizontalGlue())
+                        add(wlAddButton)
+                        add(Box.createHorizontalGlue())
+                        add(wlDeleteButton)
+                        add(Box.createHorizontalGlue())
+                    })
+                })
+            })
+            add(Box(BoxLayout.X_AXIS).apply {
+                border = BorderFactory.createEmptyBorder(BW, BW2, BW2, BW2)
+                add(restoreButton)
+                add(Box.createHorizontalGlue())
+                add(cancelButton)
+                add(Box.createHorizontalStrut(BW2))
+                add(saveButton)
+            })
+        }
+        pack()
+        minimumSize = size
+    }
+}