view src/name/blackcap/clipman/Main.kt @ 4:debe0413280f

Use JPanel.
author David Barts <n5jrn@me.com>
date Sat, 18 Jan 2020 12:31:53 -0800
parents 70caa73e32f7
children 0640efd6b54a
line wrap: on
line source

/*
 * The entry point and most of the view logic is here.
 */
package name.blackcap.clipman

import java.awt.BorderLayout
import java.awt.Color
import java.awt.Container
import java.awt.Dimension
import java.awt.Font
import java.awt.Toolkit;
import java.awt.datatransfer.*
import java.awt.event.WindowEvent
import java.awt.event.WindowListener
import java.util.Date
import java.util.logging.Level
import java.util.logging.Logger
import javax.swing.*
import javax.swing.border.CompoundBorder
import javax.swing.border.EmptyBorder
import javax.swing.border.LineBorder
import javax.swing.text.JTextComponent
import kotlin.concurrent.thread
import org.jsoup.Jsoup
import org.jsoup.nodes.*


/* kills the updating thread (and does a system exit) when needed */
class KillIt(val thr: Thread) : WindowListener {
    // events we don't care about
    override fun windowActivated(e: WindowEvent) {}
    override fun windowClosed(e: WindowEvent) {}
    override fun windowDeactivated(e: WindowEvent) {}
    override fun windowDeiconified(e: WindowEvent) {}
    override fun windowIconified(e: WindowEvent) {}
    override fun windowOpened(e: WindowEvent) {}

    // and the one we do
    override fun windowClosing(e: WindowEvent) {
        thr.run { interrupt(); join() }
        System.exit(0)
    }
}

/* the updating thread */
class UpdateIt(val queue: PasteboardQueue, val interval: Int): Thread() {
    @Volatile var enabled = true
    private val stdBorder =
        CompoundBorder(EmptyBorder(5, 10, 5, 10), LineBorder(Color.GRAY, 1))

    override fun run() {
        var oldContents = ""
        var newContents = ""
        while (true) {
            if (enabled) {
                var contents = PasteboardItem.read()
                if (contents != null) {
                    newContents = when (contents) {
                        is PasteboardItem.Plain -> contents.plain
                        is PasteboardItem.HTML -> contents.plain
                    }
                    if (oldContents != newContents) {
                        var widget: JComponent = when(contents) {
                            is PasteboardItem.Plain -> JTextPane().apply {
                                contentType = "text/plain"
                                toolTipText = "Plain text"
                                text = contents.plain
                                font = Font(Font.MONOSPACED, Font.PLAIN, 14)
                                border = stdBorder
                                autoSize(600)
                                setEditable(false)
                            }
                            is PasteboardItem.HTML -> JTextPane().apply {
                                contentType = "text/html"
                                toolTipText = "Styled text"
                                text = scrub(contents.html)
                                border = stdBorder
                                autoSize(600)
                                setEditable(false)
                            }
                        }
                        queue.add(QueueItem(widget, contents))
                        oldContents = newContents
                    }
                }
            }
            if (Thread.interrupted()) {
                return
            }
            try {
                Thread.sleep(interval - System.currentTimeMillis() % interval)
            } catch (e: InterruptedException) {
                return
            }
        }
    }

    private fun scrub(html: String): String {
        return Jsoup.parse(html).run {
            select(":root>head>meta").remove()
            outputSettings()
                .charset(CHARSET_NAME)
                .syntax(Document.OutputSettings.Syntax.xml)
            outerHtml()
        }
    }
}

fun main(args: Array<String>) {
    LOGGER.log(Level.INFO, "beginning execution")
    val con = JPanel().apply {
        layout = BoxLayout(this, BoxLayout.Y_AXIS)
    }
    var frame: JFrame? = null
    inSwingThread {
        frame = JFrame("ClipMan").apply {
            preferredSize = Dimension(640, 480)
            contentPane.add(
                JScrollPane(con).apply {
                    verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
                    horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
                    preferredSize = Dimension(640, 480)
                }, BorderLayout.CENTER)
            pack()
            setVisible(true)
        }
    }
    val queue = PasteboardQueue(con, 10)
    val updater = UpdateIt(queue, 1000).apply { start() }
    inSwingThread { frame?.addWindowListener(KillIt(updater)) }
    LOGGER.log(Level.INFO, "execution complete")
}

fun inSwingThread(block: () -> Unit) {
    SwingUtilities.invokeLater(Runnable(block))
}

fun JTextComponent.autoSize(width: Int): Unit {
    val SLOP = 10
    val dim = Dimension(width, width)
    preferredSize = dim
    size = dim
    val r = modelToView(document.length)
    preferredSize = Dimension(width, r.y + r.height + SLOP)
}