Mercurial > cgi-bin > hgweb.cgi > JpegWasher
view src/name/blackcap/exifwasher/SettingsDialog.kt @ 13:a59d84674fb0
Make it seamlessly work on IPTC and XMP metadata, too.
author | David Barts <davidb@stashtea.com> |
---|---|
date | Sat, 11 Apr 2020 09:14:31 -0700 |
parents | aafc9c127c7b |
children | 40911898ed23 |
line wrap: on
line source
/* * The dialog that controls our settings. */ package name.blackcap.exifwasher import java.awt.Dimension 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) { protected val BW = 9 protected val BW2 = BW * 2 /* where to send output, if not outputToInputDir */ private var homeDir = System.getProperty("user.home") protected var _outputTo = _PROPS.getNotEmpty("outputTo", homeDir) protected var _dOutputTo = DPROPERTIES.getNotEmpty("outputTo", homeDir) val outputTo: String get() = _outputTo /* make output where input was found */ 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 */ protected var _whitelist = Whitelist.parse(_PROPS.getProperty("whitelist", "")) val whitelist: Whitelist get() = _whitelist /* the default whitelist, for factory resets */ protected val _oWhitelist = _whitelist.clone() protected val _dWhitelist = Whitelist.parse(DPROPERTIES.getProperty("whitelist", "")) /* radio buttons to choose output directory policy */ protected val outputToButton = JRadioButton("Output to:", !outputToInputDir).also { it.addActionListener(ActionListener { _ -> setOutputOpts(it.isSelected()) }) it.noTaller() } protected val outputToInputButton = JRadioButton( "Output to directory containing input file.", outputToInputDir).also { it.addActionListener(ActionListener { _ -> setOutputOpts(!it.isSelected()) }) it.noTaller() } protected val buttonGroup = ButtonGroup().apply { add(outputToButton) add(outputToInputButton) } protected fun setOutputOpts(toSpecific: Boolean) { _outputToInputDir = !toSpecific changeOutputTo.setEnabled(toSpecific) } /* displays the OutputTo directory */ protected val outputToText = JTextField(outputTo, 40).apply { setEditable(false) noTaller() } /* pops up to change the above directory */ protected val outputToChooser = JFileChooser(outputToText.text).apply { fileSelectionMode = JFileChooser.DIRECTORIES_ONLY } /* requests the OutputTo directory be changed */ protected 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 */ protected val restoreButton = JButton("Restore All Defaults").also { it.addActionListener(ActionListener { restore(_dOutputToInputDir, _dOutputTo, _dWhitelist) }) } protected val cancelButton = JButton("Cancel").also { it.addActionListener(ActionListener { setVisible(false) restore(outputToInputDir, outputTo, whitelist) }) } protected val saveButton = JButton("Save").also { it.addActionListener(ActionListener { setVisible(false) writeProperties() }) } protected fun writeProperties() { _whitelist = Whitelist().apply { wlSelectorModel.toList().forEach { add(it) } } _PROPS.run { setProperty("outputTo", outputTo) setProperty("outputToInputDir", outputToInputDir.toString()) setProperty("whitelist", whitelist.toString()) } try { BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use { _PROPS.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) } } protected fun restore(outputToInput: Boolean, output: String, wl: Whitelist) { outputToButton.setSelected(!outputToInput) changeOutputTo.setEnabled(!outputToInput) outputToText.text = output outputToInputButton.setSelected(outputToInput) wlSelectorModel.reset(wl.toList()) validate() } /* so we can present a list of strings that is always sorted */ protected 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) notifyAll(ListDataEvent.INTERVAL_ADDED, index, index) } fun removeAt(index: Int): Unit { storage.removeAt(index) notifyAll(ListDataEvent.INTERVAL_REMOVED, index, index) } fun remove(oldItem: String): Unit { val index: Int = storage.binarySearch(oldItem) if (index < 0) { return } storage.removeAt(index) notifyAll(ListDataEvent.INTERVAL_REMOVED, index, index) } fun reset(basedOn: Collection<String>): Unit { val oldSize = storage.size storage.clear() notifyAll(ListDataEvent.INTERVAL_REMOVED, 0, oldSize) storage.addAll(basedOn) storage.sort() 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!") } } /* 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 } 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) } private val cancelButton = JButton("Cancel").also { it.addActionListener(ActionListener { toAdd.text = "" setVisible(false) }) } private val addButton = JButton("Add").also { it.addActionListener(ActionListener { val newItem = toAdd.text?.trim() if (newItem.isNullOrEmpty()) { Toolkit.getDefaultToolkit().beep() } else { parent.wlSelectorModel.add(newItem) } toAdd.text = "" setVisible(false) }) } init { title = "Add Item to Whitelist" contentPane.apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) add(toAdd) 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 */ protected val wlSelectorModel = WhiteListModel(whitelist.toList()) protected val wlSelector: JList<String> = JList<String>().apply { visibleRowCount = 6 model = wlSelectorModel clearSelection() addListSelectionListener(ListSelectionListener { wlDeleteButton.setEnabled(!isSelectionEmpty()) }) } /* buttons for managing the whitelist */ protected val wlAddButton = JButton("Add").also { it.addActionListener(ActionListener { wlAddDialog.setVisible(true) }) } protected val wlDeleteButton = JButton("Delete").also { it.addActionListener(ActionListener { wlSelector.selectedIndices.forEach { wlSelectorModel.removeAt(it) } setEnabled(false) }) it.setEnabled(false) } /* the dialog that the Add button pops up */ protected val wlAddDialog = WLAddDialog(this) init { 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) // yes, a one-item box. POS mis-aligns things if only // some stuff is boxed, border widths be damned. sigh. add(Box(BoxLayout.X_AXIS).apply { alignmentX = LEFT_ALIGNMENT border = BorderFactory.createEmptyBorder(BW, BW, 0, BW) add(outputToInputButton) add(Box.createHorizontalGlue()) pack() noTaller() }) add(Box(BoxLayout.X_AXIS).apply { alignmentX = LEFT_ALIGNMENT border = BorderFactory.createEmptyBorder(BW, BW, 0, BW) add(outputToButton) add(Box.createHorizontalStrut(BW2)) add(outputToText) add(Box.createHorizontalGlue()) pack() noTaller() }) add(Box(BoxLayout.X_AXIS).apply { alignmentX = LEFT_ALIGNMENT border = BorderFactory.createEmptyBorder(0, BW, BW, BW) add(Box.createHorizontalGlue()) add(changeOutputTo) pack() noTaller() }) add(Box.createVerticalGlue()) }) 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.createVerticalGlue()) 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 } } fun Properties.getNotEmpty(key: String, default: String): String { val ret = getProperty(key) return if (ret.isNullOrEmpty()) default else ret } fun JComponent.noTaller() { maximumSize = Dimension(maximumSize.width, preferredSize.height) }