Wizard: add descriptions for settings

This commit is contained in:
Ilya Kirillov
2020-08-27 14:06:51 +03:00
parent 09ed0c3e82
commit 37b869ebc5
16 changed files with 120 additions and 35 deletions
@@ -7,11 +7,16 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import javax.swing.JComponent
import javax.swing.SwingConstants
class CommentLabel : JBLabel() {
class CommentLabel(text: String? = null) : JBLabel() {
init {
if (text != null) {
this.text = text
}
verticalAlignment = SwingConstants.TOP
isFocusable = false
foreground = UIUtil.getContextHelpForeground()
@@ -24,4 +29,26 @@ class CommentLabel : JBLabel() {
this.font = smallFont
}
}
}
fun commentLabel(text: String, init: JBLabel.() -> Unit = {}) =
CommentLabel(text).apply(init)
fun componentWithCommentAtRight(component: JComponent, label: String?) = borderPanel {
addToLeft(component)
label?.let {
addToCenter(commentLabel(it) {
withBorder(JBUI.Borders.emptyLeft(5))
verticalAlignment = SwingConstants.CENTER
})
}
}
fun componentWithCommentAtBottom(component: JComponent, label: String?) = borderPanel {
addToTop(component)
label?.let {
addToCenter(commentLabel(it) {
withBorder(JBUI.Borders.emptyLeft(4))
})
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.Writer
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingType
import javax.swing.JComponent
abstract class Component : Displayable, ErrorNavigatable {
private val subComponents = mutableListOf<Component>()
@@ -67,13 +68,19 @@ abstract class DynamicComponent(private val context: Context) : Component() {
}
abstract class TitledComponent(context: Context) : DynamicComponent(context) {
open val forceLabelCenteringOffset: Int? = null
open val alignment: TitleComponentAlignment get() = TitleComponentAlignment.AlignAgainstMainComponent
open val additionalComponentPadding: Int = 0
open val maximumWidth: Int? = null
abstract val title: String?
open fun shouldBeShow(): Boolean = true
}
sealed class TitleComponentAlignment {
object AlignAgainstMainComponent : TitleComponentAlignment()
data class AlignAgainstSpecificComponent(val alignTarget: JComponent) : TitleComponentAlignment()
data class AlignFormTopWithPadding(val padding: Int) : TitleComponentAlignment()
}
interface FocusableComponent {
fun focusOn() {}
}
@@ -4,11 +4,13 @@ import com.intellij.ui.components.JBCheckBox
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import java.awt.Font
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.componentWithCommentAtRight
import javax.swing.JComponent
class CheckboxComponent(
context: Context,
labelText: String? = null,
description: String? = null,
initialValue: Boolean? = null,
validator: SettingValidator<Boolean>? = null,
onValueUpdate: (Boolean) -> Unit = {}
@@ -18,16 +20,20 @@ class CheckboxComponent(
validator = validator,
onValueUpdate = onValueUpdate
) {
override val uiComponent: JBCheckBox = JBCheckBox(labelText, initialValue ?: false).apply {
private val checkbox = JBCheckBox(labelText, initialValue ?: false).apply {
font = UIUtil.getButtonFont()
addChangeListener {
fireValueUpdated(this@apply.isSelected)
}
}
override val alignTarget: JComponent? get() = checkbox
override val uiComponent = componentWithCommentAtRight(checkbox, description)
override fun updateUiValue(newValue: Boolean) = safeUpdateUi {
uiComponent.isSelected = newValue
checkbox.isSelected = newValue
}
override fun getUiValue(): Boolean = uiComponent.isSelected
override fun getUiValue(): Boolean = checkbox.isSelected
}
@@ -10,16 +10,18 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.componentWithCommentAtBottom
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.event.ItemEvent
import javax.swing.DefaultComboBoxModel
import javax.swing.Icon
import javax.swing.JComponent
import javax.swing.JList
import javax.swing.SwingUtilities
class DropDownComponent<T : DisplayableSettingItem>(
context: Context,
private val initialValues: List<T> = emptyList(),
description: String? = null,
initiallySelectedValue: T? = null,
labelText: String? = null,
private val filter: (T) -> Boolean = { true },
@@ -32,8 +34,9 @@ class DropDownComponent<T : DisplayableSettingItem>(
validator,
onValueUpdate
) {
@Suppress("UNCHECKED_CAST")
override val uiComponent: ComboBox<T> = ComboBox(
private val combobox = ComboBox(
initialValues.filter(filter).toTypedArray<DisplayableSettingItem>() as Array<T>
).apply {
selectedItem = initiallySelectedValue
@@ -71,6 +74,10 @@ class DropDownComponent<T : DisplayableSettingItem>(
}
}
override val alignTarget: JComponent? get() = combobox
override val uiComponent = componentWithCommentAtBottom(combobox, description)
fun filterValues() {
val selectedItem = model.selectedItem
@@ -82,8 +89,8 @@ class DropDownComponent<T : DisplayableSettingItem>(
model.selectedItem = selectedItem
}
model.size != 0 -> {
uiComponent.selectedIndex = 0
uiComponent.selectedItem?.let {
combobox.selectedIndex = 0
combobox.selectedItem?.let {
ApplicationManager.getApplication().invokeLater {
@Suppress("UNCHECKED_CAST")
forceValueUpdate(it as T)
@@ -95,13 +102,13 @@ class DropDownComponent<T : DisplayableSettingItem>(
uiComponent.repaint()
}
private val model get() = uiComponent.model as DefaultComboBoxModel
private val model get() = combobox.model as DefaultComboBoxModel
val valuesCount
get() = uiComponent.model.size
get() = combobox.model.size
override fun updateUiValue(newValue: T) = safeUpdateUi {
uiComponent.selectedItem = newValue
combobox.selectedItem = newValue
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
@@ -110,5 +117,5 @@ class DropDownComponent<T : DisplayableSettingItem>(
}
@Suppress("UNCHECKED_CAST")
override fun getUiValue(): T? = uiComponent.selectedItem as? T
override fun getUiValue(): T? = combobox.selectedItem as? T
}
@@ -6,7 +6,7 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.asPath
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.componentWithCommentAtBottom
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.withOnUpdatedListener
import java.nio.file.Path
import java.nio.file.Paths
@@ -15,6 +15,7 @@ import javax.swing.JComponent
class PathFieldComponent(
context: Context,
labelText: String? = null,
description: String? = null,
initialValue: Path? = null,
validator: SettingValidator<Path>? = null,
onValueUpdate: (Path) -> Unit = {}
@@ -24,7 +25,7 @@ class PathFieldComponent(
validator,
onValueUpdate
) {
override val uiComponent: TextFieldWithBrowseButton = TextFieldWithBrowseButton().apply {
val textFieldWithBrowseButton = TextFieldWithBrowseButton().apply {
textField.text = initialValue?.toString().orEmpty()
textField.withOnUpdatedListener { path -> fireValueUpdated(path.trim().asPath()) }
addBrowseFolderListener(
@@ -35,15 +36,19 @@ class PathFieldComponent(
)
}
override fun getValidatorTarget(): JComponent = uiComponent.textField
override val alignTarget: JComponent? get() = textFieldWithBrowseButton
override val uiComponent = componentWithCommentAtBottom(textFieldWithBrowseButton, description)
override fun getValidatorTarget(): JComponent = textFieldWithBrowseButton.textField
override fun updateUiValue(newValue: Path) = safeUpdateUi {
uiComponent.text = newValue.toString()
textFieldWithBrowseButton.text = newValue.toString()
}
override fun getUiValue(): Path = Paths.get(uiComponent.text.trim())
override fun getUiValue(): Path = Paths.get(textFieldWithBrowseButton.text.trim())
override fun focusOn() {
uiComponent.textField.requestFocus()
textFieldWithBrowseButton.textField.requestFocus()
}
}
@@ -1,16 +1,17 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import com.intellij.ui.components.JBTextField
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.componentWithCommentAtBottom
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import javax.swing.JComponent
class TextFieldComponent(
context: Context,
labelText: String? = null,
description: String? = null,
initialValue: String? = null,
validator: SettingValidator<String>? = null,
onValueUpdate: (String) -> Unit = {}
@@ -23,16 +24,20 @@ class TextFieldComponent(
private var isDisabled: Boolean = false
private var cachedValueWhenDisabled: String? = null
override val uiComponent: JBTextField = textField(initialValue.orEmpty(), ::fireValueUpdated)
private val textField = textField(initialValue.orEmpty(), ::fireValueUpdated)
override val alignTarget: JComponent? get() = textField
override val uiComponent = componentWithCommentAtBottom(textField, description)
override fun updateUiValue(newValue: String) = safeUpdateUi {
uiComponent.text = newValue
textField.text = newValue
}
fun disable(@Nls message: String) {
cachedValueWhenDisabled = getUiValue()
uiComponent.isEditable = false
uiComponent.foreground = UIUtil.getLabelDisabledForeground()
textField.isEditable = false
textField.foreground = UIUtil.getLabelDisabledForeground()
isDisabled = true
updateUiValue(message)
}
@@ -42,5 +47,5 @@ class TextFieldComponent(
super.validate(value)
}
override fun getUiValue(): String = cachedValueWhenDisabled ?: uiComponent.text
override fun getUiValue(): String = cachedValueWhenDisabled ?: textField.text
}
@@ -27,6 +27,7 @@ abstract class UIComponent<V : Any>(
private val validator: SettingValidator<V>? = null,
private val onValueUpdate: (V) -> Unit = {}
) : DynamicComponent(context), FocusableComponent, Disposable {
open val alignTarget: JComponent? = null
private val validationIndicator by lazy(LazyThreadSafetyMode.NONE) {
if (validator != null)
IdeaBasedComponentValidator(this, getValidatorTarget())
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.DropDownSet
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitleComponentAlignment
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.IdeaBasedComponentValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
@@ -38,6 +39,10 @@ class BuildSystemTypeSettingComponent(
val actionGroup = DefaultActionGroup(buildSystemTypes.map(::BuildSystemTypeAction))
BuildSystemToolbar(ActionPlaces.UNKNOWN, actionGroup, true)
}
override val alignment: TitleComponentAlignment
get() = TitleComponentAlignment.AlignFormTopWithPadding(6)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
toolbar
}
@@ -27,7 +27,6 @@ class ProjectTemplateSettingComponent(
context
) {
override val validationIndicator: ValidationIndicator? get() = null
override val forceLabelCenteringOffset: Int? = 4
private val templateDescriptionComponent = TemplateDescriptionComponent().asSubComponent()
private val templateGroups = setting.type.values
@@ -45,6 +44,9 @@ class ProjectTemplateSettingComponent(
onValueSelected = { value = it }
)
override val alignment: TitleComponentAlignment
get() = TitleComponentAlignment.AlignFormTopWithPadding(4)
private val borderedPanel = list.addBorder(BorderFactory.createLineBorder(JBColor.border()))
override val component: JComponent = borderPanel {
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.Dimension
import javax.swing.Icon
import javax.swing.JComponent
import javax.swing.JPanel
import kotlin.reflect.KFunction0
@@ -27,7 +28,8 @@ class ModuleDependenciesComponent(
) : TitledComponent(context) {
override val title: String = KotlinNewProjectWizardUIBundle.message("module.dependencies.module.dependencies")
private val dependenciesList = ModuleDependenciesList(::possibleDependencies)
override val forceLabelCenteringOffset: Int? = 4
override val alignment: TitleComponentAlignment
get() = TitleComponentAlignment.AlignAgainstSpecificComponent(dependenciesList)
override val additionalComponentPadding: Int = 1
override val maximumWidth: Int = 500
@@ -132,7 +132,9 @@ private class ModuleTemplateComponent(
onTemplateChanged()
}.asSubComponent()
override val forceLabelCenteringOffset: Int? = 4
override val alignment: TitleComponentAlignment
get() = TitleComponentAlignment.AlignAgainstSpecificComponent(dropDown.component)
private val templateDescriptionLabel = CommentLabel().apply {
addBorder(JBUI.Borders.empty(2, 4))
}
@@ -66,6 +66,7 @@ class VersionSettingComponent(
reference: SettingReference<Version, VersionSettingType>,
context: Context
) : SettingComponent<Version, VersionSettingType>(reference, context) {
private val settingLabel = label(setting.title)
private val comboBox = ComboBox<Version>().apply {
addItemListener { e ->
@@ -111,6 +112,7 @@ class DropdownSettingComponent(
override val uiComponent = DropDownComponent(
context = context,
initialValues = setting.type.values,
description = setting.description,
validator = setting.validator,
filter = { value ->
context.read { setting.type.filter(this, reference, value) }
@@ -135,6 +137,7 @@ class BooleanSettingComponent(
override val uiComponent = CheckboxComponent(
context = context,
labelText = setting.title,
description = setting.description,
initialValue = null,
validator = setting.validator,
onValueUpdate = { newValue -> value = newValue }
@@ -152,6 +155,7 @@ class StringSettingComponent(
override val uiComponent = TextFieldComponent(
context = context,
initialValue = null,
description = setting.description,
labelText = setting.title.takeIf { needLabel },
validator = setting.validator,
onValueUpdate = { newValue -> value = newValue }
@@ -168,6 +172,7 @@ class PathSettingComponent(
) {
override val uiComponent = PathFieldComponent(
context = context,
description = setting.description,
labelText = setting.title.takeIf { needLabel },
validator = settingValidator { path -> setting.validator.validate(this, path) },
onValueUpdate = { newValue -> value = newValue }
@@ -53,15 +53,17 @@ open class TitledComponentsList(
TitledComponentData(
label.also { add(it) }.constraints(),
component.component.also { add(it) }.constraints(),
component.forceLabelCenteringOffset,
component.alignment,
component.additionalComponentPadding,
component.maximumWidth
)
}
fun TitledComponentData.centerConstraint() =
if (forceCenteringOffset == null) component.height * .5f - label.height * .5f
else forceCenteringOffset.asSpring()
fun TitledComponentData.centerConstraint(): Spring = when (alignment) {
TitleComponentAlignment.AlignAgainstMainComponent -> component.height * .5f - label.height * .5f
is TitleComponentAlignment.AlignAgainstSpecificComponent -> alignment.alignTarget.constraints().height * .5f - label.height * .5f
is TitleComponentAlignment.AlignFormTopWithPadding -> alignment.padding.asSpring()
}
val labelWidth = componentsWithLabels.fold(componentsWithLabels.first().label.width) { spring, row ->
Spring.max(spring, row.label.width)
@@ -114,7 +116,7 @@ open class TitledComponentsList(
private data class TitledComponentData(
val label: SpringLayout.Constraints,
val component: SpringLayout.Constraints,
val forceCenteringOffset: Int?,
val alignment: TitleComponentAlignment,
val additionalComponentGap: Int,
val maximumWidth: Int?
)
@@ -9,6 +9,7 @@ import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingType
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitleComponentAlignment
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.UIComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.valueForSetting
import javax.swing.JComponent
@@ -19,6 +20,10 @@ abstract class UIComponentDelegatingSettingComponent<V : Any, T : SettingType<V>
) : SettingComponent<V, T>(reference, context) {
abstract val uiComponent: UIComponent<V>
override val alignment: TitleComponentAlignment
get() = uiComponent.alignTarget?.let { TitleComponentAlignment.AlignAgainstSpecificComponent(it) }
?: TitleComponentAlignment.AlignAgainstMainComponent
// As there is one in UIComponent
override val validationIndicator: ValidationIndicator? = null
@@ -20,6 +20,7 @@ typealias AnySetting = Setting<*, *>
interface Setting<out V : Any, out T : SettingType<V>> : Entity, ActivityCheckerOwner, Validatable<V> {
val title: String
val description: String?
val defaultValue: SettingDefaultValue<V>?
val isRequired: Boolean
val isSavable: Boolean
@@ -31,6 +32,7 @@ interface Setting<out V : Any, out T : SettingType<V>> : Entity, ActivityChecker
data class InternalSetting<out V : Any, out T : SettingType<V>>(
override val path: String,
override val title: String,
override val description: String?,
override val defaultValue: SettingDefaultValue<V>?,
override val isAvailable: Checker,
override val isRequired: Boolean,
@@ -23,6 +23,7 @@ abstract class SettingBuilder<V : Any, T : SettingType<V>>(
var validateOnProjectCreation = true
var isSavable: Boolean = false
var isRequired: Boolean? = null
var description: String? = null
fun value(value: V) = SettingDefaultValue.Value(value)
fun dynamic(getter: Reader.(SettingReference<V, SettingType<V>>) -> V?) =
@@ -47,6 +48,7 @@ abstract class SettingBuilder<V : Any, T : SettingType<V>>(
fun buildInternal() = InternalSetting(
path = path,
title = title,
description = description,
defaultValue = defaultValue,
isAvailable = isAvailable,
isRequired = isRequired ?: (defaultValue == null),