Mercurial > cgi-bin > hgweb.cgi > JpegWasher
view src/name/blackcap/exifwasher/Misc.kt @ 60:d0b83fc1d62a default tip
Remember our input directory on a per-invocation basis.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sun, 26 Jul 2020 15:14:03 -0700 |
parents | f5faf70c7d10 |
children |
line wrap: on
line source
/* * Miscellaneous utility stuff. */ package name.blackcap.exifwasher import java.awt.Component import java.awt.Cursor import java.awt.Dimension import java.awt.Font import java.awt.FontMetrics import java.awt.Graphics import java.awt.Point import java.awt.Toolkit import java.awt.event.MouseEvent import javax.swing.* import javax.swing.border.Border import javax.swing.table.TableColumnModel import kotlin.annotation.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.* /** * Delegate that makes a var that can only be set once. This is commonly * needed in Swing, because some vars inevitably need to be declared at * outer levels but initialized in the Swing event dispatch thread. */ class SetOnceImpl<T: Any>: ReadWriteProperty<Any?,T> { private var setOnceValue: T? = null override operator fun getValue(thisRef: Any?, property: KProperty<*>): T { if (setOnceValue == null) { throw RuntimeException("${property.name} has not been initialized") } else { return setOnceValue!! } } @Synchronized override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T): Unit { if (setOnceValue != null) { throw RuntimeException("${property.name} has already been initialized") } setOnceValue = value } } fun <T: Any> setOnce(): SetOnceImpl<T> = SetOnceImpl<T>() /** * Run something in the Swing thread, asynchronously. * @param block lambda containing code to run */ fun inSwingThread(block: () -> Unit) { SwingUtilities.invokeLater(Runnable(block)) } /** * Run something in the Swing thread, synchronously. * @param block lambda containing code to run */ fun inSynSwingThread(block: () -> Unit) { SwingUtilities.invokeAndWait(Runnable(block)) } /** * Make a shortcut for a menu item, using the standard combining key * (control, command, etc.) for the system we're on. * @param key KeyEvent constant describing the key */ fun JMenuItem.makeShortcut(key: Int): Unit { val SC_KEY_MASK = Toolkit.getDefaultToolkit().menuShortcutKeyMask setAccelerator(KeyStroke.getKeyStroke(key, SC_KEY_MASK)) } /** * Given a MenuElement object, get the item whose text matches the * specified text. * @param text to match * @return first matched element, null if no match found */ fun MenuElement.getItem(name: String) : JMenuItem? { subElements.forEach { val jMenuItem = it.component as? JMenuItem if (jMenuItem?.text == name) { return jMenuItem } } return null } /** * Change to the standard wait cursor. */ fun Component.useWaitCursor() { this.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) } /** * Return back to the normal cursor(). */ fun Component.useNormalCursor() { this.cursor = Cursor.getDefaultCursor() } /** * Thrown if the programmer botches something in our DSL. */ class SwingWorkerException(message: String): Exception(message) { } /** * A simplified SwingWorker DSL. It does not support intermediate * results. Just lets one define a background task and something * to execute when complete. * * @param T Type returned by inBackground (Java doInBackground) task. */ class SwingWorkerBuilder<T>: SwingWorker<T,Unit>() { private var inBackgroundLambda: (SwingWorkerBuilder<T>.() -> T)? = null private var whenDoneLambda: (SwingWorkerBuilder<T>.() -> Unit)? = null private fun <U> setOnce(prop: KMutableProperty0<(SwingWorkerBuilder<T>.() -> U)?>, value: SwingWorkerBuilder<T>.() -> U) { if (prop.get() != null) { throw SwingWorkerException(prop.name.removeSuffix("Lambda") + " already defined!") } prop.set(value) } /** * Define the inBackground task. */ fun inBackground(lambda: SwingWorkerBuilder<T>.() -> T): Unit { setOnce<T>(::inBackgroundLambda, lambda) } /** * Define the whenDone task. */ fun whenDone(lambda: SwingWorkerBuilder<T>.() -> Unit): Unit { setOnce<Unit>(::whenDoneLambda, lambda) } /** * Validates we've been properly initialized. */ fun validate(): Unit { if (inBackgroundLambda == null) { throw SwingWorkerException("inBackground not defined!") } } /* standard overrides for SwingWorker follow */ override fun doInBackground(): T = inBackgroundLambda!!.invoke(this) override fun done(): Unit = whenDoneLambda?.invoke(this) ?: Unit } /** * Provides for an outer swingWorker block to contain the DSL. */ fun <T> swingWorker(initializer: SwingWorkerBuilder<T>.() -> Unit): Unit { SwingWorkerBuilder<T>().run { initializer() validate() execute() } } /** * Close a dialog (don't just hide it). */ fun JDialog.close() { setVisible(false) dispose() } /** * Set column width of a table. */ fun JTable.setColWidth(col: Int, width: Int, string: String?) { val FUZZ = 16 columnModel.getColumn(col).preferredWidth = if (string.isNullOrEmpty()) { width } else { maxOf(width, graphics.fontMetrics.stringWidth(string) + FUZZ) } } /** * Set overall width of a table. */ fun JTable.setOverallWidth() { val tcm = columnModel val limit = tcm.columnCount - 1 var total = 0 for (i in 0 .. limit) { total += tcm.getColumn(i).preferredWidth } preferredSize = Dimension(total, preferredSize.height) } /** * A JTable for displaying metadata. Columns that might get harmfully * truncated have tooltips when truncation happens. */ class JExifTable(rowData: Array<Array<out Any?>>, colNames: Array<out Any?>): JTable(rowData, colNames) { override fun getToolTipText(e: MouseEvent): String? { val pos = e.point val col = columnAtPoint(pos) if (!setOf("Key", "Type").contains(getColumnName(col))) { return null } val contents = getValueAt(rowAtPoint(pos), col) as String? if (contents == null) { return null } val needed = graphics.fontMetrics.stringWidth(contents) val actual = columnModel.getColumn(col).width return if (needed > actual) contents else null } } /** * Add a border to a JComponent. The new border is in addition to (and outside * of) whatever existing standard border the component had. */ fun JComponent.addBorder(b: Border) { if (border == null) border = b else border = BorderFactory.createCompoundBorder(b, border) }