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