view src/name/blackcap/clipman/CoerceDialog.kt @ 56:22725d4d7849

An attempt to get it to troff-ize styled text.
author David Barts <n5jrn@me.com>
date Sat, 19 Mar 2022 23:04:40 -0700
parents 19d9da731c43
children 88056f373a94
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(Application.frame), 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)

    /* 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.selectedItem = PROP_SIZE.toFloat()
        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.selectedItem = MONO_SIZE.toFloat()
        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 = Application.queue.getSelected()
        if (!suitedForCoercing(selected)) {
            return
        }
        if (selected == null) {
            return /* redundant, but makes kotlinc happy */
        }
        if (badSize(_pSize, PROP_SIZE, "proportionally-spaced") || badSize(_mSize, MONO_SIZE, "monospaced")) {
            return
        }
        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)
        }
        PasteboardItem.write(
            PasteboardItem.HTML(
                plain,
                coerceHTML(html!!, normalizeFont(pFamily), pSize,
                    normalizeFont(mFamily), mSize)))
    }

    private fun badSize(control: JComboBox<Float>, default: Int, fontType: String): Boolean {
        val size = control.selectedItem as? Float
        if (size == null || size < 1.0f) {
            JOptionPane.showMessageDialog(Application.frame,
                "Invalid ${fontType} font size.",
                "Error",
                JOptionPane.ERROR_MESSAGE)
            control.selectedItem = default.toFloat()
            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
        }
    }
}

/**
 * See if the selected pasteboard item is suitable for coercing. If not,
 * issue an error dialog.
 */
fun suitedForCoercing(selected: QueueItem?): Boolean {
    if (selected == null) {
        JOptionPane.showMessageDialog(Application.frame,
            "No item selected.",
            "Error",
            JOptionPane.ERROR_MESSAGE)
        return false
    }
    if (selected.contents is PasteboardItem.Plain) {
        JOptionPane.showMessageDialog(Application.frame,
            "Only styled texts may be coerced.",
            "Error",
            JOptionPane.ERROR_MESSAGE)
        return false
    }
    return true
}