# HG changeset patch # User David Barts # Date 1579652658 28800 # Node ID 9dd58db4d15a312607ecf8dc11a98549fff659b4 # Parent 88703ca72fc363c4122ba6db22d58df40aa5116e Only convert RTF to HTML if needed. Much more efficient. diff -r 88703ca72fc3 -r 9dd58db4d15a src/name/blackcap/clipman/Main.kt --- 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()) { diff -r 88703ca72fc3 -r 9dd58db4d15a src/name/blackcap/clipman/Pasteboard.kt --- 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().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 - } - } } }