comparison src/name/blackcap/clipman/Pasteboard.kt @ 0:be282c48010a

Incomplete; checking it in as a backup.
author David Barts <n5jrn@me.com>
date Tue, 14 Jan 2020 14:07:19 -0800
parents
children 9dd58db4d15a
comparison
equal deleted inserted replaced
-1:000000000000 0:be282c48010a
1 /*
2 * We call the clipboard a "pasteboard" for our internal class name, not
3 * because I prefer that term (I don't) but so as to not clash with the
4 * AWT's Clipboard class.
5 */
6 package name.blackcap.clipman
7
8 import java.awt.Toolkit
9 import java.awt.datatransfer.Clipboard
10 import java.awt.datatransfer.ClipboardOwner
11 import java.awt.datatransfer.DataFlavor
12 import java.awt.datatransfer.Transferable
13 import java.awt.datatransfer.UnsupportedFlavorException
14 import java.io.IOException
15 import java.io.InputStream
16 import java.nio.charset.Charset
17 import java.util.logging.Level
18 import java.util.logging.Logger
19 import kotlin.collections.HashMap
20
21 /* Constants, etc. */
22 val CHARSET_NAME = "UTF-8"
23
24 /*
25 * Represents an error dealing with pasteboard items.
26 */
27 class PasteboardError(): Exception()
28
29 /**
30 * Represents an item of data in the clipboard and how to read and
31 * write it.
32 */
33 sealed class PasteboardItem {
34 data class Plain(val plain: String): PasteboardItem()
35 data class HTML(val plain: String, val html: String): PasteboardItem()
36
37 private class PasteboardData(val item: PasteboardItem):
38 Transferable, ClipboardOwner {
39 private val CHARSET = Charset.forName(CHARSET_NAME)
40 private val HTML_FLAVOR = DataFlavor("text/html; document=all; class=\"[B\"; charset=" + CHARSET_NAME)
41 private val _data: HashMap<DataFlavor, Any>
42 private val flavors: Array<DataFlavor>
43
44 init {
45 _data = HashMap<DataFlavor, Any>().apply {
46 when (item) {
47 is Plain -> put(DataFlavor.stringFlavor, item.plain as Any)
48 is HTML -> {
49 put(DataFlavor.stringFlavor, item.plain as Any)
50 put(HTML_FLAVOR, item.html as Any)
51 }
52 }
53 }
54 _data.keys.asIterable().run {
55 flavors = Array<DataFlavor>(count()) { elementAt(it) }
56 }
57 }
58
59 override fun getTransferData(flavor: DataFlavor): Any {
60 return _data.get(flavor) ?: throw UnsupportedFlavorException(flavor)
61 }
62
63 override fun getTransferDataFlavors(): Array<DataFlavor> = flavors
64 override fun isDataFlavorSupported(flavor: DataFlavor) = _data.containsKey(flavor)
65 override fun lostOwnership(clipboard: Clipboard, contents: Transferable) {}
66 }
67
68 companion object {
69 private val CLIPBOARD = Toolkit.getDefaultToolkit().systemClipboard
70
71 /**
72 * Read the item in the pasteboard.
73 * @return a PasteboardItem? object, null if nothing could be read
74 */
75 fun read() : PasteboardItem? {
76 check()
77 var plain = getClipboardData(DataFlavor.stringFlavor)
78 if (plain == null) {
79 return null
80 }
81 var html = getClipboardData(DataFlavor.allHtmlFlavor)
82 if (html == null) {
83 html = htmlFromRTF()
84 }
85 return if (html == null) { Plain(plain) } else { HTML(plain, html) }
86 }
87
88 /**
89 * Write an item to the pasteboard.
90 * @param item a PasteboardItem to write
91 */
92 fun write(item: PasteboardItem) {
93 check()
94 val pbdata = PasteboardData(item)
95 CLIPBOARD.setContents(pbdata, pbdata)
96 }
97
98 private fun check() {
99 if (CLIPBOARD == null) {
100 throw RuntimeException("no clipboard available!")
101 }
102 }
103
104 private fun getClipboardData(flavor: DataFlavor): String? {
105 try {
106 return CLIPBOARD.getData(flavor) as String?
107 } catch (e: IOException) {
108 return null
109 } catch (e: UnsupportedFlavorException) {
110 return null
111 }
112 }
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 }
139 }