comparison src/name/blackcap/clipman/PasteboardQueue.kt @ 27:8aa2dfac27eb

Big reorg; compiled but untested.
author David Barts <n5jrn@me.com>
date Wed, 29 Jan 2020 10:50:07 -0800
parents c10a447b9e1b
children c4f53bc01732
comparison
equal deleted inserted replaced
26:ff35fabaea3a 27:8aa2dfac27eb
9 import java.util.Collections 9 import java.util.Collections
10 import java.util.LinkedList 10 import java.util.LinkedList
11 import java.util.logging.Level 11 import java.util.logging.Level
12 import java.util.logging.Logger 12 import java.util.logging.Logger
13 import javax.swing.* 13 import javax.swing.*
14 import javax.swing.text.JTextComponent 14 import javax.swing.text.DefaultHighlighter
15 15
16 /** 16 /**
17 * A queue that tracks the data we display and the widgets used to 17 * A queue that tracks the data we display and the widgets used to
18 * display them. We never explicitly remove stuff from the queue, 18 * display them. We never explicitly remove stuff from the queue,
19 * though items will get silently discarded to prevent the queue from 19 * though items will get silently discarded to prevent the queue from
54 * Add a QueueItem to the end of the queue. 54 * Add a QueueItem to the end of the queue.
55 * @param item QueueItem to add 55 * @param item QueueItem to add
56 */ 56 */
57 @Synchronized fun add(item: QueueItem) { 57 @Synchronized fun add(item: QueueItem) {
58 inSwingThread { 58 inSwingThread {
59 parent.add(item.component) 59 parent.add(item.view.contents)
60 scrollPane?.run { 60 scrollPane?.run {
61 validate() 61 validate()
62 verticalScrollBar.run { value = maximum + 1 } 62 verticalScrollBar.run { value = maximum + 1 }
63 } 63 }
64 } 64 }
65 queue.addLast(item) 65 queue.addLast(item)
66 truncate() 66 truncate()
67 }
68
69 /**
70 * Delete something from the queue.
71 * @param object to delete
72 */
73 @Synchronized fun delete(item: QueueItem) {
74 if (queue.remove(item)) {
75 parent.validate()
76 }
67 } 77 }
68 78
69 /** 79 /**
70 * Find and highlight the next occurrence of the specified string 80 * Find and highlight the next occurrence of the specified string
71 * @param string to search 81 * @param string to search
75 * @return position where start of string was found, or null 85 * @return position where start of string was found, or null
76 */ 86 */
77 fun find(needle: String, direction: Direction = Direction.FORWARDS, 87 fun find(needle: String, direction: Direction = Direction.FORWARDS,
78 foldCase: Boolean = true, origin: Offset? = null): Offset? 88 foldCase: Boolean = true, origin: Offset? = null): Offset?
79 { 89 {
80 /* canonicalize the origin */ 90 /* clean up any old highlights */
81 val norigin = if (origin == null) { 91 queue.forEach {
82 if (direction == Direction.FORWARDS) { 92 val hiliter = it.view.searchable.highlighter
83 Offset(0, 0) 93 hiliter.highlights.forEach {
84 } else { 94 hiliter.removeHighlight(it)
85 Offset(queue.size - 1, queue.last.searchable.document.length)
86 } 95 }
87 } else {
88 origin
89 } 96 }
90 97
91 /* XXX - not finished */ 98 /* get starting item index */
99 val qMax = queue.size
100 var norigin = origin ?: when (direction) {
101 Direction.FORWARDS -> Offset(0, 0)
102 Direction.BACKWARDS -> Offset(qMax - 1, -1)
103 }
104
105 /* loop initialization */
106 val (start, incr, search) = if (direction == Direction.FORWARDS) {
107 Triple( 0, 1, { n: String, h: String, o: Int -> h.indexOf(n, o, foldCase) })
108 } else {
109 Triple(-1, -1, { n: String, h: String, o: Int -> h.lastIndexOf(n, o, foldCase) })
110 }
111 val painter = DefaultHighlighter.DefaultHighlightPainter(null);
112 var pos = -1
113
114 /* try and find it */
115 while (norigin.inQueue >= 0 && norigin.inQueue < qMax) {
116 val si = queue.get(norigin.inQueue).view.searchable
117 val doc = si.document
118 val text = doc.getText(0, doc.length)
119 pos = if (norigin.inItem >= 0) norigin.inItem else text.length - 1
120 pos = search(needle, text, pos)
121 if (pos >= 0) {
122 si.highlighter.addHighlight(pos, pos+needle.length, painter)
123 break
124 }
125 norigin = Offset(norigin.inQueue + incr, start)
126 }
127 return if (pos >= 0) Offset(norigin.inQueue, pos) else null
128 }
129
130 /**
131 * Ensure none of the searchables in this queue are selected.
132 */
133 fun deselectAll() {
134 queue.forEach {
135 val s = it.view.searchable as? ClipText
136 if (s != null && s.selected) {
137 s.selected = false
138 s.validate()
139 }
140 }
141 }
142
143 /**
144 * Return the selected item, or null if nothing has been selected
145 */
146 fun getSelected(): QueueItem? {
147 queue.forEach {
148 if ((it.view.searchable as? ClipText)?.selected ?: false) {
149 return it
150 }
151 }
92 return null 152 return null
93 } 153 }
94 154
95 private fun truncate() { 155 private fun truncate() {
96 if (_maxSize > 0) { 156 if (_maxSize > 0) {
97 var size = queue.size 157 var size = queue.size
98 var dirty = false 158 var dirty = false
99 while (size > _maxSize) { 159 while (size > _maxSize) {
100 var extra = queue.removeFirst().component 160 var extra = queue.removeFirst().view
101 inSwingThread { parent.remove(extra) } 161 inSwingThread {
162 if (extra.searchable.selected) {
163 SelectionRequired.disable()
164 }
165 parent.remove(extra.contents)
166 }
102 dirty = true 167 dirty = true
103 size -= 1 168 size -= 1
104 } 169 }
105 if (dirty) { 170 if (dirty) {
106 inSwingThread { parent.validate() } 171 inSwingThread { parent.validate() }
108 } 173 }
109 } 174 }
110 } 175 }
111 176
112 /** 177 /**
113 * An item in the above queue. 178 * An item in the above queue. Linking model to view here sorta violates
179 * MVC principles, but rules are sometimes best broken. Doing it this way
180 * makes it impossible for the view queue to fail to follow the data
181 * queue.
114 */ 182 */
115 data class QueueItem( 183 data class QueueItem(val contents: PasteboardItem, val view: PasteboardItemView)
116 val component: JComponent,
117 val searchable: JTextComponent,
118 val contents: PasteboardItem)