changeset 17:9dd58db4d15a

Only convert RTF to HTML if needed. Much more efficient.
author David Barts <n5jrn@me.com>
date Tue, 21 Jan 2020 16:24:18 -0800
parents 88703ca72fc3
children 96cc73ae2904
files src/name/blackcap/clipman/Main.kt src/name/blackcap/clipman/Pasteboard.kt
diffstat 2 files changed, 102 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/src/name/blackcap/clipman/Main.kt	Tue Jan 21 13:07:35 2020 -0800
+++ b/src/name/blackcap/clipman/Main.kt	Tue Jan 21 16:24:18 2020 -0800
@@ -16,6 +16,7 @@
 import java.util.concurrent.Semaphore
 import java.util.logging.Level
 import java.util.logging.Logger
+import java.util.zip.CRC32
 import javax.swing.*
 import javax.swing.border.*
 import javax.swing.text.JTextComponent
@@ -78,59 +79,55 @@
             EmptyBorder(MARGIN_BORDER, MARGIN_BORDER, MARGIN_BORDER, MARGIN_BORDER))
 
     override fun run() {
-        var oldContents = ""
-        var newContents = ""
+        var oldContents: PasteboardItem? = null
         while (true) {
             if (enabled) {
                 var contents = PasteboardItem.read()
-                if (contents != null) {
-                    newContents = when (contents) {
-                        is PasteboardItem.Plain -> contents.plain
-                        is PasteboardItem.HTML -> contents.html
+                if ((contents != null) && (contents != oldContents)) {
+                    val stdWidth = queue.parent.size.width - 2 * (PANEL_BORDER+OUTER_BORDER+INNER_BORDER+MARGIN_BORDER)
+                    val widget = JPanel().apply {
+                        layout = BoxLayout(this, BoxLayout.Y_AXIS)
+                        background = queue.parent.background
+                        border = outerBorder
+                    }
+                    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)
                     }
-                    if (oldContents != newContents) {
-                        var stdWidth: Int? = null
-                        inSynSwingThread {
-                            stdWidth = queue.parent.size.width - 2 * (PANEL_BORDER+OUTER_BORDER+INNER_BORDER+MARGIN_BORDER)
-                        }
-                        var widget = JPanel().apply {
-                            layout = BoxLayout(this, BoxLayout.Y_AXIS)
-                            background = queue.parent.background
-                            border = outerBorder
+                    if (html == null) {
+                        widget.run {
+                            add(stdLabel("Plain text"))
+                            add(ClipText().apply {
+                                contentType = "text/plain"
+                                text = plain
+                                font = Font(Font.MONOSPACED, Font.PLAIN, MONO_SIZE)
+                                border = stdBorder
+                                autoSize(stdWidth)
+                                setEditable(false)
+                                alignmentX = JTextPane.LEFT_ALIGNMENT
+                            })
                         }
-                        when (contents) {
-                            is PasteboardItem.Plain -> widget.run {
-                                add(stdLabel("Plain text"))
-                                add(ClipText().apply {
-                                    contentType = "text/plain"
-                                    text = contents.plain
-                                    font = Font(Font.MONOSPACED, Font.PLAIN, MONO_SIZE)
-                                    border = stdBorder
-                                    autoSize(stdWidth!!)
-                                    setEditable(false)
-                                    alignmentX = JTextPane.LEFT_ALIGNMENT
-                                })
+                    } else {
+                        widget.run {
+                            add(stdLabel("Styled text"))
+                            val (dhtml, style) = preproc(html)
+                            val hek = HTMLEditorKit().apply {
+                                style.addStyleSheet(styleSheet)
+                                styleSheet = style
                             }
-                            is PasteboardItem.HTML -> widget.run {
-                                add(stdLabel("Styled text"))
-                                val (html, style) = preproc(contents.html)
-                                val hek = HTMLEditorKit().apply {
-                                    style.addStyleSheet(styleSheet)
-                                    styleSheet = style
-                                }
-                                add(ClipText().apply {
-                                    editorKit = hek
-                                    text = html
-                                    border = stdBorder
-                                    autoSize(stdWidth!!)
-                                    setEditable(false)
-                                    alignmentX = JTextPane.LEFT_ALIGNMENT
-                                })
-                            }
+                            add(ClipText().apply {
+                                editorKit = hek
+                                text = dhtml
+                                border = stdBorder
+                                autoSize(stdWidth)
+                                setEditable(false)
+                                alignmentX = JTextPane.LEFT_ALIGNMENT
+                            })
                         }
-                        queue.add(QueueItem(widget, contents))
-                        oldContents = newContents
                     }
+                    queue.add(QueueItem(widget, contents))
+                    oldContents = contents
                 }
             }
             if (Thread.interrupted()) {
--- a/src/name/blackcap/clipman/Pasteboard.kt	Tue Jan 21 13:07:35 2020 -0800
+++ b/src/name/blackcap/clipman/Pasteboard.kt	Tue Jan 21 16:24:18 2020 -0800
@@ -12,7 +12,7 @@
 import java.awt.datatransfer.Transferable
 import java.awt.datatransfer.UnsupportedFlavorException
 import java.io.IOException
-import java.io.InputStream
+import java.io.ByteArrayInputStream
 import java.nio.charset.Charset
 import java.util.logging.Level
 import java.util.logging.Logger
@@ -31,8 +31,40 @@
  * write it.
  */
 sealed class PasteboardItem {
-    data class Plain(val plain: String): PasteboardItem()
-    data class HTML(val plain: String, val html: String): PasteboardItem()
+    /* the three possibilities for this class */
+
+    class Plain(val plain: String): PasteboardItem()
+
+    class HTML(val plain: String, val html: String): PasteboardItem()
+
+    class RTF(val plain: String, val rtf: ByteArray): PasteboardItem() {
+        private var failed = false
+
+        /* lazy conversion to HTML */
+        private var _html: String? = null
+        val html: String?
+        get() {
+            if (failed || _html != null) {
+                return _html
+            }
+            _html = htmlFromRTF()
+            failed = _html == null
+            return _html
+        }
+
+        private fun htmlFromRTF(): String? {
+            ByteArrayInputStream(rtf).use {
+                val (html, errors) = rtfToHtml(it)
+                if (errors != null) {
+                    LOGGER.log(Level.WARNING, errors)
+                    return null
+                }
+                return html
+            }
+        }
+    }
+
+    /* we use this when writing data back to the clipboard */
 
     private class PasteboardData(val item: PasteboardItem):
     Transferable, ClipboardOwner {
@@ -44,10 +76,16 @@
         init {
             _data = HashMap<DataFlavor, Any>().apply {
                 when (item) {
-                    is Plain ->  put(DataFlavor.stringFlavor, item.plain as Any)
+                    is Plain -> put(DataFlavor.stringFlavor, item.plain as Any)
                     is HTML -> {
                         put(DataFlavor.stringFlavor, item.plain as Any)
-                        put(HTML_FLAVOR, item.html as Any)
+                        put(HTML_FLAVOR, item.html.toByteArray(CHARSET) as Any)
+                    }
+                    is RTF -> {
+                        put(DataFlavor.stringFlavor, item.plain as Any)
+                        if (item.html != null) {
+                            put(HTML_FLAVOR, item.html?.toByteArray(CHARSET) as Any)
+                        }
                     }
                 }
             }
@@ -65,7 +103,16 @@
         override fun lostOwnership(clipboard: Clipboard, contents: Transferable) {}
     }
 
+    override operator fun equals(other: Any?): Boolean {
+        return when (this) {
+            is Plain -> (other is Plain) && (this.plain == other.plain)
+            is HTML -> (other is HTML) && (this.html == other.html)
+            is RTF -> (other is RTF) && (this.rtf contentEquals other.rtf)
+        }
+    }
+
     companion object {
+        private val RTF_FLAVOR = DataFlavor("text/rtf; class=\"[B\"")
         private val CLIPBOARD = Toolkit.getDefaultToolkit().systemClipboard
 
         /**
@@ -74,15 +121,17 @@
          */
         fun read() : PasteboardItem? {
             check()
-            var plain = getClipboardData(DataFlavor.stringFlavor)
+            val plain = getClipboardData(DataFlavor.stringFlavor) as String?
             if (plain == null) {
                 return null
             }
-            var html = getClipboardData(DataFlavor.allHtmlFlavor)
+            val html = getClipboardData(DataFlavor.allHtmlFlavor) as String?
             if (html == null) {
-                html = htmlFromRTF()
+                val rtf = getClipboardData(RTF_FLAVOR) as ByteArray?
+                return if (rtf == null) { Plain(plain) } else { RTF(plain, rtf) }
+            } else {
+                return HTML(plain, html)
             }
-            return if (html == null) { Plain(plain) } else { HTML(plain, html) }
         }
 
         /**
@@ -101,39 +150,14 @@
             }
         }
 
-        private fun getClipboardData(flavor: DataFlavor): String? {
+        private fun getClipboardData(flavor: DataFlavor): Any? {
             try {
-                return CLIPBOARD.getData(flavor) as String?
+                return CLIPBOARD.getData(flavor)
             } catch (e: IOException) {
                 return null
             } catch (e: UnsupportedFlavorException) {
                 return null
             }
         }
-
-        private fun htmlFromRTF(): String? {
-            /* see if there's an appropriate flavor */
-            var rtf: DataFlavor? = null
-            for (flavor in CLIPBOARD.availableDataFlavors) {
-                if (flavor.isRepresentationClassInputStream() &&
-                    "text".equals(flavor.primaryType ?: "", ignoreCase=true) &&
-                    "rtf".equals(flavor.subType ?: "", ignoreCase=true)) {
-                        rtf = flavor
-                        break
-                }
-            }
-            if (rtf == null) {
-                return null
-            }
-
-            (CLIPBOARD.getData(rtf) as InputStream).use {
-                val (html, errors) = rtfToHtml(it)
-                if (errors != null) {
-                    LOGGER.log(Level.WARNING, errors)
-                    return null
-                }
-                return html
-            }
-        }
     }
 }