UI DSL — to not require use radio buttons directly (part 1)

there are some issues regarding enabled state if some nested component has own enabled state (so, should stay disabled if containing row is enabled)
This commit is contained in:
Vladimir Krivosheev
2018-10-31 19:20:46 +01:00
parent f126a04c76
commit dff117065c
7 changed files with 99 additions and 40 deletions
@@ -1,4 +1,7 @@
<root>
<item name='javax.swing.ButtonGroup javax.swing.ButtonModel getSelection()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='javax.swing.JComponent void paint(java.awt.Graphics) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -22,12 +22,10 @@ import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.CollectionComboBoxModel
import com.intellij.ui.components.RadioButton
import com.intellij.ui.layout.*
import com.intellij.util.io.exists
import com.intellij.util.io.isDirectory
import com.intellij.util.text.nullize
import gnu.trove.THashMap
import java.io.File
import java.nio.file.Paths
import javax.swing.JPanel
@@ -41,16 +39,10 @@ internal class PasswordSafeConfigurable(private val settings: PasswordSafeSettin
}
internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings> {
private val inKeychain = RadioButton("In native Keychain")
private val inKeePass = RadioButton("In KeePass")
private var keePassDbFile: TextFieldWithBrowseButton? = null
private var isUsePgp = BooleanPropertyWithComboBoxUiManager(CollectionComboBoxModel<PgpKey>())
private val rememberPasswordsUntilClosing = RadioButton("Do not save, forget passwords after restart")
private val modeToRow = THashMap<ProviderType, Row>()
private val providerTypeModel = ChoicePropertyUiManager(ProviderType.KEYCHAIN)
private val pgp by lazy { Pgp() }
@@ -59,16 +51,10 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
private val secureRandom = lazy { createSecureRandom() }
override fun reset(settings: PasswordSafeSettings) {
when (settings.providerType) {
ProviderType.MEMORY_ONLY -> rememberPasswordsUntilClosing.isSelected = true
ProviderType.KEYCHAIN -> inKeychain.isSelected = true
ProviderType.KEEPASS -> inKeePass.isSelected = true
else -> throw IllegalStateException("Unknown provider type: ${settings.providerType}")
}
providerTypeModel.selected = settings.providerType
@Suppress("IfThenToElvis")
keePassDbFile?.text = settings.keepassDb ?: getDefaultKeePassDbFile().toString()
updateEnabledState()
val secretKeys = pgp.listKeys()
isUsePgp.listModel.replaceAll(secretKeys)
@@ -76,7 +62,7 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
val currentKeyId = settings.state.pgpKeyId
isUsePgp.selected = (if (currentKeyId == null) null else secretKeys.firstOrNull { it.keyId == currentKeyId }) ?: secretKeys.firstOrNull()
isUsePgp.value = !secretKeys.isEmpty() && currentKeyId != null
isUsePgp.isEnabled = !secretKeys.isEmpty()
isUsePgp.isEnabled = providerTypeModel.selected == ProviderType.KEEPASS && !secretKeys.isEmpty()
}
override fun isModified(settings: PasswordSafeSettings): Boolean {
@@ -190,23 +176,19 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
private fun getNewDbFileAsString() = keePassDbFile!!.text.trim().nullize()
private fun updateEnabledState() {
modeToRow[ProviderType.KEEPASS]?.subRowsEnabled = getNewProviderType() == ProviderType.KEEPASS
}
override fun getComponent(): JPanel {
return panel {
row { label("Save passwords:") }
buttonGroup({ updateEnabledState() }) {
buttonGroup(providerTypeModel) {
if (SystemInfo.isLinux || isMacOsCredentialStoreSupported) {
row {
inKeychain()
radioButton("In native Keychain", ProviderType.KEYCHAIN)
}
}
modeToRow[ProviderType.KEEPASS] = row {
inKeePass()
row {
radioButton("In KeePass", ProviderType.KEEPASS)
row("Database:") {
val fileChooserDescriptor = FileChooserDescriptorFactory.createSingleLocalFileDescriptor().withFileFilter {
it.isDirectory || it.name.endsWith(".kdbx")
@@ -237,7 +219,7 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
}
}
row {
rememberPasswordsUntilClosing()
radioButton("Do not save, forget passwords after restart", ProviderType.MEMORY_ONLY)
}
}
}
@@ -257,13 +239,7 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
private fun getNewPgpKey() = isUsePgp.selected
private fun getNewProviderType(): ProviderType {
return when {
rememberPasswordsUntilClosing.isSelected -> ProviderType.MEMORY_ONLY
inKeePass.isSelected -> ProviderType.KEEPASS
else -> ProviderType.KEYCHAIN
}
}
private fun getNewProviderType() = providerTypeModel.selected
private inner class ClearKeePassDatabaseAction : DumbAwareAction("Clear") {
override fun actionPerformed(event: AnActionEvent) {
@@ -38,7 +38,10 @@ open class BooleanPropertyUiManager {
updateDependentComponentState()
})
updateCheckBoxEnabledState()
// do not modify initial enabled state of component - maybe disabled as part of inactive choice group
if (!isEnabled) {
updateCheckBoxEnabledState()
}
}
private fun updateDependentComponentState() {
@@ -0,0 +1,52 @@
// Copyright 2000-2018 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.
package com.intellij.ui.layout
import java.awt.event.ActionListener
import javax.swing.ButtonModel
import javax.swing.JRadioButton
class ChoicePropertyUiManager<T : Any>(defaultChoice: T) {
private val components = ArrayList<ChoiceInfo<T>>()
private var currentSelection: ChoiceInfo<T>? = null
private var _selected: T = defaultChoice
var selected: T
get() = _selected
set(value) {
updateSelection(components.firstOrNull { it.id == value })
}
private fun updateSelection(newSelection: ChoiceInfo<T>?) {
currentSelection?.select(false)
currentSelection = newSelection ?: return
newSelection.select(true)
}
internal fun addRadioButton(component: JRadioButton, id: T, row: Row) {
val isSelected = id == _selected
component.isSelected = isSelected
val info = ChoiceInfo(id, component.model, row)
components.add(info)
if (isSelected) {
currentSelection = info
}
else {
row.subRowsEnabled = false
}
component.addActionListener(ActionListener {
if (component.isSelected) {
updateSelection(info)
}
})
}
}
private data class ChoiceInfo<T>(val id: T, val model: ButtonModel, val row: Row) {
fun select(value: Boolean) {
model.isSelected = value
row.subRowsEnabled = value
}
}
@@ -11,8 +11,8 @@ import java.awt.event.ActionListener
import javax.swing.ButtonGroup
import javax.swing.JLabel
class LayoutBuilder @PublishedApi internal constructor(@PublishedApi internal val builder: LayoutBuilderImpl, val buttonGroup: ButtonGroup? = null) {
inline fun row(label: String, init: Row.() -> Unit): Row = row(label = Label(label), init = init)
open class LayoutBuilder @PublishedApi internal constructor(@PublishedApi internal val builder: LayoutBuilderImpl, val buttonGroup: ButtonGroup? = null) {
inline fun row(label: String, init: Row.() -> Unit) = row(label = Label(label), init = init)
inline fun row(label: JLabel? = null, separated: Boolean = false, init: Row.() -> Unit): Row {
val row = builder.newRow(label, buttonGroup, separated)
@@ -54,6 +54,10 @@ class LayoutBuilder @PublishedApi internal constructor(@PublishedApi internal va
return group
}
inline fun <T : Any> buttonGroup(propertyManager: ChoicePropertyUiManager<T>, init: LayoutBuilderWithButtonGroup<T>.() -> Unit) {
LayoutBuilderWithButtonGroup(builder, propertyManager).init()
}
@Suppress("PropertyName")
@PublishedApi
@Deprecated("", replaceWith = ReplaceWith("builder"), level = DeprecationLevel.ERROR)
@@ -61,6 +65,9 @@ class LayoutBuilder @PublishedApi internal constructor(@PublishedApi internal va
get() = builder
}
@Suppress("unused")
class LayoutBuilderWithButtonGroup<T : Any> @PublishedApi internal constructor(builder: LayoutBuilderImpl, internal val propertyManager: ChoicePropertyUiManager<T>) : LayoutBuilder(builder)
fun FileChooserDescriptor.chooseFile(event: AnActionEvent, fileChosen: (chosenFile: VirtualFile) -> Unit) {
FileChooser.chooseFile(this, event.getData(PlatformDataKeys.PROJECT), event.getData(PlatformDataKeys.CONTEXT_COMPONENT), null, fileChosen)
}
@@ -2,6 +2,7 @@
package com.intellij.ui.layout
import com.intellij.ui.components.Label
import com.intellij.ui.components.RadioButton
import com.intellij.util.ui.UIUtil.ComponentStyle
import com.intellij.util.ui.UIUtil.FontColor
import javax.swing.JComponent
@@ -23,6 +24,12 @@ abstract class Row : Cell() {
label(gapLeft = gapLeft)
}
fun <T : Any> LayoutBuilderWithButtonGroup<T>.radioButton(text: String, id: T) {
val component = RadioButton(text)
propertyManager.addRadioButton(component, id, this@Row)
component()
}
/**
* Specifies the right alignment for the component if the cell is larger than the component plus its gaps.
*/
@@ -70,7 +70,7 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
private val spacing: SpacingConfiguration
get() = builder.spacing
override var enabled: Boolean = true
override var enabled = true
set(value) {
if (field == value) {
return
@@ -82,7 +82,7 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
}
}
override var visible: Boolean = true
override var visible = true
set(value) {
if (field == value) {
return
@@ -94,7 +94,7 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
}
}
override var subRowsEnabled: Boolean = true
override var subRowsEnabled = true
set(value) {
if (field == value) {
return
@@ -104,7 +104,7 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
subRows?.forEach { it.enabled = value }
}
override var subRowsVisible: Boolean = true
override var subRowsVisible = true
set(value) {
if (field == value) {
return
@@ -132,10 +132,14 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
if (isSeparated) {
val separatorRow = MigLayoutRow(this, componentConstraints, builder, indent = indent, noGrid = true)
configureSeparatorRow(separatorRow, title)
separatorRow.enabled = subRowsEnabled
separatorRow.visible = subRowsVisible
row.getOrCreateSubRowsList().add(separatorRow)
}
subRows.add(row)
row.enabled = subRowsEnabled
row.visible = subRowsVisible
if (label != null) {
row.addComponent(label)
@@ -195,6 +199,13 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?,
internal fun addComponent(component: JComponent, cc: Lazy<CC> = lazy { CC() }, gapLeft: Int = 0, growPolicy: GrowPolicy? = null, comment: String? = null) {
components.add(component)
if (!visible) {
component.isVisible = false
}
if (!enabled) {
component.isEnabled = false
}
if (!shareCellWithPreviousComponentIfNeed(component, cc)) {
// increase column index if cell mode not enabled or it is a first component of cell
if (componentIndexWhenCellModeWasEnabled == -1 || componentIndexWhenCellModeWasEnabled == (components.size - 1)) {