Wizard: don't show error dialog on validation, instead navigate to the error
This commit is contained in:
+15
-9
@@ -61,11 +61,8 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
|
||||
companion object {
|
||||
const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder"
|
||||
private const val DEFAULT_GROUP_ID = "me.user"
|
||||
private val projectNameValidator = StringValidators.shouldBeValidIdentifier(
|
||||
KotlinNewProjectWizardBundle.message("editor.entity.project.name"),
|
||||
setOf('-', '_')
|
||||
)
|
||||
private val INVALID_PROJECT_NAME_MESSAGE = KotlinNewProjectWizardBundle.message("error.invalid.project.name")
|
||||
private val projectNameValidator = StringValidators.shouldBeValidIdentifier("Project name", setOf('-', '_'))
|
||||
private const val INVALID_PROJECT_NAME_MESSAGE = "Invalid project name"
|
||||
}
|
||||
|
||||
override fun isAvailable(): Boolean = ExperimentalFeatures.NewWizard.isEnabled
|
||||
@@ -96,7 +93,7 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
|
||||
phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP)
|
||||
).onFailure { errors ->
|
||||
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
|
||||
if (success) {
|
||||
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 validate(): Boolean =
|
||||
when (val result = wizard.context.read { with(wizard) { validate(setOf(phase)) } }) {
|
||||
is Success<*> -> true
|
||||
is Failure -> {
|
||||
throw ConfigurationException(result.asHtml(), KotlinNewProjectWizardBundle.message("error.validation"))
|
||||
ValidationResult.OK -> true
|
||||
is ValidationResult.ValidationError -> {
|
||||
handleErrors(result)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun handleErrors(error: ValidationResult.ValidationError) {
|
||||
throw ConfigurationException(error.asHtml(), "Validation Error")
|
||||
}
|
||||
}
|
||||
|
||||
private class PomWizardStep(
|
||||
@@ -256,4 +258,8 @@ class ModuleNewWizardSecondStep(
|
||||
override fun _init() {
|
||||
component.onInit()
|
||||
}
|
||||
|
||||
override fun handleErrors(error: ValidationResult.ValidationError) {
|
||||
component.navigateTo(error)
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -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.SettingsWriter
|
||||
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.SettingReference
|
||||
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingType
|
||||
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
|
||||
|
||||
abstract class Component : Displayable {
|
||||
abstract class Component : Displayable, ErrorNavigatable {
|
||||
private val subComponents = mutableListOf<Component>()
|
||||
|
||||
open fun onInit() {
|
||||
@@ -20,6 +21,10 @@ abstract class Component : Displayable {
|
||||
protected fun <C : Component> C.asSubComponent(): C = also {
|
||||
this@Component.subComponents += it
|
||||
}
|
||||
|
||||
override fun navigateTo(error: ValidationResult.ValidationError) {
|
||||
subComponents.forEach { it.navigateTo(error) }
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DynamicComponent(private val context: Context) : Component() {
|
||||
@@ -66,4 +71,8 @@ abstract class TitledComponent(context: Context) : DynamicComponent(context) {
|
||||
|
||||
interface FocusableComponent {
|
||||
fun focusOn() {}
|
||||
}
|
||||
|
||||
interface ErrorNavigatable {
|
||||
fun navigateTo(error: ValidationResult.ValidationError)
|
||||
}
|
||||
+7
-4
@@ -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.label
|
||||
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 java.awt.BorderLayout
|
||||
import javax.swing.JComponent
|
||||
@@ -27,7 +26,7 @@ abstract class UIComponent<V : Any>(
|
||||
labelText: String? = null,
|
||||
private val validator: SettingValidator<V>? = null,
|
||||
private val onValueUpdate: (V) -> Unit = {}
|
||||
) : DynamicComponent(context), ErrorAwareComponent, FocusableComponent, Disposable {
|
||||
) : DynamicComponent(context), FocusableComponent, Disposable {
|
||||
private val validationIndicator by lazy(LazyThreadSafetyMode.NONE) {
|
||||
if (validator != null)
|
||||
IdeaBasedComponentValidator(this, getValidatorTarget())
|
||||
@@ -83,10 +82,14 @@ abstract class UIComponent<V : Any>(
|
||||
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
|
||||
}
|
||||
if (errorInThisComponent) {
|
||||
focusOn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -65,7 +65,7 @@ class ModuleSettingsComponent(
|
||||
}
|
||||
|
||||
private class ModuleNameComponent(context: Context, module: Module) : TitledComponent(context) {
|
||||
override val component: JComponent = TextFieldComponent(
|
||||
private val textField = TextFieldComponent(
|
||||
context,
|
||||
labelText = null,
|
||||
initialValue = module.name,
|
||||
@@ -73,7 +73,10 @@ private class ModuleNameComponent(context: Context, module: Module) : TitledComp
|
||||
) { value ->
|
||||
module.name = value
|
||||
context.write { eventManager.fireListeners(null) }
|
||||
}.component
|
||||
}.asSubComponent()
|
||||
|
||||
override val component: JComponent
|
||||
get() = textField.component
|
||||
|
||||
override val title: String = "Name"
|
||||
|
||||
@@ -91,7 +94,7 @@ private class ModuleTemplateComponent(
|
||||
onTemplateChanged: () -> Unit
|
||||
) : TitledComponent(context) {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val component = DropDownComponent(
|
||||
private val dropDown = DropDownComponent(
|
||||
context,
|
||||
initialValues = buildList {
|
||||
add(NoneTemplate)
|
||||
@@ -102,7 +105,10 @@ private class ModuleTemplateComponent(
|
||||
) { value ->
|
||||
module.template = value.takeIf { it != NoneTemplate }
|
||||
onTemplateChanged()
|
||||
}.component
|
||||
}.asSubComponent()
|
||||
|
||||
override val component: JComponent
|
||||
get() = dropDown.component
|
||||
|
||||
override val title: String = "Template"
|
||||
|
||||
|
||||
+5
@@ -24,6 +24,11 @@ class SecondStepWizardComponent(
|
||||
addToCenter(moduleSettingsComponent.component)
|
||||
}
|
||||
|
||||
override fun navigateTo(error: ValidationResult.ValidationError) {
|
||||
moduleEditorComponent.navigateTo(error)
|
||||
moduleSettingsComponent.navigateTo(error)
|
||||
}
|
||||
|
||||
private fun onNodeSelected(data: DisplayableSettingItem?) {
|
||||
moduleSettingsComponent.selectedNode = data
|
||||
}
|
||||
|
||||
-281
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -1,8 +1,10 @@
|
||||
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
|
||||
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.util.ui.JBUI
|
||||
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.core.entity.settings.ListSettingType
|
||||
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
|
||||
@@ -27,6 +29,8 @@ class ModulesEditorComponent(
|
||||
private val tree: ModulesEditorTree =
|
||||
ModulesEditorTree(
|
||||
onSelected = { oneEntrySelected(it) },
|
||||
context = context,
|
||||
isTreeEditable = editable,
|
||||
addModule = { component ->
|
||||
val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Singleplatform
|
||||
moduleCreator.create(
|
||||
@@ -39,7 +43,9 @@ class ModulesEditorComponent(
|
||||
createModule = model::add
|
||||
)?.showInCenterOf(component)
|
||||
}
|
||||
)
|
||||
).apply {
|
||||
border = JBUI.Borders.emptyRight(10)
|
||||
}
|
||||
|
||||
private val model = TargetsModel(tree, ::value, context, uiEditorUsagesStats)
|
||||
|
||||
@@ -52,6 +58,11 @@ class ModulesEditorComponent(
|
||||
model.update()
|
||||
}
|
||||
|
||||
override fun navigateTo(error: ValidationResult.ValidationError) {
|
||||
val targetModule = error.target as? Module ?: return
|
||||
tree.selectModule(targetModule)
|
||||
}
|
||||
|
||||
private val moduleCreator = NewModuleCreator()
|
||||
|
||||
private val toolbarDecorator = if (editable) ModulesEditorToolbarDecorator(
|
||||
|
||||
+78
-33
@@ -3,23 +3,28 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEdi
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ui.ColoredTreeCellRenderer
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.JBLabel
|
||||
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.buildsystem.Module
|
||||
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.KotlinNewProjectWizardBundle
|
||||
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.borderPanel
|
||||
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import java.awt.Component
|
||||
import java.awt.Graphics2D
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JTree
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.TreePath
|
||||
import javax.swing.tree.TreeSelectionModel
|
||||
import javax.swing.tree.*
|
||||
|
||||
class ModulesEditorTree(
|
||||
private val onSelected: (DisplayableSettingItem?) -> Unit,
|
||||
context: Context,
|
||||
isTreeEditable: Boolean,
|
||||
private val addModule: (JComponent) -> Unit
|
||||
) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) {
|
||||
val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel
|
||||
@@ -64,41 +69,81 @@ class ModulesEditorTree(
|
||||
}
|
||||
|
||||
emptyText.clear()
|
||||
emptyText.appendText(KotlinNewProjectWizardBundle.message("editor.no.modules.created"))
|
||||
emptyText.appendText("No modules created")
|
||||
emptyText.appendSecondaryText(
|
||||
KotlinNewProjectWizardBundle.message("editor.add.module.to.project"),
|
||||
"Add a module to the project",
|
||||
SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES
|
||||
) {
|
||||
addModule(this)
|
||||
}
|
||||
|
||||
setCellRenderer(object : ColoredTreeCellRenderer() {
|
||||
override fun customizeCellRenderer(
|
||||
tree: JTree,
|
||||
value: Any?,
|
||||
selected: Boolean,
|
||||
expanded: Boolean,
|
||||
leaf: Boolean,
|
||||
row: Int,
|
||||
hasFocus: Boolean
|
||||
) {
|
||||
if (value?.safeAs<DefaultMutableTreeNode>()?.userObject == PROJECT_USER_OBJECT) {
|
||||
icon = AllIcons.Nodes.Project
|
||||
append(KotlinNewProjectWizardBundle.message("editor.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)
|
||||
setCellRenderer(CellRenderer(context, renderErrors = isTreeEditable))
|
||||
}
|
||||
}
|
||||
|
||||
private class CellRenderer(private val context: Context, renderErrors: Boolean) : TreeCellRenderer {
|
||||
override fun getTreeCellRendererComponent(
|
||||
tree: JTree?,
|
||||
value: Any?,
|
||||
selected: Boolean,
|
||||
expanded: Boolean,
|
||||
leaf: Boolean,
|
||||
row: Int,
|
||||
hasFocus: Boolean
|
||||
): Component {
|
||||
setError(null)
|
||||
val renderedCell = coloredCellRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus)
|
||||
return borderPanel {
|
||||
addToCenter(renderedCell)
|
||||
addToRight(iconLabel)
|
||||
}
|
||||
}
|
||||
|
||||
private val iconLabel = JBLabel("")
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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.TitledComponent
|
||||
|
||||
interface ErrorAwareComponent {
|
||||
fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent?
|
||||
}
|
||||
|
||||
class TitledComponentsList(
|
||||
private var components: List<TitledComponent>,
|
||||
private val context: Context
|
||||
@@ -25,6 +21,10 @@ class TitledComponentsList(
|
||||
|
||||
override val component get() = ui
|
||||
|
||||
override fun navigateTo(error: ValidationResult.ValidationError) {
|
||||
components.forEach { it.navigateTo(error) }
|
||||
}
|
||||
|
||||
override fun onInit() {
|
||||
super.onInit()
|
||||
components.forEach { it.onInit() }
|
||||
|
||||
+4
-3
@@ -15,6 +15,7 @@ import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
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.SimpleTargetConfigurator
|
||||
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
|
||||
@@ -69,11 +70,11 @@ internal fun hyperlinkLabel(
|
||||
|
||||
internal fun String.asHtml() = "<html><body>$this</body></html>"
|
||||
|
||||
fun Failure.asHtml() = when (errors.size) {
|
||||
fun ValidationResult.ValidationError.asHtml() = when (messages.size) {
|
||||
0 -> ""
|
||||
1 -> errors.single().message
|
||||
1 -> messages.single()
|
||||
else -> {
|
||||
val errorsList = errors.joinToString(separator = "") { "<li>${it.message}</li>" }
|
||||
val errorsList = messages.joinToString(separator = "") { "<li>${it}</li>" }
|
||||
"<ul>$errorsList</ul>".asHtml()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ sealed class ValidationResult {
|
||||
|
||||
infix fun and(other: ValidationResult) = when {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -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.ir.buildsystem.PomIR
|
||||
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 java.nio.file.Paths
|
||||
|
||||
@@ -15,7 +16,10 @@ class StructurePlugin(context: Context) : Plugin(context) {
|
||||
val projectPath by pathSetting("Location", GenerationPhase.PROJECT_GENERATION) {
|
||||
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) {
|
||||
isSavable = true
|
||||
|
||||
+3
-3
@@ -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 ->
|
||||
val value = setting.reference.notRequiredSettingValue ?: return@map ValidationResult.OK
|
||||
if (setting.neededAtPhase in phases && setting.isActive(this))
|
||||
(setting.validator as SettingValidator<Any>).validate(this, value)
|
||||
else ValidationResult.OK
|
||||
}.fold().toResult()
|
||||
}.fold()
|
||||
}
|
||||
|
||||
private fun saveSettingValues(phases: Set<GenerationPhase>) = context.read {
|
||||
@@ -69,7 +69,7 @@ abstract class Wizard(createPlugins: PluginsCreator, servicesManager: ServicesMa
|
||||
initPluginSettingsDefaultValues()
|
||||
initNonPluginDefaultValues()
|
||||
checkAllRequiredSettingPresent(phases).ensure()
|
||||
validate(phases).ensure()
|
||||
validate(phases).toResult().ensure()
|
||||
saveSettingValues(phases)
|
||||
val (tasksSorted) = context.sortTasks().map { tasks ->
|
||||
tasks.groupBy { it.phase }.toList().sortedBy { it.first }.flatMap { it.second }
|
||||
|
||||
Reference in New Issue
Block a user