comparison 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
comparison
equal deleted inserted replaced
2:efd9fe2d70d7 3:19c381c536ec
1 /*
2 * The dialog that controls our settings.
3 */
4 package name.blackcap.exifwasher
5
6 import java.awt.Toolkit
7 import java.awt.event.ActionEvent
8 import java.awt.event.ActionListener
9 import java.io.File
10 import java.io.IOException
11 import java.util.logging.Level
12 import java.util.logging.Logger
13 import javax.swing.*
14 import javax.swing.event.ListDataEvent
15 import javax.swing.event.ListDataListener
16 import kotlin.text.toBoolean
17
18 import name.blackcap.exifwasher.exiv2.*
19
20 class SettingsDialog : JDialog(Application.mainFrame) {
21 private val BW = 9
22 private val BW2 = BW * 2
23
24 /* where to send output, if not outputToInputDir */
25 private var _outputTo = PROPERTIES.getProperty("outputTo")
26 private var _dOutputTo = DPROPERTIES.getProperty("outputTo")
27 val outputTo: String
28 get() = _outputTo
29
30 /* make output where input was found */
31 private var _outputToInputDir = (PROPERTIES.getProperty("outputToInputDir") ?: "false").toBoolean()
32 private var _dOutputToInputDir = (DPROPERTIES.getProperty("outputToInputDir") ?: "false").toBoolean()
33 val outputToInputDir: Boolean
34 get() = _outputToInputDir
35
36 /* the whitelist of allowed Exif tags */
37 private var _whitelist = Whitelist.parse(PROPERTIES.getProperty("whitelist") :? "")
38 val whitelist: Whitelist
39 get() = _whitelist
40
41 /* the default whitelist, for factory resets */
42 private val _oWhitelist = _whitelist.clone()
43 private val _dWhitelist = Whitelist.parse(DPROPERTIES.getProperty("whitelist") :? "")
44
45 /* radio buttons to choose output directory policy */
46 private val outputToButton = JRadioButton("Output to:", !outputToInputDir).apply {
47 addActionListener(ActionListener { setOutputOpts(isSelected()) })
48 }
49 private val outputToInputButton = JRadioButton(
50 "Output to directory containing input file.", outputToInputDir).apply {
51 addActionListener(ActionListener { setOutputOpts(!isSelected()) })
52 }
53 private val buttonGroup = ButtonGroup().apply {
54 add(outputToButton)
55 add(outputToInputButton)
56 }
57 private fun setOutputOpts(toSpecific: Boolean) {
58 _outputToInputDir = !toSpecific
59 changeOutputTo.setEnabled(toSpecific)
60 }
61
62 /* displays the OutputTo directory */
63 private val outputToText = JTextField(outputTo, 50).apply {
64 setEditable(false)
65 }
66
67 /* pops up to change the above directory */
68 private val outputToChooser = JFileChooser(outputToText.text).apply {
69 fileSelectionMode = DIRECTORIES_ONLY
70 }
71
72 /* requests the OutputTo directory be changed */
73 private val changeOutputTo = JButton("Change").also {
74 it.addActionListener(ActionListener {
75 val status = outputToChooser.showOpenDialog(this)
76 if (status == JFileChooser.APPROVE_OPTION) {
77 _outputTo = outputToChooser.selectedFile.canonicalPath
78 outputToText.text = _outputTo
79 }
80 })
81 it.setEnabled(!outputToInputDir)
82 }
83
84 /* bottom buttons to restore defaults, cancel, save */
85 private val restoreButton = JButton("Restore All Defaults").also {
86 addActionListener(ActionListener {
87 restore(_dOutputToInputDir, _dOutputTo, _dWhitelist)
88 }
89 }
90 private val cancelButton = JButton("Cancel").also {
91 addActionListener(ActionListener {
92 setVisible(false)
93 restore(outputToInputDir, outputTo, whitelist)
94 })
95 }
96 private val saveButton = JButton("Save").also {
97 addActionListener(ActionListener {
98 setVisible(false)
99 writeProperties()
100 })
101 }
102
103 private fun writeProperties() {
104 PROPERTIES.run {
105 setProperty("outputTo", outputTo)
106 setProperty("outputToInputDir", outputToInputDir.toString())
107 setProperty("whitelist", whitelist.toString())
108 }
109 try {
110 BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use {
111 PROPERTIES.store(it, null)
112 }
113 } catch (e: IOException) {
114 LOGGER.log(Level.SEVERE, "unable to write settings", e)
115 JOptionPane.showMessageDialog(Application.mainFrame,
116 "Unable to write settings.",
117 "Error",
118 JOptionPane.ERROR_MESSAGE)
119 }
120 }
121
122 private fun restore(outputToInput: Boolean, output: String, wl: Whitelist) {
123 outputToButton.setSelected(!outputToInput)
124 changeOutputTo.setEnabled(!outputToInput)
125 outputToText.text = output
126 outputToInputButton.setSelected(outputToInput)
127 wlSelectorModel.reset(wl.toList())
128 }
129
130 /* so we can present a list of strings that is always sorted */
131 private class WhiteListModel(basedOn: Collection<String>): ListModel<String> {
132 private val storage = ArrayList<String>(basedOn).apply { sort() }
133 private val listeners = mutableListOf<ListDataListener>()
134
135 /* so we can mutate the list */
136
137 fun add(newItem: String): Unit {
138 var index = storage.binarySearch(newItem)
139 if (index < 0) {
140 index = -(index + 1)
141 }
142 storage.add(index, newItem)
143 val event = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)
144 listeners.forEach { it.intervalAdded(event) }
145 }
146
147 fun removeAt(index: Int): Unit {
148 if (storage.removeAt(index)) {
149 val event = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)
150 listeners.forEach { it.intervalRemoved(event) }
151 }
152 }
153
154 fun remove(oldItem: String): Unit {
155 val index = basedOn.binarySearch(oldItem)
156 if (index < 0) {
157 return
158 }
159 var start = index
160 while (start > 0 && storage[start] == oldItem) {
161 start -= 1
162 }
163 var end = index
164 var max = storage.size - 1
165 while (end < max && storage[end] == oldItem) {
166 end += 1
167 }
168 storage.removeRange(start, end+1)
169 val event = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, start, end)
170 listeners.forEach { it.intervalRemoved(event) }
171 }
172
173 fun reset(basedOn: Collection<String>): Unit {
174 val removeEvent = ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, storage.size)
175 storage.clear()
176 storage.addAll(basedOn)
177 storage.sort()
178 val addEvent = ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, 0, storage.size)
179 listeners.forEach {
180 it.contentsChanged(removeEvent)
181 it.contentsChanged(addEvent)
182 }
183 }
184
185 fun toList(): List<String> = storage
186
187 /* so we are a proper ListModel */
188
189 override fun addListDataListener(l: ListDataListener): Unit {
190 listeners.add(l)
191 }
192
193 override fun removeListDataListener(l: ListDataListener): Unit {
194 listeners.remove(l)
195 }
196
197 override fun getElementAt(index: Int): String = storage[index]
198
199 override fun getSize(): Int = storage.size
200 }
201
202 private class WLAddDialog(parent: SettingsDialog): JDialog(parent) {
203 JTextField text = JTextField(40).apply {
204 alignmentX = CENTER_ALIGNMENT
205 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
206 }
207
208 JButton cancelButton = JButton("Cancel").apply {
209 addActionListener(ActionListener {
210 text.text = ""
211 setVisible(false)
212 })
213 }
214
215 JButton addButton = JButton("Add").apply {
216 addActionListener(ActionListener {
217 val newItem = text.text?.trim()
218 if (newItem.isNullOrEmpty()) {
219 Toolkit.getDefaultToolkit().beep()
220 } else {
221 wlSelectorModel.add(newItem)
222 }
223 text.text = ""
224 setVisible(false)
225 }
226 })
227 }
228
229 init {
230 title = "Add Item to Whitelist"
231 contentPane.apply {
232 layout = BoxLayout(this, BoxLayout.Y_AXIS)
233 add(text)
234 add(Box(BoxLayout.X_AXIS).apply {
235 alignmentX = CENTER_ALIGNMENT
236 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
237 add(Box.createHorizontalGlue())
238 add(cancelButton)
239 add(Box.createHorizontalGlue())
240 add(addButton)
241 add(Box.createHorizontalGlue())
242 })
243 }
244 pack()
245 setResizable(false)
246 }
247 }
248
249 /* the JList that holds our whitelist */
250 private val wlSelectorModel = WhiteListModel(whitelist.toList())
251 private val wlSelector = JList().apply {
252 visibleRowCount = -1
253 model = wlSelectorModel
254 clearSelection()
255 addListSelectionListener(ListSelectionListener {
256 wlDeleteButton.setEnabled(!isSelectionEmpty())
257 }
258 }
259
260 /* buttons for managing the whitelist */
261 private val wlAddButton = JButton("Add").apply {
262 addActionListener(ActionListener { wlAddDialog.setVisible(true) })
263 }
264 private val wlDeleteButton = JButton("Delete").apply {
265 addActionListener(ActionListener {
266 wlSelector.selectedIndices.forEach { wlSelectorModel.removeAt(it) }
267 })
268 setEnabled(false)
269 }
270
271 /* the dialog that the Add button pops up */
272 private val wlAddDialog = WLAddDialog(this)
273
274 init {
275 if (_outputTo.isNullOrEmpty()) {
276 _outputTo = System.getProperty("user.dir")
277 }
278 title = "Settings"
279 contentPane.apply {
280 layout = BoxLayout(this, BoxLayout.Y_AXIS)
281 add(JTabbedPane().apply {
282 addTab("Folders", JPanel().apply {
283 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
284 layout = BoxLayout(this, BoxLayout.Y_AXIS)
285 add(outputToInputButton.apply {
286 alignmentX = LEFT_ALIGNMENT
287 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
288 })
289 add(Box(BoxLayout.X_AXIS).apply {
290 alignmentX = LEFT_ALIGNMENT
291 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
292 add(outputToButton)
293 add(Box.createHorizontalStrut(BW2))
294 add(outputToText)
295 add(Box.createHorizontalGlue())
296 })
297 add(Box(BoxLayout.X_AXIS).apply {
298 alignmentX = LEFT_ALIGNMENT
299 border = BorderFactory.createEmptyBorder(0, BW, BW, BW)
300 add(Box.createHorizontalGlue())
301 add(changeOutputTo)
302 })
303 })
304 addTab("Whitelist", JPanel().apply {
305 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
306 layout = BoxLayout(this, BoxLayout.Y_AXIS)
307 add(JScrollPane(wlSelector).apply {
308 alignmentX = CENTER_ALIGNMENT
309 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
310 verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
311 horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
312 })
313 add(Box(BoxLayout.X_AXIS).apply {
314 alignmentX = CENTER_ALIGNMENT
315 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW)
316 add(Box.createHorizontalGlue())
317 add(wlAddButton)
318 add(Box.createHorizontalGlue())
319 add(wlDeleteButton)
320 add(Box.createHorizontalGlue())
321 })
322 })
323 })
324 add(Box(BoxLayout.X_AXIS).apply {
325 border = BorderFactory.createEmptyBorder(BW, BW2, BW2, BW2)
326 add(restoreButton)
327 add(Box.createHorizontalGlue())
328 add(cancelButton)
329 add(Box.createHorizontalStrut(BW2))
330 add(saveButton)
331 })
332 }
333 pack()
334 minimumSize = size
335 }
336 }