view src/name/blackcap/clipman/SettingsDialog.kt @ 42:99220aa136d9

Add default.properties.
author David Barts <n5jrn@me.com>
date Sat, 08 Feb 2020 23:05:39 -0700
parents 33fbe3a78d84
children 339e2da5bf83
line wrap: on
line source

/*
 * The dialog that controls font corecion.
 */
package name.blackcap.clipman

import java.awt.Color
import java.awt.Container
import java.awt.Dimension
import java.awt.Font
import java.awt.GraphicsEnvironment
import java.awt.Toolkit
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.io.BufferedWriter
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.toFloat
import kotlin.text.toInt

/* work around name shadowing */
private val _PROPS = PROPERTIES

class SettingsDialog: JDialog(frame.v), ActionListener, ChangeListener {
    /* the proportional font family */
    private val _pFamily = JComboBox<String>(FONTS).apply {
        selectedItem = _PROPS.getString("prop.family")
        alignmentX = JComboBox.LEFT_ALIGNMENT
    }
    val pFamily: String
    get() {
        return _pFamily.selectedItem as String
    }

    /* the proportional font size */
    private val _pSize = JComboBox<Float>(SIZES).also {
        it.selectedItem = _PROPS.getFloat("prop.size")
        it.alignmentX = JComboBox.LEFT_ALIGNMENT
        it.setEditable(true)
    }
    val pSize: Float
    get() {
        return _pSize.selectedItem as Float
    }

    /* the monospaced font family */
    private val _mFamily = JComboBox<String>(FONTS).apply {
        selectedItem = _PROPS.getString("mono.family")
        alignmentX = JComboBox.LEFT_ALIGNMENT
    }
    val mFamily: String
    get() {
        return _mFamily.selectedItem as String
    }

    /* the monospaced font size */
    private val _mSize = JComboBox<Float>(SIZES).also {
        it.selectedItem = _PROPS.getFloat("mono.size")
        it.alignmentX = JComboBox.LEFT_ALIGNMENT
        it.setEditable(true)
    }
    val mSize: Float
    get() {
        return _mSize.selectedItem as Float
    }

    /* max queue length */
    private val _qLength = _PROPS.getInt("queue.length")
    private val _qlSlider = JSlider(10000, 30000, spinToSlide(_qLength)).also {
        it.majorTickSpacing = 10000
        it.paintTicks = true
        it.labelTable = Hashtable<Int, String>().apply {
            put(10000, "10")
            put(20000, "100")
            put(30000, "1000")
        }
        it.addChangeListener(this)
    }
    private val _qlSpinner = JSpinner(SpinnerNumberModel(_qLength, 10, 1000, 1)).also {
        it.addChangeListener(this)
    }
    val qLength: Int
    get() {
        return _qlSpinner.value as Int
    }

    /* 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.actionCommand = "OK"
        it.addActionListener(this)
    }

    private val _cancel = JButton("Cancel").also {
        it.actionCommand = "Cancel"
        it.addActionListener(this)
    }

    private val _rad = JButton("Restore All Defaults").also {
        it.actionCommand = "Restore"
        it.addActionListener(this)
    }

    /* initializer */
    init {
        title = "Preferences"
        contentPane.apply {
            add(Box(BoxLayout.Y_AXIS).apply {
                add(Box(BoxLayout.Y_AXIS).apply {
                    border = BorderFactory.createEmptyBorder(BW2, BW2, BW, BW2)
                    alignmentX = Box.CENTER_ALIGNMENT
                    add(leftLabel("Default proportionally-spaced font:"))
                    add(Box.createVerticalStrut(BW))
                    add(Box(BoxLayout.X_AXIS).apply {
                        alignmentX = Box.LEFT_ALIGNMENT
                        add(Box.createGlue())
                        add(Box(BoxLayout.Y_AXIS).apply {
                            add(leftLabel("Family:"))
                            add(_pFamily)
                        })
                        add(Box.createGlue())
                        add(Box(BoxLayout.Y_AXIS).apply {
                            add(leftLabel("Size:"))
                            add(_pSize)
                        })
                        add(Box.createGlue())
                    })
                })
                add(JSeparator())
                add(Box(BoxLayout.Y_AXIS).apply {
                    alignmentX = Box.CENTER_ALIGNMENT
                    border = BorderFactory.createEmptyBorder(BW, BW2, BW, BW2)
                    add(leftLabel("Default monospaced font:"))
                    add(Box.createVerticalStrut(BW))
                    add(Box(BoxLayout.X_AXIS).apply {
                        alignmentX = Box.LEFT_ALIGNMENT
                        add(Box.createGlue())
                        add(Box(BoxLayout.Y_AXIS).apply {
                            add(leftLabel("Family:"))
                            add(_mFamily)
                        })
                        add(Box.createGlue())
                        add(Box(BoxLayout.Y_AXIS).apply {
                            add(leftLabel("Size:"))
                            add(_mSize)
                        })
                        add(Box.createGlue())
                    })
                })
                add(JSeparator())
                add(Box(BoxLayout.Y_AXIS).apply {
                    alignmentX = Box.CENTER_ALIGNMENT
                    border = BorderFactory.createEmptyBorder(BW, BW2, BW, BW2)
                    add(leftLabel("Maximum queue size:"))
                    add(Box.createVerticalStrut(BW))
                    add(Box(BoxLayout.X_AXIS).apply {
                        alignmentX = Box.LEFT_ALIGNMENT
                        add(Box.createGlue())
                        add(_qlSlider)
                        add(Box.createGlue())
                        add(_qlSpinner)
                        add(Box.createGlue())
                    })
                })
                add(JSeparator())
                add(Box(BoxLayout.X_AXIS).apply {
                    alignmentX = Box.CENTER_ALIGNMENT
                    border = BorderFactory.createEmptyBorder(BW, BW2, BW, BW2)
                    add(Box.createGlue())
                    add(_rad)
                    add(Box.createGlue())
                    add(_cancel)
                    add(Box.createGlue())
                    add(_ok)
                    add(Box.createGlue())
                })
            })
        }
        rootPane.setDefaultButton(_ok)
        pack()
        setResizable(false)
    }

    override fun actionPerformed(e: ActionEvent) {
        when (e.actionCommand) {
            "OK" -> {
                writeProperties()
                queue.v.maxSize = qLength
                setVisible(false)
            }
            "Cancel" -> setVisible(false)
            "Restore" -> revertProperties()
        }
    }

    override fun stateChanged(e: ChangeEvent) {
        when (val source = e.source) {
            source === _qlSlider ->
                _qlSpinner.value = (10.0).pow(_qlSlider.value.toDouble() / 10000.0).roundToInt()
            source === _qlSpinner ->
                _qlSlider.value = spinToSlide(_qlSpinner.value as Int)
        }
    }

    private fun spinToSlide(value: Int): Int =
        (log10(value.toDouble()) * 10000.0).roundToInt()

    private fun leftLabel(text: String) = JLabel(text).apply {
        alignmentX = JLabel.LEFT_ALIGNMENT
    }

    private fun badSize(control: JComboBox<Float>, default: Float, fontType: String): Boolean {
        val size = control.selectedItem as? Float
        if (size == null || size < 1.0f) {
            JOptionPane.showMessageDialog(frame.v,
                "Invalid ${fontType} font size.",
                "Error",
                JOptionPane.ERROR_MESSAGE)
            control.selectedItem = default
            return true
        }
        return false
    }

    private fun revertProperties()
    {
        val params = arrayOf("mono.family", "mono.size", "prop.family", "prop.size", "queue.length")
        for (param in params) {
            _PROPS.put(param, DPROPERTIES.get(param))
        }
        _mFamily.selectedItem = _PROPS.getString("mono.family")
        _mSize.selectedItem = _PROPS.getFloat("mono.size")
        _pFamily.selectedItem = _PROPS.getString("prop.family")
        _pSize.selectedItem = _PROPS.getFloat("prop.size")
        val ql = _PROPS.getInt("queue.length")
        _qlSpinner.value = ql
        _qlSlider.value = spinToSlide(ql)
    }

    private fun writeProperties()
    {
        try {
            BufferedWriter(OutputStreamWriter(FileOutputStream(PROP_FILE), CHARSET)).use {
                _PROPS.put("mono.family", mFamily)
                _PROPS.put("mono.size", mSize.toString())
                _PROPS.put("prop.family", pFamily)
                _PROPS.put("prop.size", pSize.toString())
                _PROPS.put("queue.length", qLength.toString())
                _PROPS.store(it, null)
            }
        } 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(frame.v,
                "Unable to write settings.",
                "Error",
                JOptionPane.ERROR_MESSAGE)
        }
    }
}

val settingsDialog = SettingsDialog()

fun Properties.getString(key: String): String = get(key) as String

fun Properties.getInt(key: String): Int = getString(key).toInt()

fun Properties.getFloat(key: String): Float = getString(key).toFloat()