Wizard: make error messages in ui clickable

#KT-36162 fixed
This commit is contained in:
Ilya Kirillov
2020-02-11 14:07:11 +03:00
parent 087de68b3a
commit bb455ec8f6
14 changed files with 144 additions and 22 deletions
@@ -47,4 +47,8 @@ abstract class DynamicComponent(private val valuesReadingContext: ValuesReadingC
reader(valuesReadingContext) reader(valuesReadingContext)
open fun onValueUpdated(reference: SettingReference<*, *>?) {} open fun onValueUpdated(reference: SettingReference<*, *>?) {}
}
interface FocusableComponent {
fun focusOn() {}
} }
@@ -46,4 +46,8 @@ class PathFieldComponent(
} }
override fun getUiValue(): Path = Paths.get(uiComponent.text.trim()) override fun getUiValue(): Path = Paths.get(uiComponent.text.trim())
override fun focusOn() {
uiComponent.textField.requestFocus()
}
} }
@@ -6,11 +6,12 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.Setting import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ErrorAwareComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout import java.awt.BorderLayout
import javax.swing.JComponent import javax.swing.JComponent
@@ -20,7 +21,7 @@ abstract class UIComponent<V : Any>(
labelText: String? = null, labelText: String? = null,
private val validator: SettingValidator<V>? = null, private val validator: SettingValidator<V>? = null,
private val onValueUpdate: (V) -> Unit = {} private val onValueUpdate: (V) -> Unit = {}
) : DynamicComponent(valuesReadingContext) { ) : DynamicComponent(valuesReadingContext), ErrorAwareComponent, FocusableComponent {
private val validationIndicator = if (validator != null) private val validationIndicator = if (validator != null)
ValidationIndicator(defaultText = labelText, showText = true) ValidationIndicator(defaultText = labelText, showText = true)
else null else null
@@ -63,6 +64,14 @@ abstract class UIComponent<V : Any>(
if (validationIndicator == null) return if (validationIndicator == null) return
validationIndicator.validationState = validator.validate(valuesReadingContext, value) validationIndicator.validationState = validator.validate(valuesReadingContext, value)
} }
override fun focusOn() {
uiComponent.requestFocus()
}
override fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent? = takeIf {
getUiValue()?.let { validator?.validate?.invoke(valuesReadingContext, it)?.isSpecificError(error) } == true
}
} }
fun <V : Any> UIComponent<V>.valueForSetting(setting: Setting<V, SettingType<V>>): V? = fun <V : Any> UIComponent<V>.valueForSetting(setting: Setting<V, SettingType<V>>): V? =
@@ -4,6 +4,7 @@ import com.intellij.ui.components.JBTabbedPane
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.configuratorSettings import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.configuratorSettings
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
@@ -11,6 +12,7 @@ import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module.Companion.ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module.Companion.ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
@@ -33,6 +35,13 @@ class ModuleSettingsComponent(
add("Module Settings", moduleConfiguratorSettingsList.component) add("Module Settings", moduleConfiguratorSettingsList.component)
} }
fun selectSettingWithError(error: ValidationResult.ValidationError) {
val componentWithError = nameField.findComponentWithError(error)
?: moduleConfiguratorSettingsList.findComponentWithError(error)?.also { tabPanel.selectedIndex = 1 }
?: templateComponent.findComponentWithError(error)?.also { tabPanel.selectedIndex = 0 }
componentWithError?.focusOn()
}
private val nameField = TextFieldComponent( private val nameField = TextFieldComponent(
valuesReadingContext, valuesReadingContext,
labelText = "Name", labelText = "Name",
@@ -2,6 +2,7 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
@@ -17,7 +18,9 @@ class SecondStepWizardComponent(
uiEditorUsagesStats: UiEditorUsageStats uiEditorUsagesStats: UiEditorUsageStats
) : WizardStepComponent(wizard.valuesReadingContext) { ) : WizardStepComponent(wizard.valuesReadingContext) {
private val moduleEditorSubStep = private val moduleEditorSubStep =
ModulesEditorSubStep(wizard.valuesReadingContext, uiEditorUsagesStats, ::onNodeSelected).asSubComponent() ModulesEditorSubStep(wizard.valuesReadingContext, uiEditorUsagesStats, ::onNodeSelected) {
templatesSubStep.selectSettingWithError(it)
}.asSubComponent()
private val templatesSubStep = ModuleSettingsSubStep(wizard, uiEditorUsagesStats).asSubComponent() private val templatesSubStep = ModuleSettingsSubStep(wizard, uiEditorUsagesStats).asSubComponent()
override val component = splitterFor( override val component = splitterFor(
@@ -34,12 +37,14 @@ class SecondStepWizardComponent(
class ModulesEditorSubStep( class ModulesEditorSubStep(
valuesReadingContext: ValuesReadingContext, valuesReadingContext: ValuesReadingContext,
uiEditorUsagesStats: UiEditorUsageStats, uiEditorUsagesStats: UiEditorUsageStats,
onNodeSelected: (data: DisplayableSettingItem?) -> Unit onNodeSelected: (data: DisplayableSettingItem?) -> Unit,
selectSettingWithError: (ValidationResult.ValidationError) -> Unit
) : SubStep(valuesReadingContext) { ) : SubStep(valuesReadingContext) {
private val moduleSettingComponent = ModulesEditorComponent( private val moduleSettingComponent = ModulesEditorComponent(
valuesReadingContext, valuesReadingContext,
uiEditorUsagesStats, uiEditorUsagesStats,
onNodeSelected onNodeSelected,
selectSettingWithError
).asSubComponent() ).asSubComponent()
override fun buildContent(): JComponent = panel { override fun buildContent(): JComponent = panel {
@@ -74,6 +79,10 @@ class ModuleSettingsSubStep(
changeComponent() changeComponent()
} }
fun selectSettingWithError(error: ValidationResult.ValidationError) {
moduleSettingsComponent.selectSettingWithError(error)
}
private fun changeComponent() { private fun changeComponent() {
panel.removeAll() panel.removeAll()
val component = when (selectedNode) { val component = when (selectedNode) {
@@ -10,6 +10,7 @@ import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
@@ -17,6 +18,8 @@ import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import org.jetbrains.kotlin.tools.projectWizard.templates.settings import org.jetbrains.kotlin.tools.projectWizard.templates.settings
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.* import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ErrorAwareComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
import java.awt.* import java.awt.*
import javax.swing.JComponent import javax.swing.JComponent
@@ -25,7 +28,7 @@ import javax.swing.JPanel
class TemplatesComponent( class TemplatesComponent(
valuesReadingContext: ValuesReadingContext, valuesReadingContext: ValuesReadingContext,
uiEditorUsagesStats: UiEditorUsageStats uiEditorUsagesStats: UiEditorUsageStats
) : DynamicComponent(valuesReadingContext) { ) : DynamicComponent(valuesReadingContext), ErrorAwareComponent {
private val chooseTemplateComponent: ChooseTemplateComponent = private val chooseTemplateComponent: ChooseTemplateComponent =
ChooseTemplateComponent(valuesReadingContext) { template -> ChooseTemplateComponent(valuesReadingContext) { template ->
uiEditorUsagesStats.moduleTemplatesSet++ uiEditorUsagesStats.moduleTemplatesSet++
@@ -49,6 +52,9 @@ class TemplatesComponent(
} }
} }
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? =
templateSettingsComponent.findComponentWithError(error)
private fun switchState(selectedTemplate: Template?) { private fun switchState(selectedTemplate: Template?) {
panel.removeAll() panel.removeAll()
when (selectedTemplate) { when (selectedTemplate) {
@@ -243,7 +249,7 @@ class TemplateDescriptionComponent(
private class TemplateSettingsComponent( private class TemplateSettingsComponent(
valuesReadingContext: ValuesReadingContext, valuesReadingContext: ValuesReadingContext,
removeTemplate: () -> Unit removeTemplate: () -> Unit
) : DynamicComponent(valuesReadingContext) { ) : DynamicComponent(valuesReadingContext), ErrorAwareComponent {
private val templateDescriptionComponent = TemplateDescriptionComponent( private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = true, needRemoveButton = true,
nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(), nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(),
@@ -256,6 +262,10 @@ private class TemplateSettingsComponent(
component.bordered() component.bordered()
} }
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? =
settings.findComponentWithError(error)
fun setTemplate(module: Module, selectedTemplate: Template) { fun setTemplate(module: Module, selectedTemplate: Template) {
settings.setSettings(selectedTemplate.settings(module)) settings.setSettings(selectedTemplate.settings(module))
templateDescriptionComponent.updateSelectedTemplate(selectedTemplate) templateDescriptionComponent.updateSelectedTemplate(selectedTemplate)
@@ -4,6 +4,7 @@ import com.intellij.ui.JBColor
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ListSettingType import org.jetbrains.kotlin.tools.projectWizard.core.entity.ListSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
@@ -20,7 +21,8 @@ import javax.swing.JComponent
class ModulesEditorComponent( class ModulesEditorComponent(
valuesReadingContext: ValuesReadingContext, valuesReadingContext: ValuesReadingContext,
uiEditorUsagesStats: UiEditorUsageStats, uiEditorUsagesStats: UiEditorUsageStats,
oneEntrySelected: (data: DisplayableSettingItem?) -> Unit oneEntrySelected: (data: DisplayableSettingItem?) -> Unit,
selectSettingWithError: (ValidationResult.ValidationError) -> Unit
) : SettingComponent<List<Module>, ListSettingType<Module>>(KotlinPlugin::modules.reference, valuesReadingContext) { ) : SettingComponent<List<Module>, ListSettingType<Module>>(KotlinPlugin::modules.reference, valuesReadingContext) {
private val tree: ModulesEditorTree = private val tree: ModulesEditorTree =
ModulesEditorTree( ModulesEditorTree(
@@ -64,7 +66,11 @@ class ModulesEditorComponent(
} }
} }
override val validationIndicator = ValidationIndicator(showText = true).apply { override val validationIndicator = ValidationIndicator(showText = true) { error ->
val module = error.target as? Module ?: return@ValidationIndicator
tree.selectModule(module)
selectSettingWithError(error)
}.apply {
background = tree.background background = tree.background
} }
} }
@@ -7,12 +7,14 @@ import com.intellij.ui.treeStructure.Tree
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.path
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JTree import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
import javax.swing.tree.TreeSelectionModel import javax.swing.tree.TreeSelectionModel
class ModulesEditorTree( class ModulesEditorTree(
@@ -32,6 +34,17 @@ class ModulesEditorTree(
openPaths.forEach(::expandPath) openPaths.forEach(::expandPath)
} }
fun selectModule(moduleToSelect: Module) {
for (value in (model.root as DefaultMutableTreeNode).depthFirstEnumeration().iterator()) {
val node = value as? DefaultMutableTreeNode ?: continue
val module = node.userObject as? Module ?: continue
if (module.path == moduleToSelect.path) {
selectionPath = TreePath(node.path)
break
}
}
}
val selectedSettingItem val selectedSettingItem
get() = selectedNode?.userObject?.safeAs<DisplayableSettingItem>() get() = selectedNode?.userObject?.safeAs<DisplayableSettingItem>()
@@ -4,11 +4,12 @@ import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.* import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Displayable import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Displayable
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
abstract class SettingComponent<V : Any, T: SettingType<V>>( abstract class SettingComponent<V : Any, T: SettingType<V>>(
val reference: SettingReference<V, T>, val reference: SettingReference<V, T>,
val valuesReadingContext: ValuesReadingContext val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext), Displayable { ) : DynamicComponent(valuesReadingContext), Displayable, FocusableComponent {
var value: V? var value: V?
get() = reference.value get() = reference.value
set(value) { set(value) {
@@ -3,18 +3,25 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.ui.components.panels.VerticalLayout import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.isSpecificError
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.PanelWithStatusText import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.PanelWithStatusText
import javax.swing.JComponent import javax.swing.JComponent
interface ErrorAwareComponent {
fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent?
}
class SettingsList( class SettingsList(
settings: List<SettingReference<*, *>>, settings: List<SettingReference<*, *>>,
private val valuesReadingContext: ValuesReadingContext private val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) { ) : DynamicComponent(valuesReadingContext), ErrorAwareComponent {
private val panel = PanelWithStatusText(VerticalLayout(5), "This module has no settings to configure") private val panel = PanelWithStatusText(VerticalLayout(5), "This module has no settings to configure")
private var settingComponents: List<Component> = emptyList() var settingComponents: List<SettingComponent<*, *>> = emptyList()
private set
init { init {
setSettings(settings) setSettings(settings)
@@ -35,4 +42,11 @@ class SettingsList(
} }
settingComponents.forEach { setting -> setting.onInit(); panel.add(setting.component) } settingComponents.forEach { setting -> setting.onInit(); panel.add(setting.component) }
} }
override fun findComponentWithError(error: ValidationResult.ValidationError) =
settingComponents.firstOrNull { component ->
val value = component.value ?: return@firstOrNull false
val result = component.setting.validator.validate(valuesReadingContext, value)
result isSpecificError error
}
} }
@@ -29,5 +29,9 @@ abstract class UIComponentDelegatingSettingComponent<V : Any, T : SettingType<V>
value?.let(uiComponent::updateUiValue) value?.let(uiComponent::updateUiValue)
} }
override fun focusOn() {
uiComponent.focusOn()
}
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) { uiComponent.component } override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) { uiComponent.component }
} }
@@ -2,28 +2,49 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.ui.JBColor import com.intellij.ui.JBColor
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.EmptyIcon import com.intellij.util.ui.EmptyIcon
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.asHtml
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Cursor
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import javax.swing.BorderFactory import javax.swing.BorderFactory
import javax.swing.Icon import javax.swing.Icon
import javax.swing.JPanel import javax.swing.JPanel
class ValidationIndicator( class ValidationIndicator(
defaultText: String? = null, defaultText: String? = null,
private val showText: Boolean private val showText: Boolean,
private val onClickAction: ((ValidationResult.ValidationError) -> Unit)? = null
) : JPanel(BorderLayout()) { ) : JPanel(BorderLayout()) {
private val textLabel = defaultText?.let { label("$it: ") } private val textLabel = defaultText?.let { label("$it: ") }
private val errorLabel = label(" ").apply { private val errorLabel = label(" ").apply {
foreground = JBColor.red foreground = JBColor.red
} }
private var currentError: ValidationResult.ValidationError? = null
init { init {
border = BorderFactory.createEmptyBorder(3, 3, 3, 3) border = BorderFactory.createEmptyBorder(3, 3, 3, 3)
textLabel?.let { add(it, BorderLayout.WEST) } textLabel?.let { add(it, BorderLayout.WEST) }
add(errorLabel, BorderLayout.CENTER) add(errorLabel, BorderLayout.CENTER)
if (onClickAction != null) {
cursor = Cursor(Cursor.HAND_CURSOR)
addMouseListener(object : MouseListener {
override fun mouseReleased(p0: MouseEvent?) {}
override fun mouseEntered(p0: MouseEvent?) {}
override fun mouseExited(p0: MouseEvent?) {}
override fun mousePressed(p0: MouseEvent?) {}
override fun mouseClicked(p0: MouseEvent?) {
currentError?.let(onClickAction)
}
})
}
} }
private fun setIcon(icon: Icon?) { private fun setIcon(icon: Icon?) {
@@ -31,6 +52,9 @@ class ValidationIndicator(
else errorLabel.icon = icon ?: EmptyIcon.ICON_16 else errorLabel.icon = icon ?: EmptyIcon.ICON_16
} }
private fun String.linkifyErrorText() =
if (onClickAction == null) this
else "<u>$this</u>".asHtml()
var validationState: ValidationResult = ValidationResult.OK var validationState: ValidationResult = ValidationResult.OK
set(value) { set(value) {
@@ -40,12 +64,14 @@ class ValidationIndicator(
setIcon(null) setIcon(null)
errorLabel.text = " " errorLabel.text = " "
toolTipText = null toolTipText = null
currentError = null
} }
is ValidationResult.ValidationError -> { is ValidationResult.ValidationError -> {
setIcon(AllIcons.General.Error) setIcon(AllIcons.General.Error)
val errors = value.messages.firstOrNull().orEmpty() val errors = value.messages.firstOrNull().orEmpty()
toolTipText = errors toolTipText = errors
if (showText) errorLabel.text = errors if (showText) errorLabel.text = errors.linkifyErrorText()
currentError = value
} }
} }
} }
@@ -354,7 +354,7 @@ class ListSettingType<V : Any>(private val parser: Parser<V>) : SettingType<List
validate { values -> validate { values ->
values.fold(ValidationResult.OK as ValidationResult) { result, value -> values.fold(ValidationResult.OK as ValidationResult) { result, value ->
result and when (value) { result and when (value) {
is Validatable<*> -> (value.validator as SettingValidator<Any>).validate(this, value) is Validatable<*> -> (value.validator as SettingValidator<Any>).validate(this, value).withTargetIfNull(value)
else -> ValidationResult.OK else -> ValidationResult.OK
} }
} }
@@ -55,8 +55,8 @@ sealed class ValidationResult {
override val isOk = true override val isOk = true
} }
class ValidationError(val messages: List<String>) : ValidationResult() { data class ValidationError(val messages: List<String>, val target: Any? = null) : ValidationResult() {
constructor(message: String) : this(listOf(message)) constructor(message: String, target: Any? = null) : this(listOf(message), target)
override val isOk = false override val isOk = false
} }
@@ -73,6 +73,19 @@ sealed class ValidationResult {
} }
} }
infix fun ValidationResult.isSpecificError(error: ValidationResult.ValidationError) =
this is ValidationResult.ValidationError && messages.firstOrNull() == error.messages.firstOrNull()
fun ValidationResult.withTarget(target: Any) = when (this) {
ValidationResult.OK -> this
is ValidationResult.ValidationError -> copy(target = target)
}
fun ValidationResult.withTargetIfNull(target: Any) = when (this) {
ValidationResult.OK -> this
is ValidationResult.ValidationError -> if (this.target == null) copy(target = target) else this
}
fun ValidationResult.toResult() = when (this) { fun ValidationResult.toResult() = when (this) {
ValidationResult.OK -> UNIT_SUCCESS ValidationResult.OK -> UNIT_SUCCESS
is ValidationResult.ValidationError -> Failure(messages.map { ValidationError(it) }) is ValidationResult.ValidationError -> Failure(messages.map { ValidationError(it) })
@@ -85,7 +98,7 @@ interface Validatable<out V> {
fun <V, Q : Validatable<Q>> ValuesReadingContext.validateList(list: List<Q>) = settingValidator<V> { fun <V, Q : Validatable<Q>> ValuesReadingContext.validateList(list: List<Q>) = settingValidator<V> {
list.fold(ValidationResult.OK as ValidationResult) { result, value -> list.fold(ValidationResult.OK as ValidationResult) { result, value ->
result and value.validator.validate(this, value) result and value.validator.validate(this, value).withTarget(value)
} }
} }