Wizard: don't show error dialog on validation, instead navigate to the error

This commit is contained in:
Ilya Kirillov
2020-03-16 19:53:48 +03:00
parent f640477464
commit c71972afc9
13 changed files with 154 additions and 345 deletions
@@ -61,11 +61,8 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
companion object { companion object {
const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder" const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder"
private const val DEFAULT_GROUP_ID = "me.user" private const val DEFAULT_GROUP_ID = "me.user"
private val projectNameValidator = StringValidators.shouldBeValidIdentifier( private val projectNameValidator = StringValidators.shouldBeValidIdentifier("Project name", setOf('-', '_'))
KotlinNewProjectWizardBundle.message("editor.entity.project.name"), private const val INVALID_PROJECT_NAME_MESSAGE = "Invalid project name"
setOf('-', '_')
)
private val INVALID_PROJECT_NAME_MESSAGE = KotlinNewProjectWizardBundle.message("error.invalid.project.name")
} }
override fun isAvailable(): Boolean = ExperimentalFeatures.NewWizard.isEnabled override fun isAvailable(): Boolean = ExperimentalFeatures.NewWizard.isEnabled
@@ -96,7 +93,7 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP) phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP)
).onFailure { errors -> ).onFailure { errors ->
val errorMessages = errors.joinToString(separator = "\n") { it.message } val errorMessages = errors.joinToString(separator = "\n") { it.message }
Messages.showErrorDialog(project, errorMessages, KotlinNewProjectWizardBundle.message("error.following")) Messages.showErrorDialog(project, errorMessages, "The following errors arose during project generation")
}.isSuccess }.isSuccess
if (success) { if (success) {
val projectCreationStats = ProjectCreationStats( val projectCreationStats = ProjectCreationStats(
@@ -186,11 +183,16 @@ abstract class WizardStep(protected val wizard: IdeWizard, private val phase: Ge
override fun updateDataModel() = Unit // model is updated on every UI action override fun updateDataModel() = Unit // model is updated on every UI action
override fun validate(): Boolean = override fun validate(): Boolean =
when (val result = wizard.context.read { with(wizard) { validate(setOf(phase)) } }) { when (val result = wizard.context.read { with(wizard) { validate(setOf(phase)) } }) {
is Success<*> -> true ValidationResult.OK -> true
is Failure -> { is ValidationResult.ValidationError -> {
throw ConfigurationException(result.asHtml(), KotlinNewProjectWizardBundle.message("error.validation")) handleErrors(result)
false
} }
} }
protected open fun handleErrors(error: ValidationResult.ValidationError) {
throw ConfigurationException(error.asHtml(), "Validation Error")
}
} }
private class PomWizardStep( private class PomWizardStep(
@@ -256,4 +258,8 @@ class ModuleNewWizardSecondStep(
override fun _init() { override fun _init() {
component.onInit() component.onInit()
} }
override fun handleErrors(error: ValidationResult.ValidationError) {
component.navigateTo(error)
}
} }
@@ -5,12 +5,13 @@ import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Reader import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.core.SettingsWriter import org.jetbrains.kotlin.tools.projectWizard.core.SettingsWriter
import org.jetbrains.kotlin.tools.projectWizard.core.Writer 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.PluginSettingPropertyReference import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.PluginSettingPropertyReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference 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.core.entity.settings.SettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
abstract class Component : Displayable { abstract class Component : Displayable, ErrorNavigatable {
private val subComponents = mutableListOf<Component>() private val subComponents = mutableListOf<Component>()
open fun onInit() { open fun onInit() {
@@ -20,6 +21,10 @@ abstract class Component : Displayable {
protected fun <C : Component> C.asSubComponent(): C = also { protected fun <C : Component> C.asSubComponent(): C = also {
this@Component.subComponents += it this@Component.subComponents += it
} }
override fun navigateTo(error: ValidationResult.ValidationError) {
subComponents.forEach { it.navigateTo(error) }
}
} }
abstract class DynamicComponent(private val context: Context) : Component() { abstract class DynamicComponent(private val context: Context) : Component() {
@@ -67,3 +72,7 @@ abstract class TitledComponent(context: Context) : DynamicComponent(context) {
interface FocusableComponent { interface FocusableComponent {
fun focusOn() {} fun focusOn() {}
} }
interface ErrorNavigatable {
fun navigateTo(error: ValidationResult.ValidationError)
}
@@ -17,7 +17,6 @@ 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.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
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.IdeaBasedComponentValidator import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.IdeaBasedComponentValidator
import java.awt.BorderLayout import java.awt.BorderLayout
import javax.swing.JComponent import javax.swing.JComponent
@@ -27,7 +26,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(context), ErrorAwareComponent, FocusableComponent, Disposable { ) : DynamicComponent(context), FocusableComponent, Disposable {
private val validationIndicator by lazy(LazyThreadSafetyMode.NONE) { private val validationIndicator by lazy(LazyThreadSafetyMode.NONE) {
if (validator != null) if (validator != null)
IdeaBasedComponentValidator(this, getValidatorTarget()) IdeaBasedComponentValidator(this, getValidatorTarget())
@@ -83,10 +82,14 @@ abstract class UIComponent<V : Any>(
uiComponent.requestFocus() uiComponent.requestFocus()
} }
override fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent? = takeIf {
read { override fun navigateTo(error: ValidationResult.ValidationError) {
val errorInThisComponent = read {
getUiValue()?.let { validator?.validate?.invoke(this, it)?.isSpecificError(error) } == true getUiValue()?.let { validator?.validate?.invoke(this, it)?.isSpecificError(error) } == true
} }
if (errorInThisComponent) {
focusOn()
}
} }
} }
@@ -65,7 +65,7 @@ class ModuleSettingsComponent(
} }
private class ModuleNameComponent(context: Context, module: Module) : TitledComponent(context) { private class ModuleNameComponent(context: Context, module: Module) : TitledComponent(context) {
override val component: JComponent = TextFieldComponent( private val textField = TextFieldComponent(
context, context,
labelText = null, labelText = null,
initialValue = module.name, initialValue = module.name,
@@ -73,7 +73,10 @@ private class ModuleNameComponent(context: Context, module: Module) : TitledComp
) { value -> ) { value ->
module.name = value module.name = value
context.write { eventManager.fireListeners(null) } context.write { eventManager.fireListeners(null) }
}.component }.asSubComponent()
override val component: JComponent
get() = textField.component
override val title: String = "Name" override val title: String = "Name"
@@ -91,7 +94,7 @@ private class ModuleTemplateComponent(
onTemplateChanged: () -> Unit onTemplateChanged: () -> Unit
) : TitledComponent(context) { ) : TitledComponent(context) {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override val component = DropDownComponent( private val dropDown = DropDownComponent(
context, context,
initialValues = buildList { initialValues = buildList {
add(NoneTemplate) add(NoneTemplate)
@@ -102,7 +105,10 @@ private class ModuleTemplateComponent(
) { value -> ) { value ->
module.template = value.takeIf { it != NoneTemplate } module.template = value.takeIf { it != NoneTemplate }
onTemplateChanged() onTemplateChanged()
}.component }.asSubComponent()
override val component: JComponent
get() = dropDown.component
override val title: String = "Template" override val title: String = "Template"
@@ -24,6 +24,11 @@ class SecondStepWizardComponent(
addToCenter(moduleSettingsComponent.component) addToCenter(moduleSettingsComponent.component)
} }
override fun navigateTo(error: ValidationResult.ValidationError) {
moduleEditorComponent.navigateTo(error)
moduleSettingsComponent.navigateTo(error)
}
private fun onNodeSelected(data: DisplayableSettingItem?) { private fun onNodeSelected(data: DisplayableSettingItem?) {
moduleSettingsComponent.selectedNode = data moduleSettingsComponent.selectedNode = data
} }
@@ -1,281 +0,0 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.ui.JBSplitter
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ui.StatusText
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import org.jetbrains.kotlin.tools.projectWizard.templates.settings
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.setting.ErrorAwareComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.TitledComponentsList
import java.awt.*
import javax.swing.JComponent
import javax.swing.JPanel
class TemplatesComponent(
context: Context,
uiEditorUsagesStats: UiEditorUsageStats
) : DynamicComponent(context), ErrorAwareComponent {
private val chooseTemplateComponent: ChooseTemplateComponent =
ChooseTemplateComponent(context) { template ->
uiEditorUsagesStats.moduleTemplatesSet++
module?.template = template
switchState(template)
}.asSubComponent()
private val templateSettingsComponent = TemplateSettingsComponent(context) {
if (MessagesEx.showOkCancelDialog(
component,
"Do you want to remove selected template from module",
"Remove selected template",
"Remove",
"Cancel",
null
) == Messages.OK
) {
uiEditorUsagesStats.moduleTemplatesRemoved++
module?.template = null
switchState(null)
}
}.asSubComponent()
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? =
templateSettingsComponent.findComponentWithError(error)
private fun switchState(selectedTemplate: Template?) {
panel.removeAll()
when (selectedTemplate) {
null -> panel.add(chooseTemplateComponent.component, BorderLayout.CENTER)
else -> {
if (module != null) {
templateSettingsComponent.setTemplate(module!!, selectedTemplate)
}
panel.add(templateSettingsComponent.component, BorderLayout.CENTER)
}
}
panel.updateUI()
}
val panel = panel {
add(chooseTemplateComponent.component, BorderLayout.CENTER)
}
override val component: JComponent = panel
var module: Module? = null
set(value) {
field = value
switchState(value?.template)
chooseTemplateComponent.selectedModule = value
}
}
class ChooseTemplateComponent(
context: Context,
private val onTemplateChosen: (Template) -> Unit
) : DynamicComponent(context) {
private enum class State(val text: String) {
MODULE_SELECTED_AND_TEMPLATES_AVAILABLE("You can configure a template for selected module"),
MODULE_SELECTED_AND_NO_TEMPLATES_AVAILABLE("No templates available for selected module"),
NO_MODULE_SELECTED("Please select a module to configure")
}
override val component: JPanel = object : JPanel() {
override fun paint(g: Graphics?) {
super.paint(g)
statusText.paint(this, g)
}
}
private val statusText = object : StatusText(component) {
override fun isStatusVisible() = true
}
private val allTemplates
get() = read {
TemplatesPlugin::templates.propertyValue
}
var selectedModule: Module? = null
set(value) {
field = value
onStateUpdated()
}
init {
onStateUpdated()
}
private val availableTemplates
get() = allTemplates.values.filter { template ->
selectedModule!!.configurator.moduleType in template.moduleTypes
&& template.isApplicableTo(selectedModule!!)
}
private val state: State
get() = when {
selectedModule == null -> State.NO_MODULE_SELECTED
availableTemplates.isEmpty() -> State.MODULE_SELECTED_AND_NO_TEMPLATES_AVAILABLE
else -> State.MODULE_SELECTED_AND_TEMPLATES_AVAILABLE
}
private fun onStateUpdated() {
statusText.clear()
statusText.appendText(state.text)
if (state == State.MODULE_SELECTED_AND_TEMPLATES_AVAILABLE) {
statusText.appendSecondaryText("Configure", SimpleTextAttributes.LINK_ATTRIBUTES) {
showDialog()
}
}
}
private fun showDialog() {
val availableTemplates = availableTemplates
if (availableTemplates.isEmpty()) {
MessagesEx.error(null, "No templates available for the selected module")
} else {
val dialog = TemplateListDialog(availableTemplates, component)
if (dialog.showAndGet()) {
onTemplateChosen(dialog.selectedTemplate ?: return)
}
}
}
}
private class TemplateListDialog(
values: List<Template>,
parent: JComponent
) : DialogWrapper(parent, false) {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = false
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val templatesList = ImmutableSingleSelectableListWithIcon(
values = values,
renderValue = { value ->
icon = when {
value.moduleTypes.size == 1 -> value.moduleTypes.single().icon
else -> KotlinIcons.MPP
}
append(value.title)
},
onValueSelected = { templateDescriptor ->
templateDescriptionComponent.updateSelectedTemplate(templateDescriptor)
}
).bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
val selectedTemplate: Template?
get() = templatesList.selectedValue
init {
title = "Choose a template to configure"
init()
if (values.isNotEmpty()) {
templateDescriptionComponent.updateSelectedTemplate(values.first())
}
}
override fun createCenterPanel() = panel(BorderLayout()) {
preferredSize = Dimension(700, 300)
val splitter = JBSplitter(false, .3f).apply {
firstComponent = templatesList
secondComponent = templateDescriptionComponent.component
orientation = false
}
add(splitter, BorderLayout.CENTER)
}
}
class TemplateDescriptionComponent(
needRemoveButton: Boolean = false,
private val nonDefaultBackgroundColor: Color? = null,
onRemoveButtonClicked: () -> Unit = {}
) : Component() {
private val removeButton = if (needRemoveButton) {
hyperlinkLabel("Remove template", onClick = onRemoveButtonClicked)
} else null
private val titleLabel = label("", bold = true) {
font = UIUtil.getListFont().let { font ->
Font(font.family, Font.BOLD, (font.size * 1.3).toInt())
}
}
private val title = panel {
nonDefaultBackgroundColor?.let { background = it }
add(titleLabel, BorderLayout.CENTER)
removeButton?.let { add(it, BorderLayout.EAST) }
}
private val descriptionLabel = DescriptionPanel()
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
nonDefaultBackgroundColor?.let { background = it }
add(title, BorderLayout.NORTH)
add(descriptionLabel, BorderLayout.CENTER)
}
}
private fun setComponentsVisibility(isVisible: Boolean) {
titleLabel.isVisible = isVisible
descriptionLabel.isVisible = isVisible
}
fun updateSelectedTemplate(template: Template?) {
setComponentsVisibility(template != null)
if (template != null) {
titleLabel.text = template.title
descriptionLabel.updateText(template.htmlDescription)
}
}
}
private class TemplateSettingsComponent(
context: Context,
removeTemplate: () -> Unit
) : DynamicComponent(context), ErrorAwareComponent {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = true,
nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(),
onRemoveButtonClicked = removeTemplate
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val settings = TitledComponentsList(emptyList(), context).apply {
component.bordered()
}
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? = null
fun setTemplate(module: Module, selectedTemplate: Template) {
settings.setSettings(selectedTemplate.settings(module))
templateDescriptionComponent.updateSelectedTemplate(selectedTemplate)
}
override val component: JComponent = splitterFor(
templateDescriptionComponent.component,
settings.component,
vertical = true
).apply {
(this as JBSplitter).proportion = .7f
}
}
@@ -1,8 +1,10 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.ui.JBColor import com.intellij.ui.JBColor
import com.intellij.util.ui.JBUI
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.Context import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.ListSettingType import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.ListSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
@@ -27,6 +29,8 @@ class ModulesEditorComponent(
private val tree: ModulesEditorTree = private val tree: ModulesEditorTree =
ModulesEditorTree( ModulesEditorTree(
onSelected = { oneEntrySelected(it) }, onSelected = { oneEntrySelected(it) },
context = context,
isTreeEditable = editable,
addModule = { component -> addModule = { component ->
val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Singleplatform val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Singleplatform
moduleCreator.create( moduleCreator.create(
@@ -39,7 +43,9 @@ class ModulesEditorComponent(
createModule = model::add createModule = model::add
)?.showInCenterOf(component) )?.showInCenterOf(component)
} }
) ).apply {
border = JBUI.Borders.emptyRight(10)
}
private val model = TargetsModel(tree, ::value, context, uiEditorUsagesStats) private val model = TargetsModel(tree, ::value, context, uiEditorUsagesStats)
@@ -52,6 +58,11 @@ class ModulesEditorComponent(
model.update() model.update()
} }
override fun navigateTo(error: ValidationResult.ValidationError) {
val targetModule = error.target as? Module ?: return
tree.selectModule(targetModule)
}
private val moduleCreator = NewModuleCreator() private val moduleCreator = NewModuleCreator()
private val toolbarDecorator = if (editable) ModulesEditorToolbarDecorator( private val toolbarDecorator = if (editable) ModulesEditorToolbarDecorator(
@@ -3,23 +3,28 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEdi
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.ui.ColoredTreeCellRenderer import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.JBLabel
import com.intellij.ui.treeStructure.Tree import com.intellij.ui.treeStructure.Tree
import org.jetbrains.kotlin.tools.projectWizard.core.Context
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
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.path import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.path
import org.jetbrains.kotlin.tools.projectWizard.wizard.KotlinNewProjectWizardBundle import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.borderPanel
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 java.awt.Component
import java.awt.Graphics2D
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JTree import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode import javax.swing.tree.*
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
import javax.swing.tree.TreeSelectionModel
class ModulesEditorTree( class ModulesEditorTree(
private val onSelected: (DisplayableSettingItem?) -> Unit, private val onSelected: (DisplayableSettingItem?) -> Unit,
context: Context,
isTreeEditable: Boolean,
private val addModule: (JComponent) -> Unit private val addModule: (JComponent) -> Unit
) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) { ) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) {
val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel
@@ -64,41 +69,81 @@ class ModulesEditorTree(
} }
emptyText.clear() emptyText.clear()
emptyText.appendText(KotlinNewProjectWizardBundle.message("editor.no.modules.created")) emptyText.appendText("No modules created")
emptyText.appendSecondaryText( emptyText.appendSecondaryText(
KotlinNewProjectWizardBundle.message("editor.add.module.to.project"), "Add a module to the project",
SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES
) { ) {
addModule(this) addModule(this)
} }
setCellRenderer(object : ColoredTreeCellRenderer() { setCellRenderer(CellRenderer(context, renderErrors = isTreeEditable))
override fun customizeCellRenderer( }
tree: JTree, }
value: Any?,
selected: Boolean, private class CellRenderer(private val context: Context, renderErrors: Boolean) : TreeCellRenderer {
expanded: Boolean, override fun getTreeCellRendererComponent(
leaf: Boolean, tree: JTree?,
row: Int, value: Any?,
hasFocus: Boolean selected: Boolean,
) { expanded: Boolean,
if (value?.safeAs<DefaultMutableTreeNode>()?.userObject == PROJECT_USER_OBJECT) { leaf: Boolean,
icon = AllIcons.Nodes.Project row: Int,
append(KotlinNewProjectWizardBundle.message("editor.project")) hasFocus: Boolean
return ): Component {
} setError(null)
val setting = (value as? DefaultMutableTreeNode)?.userObject as? DisplayableSettingItem ?: return val renderedCell = coloredCellRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus)
icon = when (setting) { return borderPanel {
is Module -> setting.icon addToCenter(renderedCell)
is Sourceset -> AllIcons.Nodes.Module addToRight(iconLabel)
else -> null }
} }
append(setting.text)
setting.greyText?.let { greyText -> private val iconLabel = JBLabel("")
append(" ")
append(greyText, SimpleTextAttributes.GRAYED_ATTRIBUTES) private fun setError(error: ValidationResult.ValidationError?) {
} val message = error?.messages?.firstOrNull()
} if (message == null) {
}) iconLabel.icon = null
} else {
iconLabel.icon = AllIcons.General.Error
}
}
private val coloredCellRenderer = object : ColoredTreeCellRenderer() {
override fun customizeCellRenderer(
tree: JTree,
value: Any?,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
hasFocus: Boolean
) {
if (value?.safeAs<DefaultMutableTreeNode>()?.userObject == ModulesEditorTree.PROJECT_USER_OBJECT) {
icon = AllIcons.Nodes.Project
append("Project")
return
}
val setting = (value as? DefaultMutableTreeNode)?.userObject as? DisplayableSettingItem ?: return
icon = when (setting) {
is Module -> setting.icon
is Sourceset -> AllIcons.Nodes.Module
else -> null
}
append(setting.text)
setting.greyText?.let { greyText ->
append(" ")
append(greyText, SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
if (renderErrors) {
if (setting is Module) context.read {
val validationResult = setting.validator.validate(this, setting)
setError(validationResult as? ValidationResult.ValidationError)
} else {
setError(null)
}
}
}
} }
} }
@@ -9,10 +9,6 @@ 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.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitledComponent import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitledComponent
interface ErrorAwareComponent {
fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent?
}
class TitledComponentsList( class TitledComponentsList(
private var components: List<TitledComponent>, private var components: List<TitledComponent>,
private val context: Context private val context: Context
@@ -25,6 +21,10 @@ class TitledComponentsList(
override val component get() = ui override val component get() = ui
override fun navigateTo(error: ValidationResult.ValidationError) {
components.forEach { it.navigateTo(error) }
}
override fun onInit() { override fun onInit() {
super.onInit() super.onInit()
components.forEach { it.onInit() } components.forEach { it.onInit() }
@@ -15,6 +15,7 @@ import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.components.BorderLayoutPanel import com.intellij.util.ui.components.BorderLayoutPanel
import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.core.Failure import org.jetbrains.kotlin.tools.projectWizard.core.Failure
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
@@ -69,11 +70,11 @@ internal fun hyperlinkLabel(
internal fun String.asHtml() = "<html><body>$this</body></html>" internal fun String.asHtml() = "<html><body>$this</body></html>"
fun Failure.asHtml() = when (errors.size) { fun ValidationResult.ValidationError.asHtml() = when (messages.size) {
0 -> "" 0 -> ""
1 -> errors.single().message 1 -> messages.single()
else -> { else -> {
val errorsList = errors.joinToString(separator = "") { "<li>${it.message}</li>" } val errorsList = messages.joinToString(separator = "") { "<li>${it}</li>" }
"<ul>$errorsList</ul>".asHtml() "<ul>$errorsList</ul>".asHtml()
} }
} }
@@ -61,7 +61,7 @@ sealed class ValidationResult {
infix fun and(other: ValidationResult) = when { infix fun and(other: ValidationResult) = when {
this is OK -> other this is OK -> other
this is ValidationError && other is ValidationError -> ValidationError(messages + other.messages) this is ValidationError && other is ValidationError -> ValidationError(messages + other.messages, target ?: other.target)
else -> this else -> this
} }
@@ -8,6 +8,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.PomIR import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.PomIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import java.nio.file.Paths import java.nio.file.Paths
@@ -15,7 +16,10 @@ class StructurePlugin(context: Context) : Plugin(context) {
val projectPath by pathSetting("Location", GenerationPhase.PROJECT_GENERATION) { val projectPath by pathSetting("Location", GenerationPhase.PROJECT_GENERATION) {
defaultValue = value(Paths.get(".")) defaultValue = value(Paths.get("."))
} }
val name by stringSetting("Name", GenerationPhase.PROJECT_GENERATION) val name by stringSetting("Name", GenerationPhase.PROJECT_GENERATION) {
shouldNotBeBlank()
validate(StringValidators.shouldBeValidIdentifier("Name", Module.ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES))
}
val groupId by stringSetting("Group ID", GenerationPhase.PROJECT_GENERATION) { val groupId by stringSetting("Group ID", GenerationPhase.PROJECT_GENERATION) {
isSavable = true isSavable = true
@@ -39,13 +39,13 @@ abstract class Wizard(createPlugins: PluginsCreator, servicesManager: ServicesMa
} }
} }
fun validate(phases: Set<GenerationPhase>): TaskResult<Unit> = context.read { fun validate(phases: Set<GenerationPhase>): ValidationResult = context.read {
pluginSettings.map { setting -> pluginSettings.map { setting ->
val value = setting.reference.notRequiredSettingValue ?: return@map ValidationResult.OK val value = setting.reference.notRequiredSettingValue ?: return@map ValidationResult.OK
if (setting.neededAtPhase in phases && setting.isActive(this)) if (setting.neededAtPhase in phases && setting.isActive(this))
(setting.validator as SettingValidator<Any>).validate(this, value) (setting.validator as SettingValidator<Any>).validate(this, value)
else ValidationResult.OK else ValidationResult.OK
}.fold().toResult() }.fold()
} }
private fun saveSettingValues(phases: Set<GenerationPhase>) = context.read { private fun saveSettingValues(phases: Set<GenerationPhase>) = context.read {
@@ -69,7 +69,7 @@ abstract class Wizard(createPlugins: PluginsCreator, servicesManager: ServicesMa
initPluginSettingsDefaultValues() initPluginSettingsDefaultValues()
initNonPluginDefaultValues() initNonPluginDefaultValues()
checkAllRequiredSettingPresent(phases).ensure() checkAllRequiredSettingPresent(phases).ensure()
validate(phases).ensure() validate(phases).toResult().ensure()
saveSettingValues(phases) saveSettingValues(phases)
val (tasksSorted) = context.sortTasks().map { tasks -> val (tasksSorted) = context.sortTasks().map { tasks ->
tasks.groupBy { it.phase }.toList().sortedBy { it.first }.flatMap { it.second } tasks.groupBy { it.phase }.toList().sortedBy { it.first }.flatMap { it.second }