comparison src/name/blackcap/exifwasher/Misc.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 * Miscellaneous utility stuff.
3 */
4 package name.blackcap.exifwasher
5
6 import java.awt.Component
7 import java.awt.Cursor
8 import java.awt.Toolkit
9 import javax.swing.*
10 import kotlin.annotation.*
11 import kotlin.properties.ReadWriteProperty
12 import kotlin.reflect.KProperty
13
14 /**
15 * Delegate that makes a var that can only be set once. This is commonly
16 * needed in Swing, because some vars inevitably need to be declared at
17 * outer levels but initialized in the Swing event dispatch thread.
18 */
19 class SetOnce<T: Any>: ReadWriteProperty<Any?,T> {
20 private var value: T? = null
21
22 override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
23 if (value == null) {
24 throw RuntimeException("${property.name} has not been initialized")
25 } else {
26 return value!!
27 }
28 }
29
30 @Synchronized
31 override operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T): Unit {
32 if (value != null) {
33 throw RuntimeException("${property.name} has already been initialized")
34 }
35 value = newValue
36 }
37 }
38
39 /**
40 * Run something in the Swing thread, asynchronously.
41 * @param block lambda containing code to run
42 */
43 fun inSwingThread(block: () -> Unit) {
44 SwingUtilities.invokeLater(Runnable(block))
45 }
46
47 /**
48 * Run something in the Swing thread, synchronously.
49 * @param block lambda containing code to run
50 */
51 fun inSynSwingThread(block: () -> Unit) {
52 SwingUtilities.invokeAndWait(Runnable(block))
53 }
54
55 /**
56 * Make a shortcut for a menu item, using the standard combining key
57 * (control, command, etc.) for the system we're on.
58 * @param key KeyEvent constant describing the key
59 */
60 fun JMenuItem.makeShortcut(key: Int): Unit {
61 val SC_KEY_MASK = Toolkit.getDefaultToolkit().menuShortcutKeyMask
62 setAccelerator(KeyStroke.getKeyStroke(key, SC_KEY_MASK))
63 }
64
65 /**
66 * Given a MenuElement object, get the item whose text matches the
67 * specified text.
68 * @param text to match
69 * @return first matched element, null if no match found
70 */
71 fun MenuElement.getItem(name: String) : JMenuItem? {
72 subElements.forEach {
73 val jMenuItem = it.component as? JMenuItem
74 if (jMenuItem?.text == name) {
75 return jMenuItem
76 }
77 }
78 return null
79 }
80
81 /**
82 * Change to the standard wait cursor.
83 */
84 fun Component.useWaitCursor() {
85 this.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR))
86 }
87
88 /**
89 * Return back to the normal cursor().
90 */
91 fun Component.useNormalCursor() {
92 this.cursor = Cursor.defaultCursor
93 }
94
95 /**
96 * Thrown if the programmer botches something in our DSL.
97 */
98 class SwingWorkerException(message: String): Exception(message) { }
99
100 /**
101 * A simplified SwingWorker DSL. It does not support intermediate
102 * results. Just lets one define a background task and something
103 * to execute when complete.
104 *
105 * @param T Type returned by inBackground (Java doInBackground) task.
106 */
107 class SwingWorkerBuilder<T>: SwingWorker<T,Unit>() {
108 private var inBackgroundLambda: (SwingWorkerBuilder.() -> T)? = null
109 private var whenDoneLambda: (SwingWorkerBuilder.() -> Unit)? = null
110
111 private fun setOnce<U>(prop: KMutableProperty<(SwingWorkerBuilder.() -> U)?>, value: SwingWorkerBuilder.() -> U) {
112 if (prop.get() != null) {
113 throw SwingWorkerException(prop.name.removeSuffix("Lambda") + " already defined!")
114 }
115 prop.set(value)
116 }
117
118 /**
119 * Define the inBackground task.
120 */
121 fun inBackground(lambda: SwingWorkerBuilder.() -> T): Unit {
122 setOnce<T>(::inBackgroundLambda, lambda)
123 }
124
125 /**
126 * Define the whenDone task.
127 */
128 fun whenDone(lambda: SwingWorkerBuilder.() -> Unit): Unit {
129 setOnce<Unit>(::whenDoneLambda, lambda)
130 }
131
132 /* standard overrides for SwingWorker follow */
133
134 override fun doInBackground(): T = inBackgroundLambda?.invoke(this)
135
136 override fun done(): Unit = whenDoneLambda?.invoke(this)
137
138 override fun execute(): Unit {
139 if (inBackgroundLambda == null) {
140 throw SwingWorkerException("inBackground not defined!")
141 } else {
142 super.execute()
143 }
144 }
145 }
146
147 /**
148 * Provides for an outer swingWorker block to contain the DSL.
149 */
150 fun swingWorker<T>(initializer: SwingWorkerBuilder.() -> Unit): Unit {
151 SwingWorkerBuilder<T>().run {
152 initializer()
153 execute()
154 }
155 }
156
157 /**
158 * Close a dialog (don't just hide it).
159 */
160 fun JDialog.close() {
161 setVisible(false)
162 dispose()
163 }