comparison src/name/blackcap/clipman/Pasteboard.kt @ 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 be282c48010a
children 96cc73ae2904
comparison
equal deleted inserted replaced
16:88703ca72fc3 17:9dd58db4d15a
10 import java.awt.datatransfer.ClipboardOwner 10 import java.awt.datatransfer.ClipboardOwner
11 import java.awt.datatransfer.DataFlavor 11 import java.awt.datatransfer.DataFlavor
12 import java.awt.datatransfer.Transferable 12 import java.awt.datatransfer.Transferable
13 import java.awt.datatransfer.UnsupportedFlavorException 13 import java.awt.datatransfer.UnsupportedFlavorException
14 import java.io.IOException 14 import java.io.IOException
15 import java.io.InputStream 15 import java.io.ByteArrayInputStream
16 import java.nio.charset.Charset 16 import java.nio.charset.Charset
17 import java.util.logging.Level 17 import java.util.logging.Level
18 import java.util.logging.Logger 18 import java.util.logging.Logger
19 import kotlin.collections.HashMap 19 import kotlin.collections.HashMap
20 20
29 /** 29 /**
30 * Represents an item of data in the clipboard and how to read and 30 * Represents an item of data in the clipboard and how to read and
31 * write it. 31 * write it.
32 */ 32 */
33 sealed class PasteboardItem { 33 sealed class PasteboardItem {
34 data class Plain(val plain: String): PasteboardItem() 34 /* the three possibilities for this class */
35 data class HTML(val plain: String, val html: String): PasteboardItem() 35
36 class Plain(val plain: String): PasteboardItem()
37
38 class HTML(val plain: String, val html: String): PasteboardItem()
39
40 class RTF(val plain: String, val rtf: ByteArray): PasteboardItem() {
41 private var failed = false
42
43 /* lazy conversion to HTML */
44 private var _html: String? = null
45 val html: String?
46 get() {
47 if (failed || _html != null) {
48 return _html
49 }
50 _html = htmlFromRTF()
51 failed = _html == null
52 return _html
53 }
54
55 private fun htmlFromRTF(): String? {
56 ByteArrayInputStream(rtf).use {
57 val (html, errors) = rtfToHtml(it)
58 if (errors != null) {
59 LOGGER.log(Level.WARNING, errors)
60 return null
61 }
62 return html
63 }
64 }
65 }
66
67 /* we use this when writing data back to the clipboard */
36 68
37 private class PasteboardData(val item: PasteboardItem): 69 private class PasteboardData(val item: PasteboardItem):
38 Transferable, ClipboardOwner { 70 Transferable, ClipboardOwner {
39 private val CHARSET = Charset.forName(CHARSET_NAME) 71 private val CHARSET = Charset.forName(CHARSET_NAME)
40 private val HTML_FLAVOR = DataFlavor("text/html; document=all; class=\"[B\"; charset=" + CHARSET_NAME) 72 private val HTML_FLAVOR = DataFlavor("text/html; document=all; class=\"[B\"; charset=" + CHARSET_NAME)
42 private val flavors: Array<DataFlavor> 74 private val flavors: Array<DataFlavor>
43 75
44 init { 76 init {
45 _data = HashMap<DataFlavor, Any>().apply { 77 _data = HashMap<DataFlavor, Any>().apply {
46 when (item) { 78 when (item) {
47 is Plain -> put(DataFlavor.stringFlavor, item.plain as Any) 79 is Plain -> put(DataFlavor.stringFlavor, item.plain as Any)
48 is HTML -> { 80 is HTML -> {
49 put(DataFlavor.stringFlavor, item.plain as Any) 81 put(DataFlavor.stringFlavor, item.plain as Any)
50 put(HTML_FLAVOR, item.html as Any) 82 put(HTML_FLAVOR, item.html.toByteArray(CHARSET) as Any)
83 }
84 is RTF -> {
85 put(DataFlavor.stringFlavor, item.plain as Any)
86 if (item.html != null) {
87 put(HTML_FLAVOR, item.html?.toByteArray(CHARSET) as Any)
88 }
51 } 89 }
52 } 90 }
53 } 91 }
54 _data.keys.asIterable().run { 92 _data.keys.asIterable().run {
55 flavors = Array<DataFlavor>(count()) { elementAt(it) } 93 flavors = Array<DataFlavor>(count()) { elementAt(it) }
63 override fun getTransferDataFlavors(): Array<DataFlavor> = flavors 101 override fun getTransferDataFlavors(): Array<DataFlavor> = flavors
64 override fun isDataFlavorSupported(flavor: DataFlavor) = _data.containsKey(flavor) 102 override fun isDataFlavorSupported(flavor: DataFlavor) = _data.containsKey(flavor)
65 override fun lostOwnership(clipboard: Clipboard, contents: Transferable) {} 103 override fun lostOwnership(clipboard: Clipboard, contents: Transferable) {}
66 } 104 }
67 105
106 override operator fun equals(other: Any?): Boolean {
107 return when (this) {
108 is Plain -> (other is Plain) && (this.plain == other.plain)
109 is HTML -> (other is HTML) && (this.html == other.html)
110 is RTF -> (other is RTF) && (this.rtf contentEquals other.rtf)
111 }
112 }
113
68 companion object { 114 companion object {
115 private val RTF_FLAVOR = DataFlavor("text/rtf; class=\"[B\"")
69 private val CLIPBOARD = Toolkit.getDefaultToolkit().systemClipboard 116 private val CLIPBOARD = Toolkit.getDefaultToolkit().systemClipboard
70 117
71 /** 118 /**
72 * Read the item in the pasteboard. 119 * Read the item in the pasteboard.
73 * @return a PasteboardItem? object, null if nothing could be read 120 * @return a PasteboardItem? object, null if nothing could be read
74 */ 121 */
75 fun read() : PasteboardItem? { 122 fun read() : PasteboardItem? {
76 check() 123 check()
77 var plain = getClipboardData(DataFlavor.stringFlavor) 124 val plain = getClipboardData(DataFlavor.stringFlavor) as String?
78 if (plain == null) { 125 if (plain == null) {
79 return null 126 return null
80 } 127 }
81 var html = getClipboardData(DataFlavor.allHtmlFlavor) 128 val html = getClipboardData(DataFlavor.allHtmlFlavor) as String?
82 if (html == null) { 129 if (html == null) {
83 html = htmlFromRTF() 130 val rtf = getClipboardData(RTF_FLAVOR) as ByteArray?
131 return if (rtf == null) { Plain(plain) } else { RTF(plain, rtf) }
132 } else {
133 return HTML(plain, html)
84 } 134 }
85 return if (html == null) { Plain(plain) } else { HTML(plain, html) }
86 } 135 }
87 136
88 /** 137 /**
89 * Write an item to the pasteboard. 138 * Write an item to the pasteboard.
90 * @param item a PasteboardItem to write 139 * @param item a PasteboardItem to write
99 if (CLIPBOARD == null) { 148 if (CLIPBOARD == null) {
100 throw RuntimeException("no clipboard available!") 149 throw RuntimeException("no clipboard available!")
101 } 150 }
102 } 151 }
103 152
104 private fun getClipboardData(flavor: DataFlavor): String? { 153 private fun getClipboardData(flavor: DataFlavor): Any? {
105 try { 154 try {
106 return CLIPBOARD.getData(flavor) as String? 155 return CLIPBOARD.getData(flavor)
107 } catch (e: IOException) { 156 } catch (e: IOException) {
108 return null 157 return null
109 } catch (e: UnsupportedFlavorException) { 158 } catch (e: UnsupportedFlavorException) {
110 return null 159 return null
111 } 160 }
112 } 161 }
113
114 private fun htmlFromRTF(): String? {
115 /* see if there's an appropriate flavor */
116 var rtf: DataFlavor? = null
117 for (flavor in CLIPBOARD.availableDataFlavors) {
118 if (flavor.isRepresentationClassInputStream() &&
119 "text".equals(flavor.primaryType ?: "", ignoreCase=true) &&
120 "rtf".equals(flavor.subType ?: "", ignoreCase=true)) {
121 rtf = flavor
122 break
123 }
124 }
125 if (rtf == null) {
126 return null
127 }
128
129 (CLIPBOARD.getData(rtf) as InputStream).use {
130 val (html, errors) = rtfToHtml(it)
131 if (errors != null) {
132 LOGGER.log(Level.WARNING, errors)
133 return null
134 }
135 return html
136 }
137 }
138 } 162 }
139 } 163 }