Mercurial > cgi-bin > hgweb.cgi > JpegWasher
comparison src/name/blackcap/exifwasher/WashDialog.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 washing a single file. | |
3 */ | |
4 package name.blackcap.exifwasher | |
5 | |
6 import java.awt.event.ActionEvent | |
7 import java.awt.event.ActionListener | |
8 import java.io.File | |
9 import java.io.FileInputStream | |
10 import java.io.FileOutputStream | |
11 import java.io.IOException | |
12 import java.util.logging.Level | |
13 import java.util.logging.Logger | |
14 import javax.swing.* | |
15 import javax.swing.table.DefaultTableModel | |
16 import javax.swing.table.TableColumn | |
17 import javax.swing.table.TableColumnModel | |
18 | |
19 import name.blackcap.exifwasher.exiv2.* | |
20 | |
21 class WashDialog : JDialog(Application.mainFrame) { | |
22 private val BW = 9 | |
23 private val BW2 = BW * 2 | |
24 private val WIDTH = 640 | |
25 private val HEIGHT = 480 | |
26 | |
27 private val myTable = JTable().apply { | |
28 autoCreateRowSorter = false | |
29 rowSorter = null | |
30 columnModel.run { | |
31 getColumn(0).preferredWidth = 10 /* checkbox */ | |
32 getColumn(1).preferredWidth = 25 /* key name */ | |
33 getColumn(2).preferredWidth = 15 /* type */ | |
34 getColumn(3).preferredWidth = 100 /* value */ | |
35 } | |
36 } | |
37 | |
38 private val selectAll = JCheckBox("Select all for deletion", false) | |
39 | |
40 private val resetButton = JButton("Reset").also { | |
41 it.addActionListener(ActionListener { doReset() }) | |
42 it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW) | |
43 } | |
44 | |
45 private val cancelButton = JButton("Cancel").also { | |
46 it.addActionListener(ActionListener { close() }) | |
47 it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW) | |
48 } | |
49 | |
50 /* deliberately not the default action, because it changes a file */ | |
51 private val washButton = JButton("Wash").also { | |
52 it.addActionListener(ActionListener { doWash() }) | |
53 it.border = BorderFactory.createEmptyBorder(0, BW, 0, BW) | |
54 } | |
55 | |
56 private lateinit var washing: File | |
57 | |
58 /* initiates the washing of the Exif data */ | |
59 fun wash(dirty: File) { | |
60 title = "Washing: ${image.name}" | |
61 selectAll.setSelected(false) | |
62 washing = dirty | |
63 useWaitCursor() | |
64 swingWorker<Array<Array<Any>>?> { | |
65 inBackground { | |
66 try { | |
67 val image = Image(dirty.canonicalPath) | |
68 val meta = image.meta | |
69 val keys = meta.keys | |
70 keys.sort() | |
71 Array<Array<String>>(keys.size) { | |
72 val key = keys[it] | |
73 val value = meta[key] | |
74 arrayOf(!settingsDialog.whitelist.contains(key), key, value.type, value.value) | |
75 } | |
76 } catch (e: Exiv2Exception) { | |
77 LOGGER.log(Level.SEVERE, "unable to read metadata", e) | |
78 null | |
79 } | |
80 } | |
81 whenDone { | |
82 useNormalCursor() | |
83 val tableData = get() | |
84 if (tableData == null) { | |
85 JOptionPane.showMessageDialog(Application.mainFrame, | |
86 "Unable to read metadata.", | |
87 "Error", JOptionPane.ERROR_MESSAGE) | |
88 } else { | |
89 val colNames = arrayOf("Delete?", "Key", "Type", "Value") | |
90 myTable.apply { | |
91 model = MyTableModel(tableData, colNames) | |
92 validate() | |
93 } | |
94 setVisible(true) | |
95 } | |
96 } | |
97 } | |
98 } | |
99 | |
100 private class MyTableModel : DefaultTableModel { | |
101 override fun isCellEditable(row: Int, col: Int) = col == 0 | |
102 override fun getColumnClass(col: Int) = if (col == 0) { | |
103 Boolean | |
104 } else { | |
105 String | |
106 } | |
107 } | |
108 | |
109 private fun doReset() { | |
110 myTable.model.run { | |
111 for (i in 0 .. rowCount - 1) { | |
112 val key = getValueAt(i, 1) as String | |
113 setValueAt(!settingsDialog.whitelist.contains(key), i, 0) | |
114 } | |
115 } | |
116 myTable.validate() | |
117 } | |
118 | |
119 private fun doWash() { | |
120 setVisible(false) | |
121 | |
122 /* get path to the directory we create */ | |
123 val outDir = if (settingsDialog.outputToInputDir) { | |
124 washing.canonicalFile.parent | |
125 } else { | |
126 settingsDialog.outputTo | |
127 } | |
128 | |
129 /* get new file name */ | |
130 val (name, ext) = splitext(washing.name) | |
131 var newFile = File(outDir, "${name}_washed${ext}") | |
132 | |
133 /* copy the file, then edit the Exif in the copy */ | |
134 useWaitCursor() | |
135 swingWorker<Boolean> { | |
136 inBackground { | |
137 try { | |
138 FileInputStream(washing).use { source -> | |
139 FileOutputStream(newFile).use { target -> | |
140 source.copyTo(target) | |
141 } | |
142 } | |
143 val image = Image(newFile.canonicalPath) | |
144 val meta = image.meta | |
145 meta.keys.forEach { | |
146 if (!settingsDialog.whitelist.contains(it)) { | |
147 meta.erase(it) | |
148 } | |
149 } | |
150 image.store() | |
151 true | |
152 } catch (e: IOException) { | |
153 LOGGER.log(Level.SEVERE, "unable to copy input", e) | |
154 false | |
155 } catch (e: Exiv2Exception) { | |
156 LOGGER.log(Level.SEVERE, "unable to edit metadata", e) | |
157 false | |
158 } | |
159 } | |
160 whenDone { | |
161 useNormalCursor() | |
162 close() | |
163 /* if all went well, show the Exif in the new file */ | |
164 if (get()) { | |
165 ShowDialog().show(newFile) | |
166 } else { | |
167 try { | |
168 if (newFile.exists()) { newFile.delete() } | |
169 } catch (e: IOException) { | |
170 LOGGER.log(Level.SEVERE, "unable to delete", e) | |
171 } | |
172 JOptionPane.showMessageDialog(Application.mainFrame, | |
173 "Error\nUnable to wash: ${washing.canonicalPath}\nTo: ${newFile.canonicalPath}", | |
174 "Error", JOptionPane.ERROR_MESSAGE) | |
175 } | |
176 } | |
177 } | |
178 } | |
179 | |
180 private fun splitext(s: String): Pair<String, String> { | |
181 val pos = s.lastIndexOf('.') | |
182 if (pos == -1) { | |
183 return Pair(s, "") | |
184 } | |
185 return Pair(s.substring(0, pos), s.substring(pos)) | |
186 } | |
187 | |
188 init { | |
189 defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE /* delete if reusing */ | |
190 title = "Untitled" | |
191 contentPane.apply { | |
192 layout = BoxLayout(this, BoxLayout.Y_AXIS) | |
193 add(Box(BoxLayout.Y_AXIS).apply { | |
194 alignmentX = Box.CENTER_ALIGNMENT | |
195 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW) | |
196 add(JScrollPane(myTable).apply { | |
197 alignmentX = JScrollPane.LEFT_ALIGNMENT | |
198 border = BorderFactory.createEmptyBorder(BW, BW, BW, BW) | |
199 verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS | |
200 horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER | |
201 preferredSize = Dimension(WIDTH, HEIGHT) | |
202 background = Application.mainFrame.background | |
203 }) | |
204 add(selectAll.apply { | |
205 alignmentX = JCheckBox.LEFT_ALIGNMENT | |
206 border = BorderFactory.createEmptyBorder(BW, BW, 0, BW) | |
207 }) | |
208 }) | |
209 add(Box(BoxLayout.X_AXIS).apply { | |
210 alignmentX = Box.CENTER_ALIGNMENT | |
211 border = BorderFactory.createEmptyBorder(BW, BW, BW2, BW) | |
212 add(resetButton) | |
213 add(Box.createHorizontalGlue()) | |
214 add(cancelButton) | |
215 add(washButton) | |
216 }) | |
217 } | |
218 pack() | |
219 } | |
220 } |