view src/name/blackcap/clipman/Main.kt @ 45:ca8a23bae4fa

Adjust spacing in preferences.
author David Barts <n5jrn@me.com>
date Mon, 10 Feb 2020 06:55:07 -0700
parents 339e2da5bf83
children 19d9da731c43
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.Container
import java.awt.Dimension
import java.awt.Font
import java.awt.datatransfer.*
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
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.text.html.StyleSheet
import kotlin.concurrent.thread
import org.jsoup.Jsoup
import org.jsoup.nodes.*

/* name we call ourselves */
val MYNAME = "ClipMan"

/* default sizes */
val CPWIDTH = 640
val CPHEIGHT = 480

/* width of main panel border */
val PANEL_BORDER = 9

/* default font sizes in the text-display panes */
val MONO_SIZE = 14
val PROP_SIZE = 16

/* the queue of data we deal with and the main application frame */
val queue = LateInit<PasteboardQueue>()
val frame = LateInit<JFrame>()

/* kills the updating thread (and does a system exit) when needed */
class KillIt() : 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) {
        LOGGER.log(Level.INFO, "execution complete")
        System.exit(0)
    }
}

/* the updating thread */
class UpdateIt(val interval: Int): Thread(), MouseListener {
    @Volatile var enabled = true

    override fun run() {
        var oldContents: PasteboardItem? = null
        while (true) {
            if (enabled) {
                var contents = PasteboardItem.read()
                if ((contents != null) && (contents != oldContents)) {
                    val (plain, html) = when(contents) {
                        is PasteboardItem.Plain -> Pair(contents.plain, null)
                        is PasteboardItem.HTML -> Pair(null, contents.html)
                        is PasteboardItem.RTF -> Pair(contents.plain, contents.html)
                    }
                    val piv = if (html == null) {
                        PasteboardItemView("Plain text", ClipText(contents).apply {
                            contentType = "text/plain"
                            text = plain
                            font = Font(Font.MONOSPACED, Font.PLAIN, MONO_SIZE)
                            resize()
                        })
                    } else {
                        val (dhtml, style) = preproc(html)
                        val hek = MyEditorKit().apply {
                            style.addStyleSheet(defaultStyleSheet)
                            styleSheet = style
                        }
                        PasteboardItemView("Styled text", ClipText(contents).apply {
                            editorKit = hek
                            text = dhtml
                            resize()
                        })
                    }
                    piv.searchable.addMouseListener(this)
                    queue.v.add(QueueItem(contents, piv))
                    oldContents = contents
                }
            }
            if (Thread.interrupted()) {
                return
            }
            try {
                Thread.sleep(interval - System.currentTimeMillis() % interval)
            } catch (e: InterruptedException) {
                return
            }
        }
    }

    private fun preproc(html: String): Pair<String, StyleSheet> {
        val sty = StyleSheet().apply {
            addRule("body { font-family: serif; font-size: ${PROP_SIZE}; }")
            addRule("code, kbd, pre, samp, tt { font-family: monospace; font-size: ${MONO_SIZE}; }")
        }
        val scrubbed = Jsoup.parse(html).run {
            select("style").forEach {
                it.dataNodes().forEach { sty.addRule(it.wholeData) }
            }
            select(":root>head>meta").remove()
            outputSettings()
                .charset(CHARSET_NAME)
                .syntax(Document.OutputSettings.Syntax.xml)
            outerHtml()
        }
        return Pair(scrubbed, sty)
    }

    /* MouseListener methods */

    override fun mouseClicked(e: MouseEvent) {
        val source = e.getSource() as? ClipText
        if (source == null) {
            return
        }
        queue.v.deselectAll()
        source.selected = true
        source.validate()
        anyRequired.enable()
        source.basedOn.let {
            if (it is PasteboardItem.HTML || it is PasteboardItem.RTF) {
                styledRequired.enable()
            } else {
                styledRequired.disable()
            }
        }
    }

 	override fun mousePressed(e: MouseEvent) {
 	    maybeShowPopup(e)
 	}

 	override fun mouseReleased(e: MouseEvent) {
 	    maybeShowPopup(e)
 	}

 	private fun maybeShowPopup(e: MouseEvent) {
 	    if (e.isPopupTrigger()) {
 	        popupMenu.show(e.component, e.x, e.y)
 	    }
 	}

 	override fun mouseEntered(e: MouseEvent) { }
 	override fun mouseExited(e: MouseEvent) { }
}

/* entry point */
fun main(args: Array<String>) {
    LOGGER.log(Level.INFO, "beginning execution")
    if (OS.type == OS.MAC) {
        System.setProperty("apple.laf.useScreenMenuBar", "true")
    }
    lateinit var con: JPanel
    inSynSwingThread {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        frame.v = JFrame(MYNAME)
        con = JPanel().apply {
            layout = BoxLayout(this, BoxLayout.Y_AXIS)
            border = BorderFactory.createEmptyBorder(PANEL_BORDER, PANEL_BORDER, PANEL_BORDER, PANEL_BORDER)
            background = frame.v.background
        }
        frame.v.jMenuBar = menuBar
        frame.v.apply {
            contentPane.add(
                JScrollPane(con).apply {
                    verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
                    horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
                    preferredSize = Dimension(CPWIDTH, CPHEIGHT)
                    background = frame.v.background
                }, BorderLayout.CENTER)
            pack()
            setVisible(true)
            addWindowListener(KillIt())
        }
        setMacMenus()
    }
    queue.v = PasteboardQueue(con, settingsDialog.qLength)
    UpdateIt(1000).apply { start() }
}