# HG changeset patch # User David Barts # Date 1605982535 28800 # Node ID 71029c9bf7cd39279cb5c4b662a99eee41f79b6d # Parent 5fa5d15b4a7bf5a46ac0539f59472986becb7e1b Commit overlooked files. diff -r 5fa5d15b4a7b -r 71029c9bf7cd src/name/blackcap/imageprep/MaxDimSpinner.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/name/blackcap/imageprep/MaxDimSpinner.kt Sat Nov 21 10:15:35 2020 -0800 @@ -0,0 +1,34 @@ +/* + * A standard spinner for inputting the maximum dimension of an image. + */ +package name.blackcap.imageprep + +import java.awt.Toolkit +import javax.swing.* +import javax.swing.event.ChangeEvent +import javax.swing.event.ChangeListener +import java.util.logging.Level +import java.util.logging.Logger + +/* maximum allowable maximum dimension */ +private val MAXDIM = 16384 + +/* preferred standard dimensions */ +private val STDDIMS = listOf(1600, 1280, 1024, 800, 640, 512, 400, 320) + +class MaxDimSpinner(val default: Int): JSpinner(SpinnerListModel(STDDIMS)) +{ + init { + editor = JSpinner.ListEditor(this) + value = default + noTaller() + addChangeListener( ChangeListener { + val v = value as? Int ?: (value as? String)?.toIntOrNull() + if ( v == null || v < 1 || v > MAXDIM ) { + LOGGER.log(Level.INFO, "bad max dimension: $value") + value = default + Toolkit.getDefaultToolkit().beep() + } + }) + } +} diff -r 5fa5d15b4a7b -r 71029c9bf7cd src/name/blackcap/imageprep/Menus.kt --- a/src/name/blackcap/imageprep/Menus.kt Fri Nov 20 21:38:06 2020 -0800 +++ b/src/name/blackcap/imageprep/Menus.kt Sat Nov 21 10:15:35 2020 -0800 @@ -85,7 +85,7 @@ private fun doOpen() { val maxDim = MaxDimSpinner(Application.settingsDialog.maxDimension) val acc = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) + layout = BoxLayout(this, BoxLayout.Y_AXIS) add(JLabel("Max. dimension:")) add(maxDim) } @@ -123,7 +123,7 @@ } val outQual = OutQualSpinner(Application.settingsDialog.outputQuality) val acc = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) + layout = BoxLayout(this, BoxLayout.Y_AXIS) add(JLabel("Quality:")) add(outQual) } diff -r 5fa5d15b4a7b -r 71029c9bf7cd src/name/blackcap/imageprep/OutQualSpinner.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/name/blackcap/imageprep/OutQualSpinner.kt Sat Nov 21 10:15:35 2020 -0800 @@ -0,0 +1,32 @@ +/* + * A standard spinner for inputting the output quality of a JPEG image. + */ +package name.blackcap.imageprep + +import java.awt.Toolkit +import javax.swing.* +import javax.swing.event.ChangeEvent +import javax.swing.event.ChangeListener +import java.util.logging.Level +import java.util.logging.Logger + +/* allowable JPEG quality range */ +private val MINQUAL = 0 +private val MAXQUAL = 100 + +class OutQualSpinner(val default: Int): JSpinner(SpinnerNumberModel(default, MINQUAL, MAXQUAL, 1)) +{ + init { + editor = JSpinner.NumberEditor(this) + value = default + noTaller() + addChangeListener( ChangeListener { + val v = value as? Int + if (v == null || v < MINQUAL || v > MAXQUAL) { + LOGGER.log(Level.INFO, "bad output quality: $value") + value = default + Toolkit.getDefaultToolkit().beep() + } + }) + } +} diff -r 5fa5d15b4a7b -r 71029c9bf7cd src/name/blackcap/imageprep/SettingsDialog.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/name/blackcap/imageprep/SettingsDialog.kt Sat Nov 21 10:15:35 2020 -0800 @@ -0,0 +1,227 @@ +/* + * The dialog that controls font corecion. + */ +package name.blackcap.imageprep + +import java.awt.Dimension +import java.awt.event.ActionEvent +import java.awt.event.ActionListener +import java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStreamWriter +import java.util.Hashtable +import java.util.Properties +import java.util.logging.Level +import java.util.logging.Logger +import javax.swing.* +import javax.swing.event.ChangeEvent +import javax.swing.event.ChangeListener +import kotlin.math.log10 +import kotlin.math.pow +import kotlin.math.roundToInt +import kotlin.text.toInt + +/* work around name shadowing */ +private val _PROPS = PROPERTIES + +class SettingsDialog: JDialog(Application.mainFrame) { + /* maximum allowed dimension in output */ + private val _maxDimension = MaxDimSpinner(_PROPS.getInt("maxDimension")) + val maxDimension: Int + get() { + return _maxDimension.value as Int + } + + /* JPEG output quality */ + private val _outputQuality = OutQualSpinner(_PROPS.getInt("outputQuality")) + val outputQuality: Int + get() { + return _outputQuality.value as Int + } + + /* file name output suffix */ + private val _outputSuffix = JTextField(_PROPS.getProperty("outputSuffix"), 24).apply { + noTaller() + } + val outputSuffix: String + get() { + return _outputSuffix.text + } + + /* output to input dir? */ + private val otid = _PROPS.getBoolean("outputToInputDir") + private val outputToInputButton = JRadioButton("Create output file in same folder as input.", otid) + val outputToInputDir: Boolean + get() { + return outputToInputButton.isSelected() + } + + /* output to some other directory */ + private val outputToButton = JRadioButton("Create output file in:", !otid) + + /* name of the other directory */ + private val oto = _getOutputTo() + protected val outputToText = JTextField(oto, 40).apply { + setEditable(false) + noTaller() + } + + /* chooser for other directory */ + private val _outputTo = JFileChooser(oto).apply { + fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + setMultiSelectionEnabled(false) + } + val outputTo: String + get() { + return _outputTo.selectedFile?.getCanonicalPath() ?: oto + } + + /* button to change that other directory */ + protected val changeOutputTo = JButton("Change").also { + it.noTaller() + it.addActionListener(ActionListener { + val status = _outputTo.showOpenDialog(this) + if (status == JFileChooser.APPROVE_OPTION) { + val path = outputTo + outputToText.text = path + } + }) + it.setEnabled(!otid) + } + + /* radio button group */ + protected val buttonGroup = ButtonGroup().apply { + add(outputToButton) + add(outputToInputButton) + } + + /* standard spacing between elements (10 pixels ≅ 1/7") and half that */ + private val BW = 5 + private val BW2 = 10 + + /* buttons */ + private val _ok = JButton("OK").also { + it.noTaller() + it.addActionListener(ActionListener { + writeProperties() + setVisible(false) + }) + } + + private val _cancel = JButton("Cancel").also { + it.noTaller() + it.addActionListener(ActionListener { + revertValues() + setVisible(false) + }) + } + + /* initializer */ + init { + title = "Preferences" + contentPane.apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + add(Box(BoxLayout.X_AXIS).apply { + border = BorderFactory.createEmptyBorder(BW2, BW2, BW, BW2) + add(leftLabel("Maximum dimension:")) + add(_maxDimension) + add(Box.createGlue()) + add(leftLabel("Output quality:")) + add(_outputQuality) + }) + add(Box(BoxLayout.X_AXIS).apply { + border = BorderFactory.createEmptyBorder(BW, BW2, BW, BW2) + add(leftLabel("Output filename suffix:")) + add(_outputSuffix) + add(Box.createGlue()) + }) + add(Box(BoxLayout.Y_AXIS).apply { + border = BorderFactory.createEmptyBorder(BW, BW2, BW2, BW2) + add(outputToInputButton) + add(outputToButton) + add(Box(BoxLayout.X_AXIS).apply { + add(outputToText) + add(changeOutputTo) + add(Box.createGlue()) + }) + }) + add(Box(BoxLayout.X_AXIS).apply { + add(_cancel) + add(Box.createGlue()) + add(_ok) + }) + } + rootPane.setDefaultButton(_ok) + pack() + setResizable(false) + } + + private fun leftLabel(text: String) = JLabel(text).apply { + alignmentX = JLabel.LEFT_ALIGNMENT + } + + private fun revertValues() + { + _maxDimension.value = _PROPS.getInt("maxDimension") + _outputQuality.value = _PROPS.getInt("outputQuality") + _outputSuffix.text = _PROPS.getProperty("outputSuffix") + val otid = _PROPS.getBoolean("outputToInputDir") + outputToInputButton.setSelected(otid) + outputToButton.setSelected(!otid) + val oto = _getOutputTo() + outputToText.text = oto + _outputTo.selectedFile = File(oto) + } + + private fun writeProperties() + { + _PROPS.setInt("maxDimension", maxDimension) + _PROPS.setInt("outputQuality", outputQuality) + _PROPS.setProperty("outputSuffix", outputSuffix) + _PROPS.setBoolean("outputToInputDir", outputToInputDir) + _PROPS.setProperty("outputTo", outputTo) + try { + BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use { + _PROPS.store(it, " -*- coding: ${CHARSET} -*-") + } + } catch (e: IOException) { + LOGGER.log(Level.WARNING, "IOException writing properties file") + val message = e.message + if (message != null && !message.isEmpty()) { + LOGGER.log(Level.WARNING, message) + } + JOptionPane.showMessageDialog(Application.mainFrame, + "Unable to write settings.", + "Error", + JOptionPane.ERROR_MESSAGE) + } + } + + private fun _getOutputTo(): String = _PROPS.getProperty("outputTo") ?: System.getProperty("user.dir") +} + +fun Properties.getString(key: String): String = getProperty(key) as String + +fun Properties.getInt(key: String): Int = getString(key).toInt() + +fun Properties.setInt(key: String, value: Int): Unit { + setProperty(key, value.toString()) +} + +fun Properties.getBoolean(key: String): Boolean { + val raw = getProperty(key) + if (raw.isNullOrEmpty()) + return false + val c1 = raw[0].toLowerCase() + return c1 == 't' || c1 == 'y' +} + +fun Properties.setBoolean(key: String, value: Boolean): Unit { + setProperty(key, value.toString()) +} + +fun JComponent.noTaller() { + maximumSize = Dimension(maximumSize.width, preferredSize.height) +}