view src/name/blackcap/clipman/CoerceDialog.kt @ 38:08eaae2aaf76

Stop dialog resizing, fix detection of bad font sizes.
author David Barts <n5jrn@me.com>
date Thu, 30 Jan 2020 22:06:37 -0800
parents 376643a09b52
children 33fbe3a78d84
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.util.logging.Level
import java.util.logging.Logger
import javax.swing.*
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener

class CoerceDialog: JDialog(frame.v), ActionListener {
    private val FONTS =
        GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames.copyOf().apply {
            sort()
        }
    private val SIZES =
        arrayOf(9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 16.0f, 18.0f,
            24.0f, 36.0f, 48.0f, 64.0f, 72.0f, 96.0f, 144.0f, 288.0f)
    private val DSIZEI = 6  /* SIZES[6] = 16 */

    /* the proportional font family */
    private val _pFamily = JComboBox<String>(FONTS).apply {
        selectedIndex = getFontIndex(Font.SERIF)
        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.selectedIndex = DSIZEI
        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 {
        selectedIndex = getFontIndex(Font.MONOSPACED)
        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.selectedIndex = DSIZEI
        it.alignmentX = JComboBox.LEFT_ALIGNMENT
        it.setEditable(true)
    }
    val mSize: Float
    get() {
        return _mSize.selectedItem as Float
    }

    /* standard spacing between elements (10 pixels ≅ 1/7") and half that */
    private val BW = 5
    private val BW2 = 10

    /* buttons */
    private val _coerce = JButton("Coerce").also {
        it.actionCommand = "Coerce"
        it.addActionListener(this)
    }

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

    /* initializer */
    init {
        title = "Coerce Fonts"
        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("Coerce proportionally-spaced text to…"))
                    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("Coerce monospaced text to…"))
                    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.X_AXIS).apply {
                    alignmentX = Box.CENTER_ALIGNMENT
                    border = BorderFactory.createEmptyBorder(BW, BW2, BW, BW2)
                    add(Box.createGlue())
                    add(_cancel)
                    add(Box.createGlue())
                    add(_coerce)
                    add(Box.createGlue())
                })
            })
        }
        rootPane.setDefaultButton(_coerce)
        pack()
        setResizable(false)
    }

    override fun actionPerformed(e: ActionEvent) {
        when (e.actionCommand) {
            "Coerce" -> {
                setVisible(false)
                coerce()
            }
            "Cancel" -> setVisible(false)
        }
    }

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

    private fun coerce() {
        val selected = queue.v.getSelected()
        if (selected == null) {
            JOptionPane.showMessageDialog(frame.v,
                "No item selected.",
                "Error",
                JOptionPane.ERROR_MESSAGE)
        } else {
            val (plain, html) = when (selected.contents) {
                is PasteboardItem.Plain ->
                    Pair(selected.contents.plain, null)
                is PasteboardItem.HTML ->
                    Pair(selected.contents.plain, selected.contents.html)
                is PasteboardItem.RTF ->
                    Pair(selected.contents.plain, selected.contents.html)
            }
            if (html == null) {
                JOptionPane.showMessageDialog(frame.v,
                    "Only styled texts may be coerced.",
                    "Error",
                    JOptionPane.ERROR_MESSAGE)
            } else {
                if (badSize(_pSize, "proportionally-spaced") || badSize(_mSize, "monospaced")) {
                    return
                }
                PasteboardItem.write(
                    PasteboardItem.HTML(
                        plain,
                        coerceHTML(html, normalizeFont(pFamily), pSize,
                            normalizeFont(mFamily), mSize)))
            }
        }
    }

    private fun badSize(control: JComboBox<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.selectedIndex = DSIZEI
            return true
        }
        return false
    }

    private fun getFontIndex(font: String): Int {
        val found = FONTS.indexOf(font)
        if (found < 0) {
            LOGGER.log(Level.WARNING, "font '${font}' not found")
            return 0
        }
        return found
    }

    private fun normalizeFont(font: String): String {
        val lcFont = font.toLowerCase()
        return when (lcFont) {
            in setOf("monospace", "serif", "sans-serif") -> lcFont
            "monospaced" -> "monospace"
            "sansserif" -> "sans-serif"
            else -> font
        }
    }
}

val coerceDialog = CoerceDialog()