Mercurial > cgi-bin > hgweb.cgi > ClipMan
view src/name/blackcap/clipman/PasteboardQueue.kt @ 28:f1fcc1281dad
Use BorderFactory; clean up Find dialog.
author | David Barts <n5jrn@me.com> |
---|---|
date | Wed, 29 Jan 2020 13:39:14 -0800 |
parents | 8aa2dfac27eb |
children | c4f53bc01732 |
line wrap: on
line source
/* * The queue of pasteboard items we manage. New stuff gets added to the * tail, and old stuff truncated off the head. */ package name.blackcap.clipman import java.awt.Container import java.awt.Rectangle import java.util.Collections import java.util.LinkedList import java.util.logging.Level import java.util.logging.Logger import javax.swing.* import javax.swing.text.DefaultHighlighter /** * A queue that tracks the data we display and the widgets used to * display them. We never explicitly remove stuff from the queue, * though items will get silently discarded to prevent the queue from * exceeding the specified maximum size. */ class PasteboardQueue(val parent: Container, maxSize: Int) { private val queue = LinkedList<QueueItem>() private var _maxSize = maxSize private var scrollPane: JScrollPane? = null init { var sp: Container? = parent while (sp != null) { if (sp is JScrollPane) { scrollPane = sp break } sp = sp.parent } } data class Offset(var inQueue: Int, var inItem: Int) enum class Direction { FORWARDS, BACKWARDS } /** * The maximum allowed size of this queue. Attempts to make the queue * larger than this size, or specifying a size smaller than the current * size, will result in the oldest item(s) being discarded. A size less * than or equal to zero means an unlimited size. */ var maxSize: Int get() { return _maxSize } @Synchronized set(value) { _maxSize = value truncate() } /** * Add a QueueItem to the end of the queue. * @param item QueueItem to add */ @Synchronized fun add(item: QueueItem) { inSwingThread { parent.add(item.view.contents) scrollPane?.run { validate() verticalScrollBar.run { value = maximum + 1 } } } queue.addLast(item) truncate() } /** * Delete something from the queue. * @param object to delete */ @Synchronized fun delete(item: QueueItem) { if (queue.remove(item)) { parent.validate() } } /** * Find and highlight the next occurrence of the specified string * @param string to search * @param whether to search backwards (default forwards) * @param case-folding flag (default true) * @param starting point (0, 0) for forwards, (m, n) for backwards * @return position where start of string was found, or null */ fun find(needle: String, direction: Direction = Direction.FORWARDS, foldCase: Boolean = true, origin: Offset? = null): Offset? { /* clean up any old highlights */ queue.forEach { val hiliter = it.view.searchable.highlighter hiliter.highlights.forEach { hiliter.removeHighlight(it) } } /* get starting item index */ val qMax = queue.size var norigin = origin ?: when (direction) { Direction.FORWARDS -> Offset(0, 0) Direction.BACKWARDS -> Offset(qMax - 1, -1) } /* loop initialization */ val (start, incr, search) = if (direction == Direction.FORWARDS) { Triple( 0, 1, { n: String, h: String, o: Int -> h.indexOf(n, o, foldCase) }) } else { Triple(-1, -1, { n: String, h: String, o: Int -> h.lastIndexOf(n, o, foldCase) }) } val painter = DefaultHighlighter.DefaultHighlightPainter(null); var pos = -1 /* try and find it */ while (norigin.inQueue >= 0 && norigin.inQueue < qMax) { val si = queue.get(norigin.inQueue).view.searchable val doc = si.document val text = doc.getText(0, doc.length) pos = if (norigin.inItem >= 0) norigin.inItem else text.length - 1 pos = search(needle, text, pos) if (pos >= 0) { si.highlighter.addHighlight(pos, pos+needle.length, painter) break } norigin = Offset(norigin.inQueue + incr, start) } return if (pos >= 0) Offset(norigin.inQueue, pos) else null } /** * Ensure none of the searchables in this queue are selected. */ fun deselectAll() { queue.forEach { val s = it.view.searchable as? ClipText if (s != null && s.selected) { s.selected = false s.validate() } } } /** * Return the selected item, or null if nothing has been selected */ fun getSelected(): QueueItem? { queue.forEach { if ((it.view.searchable as? ClipText)?.selected ?: false) { return it } } return null } private fun truncate() { if (_maxSize > 0) { var size = queue.size var dirty = false while (size > _maxSize) { var extra = queue.removeFirst().view inSwingThread { if (extra.searchable.selected) { SelectionRequired.disable() } parent.remove(extra.contents) } dirty = true size -= 1 } if (dirty) { inSwingThread { parent.validate() } } } } } /** * An item in the above queue. Linking model to view here sorta violates * MVC principles, but rules are sometimes best broken. Doing it this way * makes it impossible for the view queue to fail to follow the data * queue. */ data class QueueItem(val contents: PasteboardItem, val view: PasteboardItemView)