Wizard: flatten second UI step to fit new UI

This commit is contained in:
Ilya Kirillov
2020-03-16 12:19:57 +03:00
parent 586917ef3d
commit f3ca37fbac
10 changed files with 147 additions and 103 deletions
@@ -60,6 +60,10 @@ abstract class DynamicComponent(private val context: Context) : Component() {
open fun onValueUpdated(reference: SettingReference<*, *>?) {}
}
abstract class TitledComponent(context: Context) : DynamicComponent(context) {
abstract val title: String?
}
interface FocusableComponent {
fun focusOn() {}
}
@@ -17,6 +17,7 @@ import javax.swing.JList
class DropDownComponent<T : DisplayableSettingItem>(
context: Context,
initialValues: List<T> = emptyList(),
initiallySelectedValue: T? = null,
labelText: String? = null,
private val filter: (T) -> Boolean = { true },
private val validator: SettingValidator<T> = settingValidator { ValidationResult.OK },
@@ -29,9 +30,10 @@ class DropDownComponent<T : DisplayableSettingItem>(
onValueUpdate
) {
@Suppress("UNCHECKED_CAST")
override val uiComponent: ComboBox<T> = ComboBox<T>(
override val uiComponent: ComboBox<T> = ComboBox(
initialValues.filter(filter).toTypedArray<DisplayableSettingItem>() as Array<T>
).apply {
selectedItem = initiallySelectedValue
renderer = object : ColoredListCellRenderer<T>() {
override fun customizeCellRenderer(
list: JList<out T>,
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.Project
import org.jetbrains.kotlin.tools.projectWizard.wizard.IdeWizard
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor.ModulesEditorComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.TitledComponentsList
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.createSettingComponent
import javax.swing.JComponent
@@ -29,7 +29,7 @@ class ProjectSettingsComponent(context: Context) : DynamicComponent(context) {
private val projectTemplateComponent = ProjectTemplateSettingComponent(context).asSubComponent()
private val buildSystemSetting = BuildSystemTypeSettingComponent(context).asSubComponent()
private val nameAndLocationComponent = SettingsList(
private val nameAndLocationComponent = TitledComponentsList(
listOf(
StructurePlugin::name.reference.createSettingComponent(context),
StructurePlugin::projectPath.reference.createSettingComponent(context),
@@ -39,8 +39,10 @@ class ProjectSettingsComponent(context: Context) : DynamicComponent(context) {
context
).asSubComponent()
override val component: JComponent = nameAndLocationComponent.component.apply {
border = JBUI.Borders.empty(10)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
nameAndLocationComponent.component.apply {
border = JBUI.Borders.empty(10)
}
}
}
@@ -1,60 +1,34 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.ui.components.JBTabbedPane
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.getConfiguratorSettings
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.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.settings.buildsystem.Module.Companion.ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import org.jetbrains.kotlin.tools.projectWizard.templates.settings
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitledComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.borderPanel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
import java.awt.BorderLayout
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.TitledComponentsList
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.createSettingComponent
import javax.swing.JComponent
class ModuleSettingsComponent(
context: Context,
private val context: Context,
uiEditorUsagesStats: UiEditorUsageStats
) : DynamicComponent(context) {
private val validateModuleName =
StringValidators.shouldNotBeBlank("Module name") and
StringValidators.shouldBeValidIdentifier("Module Name", ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES)
private val settingsList = TitledComponentsList(emptyList(), context).asSubComponent()
private val moduleConfiguratorSettingsList = SettingsList(emptyList(), context).asSubComponent()
private val templateComponent = TemplatesComponent(context, uiEditorUsagesStats).asSubComponent()
private val tabPanel = JBTabbedPane().apply {
add("Template", templateComponent.component)
add("Module Settings", moduleConfiguratorSettingsList.component)
}
fun selectSettingWithError(error: ValidationResult.ValidationError) {
val componentWithError = nameField.findComponentWithError(error)
?: moduleConfiguratorSettingsList.findComponentWithError(error)?.also { tabPanel.selectedIndex = 1 }
?: templateComponent.findComponentWithError(error)?.also { tabPanel.selectedIndex = 0 }
componentWithError?.focusOn()
}
private val nameField = TextFieldComponent(
context,
labelText = "Name",
onValueUpdate = { value ->
module?.name = value
context.write { eventManager.fireListeners(null) }
},
validator = validateModuleName
).asSubComponent()
override val component: JComponent = panel {
add(nameField.component, BorderLayout.NORTH)
add(tabPanel, BorderLayout.CENTER)
override val component: JComponent = borderPanel {
addToCenter(settingsList.component)
}
var module: Module? = null
@@ -65,13 +39,82 @@ class ModuleSettingsComponent(
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun updateModule(module: Module) {
nameField.updateUiValue(module.name)
nameField.component.isVisible = module.kind != ModuleKind.target
|| module.configurator.moduleType != ModuleType.common
val moduleSettingComponents = buildList {
add(ModuleNameComponent(context, module))
createTemplatesListComponentForModule(module)?.let(::add)
addAll(module.getConfiguratorSettings().map { it.createSettingComponent(context) })
module.template?.let { template ->
addAll(template.settings(module).map { it.createSettingComponent(context) })
}
}
moduleConfiguratorSettingsList.setSettings(module.getConfiguratorSettings())
templateComponent.module = module
settingsList.setComponents(moduleSettingComponents)
}
private fun createTemplatesListComponentForModule(module: Module) =
read { availableTemplatesFor(module) }.takeIf { it.isNotEmpty() }?.let { templates ->
ModuleTemplateComponent(context, module, templates) {
updateModule(module)
component.updateUI()
}
}
}
private class ModuleNameComponent(context: Context, module: Module) : TitledComponent(context) {
override val component: JComponent = TextFieldComponent(
context,
labelText = null,
initialValue = module.name,
validator = validateModuleName
) { value ->
module.name = value
context.write { eventManager.fireListeners(null) }
}.component
override val title: String = "Name"
companion object {
private val validateModuleName =
StringValidators.shouldNotBeBlank("Module name") and
StringValidators.shouldBeValidIdentifier("Module Name", ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES)
}
}
private class ModuleTemplateComponent(
context: Context,
module: Module,
templates: List<Template>,
onTemplateChanged: () -> Unit
) : TitledComponent(context) {
@OptIn(ExperimentalStdlibApi::class)
override val component = DropDownComponent(
context,
initialValues = buildList {
add(NoneTemplate)
addAll(templates)
},
initiallySelectedValue = module.template ?: NoneTemplate,
labelText = null,
) { value ->
module.template = value.takeIf { it != NoneTemplate }
onTemplateChanged()
}.component
override val title: String = "Template"
private object NoneTemplate : Template() {
override val title = "None"
override val htmlDescription: String = ""
override val moduleTypes: Set<ModuleType> = ModuleType.ALL
override val id: String = "none"
}
}
fun Reader.availableTemplatesFor(module: Module) =
TemplatesPlugin::templates.propertyValue.values.filter { template ->
module.configurator.moduleType in template.moduleTypes && template.isApplicableTo(module)
}
@@ -10,36 +10,34 @@ import org.jetbrains.kotlin.tools.projectWizard.wizard.IdeWizard
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor.ModulesEditorComponent
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class SecondStepWizardComponent(
wizard: IdeWizard,
uiEditorUsagesStats: UiEditorUsageStats
) : WizardStepComponent(wizard.context) {
private val moduleEditorSubStep =
ModulesEditorSubStep(wizard.context, uiEditorUsagesStats, ::onNodeSelected) {
templatesSubStep.selectSettingWithError(it)
private val moduleEditorComponent =
ProjectStructureEditorComponent(wizard.context, uiEditorUsagesStats, ::onNodeSelected) {
moduleSettingsComponent.selectSettingWithError(it)
}.asSubComponent()
private val templatesSubStep = ModuleSettingsSubStep(wizard, uiEditorUsagesStats).asSubComponent()
private val moduleSettingsComponent = ModuleSettingsSubStep(wizard, uiEditorUsagesStats).asSubComponent()
override val component = splitterFor(
moduleEditorSubStep.component,
templatesSubStep.component
)
override val component = borderPanel {
addToLeft(moduleEditorComponent.component)
addToCenter(moduleSettingsComponent.component)
}
private fun onNodeSelected(data: DisplayableSettingItem?) {
templatesSubStep.selectedNode = data
moduleSettingsComponent.selectedNode = data
}
}
class ModulesEditorSubStep(
class ProjectStructureEditorComponent(
context: Context,
uiEditorUsagesStats: UiEditorUsageStats,
onNodeSelected: (data: DisplayableSettingItem?) -> Unit,
selectSettingWithError: (ValidationResult.ValidationError) -> Unit
) : SubStep(context) {
) : DynamicComponent(context) {
private val moduleSettingComponent = ModulesEditorComponent(
context,
uiEditorUsagesStats,
@@ -49,13 +47,8 @@ class ModulesEditorSubStep(
selectSettingWithError = selectSettingWithError
).asSubComponent()
override fun buildContent(): JComponent = panel {
bordered(needInnerEmptyBorder = false, needLineBorder = false)
border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(0, 0, 0, UiConstants.GAP_BORDER_SIZE),
border
)
add(moduleSettingComponent.component, BorderLayout.CENTER)
override val component = borderPanel {
addToCenter(moduleSettingComponent.component)
}
}
@@ -64,31 +57,27 @@ class ModuleSettingsSubStep(
wizard: IdeWizard,
uiEditorUsagesStats: UiEditorUsageStats
) : SubStep(wizard.context) {
private val sourcesetSettingsComponent = SourcesetSettingsComponent(wizard.context).asSubComponent()
private val moduleSettingsComponent = ModuleSettingsComponent(wizard.context, uiEditorUsagesStats).asSubComponent()
private val nothingSelectedComponent = NothingSelectedComponent().asSubComponent()
private val panel = panel {
bordered()
add(nothingSelectedComponent.component, BorderLayout.CENTER)
}
var selectedNode: DisplayableSettingItem? = null
set(value) {
field = value
sourcesetSettingsComponent.sourceset = value as? Sourceset
moduleSettingsComponent.module = value as? Module
changeComponent()
}
fun selectSettingWithError(error: ValidationResult.ValidationError) {
moduleSettingsComponent.selectSettingWithError(error)
// moduleSettingsComponent.selectSettingWithError(error)
}
private fun changeComponent() {
panel.removeAll()
val component = when (selectedNode) {
is Sourceset -> sourcesetSettingsComponent
is Module -> moduleSettingsComponent
else -> nothingSelectedComponent
}
@@ -20,7 +20,7 @@ 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.SettingsList
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.TitledComponentsList
import java.awt.*
import javax.swing.JComponent
import javax.swing.JPanel
@@ -258,12 +258,11 @@ private class TemplateSettingsComponent(
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val settings = SettingsList(emptyList(), context).apply {
private val settings = TitledComponentsList(emptyList(), context).apply {
component.bordered()
}
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? =
settings.findComponentWithError(error)
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? = null
fun setTemplate(module: Module, selectedTemplate: Template) {
@@ -128,6 +128,7 @@ class BooleanSettingComponent(
reference,
context
) {
override val title: String? = null
override val uiComponent = CheckboxComponent(
context = context,
labelText = setting.title,
@@ -4,14 +4,13 @@ import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.Setting
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingType
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Displayable
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitledComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
abstract class SettingComponent<V : Any, T : SettingType<V>>(
val reference: SettingReference<V, T>,
context: Context
) : DynamicComponent(context), Displayable, FocusableComponent {
) : TitledComponent(context), FocusableComponent {
var value: V?
get() = reference.value
set(value) {
@@ -22,6 +21,7 @@ abstract class SettingComponent<V : Any, T : SettingType<V>>(
get() = read { reference.setting }
abstract val validationIndicator: ValidationIndicator?
override val title: String? get() = setting.title
override fun onInit() {
super.onInit()
@@ -8,55 +8,52 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.isSpecificError
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
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 SettingsList(
private var settingComponents: List<SettingComponent<*, *>>,
class TitledComponentsList(
private var components: List<TitledComponent>,
private val context: Context
) : DynamicComponent(context), ErrorAwareComponent {
) : DynamicComponent(context) {
private val ui = BorderLayoutPanel()
init {
setSettingComponents(settingComponents)
ui.addToCenter(createComponentsPanel(components))
}
override val component get() = ui
override fun onInit() {
super.onInit()
settingComponents.forEach { it.onInit() }
components.forEach { it.onInit() }
}
private fun setSettingComponents(settingComponents: List<SettingComponent<*, *>>) {
this.settingComponents = settingComponents
fun setComponents(newComponents: List<TitledComponent>) {
this.components = newComponents
ui.removeAll()
ui.addToCenter(panel {
settingComponents.forEach { settingComponent ->
settingComponent.onInit()
row(settingComponent.setting.title + ":") {
settingComponent.component(growX)
}
}
})
newComponents.forEach(TitledComponent::onInit)
ui.addToCenter(createComponentsPanel(newComponents))
}
fun setSettings(settings: List<SettingReference<*, *>>) {
setSettingComponents(
setComponents(
settings.map { setting ->
DefaultSettingComponent.create(setting, context, needLabel = false)
}
)
}
override fun findComponentWithError(error: ValidationResult.ValidationError) = read {
settingComponents.firstOrNull { component ->
val value = component.value ?: return@firstOrNull false
val result = component.setting.validator.validate(this, value)
result isSpecificError error
companion object {
private fun createComponentsPanel(components: List<TitledComponent>) = panel {
components.forEach { component ->
row(component.title?.let { "$it:" }) {
component.component(growX)
}
}
}
}
}
@@ -59,7 +59,7 @@ fun <T> withSettingsOf(
): T = function(IdBasedTemplateEnvironment(template, identificator))
abstract class Template : SettingsOwner, EntitiesOwnerDescriptor {
abstract class Template : SettingsOwner, EntitiesOwnerDescriptor, DisplayableSettingItem {
final override fun <V : Any, T : SettingType<V>> settingDelegate(
create: (path: String) -> SettingBuilder<V, T>
): ReadOnlyProperty<Any, TemplateSetting<V, T>> = cached { name ->
@@ -70,6 +70,8 @@ abstract class Template : SettingsOwner, EntitiesOwnerDescriptor {
abstract val htmlDescription: String
abstract val moduleTypes: Set<ModuleType>
override val text: String get() = title
open fun isApplicableTo(module: Module): Boolean = true
open val settings: List<TemplateSetting<*, *>> = emptyList()
@@ -133,6 +135,11 @@ abstract class Template : SettingsOwner, EntitiesOwnerDescriptor {
"projectName" to StructurePlugin::name.settingValue.capitalize()
)
override fun equals(other: Any?): Boolean =
other.safeAs<Template>()?.id == id
override fun hashCode(): Int = id.hashCode()
@Suppress("UNCHECKED_CAST")
final override fun <V : DisplayableSettingItem> dropDownSetting(
title: String,