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 }