1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ui.layout
4 import com.intellij.BundleBase
5 import com.intellij.icons.AllIcons
6 import com.intellij.openapi.actionSystem.AnAction
7 import com.intellij.openapi.actionSystem.DataContext
8 import com.intellij.openapi.actionSystem.DefaultActionGroup
9 import com.intellij.openapi.actionSystem.PlatformDataKeys
10 import com.intellij.openapi.actionSystem.ex.ActionUtil
11 import com.intellij.openapi.fileChooser.FileChooserDescriptor
12 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
13 import com.intellij.openapi.observable.properties.GraphProperty
14 import com.intellij.openapi.project.Project
15 import com.intellij.openapi.ui.ComboBox
16 import com.intellij.openapi.ui.TextFieldWithBrowseButton
17 import com.intellij.openapi.ui.ValidationInfo
18 import com.intellij.openapi.ui.panel.ComponentPanelBuilder
19 import com.intellij.openapi.ui.popup.JBPopupFactory
20 import com.intellij.openapi.util.text.StringUtil
21 import com.intellij.openapi.vfs.VirtualFile
22 import com.intellij.ui.*
23 import com.intellij.ui.components.*
24 import com.intellij.ui.components.fields.ExpandableTextField
25 import com.intellij.util.Function
26 import com.intellij.util.execution.ParametersListUtil
27 import com.intellij.util.ui.UIUtil
28 import org.jetbrains.annotations.ApiStatus
29 import org.jetbrains.annotations.Nls
30 import java.awt.Component
31 import java.awt.Dimension
32 import java.awt.event.ActionEvent
33 import java.awt.event.ActionListener
34 import java.awt.event.ItemEvent
35 import java.awt.event.MouseEvent
37 import java.util.concurrent.atomic.AtomicBoolean
39 import javax.swing.event.DocumentEvent
40 import kotlin.jvm.internal.CallableReference
41 import kotlin.reflect.KMutableProperty0
44 annotation class CellMarker
46 data class PropertyBinding<V>(val get: () -> V, val set: (V) -> Unit)
49 internal fun <T> createPropertyBinding(prop: KMutableProperty0<T>, propType: Class<T>): PropertyBinding<T> {
50 if (prop is CallableReference) {
52 val receiver = (prop as CallableReference).boundReceiver
53 if (receiver != null) {
54 val baseName = name.removePrefix("is")
55 val nameCapitalized = StringUtil.capitalize(baseName)
56 val getterName = if (name.startsWith("is")) name else "get$nameCapitalized"
57 val setterName = "set$nameCapitalized"
58 val receiverClass = receiver::class.java
61 val getter = receiverClass.getMethod(getterName)
62 val setter = receiverClass.getMethod(setterName, propType)
63 return PropertyBinding({ getter.invoke(receiver) as T }, { setter.invoke(receiver, it) })
65 catch (e: Exception) {
70 val field = receiverClass.getDeclaredField(name)
71 field.isAccessible = true
72 return PropertyBinding({ field.get(receiver) as T }, { field.set(receiver, it) })
74 catch (e: Exception) {
79 return PropertyBinding(prop.getter, prop.setter)
82 fun <T> PropertyBinding<T>.toNullable(): PropertyBinding<T?> {
83 return PropertyBinding<T?>({ get() }, { set(it!!) })
86 inline fun <reified T : Any> KMutableProperty0<T>.toBinding(): PropertyBinding<T> {
87 return createPropertyBinding(this, T::class.javaPrimitiveType ?: T::class.java)
90 inline fun <reified T : Any> KMutableProperty0<T?>.toNullableBinding(defaultValue: T): PropertyBinding<T> {
91 return PropertyBinding({ get() ?: defaultValue }, { set(it) })
94 class ValidationInfoBuilder(val component: JComponent) {
95 fun error(@Nls message: String): ValidationInfo = ValidationInfo(message, component)
96 fun warning(@Nls message: String): ValidationInfo = ValidationInfo(message, component).asWarning().withOKEnabled()
99 interface CellBuilder<out T : JComponent> {
102 fun comment(@Nls text: String, maxLineLength: Int = 70, forComponent: Boolean = false): CellBuilder<T>
103 fun commentComponent(component: JComponent, forComponent: Boolean = false): CellBuilder<T>
104 fun focused(): CellBuilder<T>
105 fun withValidationOnApply(callback: ValidationInfoBuilder.(T) -> ValidationInfo?): CellBuilder<T>
106 fun withValidationOnInput(callback: ValidationInfoBuilder.(T) -> ValidationInfo?): CellBuilder<T>
107 fun onApply(callback: () -> Unit): CellBuilder<T>
108 fun onReset(callback: () -> Unit): CellBuilder<T>
109 fun onIsModified(callback: () -> Boolean): CellBuilder<T>
112 * All components of the same group share will get the same BoundSize (min/preferred/max),
113 * which is that of the biggest component in the group
115 fun sizeGroup(name: String): CellBuilder<T>
116 fun growPolicy(growPolicy: GrowPolicy): CellBuilder<T>
117 fun constraints(vararg constraints: CCFlags): CellBuilder<T>
120 * If this method is called, the value of the component will be stored to the backing property only if the component is enabled.
122 fun applyIfEnabled(): CellBuilder<T>
125 componentGet: (T) -> V,
126 componentSet: (T, V) -> Unit,
127 modelBinding: PropertyBinding<V>
129 onApply { if (shouldSaveOnApply()) modelBinding.set(componentGet(component)) }
130 onReset { componentSet(component, modelBinding.get()) }
131 onIsModified { shouldSaveOnApply() && componentGet(component) != modelBinding.get() }
135 fun withGraphProperty(property: GraphProperty<*>): CellBuilder<T>
137 fun enabled(isEnabled: Boolean)
138 fun enableIf(predicate: ComponentPredicate): CellBuilder<T>
140 fun withErrorOnApplyIf(message: String, callback: (T) -> Boolean): CellBuilder<T> {
141 withValidationOnApply { if (callback(it)) error(message) else null }
146 fun shouldSaveOnApply(): Boolean
148 fun withLargeLeftGap(): CellBuilder<T>
150 @Deprecated("Prefer not to use hardcoded values")
151 fun withLeftGap(gapLeft: Int): CellBuilder<T>
154 internal interface CheckboxCellBuilder {
158 fun <T : JCheckBox> CellBuilder<T>.actsAsLabel(): CellBuilder<T> {
159 (this as CheckboxCellBuilder).actsAsLabel()
163 fun <T : JComponent> CellBuilder<T>.applyToComponent(task: T.() -> Unit): CellBuilder<T> {
164 return also { task(component) }
167 internal interface ScrollPaneCellBuilder {
171 fun <T : JScrollPane> CellBuilder<T>.noGrowY(): CellBuilder<T> {
172 (this as ScrollPaneCellBuilder).noGrowY()
176 fun <T : JTextField> CellBuilder<T>.withTextBinding(modelBinding: PropertyBinding<String>): CellBuilder<T> {
177 return withBinding(JTextField::getText, JTextField::setText, modelBinding)
180 fun <T : AbstractButton> CellBuilder<T>.withSelectedBinding(modelBinding: PropertyBinding<Boolean>): CellBuilder<T> {
181 return withBinding(AbstractButton::isSelected, AbstractButton::setSelected, modelBinding)
184 val CellBuilder<AbstractButton>.selected
185 get() = component.selected
187 const val UNBOUND_RADIO_BUTTON = "unbound.radio.button"
189 // separate class to avoid row related methods in the `cell { } `
191 abstract class Cell : BaseBuilder {
193 * Sets how keen the component should be to grow in relation to other component **in the same cell**. Use `push` in addition if need.
194 * If this constraint is not set the grow weight is set to 0 and the component will not grow (unless some automatic rule is not applied (see [com.intellij.ui.layout.panel])).
195 * Grow weight will only be compared against the weights for the same cell.
197 val growX = CCFlags.growX
200 val growY = CCFlags.growY
201 val grow = CCFlags.grow
204 * Makes the column that the component is residing in grow with `weight`.
206 val pushX = CCFlags.pushX
209 * Makes the row that the component is residing in grow with `weight`.
212 val pushY = CCFlags.pushY
213 val push = CCFlags.push
215 fun label(@Nls text: String,
216 style: UIUtil.ComponentStyle? = null,
217 fontColor: UIUtil.FontColor? = null,
218 bold: Boolean = false): CellBuilder<JLabel> {
219 val label = Label(text, style, fontColor, bold)
220 return component(label)
223 fun link(@Nls text: String,
224 style: UIUtil.ComponentStyle? = null,
225 action: () -> Unit): CellBuilder<JComponent> {
226 val result = Link(text, action = action)
227 style?.let { UIUtil.applyStyle(it, result) }
228 return component(result)
231 fun browserLink(@Nls text: String, url: String): CellBuilder<JComponent> {
232 val result = HyperlinkLabel()
233 result.setHyperlinkText(text)
234 result.setHyperlinkTarget(url)
235 return component(result)
238 fun buttonFromAction(@Nls text: String, actionPlace: String, action: AnAction): CellBuilder<JButton> {
239 val button = JButton(BundleBase.replaceMnemonicAmpersand(text))
240 button.addActionListener { ActionUtil.invokeAction(action, button, actionPlace, null, null) }
241 return component(button)
244 fun button(@Nls text: String, actionListener: (event: ActionEvent) -> Unit): CellBuilder<JButton> {
245 val button = JButton(BundleBase.replaceMnemonicAmpersand(text))
246 button.addActionListener(actionListener)
247 return component(button)
250 inline fun checkBox(@Nls text: String,
251 isSelected: Boolean = false,
252 comment: String? = null,
253 crossinline actionListener: (event: ActionEvent, component: JCheckBox) -> Unit): CellBuilder<JBCheckBox> {
254 return checkBox(text, isSelected, comment)
256 addActionListener(ActionListener { actionListener(it, this) })
261 fun checkBox(@Nls text: String,
262 isSelected: Boolean = false,
263 comment: String? = null): CellBuilder<JBCheckBox> {
264 val result = JBCheckBox(text, isSelected)
265 return result(comment = comment)
268 fun checkBox(@Nls text: String, prop: KMutableProperty0<Boolean>, comment: String? = null): CellBuilder<JBCheckBox> {
269 return checkBox(text, prop.toBinding(), comment)
272 fun checkBox(@Nls text: String, getter: () -> Boolean, setter: (Boolean) -> Unit, comment: String? = null): CellBuilder<JBCheckBox> {
273 return checkBox(text, PropertyBinding(getter, setter), comment)
276 private fun checkBox(@Nls text: String,
277 modelBinding: PropertyBinding<Boolean>,
278 comment: String?): CellBuilder<JBCheckBox> {
279 val component = JBCheckBox(text, modelBinding.get())
280 return component(comment = comment).withSelectedBinding(modelBinding)
283 fun checkBox(@Nls text: String,
284 property: GraphProperty<Boolean>,
285 comment: String? = null): CellBuilder<JBCheckBox> {
286 val component = JBCheckBox(text, property.get())
287 return component(comment = comment).withGraphProperty(property).applyToComponent { component.bind(property) }
290 open fun radioButton(@Nls text: String, @Nls comment: String? = null): CellBuilder<JBRadioButton> {
291 val component = JBRadioButton(text)
292 component.putClientProperty(UNBOUND_RADIO_BUTTON, true)
293 return component(comment = comment)
296 open fun radioButton(@Nls text: String, getter: () -> Boolean, setter: (Boolean) -> Unit, @Nls comment: String? = null): CellBuilder<JBRadioButton> {
297 val component = JBRadioButton(text, getter())
298 return component(comment = comment).withSelectedBinding(PropertyBinding(getter, setter))
301 open fun radioButton(@Nls text: String, prop: KMutableProperty0<Boolean>, @Nls comment: String? = null): CellBuilder<JBRadioButton> {
302 val component = JBRadioButton(text, prop.get())
303 return component(comment = comment).withSelectedBinding(prop.toBinding())
306 fun <T> comboBox(model: ComboBoxModel<T>,
308 setter: (T?) -> Unit,
309 renderer: ListCellRenderer<T?>? = null): CellBuilder<ComboBox<T>> {
310 return comboBox(model, PropertyBinding(getter, setter), renderer)
313 fun <T> comboBox(model: ComboBoxModel<T>,
314 modelBinding: PropertyBinding<T?>,
315 renderer: ListCellRenderer<T?>? = null): CellBuilder<ComboBox<T>> {
316 return component(ComboBox(model))
318 this.renderer = renderer ?: SimpleListCellRenderer.create("") { it.toString() }
319 selectedItem = modelBinding.get()
322 { component -> component.selectedItem as T? },
323 { component, value -> component.setSelectedItem(value) },
328 inline fun <reified T : Any> comboBox(
329 model: ComboBoxModel<T>,
330 prop: KMutableProperty0<T>,
331 renderer: ListCellRenderer<T?>? = null
332 ): CellBuilder<ComboBox<T>> {
333 return comboBox(model, prop.toBinding().toNullable(), renderer)
337 model: ComboBoxModel<T>,
338 property: GraphProperty<T>,
339 renderer: ListCellRenderer<T?>? = null
340 ): CellBuilder<ComboBox<T>> {
341 return comboBox(model, PropertyBinding(property::get, property::set).toNullable(), renderer)
342 .withGraphProperty(property)
343 .applyToComponent { bind(property) }
346 fun textField(prop: KMutableProperty0<String>, columns: Int? = null): CellBuilder<JBTextField> = textField(prop.toBinding(), columns)
348 fun textField(getter: () -> String, setter: (String) -> Unit, columns: Int? = null) = textField(PropertyBinding(getter, setter), columns)
350 fun textField(binding: PropertyBinding<String>, columns: Int? = null): CellBuilder<JBTextField> {
351 return component(JBTextField(binding.get(), columns ?: 0))
352 .withTextBinding(binding)
355 fun textField(property: GraphProperty<String>, columns: Int? = null): CellBuilder<JBTextField> {
356 return textField(property::get, property::set, columns)
357 .withGraphProperty(property)
358 .applyToComponent { bind(property) }
361 fun intTextField(prop: KMutableProperty0<Int>, columns: Int? = null, range: IntRange? = null): CellBuilder<JBTextField> {
362 return intTextField(prop.toBinding(), columns, range)
365 fun intTextField(getter: () -> Int, setter: (Int) -> Unit, columns: Int? = null, range: IntRange? = null): CellBuilder<JBTextField> {
366 return intTextField(PropertyBinding(getter, setter), columns, range)
369 fun intTextField(binding: PropertyBinding<Int>, columns: Int? = null, range: IntRange? = null): CellBuilder<JBTextField> {
371 { binding.get().toString() },
372 { value -> value.toIntOrNull()?.let { intValue -> binding.set(range?.let { intValue.coerceIn(it.first, it.last) } ?: intValue) } },
374 ).withValidationOnInput {
375 val value = it.text.toIntOrNull()
377 value == null -> error(UIBundle.message("please.enter.a.number"))
378 range != null && value !in range -> error(UIBundle.message("please.enter.a.number.from.0.to.1", range.first, range.last))
384 fun spinner(prop: KMutableProperty0<Int>, minValue: Int, maxValue: Int, step: Int = 1): CellBuilder<JBIntSpinner> {
385 val spinner = JBIntSpinner(prop.get(), minValue, maxValue, step)
386 return component(spinner).withBinding(JBIntSpinner::getNumber, JBIntSpinner::setNumber, prop.toBinding())
389 fun spinner(getter: () -> Int, setter: (Int) -> Unit, minValue: Int, maxValue: Int, step: Int = 1): CellBuilder<JBIntSpinner> {
390 val spinner = JBIntSpinner(getter(), minValue, maxValue, step)
391 return component(spinner).withBinding(JBIntSpinner::getNumber, JBIntSpinner::setNumber, PropertyBinding(getter, setter))
394 fun textFieldWithHistoryWithBrowseButton(
395 browseDialogTitle: String,
396 value: String? = null,
397 project: Project? = null,
398 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
399 historyProvider: (() -> List<String>)? = null,
400 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
401 ): CellBuilder<TextFieldWithHistoryWithBrowseButton> {
402 val textField = textFieldWithHistoryWithBrowseButton(project, browseDialogTitle, fileChooserDescriptor, historyProvider, fileChosen)
403 if (value != null) textField.text = value
404 return component(textField)
407 fun textFieldWithBrowseButton(
408 browseDialogTitle: String? = null,
409 value: String? = null,
410 project: Project? = null,
411 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
412 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
413 ): CellBuilder<TextFieldWithBrowseButton> {
414 val textField = textFieldWithBrowseButton(project, browseDialogTitle, fileChooserDescriptor, fileChosen)
415 if (value != null) textField.text = value
416 return component(textField)
419 fun textFieldWithBrowseButton(
420 prop: KMutableProperty0<String>,
421 browseDialogTitle: String? = null,
422 project: Project? = null,
423 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
424 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
425 ): CellBuilder<TextFieldWithBrowseButton> {
426 val modelBinding = prop.toBinding()
427 return textFieldWithBrowseButton(modelBinding, browseDialogTitle, project, fileChooserDescriptor, fileChosen)
430 fun textFieldWithBrowseButton(
431 getter: () -> String,
432 setter: (String) -> Unit,
433 browseDialogTitle: String? = null,
434 project: Project? = null,
435 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
436 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
437 ): CellBuilder<TextFieldWithBrowseButton> {
438 val modelBinding = PropertyBinding(getter, setter)
439 return textFieldWithBrowseButton(modelBinding, browseDialogTitle, project, fileChooserDescriptor, fileChosen)
442 fun textFieldWithBrowseButton(
443 modelBinding: PropertyBinding<String>,
444 browseDialogTitle: String? = null,
445 project: Project? = null,
446 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
447 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
448 ): CellBuilder<TextFieldWithBrowseButton> {
449 val textField = textFieldWithBrowseButton(project, browseDialogTitle, fileChooserDescriptor, fileChosen)
450 textField.text = modelBinding.get()
451 return component(textField)
453 .withBinding(TextFieldWithBrowseButton::getText, TextFieldWithBrowseButton::setText, modelBinding)
456 fun textFieldWithBrowseButton(
457 property: GraphProperty<String>,
458 browseDialogTitle: String? = null,
459 project: Project? = null,
460 fileChooserDescriptor: FileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
461 fileChosen: ((chosenFile: VirtualFile) -> String)? = null
462 ): CellBuilder<TextFieldWithBrowseButton> {
463 return textFieldWithBrowseButton(property::get, property::set, browseDialogTitle, project, fileChooserDescriptor, fileChosen)
464 .withGraphProperty(property)
465 .applyToComponent { bind(property) }
468 fun gearButton(vararg actions: AnAction): CellBuilder<JComponent> {
469 val label = JLabel(LayeredIcon(AllIcons.General.GearPlain, AllIcons.General.Dropdown))
470 label.disabledIcon = AllIcons.General.GearPlain
471 object : ClickListener() {
472 override fun onClick(e: MouseEvent, clickCount: Int): Boolean {
473 if (!label.isEnabled) return true
474 JBPopupFactory.getInstance()
475 .createActionGroupPopup(null, DefaultActionGroup(*actions), DataContext { dataId ->
477 PlatformDataKeys.CONTEXT_COMPONENT.name -> label
481 .showUnderneathOf(label)
486 return component(label)
489 fun expandableTextField(getter: () -> String,
490 setter: (String) -> Unit,
491 parser: Function<in String, out MutableList<String>> = ParametersListUtil.DEFAULT_LINE_PARSER,
492 joiner: Function<in MutableList<String>, String> = ParametersListUtil.DEFAULT_LINE_JOINER)
493 : CellBuilder<ExpandableTextField> {
494 return ExpandableTextField(parser, joiner)()
495 .withBinding({ editor -> editor.text.orEmpty() },
496 { editor, value -> editor.text = value },
497 PropertyBinding(getter, setter))
500 fun expandableTextField(prop: KMutableProperty0<String>,
501 parser: Function<in String, out MutableList<String>> = ParametersListUtil.DEFAULT_LINE_PARSER,
502 joiner: Function<in MutableList<String>, String> = ParametersListUtil.DEFAULT_LINE_JOINER)
503 : CellBuilder<ExpandableTextField> {
504 return expandableTextField(prop::get, prop::set, parser, joiner)
507 fun expandableTextField(prop: GraphProperty<String>,
508 parser: Function<in String, out MutableList<String>> = ParametersListUtil.DEFAULT_LINE_PARSER,
509 joiner: Function<in MutableList<String>, String> = ParametersListUtil.DEFAULT_LINE_JOINER)
510 : CellBuilder<ExpandableTextField> {
511 return expandableTextField(prop::get, prop::set, parser, joiner)
512 .withGraphProperty(prop)
513 .applyToComponent { bind(prop) }
517 * @see LayoutBuilder.titledRow
520 fun panel(title: String, wrappedComponent: Component, hasSeparator: Boolean = true): CellBuilder<JPanel> {
521 val panel = Panel(title, hasSeparator)
522 panel.add(wrappedComponent)
523 return component(panel)
526 fun scrollPane(component: Component): CellBuilder<JScrollPane> {
527 return component(JBScrollPane(component))
530 fun comment(text: String, maxLineLength: Int = -1): CellBuilder<JLabel> {
531 return component(ComponentPanelBuilder.createCommentComponent(text, true, maxLineLength, true))
534 fun commentNoWrap(text: String): CellBuilder<JLabel> {
535 return component(ComponentPanelBuilder.createNonWrappingCommentComponent(text))
538 fun placeholder(): CellBuilder<JComponent> {
539 return component(JPanel().apply {
540 minimumSize = Dimension(0, 0)
541 preferredSize = Dimension(0, 0)
542 maximumSize = Dimension(0, 0)
546 abstract fun <T : JComponent> component(component: T): CellBuilder<T>
548 operator fun <T : JComponent> T.invoke(
549 vararg constraints: CCFlags,
550 growPolicy: GrowPolicy? = null,
551 comment: String? = null
552 ): CellBuilder<T> = component(this).apply {
553 constraints(*constraints)
554 if (comment != null) comment(comment)
555 if (growPolicy != null) growPolicy(growPolicy)
559 private fun JBCheckBox.bind(property: GraphProperty<Boolean>) {
560 val mutex = AtomicBoolean()
561 property.afterChange {
563 isSelected = property.get()
568 property.set(isSelected)
573 class InnerCell(val cell: Cell) : Cell() {
574 override fun <T : JComponent> component(component: T): CellBuilder<T> {
575 return cell.component(component)
578 override fun withButtonGroup(title: String?, buttonGroup: ButtonGroup, body: () -> Unit) {
579 cell.withButtonGroup(title, buttonGroup, body)
583 fun <T> listCellRenderer(renderer: SimpleListCellRenderer<T?>.(value: T, index: Int, isSelected: Boolean) -> Unit): SimpleListCellRenderer<T?> {
584 return object : SimpleListCellRenderer<T?>() {
585 override fun customize(list: JList<out T?>, value: T?, index: Int, selected: Boolean, hasFocus: Boolean) {
587 renderer(this, value, index, selected)
593 private fun <T> ComboBox<T>.bind(property: GraphProperty<T>) {
594 val mutex = AtomicBoolean()
595 property.afterChange {
601 if (it.stateChange == ItemEvent.SELECTED) {
603 @Suppress("UNCHECKED_CAST")
604 property.set(it.item as T)
610 private fun TextFieldWithBrowseButton.bind(property: GraphProperty<String>) {
611 textField.bind(property)
614 private fun JTextField.bind(property: GraphProperty<String>) {
615 val mutex = AtomicBoolean()
616 property.afterChange {
621 document.addDocumentListener(
622 object : DocumentAdapter() {
623 override fun textChanged(e: DocumentEvent) {
632 private fun AtomicBoolean.lockOrSkip(action: () -> Unit) {
633 if (!compareAndSet(false, true)) return
642 fun Cell.slider(min: Int, max: Int, minorTick: Int, majorTick: Int): CellBuilder<JSlider> {
643 val slider = JSlider()
644 UIUtil.setSliderIsFilled(slider, true)
645 slider.paintLabels = true
646 slider.paintTicks = true
647 slider.paintTrack = true
650 slider.minorTickSpacing = minorTick
651 slider.majorTickSpacing = majorTick
655 fun <T : JSlider> CellBuilder<T>.labelTable(table: Hashtable<Int, JComponent>.() -> Unit): CellBuilder<T> {
656 component.labelTable = Hashtable<Int, JComponent>().apply(table)
660 fun <T : JSlider> CellBuilder<T>.withValueBinding(modelBinding: PropertyBinding<Int>): CellBuilder<T> {
661 return withBinding(JSlider::getValue, JSlider::setValue, modelBinding)