All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tri.covid19.coda.utils.AutocompleteTextField.kt Maven / Gradle / Ivy

/*-
 * #%L
 * coda-app
 * --
 * Copyright (C) 2020 - 2021 Elisha Peterson
 * --
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package tri.covid19.coda.utils

import javafx.event.EventHandler
import javafx.geometry.Side
import javafx.scene.control.ContextMenu
import javafx.scene.control.CustomMenuItem
import javafx.scene.control.Label
import javafx.scene.control.TextField
import javafx.scene.paint.Color
import javafx.scene.text.Font
import javafx.scene.text.FontWeight
import javafx.scene.text.Text
import javafx.scene.text.TextFlow
import java.util.*


/** Text field that autocompletes. */
class AutocompleteTextField(var options: SortedSet = sortedSetOf()): TextField() {

    private val autocompleteOptions = ContextMenu()

    init {
        focusedProperty().addListener { _, _, _ -> autocompleteOptions.hide() }
        textProperty().addListener { _, _, _ ->
            val enteredText = text
            var filtered = if (text.isNullOrEmpty()) listOf() else options.filter { it.toLowerCase().contains(enteredText.toLowerCase()) }
            when {
                filtered.isEmpty() -> autocompleteOptions.hide()
                else -> {
                    filtered = filtered.filter { it.toLowerCase().startsWith(enteredText.toLowerCase()) } +
                            filtered.filter { !it.toLowerCase().startsWith(enteredText.toLowerCase()) }
                    updatePopup(filtered, enteredText)
                    if (!autocompleteOptions.isShowing && [email protected] != null) {
                        autocompleteOptions.show(this@AutocompleteTextField, Side.BOTTOM, 0.0, 0.0)
                    }
                }
            }
        }
    }

    private fun updatePopup(searchResult: List, enteredText: String) {
        val menuItems = searchResult.take(10)
                .map { it to highlightLabel(it, enteredText) }
                .map {
                    CustomMenuItem(it.second, true).apply {
                        onAction = EventHandler { _ ->
                            [email protected] = it.first
                            positionCaret(it.first.length)
                            autocompleteOptions.hide()
                        }
                    }
                }

        autocompleteOptions.items.setAll(menuItems)
    }
}

internal fun highlightLabel(label: String, enteredText: String) = Label().apply {
    graphic = buildTextFlow(label, enteredText)
    prefHeight = 10.0
}

internal fun buildTextFlow(text: String, filter: String): TextFlow {
    val filterIndex = text.toLowerCase().indexOf(filter.toLowerCase())
    val textFilter = Text(text.substring(filterIndex, filterIndex + filter.length))
    textFilter.fill = Color.ORANGE
    textFilter.font = Font.font("Helvetica", FontWeight.BOLD, 12.0)
    return TextFlow(Text(text.substring(0, filterIndex)), textFilter, Text(text.substring(filterIndex + filter.length)))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy