Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

import com.intellij.openapi.util.Key
import java.awt.event.ActionListener
import java.awt.event.FocusAdapter
import java.awt.event.FocusEvent
import java.awt.event.ItemEvent
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
Expand All @@ -26,6 +24,7 @@
object FilteringComboBox {

private val popupOpened = PopupOpened()
private val popupSelection = PopupSelection()

fun <T> create(
toString: (T?) -> String = { it.toString() },
Expand Down Expand Up @@ -53,6 +52,7 @@
toString: (T) -> String
): PopupMenuListener = object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
popupSelection.backup(comboBox)
val allItems = comboBox.filteringModel().getAllItems()
val editorText = getEditorComponent(comboBox)?.text ?: ""
val visible = if (popupOpened.isProgrammatic(comboBox)) {
Expand All @@ -65,8 +65,13 @@
popupOpened.reset(comboBox)
}

override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {}
override fun popupMenuCanceled(e: PopupMenuEvent?) {}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
popupSelection.restore(comboBox)
}

override fun popupMenuCanceled(e: PopupMenuEvent?) {
popupSelection.restore(comboBox)
}
}

private fun <T> onListItemRendered(toString: (T) -> String): ListCellRenderer<T> =
Expand Down Expand Up @@ -121,15 +126,16 @@
) {
popupOpened.setProgrammatic(true, comboBox)
val editor = getEditorComponent(comboBox)
val selection = Selection(comboBox.editor.editorComponent as? JTextComponent).backup()
val JTextSelection = JTextSelection(comboBox.editor.editorComponent as? JTextComponent).backup()

Check notice on line 129 in src/main/kotlin/com/redhat/devtools/gateway/view/ui/FilteringComboBox.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Local variable naming convention

Local variable name `JTextSelection` should start with a lowercase letter

val currentTextInEditor = editor?.text ?: ""
comboBox.filteringModel().showOnly(items)

editor?.text = currentTextInEditor

// Restore the selection after the model and text are set
selection.restore()
JTextSelection.restore()
popupSelection.backup(comboBox)
comboBox.selectedIndex = -1 // prevent pre-selection

if (items.isNotEmpty() && !comboBox.isPopupVisible) {
Expand Down Expand Up @@ -224,12 +230,12 @@
}
}

private class Selection(private val component: JTextComponent?) {
private class JTextSelection(private val component: JTextComponent?) {

private var selectionStart = -1
private var selectionEnd = -1

fun backup(): Selection {
fun backup(): JTextSelection {
if (component == null) {
return this
}
Expand All @@ -247,6 +253,26 @@
}
}

private class PopupSelection {

private val key = Key.create<Any?>("${FilteringComboBox.javaClass.name}Selection")

fun backup(comboBox: JComboBox<*>) {
if (comboBox.getClientProperty(key) == null) {
// only backup once (multiple selection changes happen in normal flow)
comboBox.putClientProperty(key, comboBox.selectedItem)
}
}

fun restore(comboBox: JComboBox<*>) {
val backedUp = comboBox.getClientProperty(key) ?: return
comboBox.putClientProperty(key, null)
if (comboBox.selectedIndex == -1) {
comboBox.selectedItem = backedUp
}
}
}

private class PopupOpened {

private val key = Key.create<Boolean>("isPopupProgrammatic")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeEach
import java.awt.event.KeyEvent
import javax.swing.*
import javax.swing.event.PopupMenuEvent

class FilteringComboBoxTest {

Expand Down Expand Up @@ -552,6 +553,31 @@ class FilteringComboBoxTest {
assertThat(comboBox.itemCount).isEqualTo(0)
}

@Test
fun `#popupMenu does preserve selection when popup is closed without choosing`() {
// given
items.forEach { comboBox.addItem(it) }
SwingUtilities.invokeAndWait {
comboBox.selectedItem = items[2]
}
Thread.sleep(100)
val popupEvent = PopupMenuEvent(comboBox)

// when - open popup then close without selecting (same as clicking outside)
SwingUtilities.invokeAndWait {
comboBox.popupMenuListeners.forEach { listener ->
listener.popupMenuWillBecomeVisible(popupEvent)
listener.popupMenuWillBecomeInvisible(popupEvent)
}
}

// then
SwingUtilities.invokeAndWait {
assertThat(comboBox.selectedItem).isEqualTo(items[2])
assertThat(comboBox.selectedIndex).isGreaterThanOrEqualTo(0)
}
}

@Test
fun `#PopupOpened does handle programmatic popup opening vs user opening`() {
// given
Expand Down
Loading