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 {
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+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.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() {
|
||||||
@@ -66,4 +71,8 @@ abstract class TitledComponent(context: Context) : DynamicComponent(context) {
|
|||||||
|
|
||||||
interface FocusableComponent {
|
interface FocusableComponent {
|
||||||
fun focusOn() {}
|
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.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -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"
|
||||||
|
|
||||||
|
|||||||
+5
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
-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
|
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(
|
||||||
|
|||||||
+78
-33
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.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() }
|
||||||
|
|||||||
+4
-3
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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.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
|
||||||
|
|||||||
+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 ->
|
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 }
|
||||||
|
|||||||
Reference in New Issue
Block a user