Mercurial > cgi-bin > hgweb.cgi > ClipMan
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 } |