Mercurial > cgi-bin > hgweb.cgi > JpegWasher
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 + } +}