Wizard: Add initial version of the new project wizard

This commit is contained in:
Ilya Kirillov
2019-12-12 15:29:27 +03:00
parent 4916f166fe
commit aca193ddd2
191 changed files with 11132 additions and 3 deletions
@@ -0,0 +1,39 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":libraries:tools:new-project-wizard"))
implementation(project(":idea:ide-common"))
implementation(project(":idea:idea-core"))
implementation(project(":idea:idea-jvm"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep())
compileOnly(intellijDep())
compileOnly(intellijPluginDep("gradle"))
excludeInAndroidStudio(rootProject) {
compileOnly(intellijPluginDep("maven"))
}
Platform[191].orLower {
compileOnly(intellijDep()) { includeJars("java-api", "java-impl") }
}
Platform[192].orHigher {
compileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") }
testCompileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") }
}
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
@@ -0,0 +1,41 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import org.jetbrains.kotlin.tools.projectWizard.core.PluginsCreator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class IdeWizard(
createPlugins: PluginsCreator,
initialServices: List<Service>
) : Wizard(createPlugins, initialServices) {
private val allSettings = plugins.flatMap { it.declaredSettings }
init {
context.settingContext.initPluginSettings(allSettings)
}
var projectPath by setting(StructurePlugin::projectPath.reference)
var projectName by setting(StructurePlugin::name.reference)
var groupId by setting(StructurePlugin::groupId.reference)
var artifactId by setting(StructurePlugin::artifactId.reference)
var buildSystemType by setting(BuildSystemPlugin::type.reference)
private fun <V : Any, T : SettingType<V>> setting(reference: SettingReference<V, T>) =
object : ReadWriteProperty<Any?, V?> {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: V?) {
context.settingContext[reference] = value ?: return
}
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): V? =
context.settingContext[reference]
}
}
@@ -0,0 +1,126 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix
import com.intellij.jarRepository.JarRepositoryManager
import com.intellij.openapi.module.ModifiableModuleModel
import com.intellij.openapi.module.ModuleTypeId
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.util.PathUtil
import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties
import org.jetbrains.kotlin.config.ResourceKotlinRootType
import org.jetbrains.kotlin.config.SourceKotlinRootType
import org.jetbrains.kotlin.config.TestResourceKotlinRootType
import org.jetbrains.kotlin.config.TestSourceKotlinRootType
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.JpsService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import java.nio.file.Path
import com.intellij.openapi.module.Module as IdeaModule
class IdeaJpsService(
private val project: Project,
private val modulesModel: ModifiableModuleModel
) : JpsService {
override fun importProject(path: Path, modulesIrs: List<ModuleIR>): TaskResult<Unit> = runWriteAction {
ProjectImporter(project, modulesModel, path, modulesIrs).import()
}
}
private class ProjectImporter(
private val project: Project,
private val modulesModel: ModifiableModuleModel,
private val path: Path,
private val modulesIrs: List<ModuleIR>
) {
private val librariesPath: Path
get() = path / "libs"
fun import() = modulesIrs.mapSequence { convertModule(it) } andThen
safe { modulesModel.commit() }
private fun convertModule(moduleIr: ModuleIR): TaskResult<IdeaModule> {
val module = modulesModel.newModule(
(moduleIr.path / "${moduleIr.name}.iml").toString(),
ModuleTypeId.JAVA_MODULE
)
val rootModel = ModuleRootManager.getInstance(module).modifiableModel
val contentRoot = rootModel.addContentEntry(moduleIr.path.url)
SourcesetType.ALL.forEach { sourceset ->
val isTest = sourceset == SourcesetType.test
contentRoot.addSourceFolder(
(moduleIr.path / "src" / sourceset.name / "kotlin").url,
if (isTest) TestSourceKotlinRootType else SourceKotlinRootType
)
contentRoot.addSourceFolder(
(moduleIr.path / "src" / sourceset.name / "resources").url,
if (isTest) TestResourceKotlinRootType else ResourceKotlinRootType
)
}
rootModel.inheritSdk()
rootModel.commit()
addLibrariesToTheModule(moduleIr, module)
return Success(module)
}
private fun addLibrariesToTheModule(moduleIr: ModuleIR, module: IdeaModule) {
moduleIr.irs.forEach { ir ->
when {
ir is LibraryDependencyIR && !ir.isKotlinStdlib -> {
attachLibraryToModule(ir, module)
}
}
}
}
private fun attachLibraryToModule(
libraryDependency: LibraryDependencyIR,
module: IdeaModule
) {
val artifact = libraryDependency.artifact as? MavenArtifact ?: return
val libraryProperties = RepositoryLibraryProperties(
artifact.groupId,
artifact.artifactId,
libraryDependency.version.toString()
)
val classesRoots = downloadLibraryAndGetItsClasses(libraryProperties)
ModuleRootModificationUtil.addModuleLibrary(
module,
if (classesRoots.size > 1) libraryProperties.artifactId else null,
OrderEntryFix.refreshAndConvertToUrls(classesRoots),
emptyList(),
when (libraryDependency.dependencyType) {
DependencyType.MAIN -> DependencyScope.COMPILE
DependencyType.TEST -> DependencyScope.TEST
}
)
}
private fun downloadLibraryAndGetItsClasses(libraryProperties: RepositoryLibraryProperties) =
JarRepositoryManager.loadDependenciesModal(
project,
libraryProperties,
false,
false,
librariesPath.toString(),
null
).asSequence()
.filter { it.type == OrderRootType.CLASSES }
.map { PathUtil.getLocalPath(it.file) }
.toList()
}
private val Path.url
get() = VfsUtil.pathToUrl(toString())
@@ -0,0 +1,183 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.ide.projectWizard.ProjectSettingsStep
import com.intellij.ide.util.projectWizard.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.module.ModifiableModuleModel
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ui.configuration.ModulesProvider
import com.intellij.openapi.ui.Messages
import com.intellij.util.SystemProperties
import org.jetbrains.kotlin.idea.framework.KotlinModuleSettingStep
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.tools.projectWizard.core.Failure
import org.jetbrains.kotlin.tools.projectWizard.core.Success
import org.jetbrains.kotlin.tools.projectWizard.core.isSuccess
import org.jetbrains.kotlin.tools.projectWizard.core.onFailure
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.Plugins
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.PomWizardStepComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep.FirstWizardStepComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.SecondStepWizardComponent
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.nio.file.Paths
import javax.swing.JComponent
import com.intellij.openapi.module.Module as IdeaModule
class NewProjectWizardModuleBuilder : ModuleBuilder() {
private val wizard = IdeWizard(Plugins.allPlugins, listOf(IdeaAndroidService()))
companion object {
const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder"
}
private var wizardContext: WizardContext? = null
override fun getModuleType(): ModuleType<*> = NewProjectWizardModuleType()
override fun createWizardSteps(
wizardContext: WizardContext,
modulesProvider: ModulesProvider
): Array<ModuleWizardStep> {
this.wizardContext = wizardContext
return arrayOf(ModuleNewWizardSecondStep(wizard))
}
override fun commit(
project: Project,
model: ModifiableModuleModel?,
modulesProvider: ModulesProvider?
): List<IdeaModule>? {
val modulesModel = model ?: ModuleManager.getInstance(project).modifiableModel
val success = wizard.apply(
services = listOf(
IdeaMavenService(project),
IdeaGradleService(project),
IdeaJpsService(project, modulesModel),
IdeaFileSystemService(),
IdeaAndroidService()
),
phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP)
).onFailure { errors ->
val errorMessages = errors.joinToString(separator = "\n") { it.message }
Messages.showErrorDialog(project, errorMessages, "The following errors arose during project generation")
}.isSuccess
return when {
!success -> null
wizard.buildSystemType == BuildSystemType.Jps -> runWriteAction {
modulesModel.modules.toList().onEach { setupModule(it) }
}
else -> emptyList()
}
}
override fun modifySettingsStep(settingsStep: SettingsStep): ModuleWizardStep {
updateProjectNameAndPomDate(settingsStep)
return when (wizard.buildSystemType) {
BuildSystemType.Jps -> {
KotlinModuleSettingStep(
JvmPlatforms.defaultJvmPlatform,
this,
settingsStep,
wizardContext
)
}
else -> PomWizardStep(settingsStep, wizard)
}
}
private fun updateProjectNameAndPomDate(settingsStep: SettingsStep) {
val suggestedProjectName = with(wizard.valuesReadingContext) {
ProjectTemplatesPlugin::template.settingValue.suggestedProjectName.decapitalize()
}
settingsStep.moduleNameLocationSettings?.apply {
val projectParentDirectory = moduleContentRoot.let { Paths.get(it).parent.toString() }
moduleName = ProjectWizardUtil.findNonExistingFileName(projectParentDirectory, suggestedProjectName, "")
}
settingsStep.safeAs<ProjectSettingsStep>()?.bindModuleSettings()
wizard.artifactId = suggestedProjectName
wizard.groupId = SystemProperties.getUserName()?.let { "me.$it" } ?: suggestedProjectName
}
override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?) =
ModuleNewWizardFirstStep(wizard)
override fun setName(name: String) {
wizard.projectName = name
}
override fun setModuleFilePath(path: String) = Unit
override fun setContentEntryPath(moduleRootPath: String) {
wizard.projectPath = Paths.get(moduleRootPath)
}
}
abstract class WizardStep(protected val wizard: IdeWizard, private val phase: GenerationPhase) : ModuleWizardStep() {
override fun updateDataModel() = Unit // model is updated on every UI action
override fun validate(): Boolean =
when (val result = with(wizard.valuesReadingContext) { with(wizard) { validate(setOf(phase)) } }) {
is Success<*> -> true
is Failure -> {
val messages = result.errors.joinToString(separator = "\n") { it.message }
throw ConfigurationException(messages)
}
}
}
private class PomWizardStep(
originalSettingStep: SettingsStep,
wizard: IdeWizard
) : WizardStep(wizard, GenerationPhase.PROJECT_GENERATION) {
private val pomWizardStepComponent = PomWizardStepComponent(wizard.valuesReadingContext)
init {
originalSettingStep.addSettingsComponent(component)
pomWizardStepComponent.onInit()
}
override fun getComponent(): JComponent = pomWizardStepComponent.component
}
class ModuleNewWizardFirstStep(wizard: IdeWizard) : WizardStep(wizard, GenerationPhase.FIRST_STEP) {
private val component = FirstWizardStepComponent(wizard)
override fun getComponent(): JComponent = component.component
init {
runPreparePhase()
component.onInit()
}
private fun runPreparePhase() = ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
wizard.apply(emptyList(), setOf(GenerationPhase.PREPARE)) { task ->
ProgressManager.getInstance().progressIndicator.text = task.title ?: ""
}
},
"",
true,
null
)
}
class ModuleNewWizardSecondStep(wizard: IdeWizard) : WizardStep(wizard, GenerationPhase.SECOND_STEP) {
private val component = SecondStepWizardComponent(wizard)
override fun getComponent(): JComponent = component.component
override fun _init() {
component.onInit()
}
}
@@ -0,0 +1,12 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.openapi.module.ModuleType
import org.jetbrains.kotlin.idea.KotlinIcons
import javax.swing.Icon
class NewProjectWizardModuleType: ModuleType<NewProjectWizardModuleBuilder>(NewProjectWizardModuleBuilder.MODULE_BUILDER_ID) {
override fun getName(): String = "New Kotlin Project Wizard"
override fun getDescription(): String = name
override fun getNodeIcon(isOpened: Boolean): Icon = KotlinIcons.SMALL_LOGO
override fun createModuleBuilder(): NewProjectWizardModuleBuilder = NewProjectWizardModuleBuilder()
}
@@ -0,0 +1,91 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.android.sdk.AndroidSdkData
import org.jetbrains.idea.maven.project.MavenProjectsManager
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.safe
import org.jetbrains.kotlin.tools.projectWizard.core.service.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import org.jetbrains.plugins.gradle.action.ImportProjectFromScriptAction
import java.nio.file.Path
class IdeaFileSystemService : FileSystemService {
override fun createDirectory(path: Path): TaskResult<Unit> = safe {
runWriteAction<Unit> {
VfsUtil.createDirectoryIfMissing(path.toString())
}
}
override fun createFile(path: Path, text: String): TaskResult<Unit> = safe {
runWriteAction {
val directoryPath = path.parent
val directory = VfsUtil.createDirectoryIfMissing(directoryPath.toFile().toString())!!
val virtualFile = directory.createChildData(this, path.fileName.toString())
VfsUtil.saveText(virtualFile, text)
}
}
}
class IdeaGradleService(private val project: Project) : GradleService {
// We have to call action directly as there is no common way
// to import Gradle project in all IDEAs from 183 to 193
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
): TaskResult<Unit> = safe {
val virtualFile = LocalFileSystem.getInstance().findFileByPath(path.toString())!!
val dataContext = SimpleDataContext.getSimpleContext(
mapOf(
CommonDataKeys.PROJECT.name to project,
CommonDataKeys.VIRTUAL_FILE.name to virtualFile
),
null
)
val action = ImportProjectFromScriptAction()
val event = AnActionEvent.createFromAnAction(action, null, ActionPlaces.UNKNOWN, dataContext)
action.actionPerformed(event)
}
}
class IdeaMavenService(private val project: Project) : MavenService {
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
): TaskResult<Unit> = safe {
val mavenProjectManager = MavenProjectsManager.getInstance(project)
val rootFile = LocalFileSystem.getInstance().findFileByPath(path.toString())!!
mavenProjectManager.addManagedFilesOrUnignore(rootFile.findAllPomFiles())
}
private fun VirtualFile.findAllPomFiles(): List<VirtualFile> {
val result = mutableListOf<VirtualFile>()
fun VirtualFile.find() {
when {
!isDirectory && name == "pom.xml" -> result += this
isDirectory -> children.forEach(VirtualFile::find)
}
}
find()
return result
}
}
class IdeaAndroidService : AndroidService {
override fun isValidAndroidSdk(path: Path): Boolean =
//todo use android plugin for that?
AndroidServiceImpl().isValidAndroidSdk(path)
}
@@ -0,0 +1,46 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
abstract class Component : Displayable {
private val subComponents = mutableListOf<Component>()
open fun onInit() {
subComponents.forEach(Component::onInit)
}
protected fun <C : Component> C.asSubComponent(): C = also {
this@Component.subComponents += it
}
}
abstract class DynamicComponent(private val valuesReadingContext: ValuesReadingContext) : Component() {
//TODO do not use it in future
protected val context = valuesReadingContext.context
protected val eventManager
get() = valuesReadingContext.context.eventManager
private var isInitialized: Boolean = false
override fun onInit() {
super.onInit()
isInitialized = true
}
var <V : Any, T : SettingType<V>> SettingReference<V, T>.value: V?
get() = with(valuesReadingContext) { notRequiredSettingValue() }
set(value) {
with(context) { settingContext[this@value] = value!! }
}
inline val <V : Any, reified T : SettingType<V>> PluginSettingPropertyReference<V, T>.value: V?
get() = reference.value
init {
valuesReadingContext.context.eventManager.addSettingUpdaterEventListener { reference ->
if (isInitialized) onValueUpdated(reference)
}
}
open fun onValueUpdated(reference: SettingReference<*, *>?) {}
}
@@ -0,0 +1,19 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.util.ui.HtmlPanel
import com.intellij.util.ui.UIUtil
import java.awt.Font
import javax.swing.event.HyperlinkEvent
class DescriptionPanel(initialText: String? = null) : HtmlPanel() {
private var bodyText: String? = initialText
fun updateText(text: String) {
bodyText = text.asHtml()
update()
}
override fun getBody() = bodyText.orEmpty()
override fun getBodyFont(): Font = UIUtil.getButtonFont().deriveFont(Font.PLAIN)
}
@@ -0,0 +1,7 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import javax.swing.JComponent
interface Displayable {
val component: JComponent
}
@@ -0,0 +1,40 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.StringSettingComponent
import javax.swing.Box
import javax.swing.JSeparator
import javax.swing.SwingConstants
class PomWizardStepComponent(valuesReadingContext: ValuesReadingContext) : WizardStepComponent(valuesReadingContext) {
private val groupIdComponent = StringSettingComponent(
StructurePlugin::groupId.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
private val artifactIdComponent = StringSettingComponent(
StructurePlugin::artifactId.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
private val versionComponent = StringSettingComponent(
StructurePlugin::version.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
override val component = panel(VerticalLayout(0)) {
add(Box.createVerticalStrut(10))
add(JSeparator(SwingConstants.HORIZONTAL))
add(Box.createVerticalStrut(10))
add(groupIdComponent.component)
add(artifactIdComponent.component)
add(versionComponent.component)
}
}
@@ -0,0 +1,68 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.components.JBList
import org.jetbrains.kotlin.tools.projectWizard.core.ignore
import javax.swing.DefaultListModel
import javax.swing.JList
import javax.swing.ListModel
import javax.swing.ListSelectionModel
import javax.swing.event.ListDataEvent
import javax.swing.event.ListDataListener
abstract class AbstractSingleSelectableListWithIcon<V>(initialValues: List<V> = emptyList()) : JBList<V>() {
protected abstract fun ColoredListCellRenderer<V>.render(value: V)
protected open fun onSelected(value: V?) {}
protected val model: DefaultListModel<V>
get() = super.getModel() as DefaultListModel<V>
fun updateValues(newValues: List<V>) {
setModel(createDefaultListModel(newValues))
if (newValues.isNotEmpty()) {
selectedIndex = 0
}
}
init {
selectionMode = ListSelectionModel.SINGLE_SELECTION
updateValues(initialValues)
selectionModel.addListSelectionListener { e ->
if (e.valueIsAdjusting) return@addListSelectionListener
val selectedValue = when (selectedIndex) {
-1 -> null
else -> this.model.getElementAt(selectedIndex)
}
onSelected(selectedValue)
}
cellRenderer = object : ColoredListCellRenderer<V>() {
override fun customizeCellRenderer(
list: JList<out V>,
value: V?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
render(value ?: return)
}
}
}
}
class ImmutableSingleSelectableListWithIcon<V>(
values: List<V>,
private val renderValue: ColoredListCellRenderer<V>.(V) -> Unit,
emptyMessage: String? = null,
private val onValueSelected: (V?) -> Unit = {}
) : AbstractSingleSelectableListWithIcon<V>(values) {
override fun ColoredListCellRenderer<V>.render(value: V) = renderValue(value)
override fun onSelected(value: V?) = onValueSelected(value)
init {
emptyMessage?.let { text ->
emptyText.text = text
}
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import java.awt.BorderLayout
import javax.swing.JComponent
abstract class SubStep(
val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
protected abstract fun buildContent(): JComponent
final override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
add(buildContent(), BorderLayout.CENTER)
}
}
}
@@ -0,0 +1,7 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
abstract class WizardStepComponent(
valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext)
@@ -0,0 +1,120 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.layout.selectedValueIs
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import java.awt.BorderLayout
import javax.swing.JComponent
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.event.ItemEvent
import javax.swing.DefaultComboBoxModel
import javax.swing.Icon
import javax.swing.JList
class DropDownComponent<T : DisplayableSettingItem>(
private val valuesReadingContext: ValuesReadingContext,
initialValues: List<T> = emptyList(),
labelText: String? = null,
private val validator: SettingValidator<T> = settingValidator { ValidationResult.OK },
private val iconProvider: (T) -> Icon? = { null },
private val onAnyValueUpdate: (T) -> Unit = {}
) : Component() {
@Suppress("UNCHECKED_CAST")
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private var allowFireEventWhenValueUpdated: Boolean = true
private fun withoutActionFiring(action: () -> Unit) {
val initialAllowFireEventWhenValueUpdated = allowFireEventWhenValueUpdated
allowFireEventWhenValueUpdated = false
action()
allowFireEventWhenValueUpdated = initialAllowFireEventWhenValueUpdated
}
@Suppress("UNCHECKED_CAST")
private val comboBox = ComboBox<T>(initialValues.toTypedArray<DisplayableSettingItem>() as Array<T>).apply {
renderer = object : ColoredListCellRenderer<T>() {
override fun customizeCellRenderer(
list: JList<out T>,
value: T?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
if (value == null) return
icon = iconProvider(value)
append(value.text)
value.greyText?.let {
append(" $it", SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
if (!this@apply.selectedValueIs(value).invoke()) {
validator.validate(valuesReadingContext, value)
.safeAs<ValidationResult.ValidationError>()
?.messages
?.firstOrNull()
?.let { error ->
append(" $error.", SimpleTextAttributes.ERROR_ATTRIBUTES)
}
}
}
}
addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
value = e.item as T
}
}
}
fun updateValues(newValues: List<T>) = withoutActionFiring {
val oldValue = comboBox.selectedItem
@Suppress("UNCHECKED_CAST")
comboBox.model = DefaultComboBoxModel(newValues.toTypedArray<DisplayableSettingItem>() as Array<T>)
if (newValues.isNotEmpty() && oldValue !in newValues) {
value = newValues.first()
}
}
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(comboBox, BorderLayout.CENTER)
}
@Suppress("UNCHECKED_CAST")
var value: T
get() = comboBox.selectedItem as T
set(value) {
comboBox.selectedItem = value
fireValueChanged(value)
}
private fun fireValueChanged(newValue: T) {
validate(newValue)
if (allowFireEventWhenValueUpdated) {
onAnyValueUpdate(newValue)
}
}
init {
initialValues.firstOrNull()?.let { first ->
value = first
validate(first)
}
}
fun validate(value: T = this.value) {
validationIndicator.validationState = validator.validate(valuesReadingContext, value)
}
}
@@ -0,0 +1,65 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import java.awt.BorderLayout
import javax.swing.JComponent
class PathFieldComponent(
private val valuesReadingContext: ValuesReadingContext,
private val initialText: String = "",
labelText: String? = "",
private val validator: StringValidator = org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator { ValidationResult.OK },
private val onSuccessValueUpdate: (String) -> Unit = {},
private val onAnyValueUpdate: (String) -> Unit = {}
) : Component() {
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private val pathField = TextFieldWithBrowseButton(
textField(initialText, this::fireValueChanged)
).apply {
addBrowseFolderListener(
TextBrowseFolderListener(
FileChooserDescriptorFactory.createSingleFolderDescriptor(),
null
)
)
}
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(pathField, BorderLayout.CENTER)
}
var value: String
get() = pathField.text
set(value) {
pathField.text = value
fireValueChanged(value)
}
private fun fireValueChanged(text: String) {
validate(text)
if (validationIndicator.validationState == ValidationResult.OK) {
onSuccessValueUpdate(text)
}
onAnyValueUpdate(text)
}
override fun onInit() {
super.onInit()
validate(initialText)
}
private fun validate(text: String) {
validationIndicator.validationState = validator.validate(valuesReadingContext, text)
}
}
@@ -0,0 +1,54 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import java.awt.BorderLayout
import javax.swing.JComponent
class TextFieldComponent(
private val valuesReadingContext: ValuesReadingContext,
private val initialText: String = "",
labelText: String? = "",
private val validator: StringValidator = settingValidator { ValidationResult.OK },
private val onSuccessValueUpdate: (String) -> Unit = {},
private val onAnyValueUpdate: (String) -> Unit = {}
) : Component() {
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private val textField = textField(initialText, this::fireValueChanged)
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(textField, BorderLayout.CENTER)
}
var value: String
get() = textField.text
set(value) {
textField.text = value
fireValueChanged(value)
}
private fun fireValueChanged(text: String) {
validate(text)
if (validationIndicator.validationState == ValidationResult.OK) {
onSuccessValueUpdate(text)
}
onAnyValueUpdate(text)
}
override fun onInit() {
super.onInit()
validate(initialText)
}
private fun validate(text: String) {
validationIndicator.validationState = validator.validate(valuesReadingContext, text)
}
}
@@ -0,0 +1,56 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import com.intellij.icons.AllIcons
import icons.GradleIcons
import icons.MavenIcons
import org.jetbrains.kotlin.tools.projectWizard.core.entity.DropDownSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
class BuildSystemTypeSettingComponent(
valuesReadingContext: ValuesReadingContext
) : SettingComponent<BuildSystemType, DropDownSettingType<BuildSystemType>>(
BuildSystemPlugin::type.reference,
valuesReadingContext
) {
override val validationIndicator: ValidationIndicator? = null
private val listComponent = DropDownComponent(
valuesReadingContext,
setting.type.values,
labelText = "Build System",
validator = setting.validator,
iconProvider = BuildSystemType::icon,
onAnyValueUpdate = { value = it }
).asSubComponent()
override val component = panel {
add(listComponent.component, BorderLayout.CENTER)
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
if (reference == KotlinPlugin::projectKind.reference) {
listComponent.component.updateUI()
}
listComponent.validate()
}
}
@Suppress("DEPRECATION")
private val BuildSystemType.icon
get() = when (this) {
BuildSystemType.GradleKotlinDsl -> GradleIcons.Gradle
BuildSystemType.GradleGroovyDsl -> GradleIcons.Gradle
BuildSystemType.Maven -> MavenIcons.MavenLogo
BuildSystemType.Jps -> AllIcons.Nodes.Module
}
@@ -0,0 +1,78 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.projectTemplates.ProjectTemplate
import org.jetbrains.kotlin.tools.projectWizard.wizard.IdeWizard
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import java.awt.BorderLayout
import javax.swing.JComponent
class FirstWizardStepComponent(wizard: IdeWizard) : WizardStepComponent(wizard.valuesReadingContext) {
private val buildSystemSubStep = BuildSystemSubStep(wizard.valuesReadingContext).asSubComponent()
private val templatesSubStep = TemplatesSubStep(wizard.valuesReadingContext).asSubComponent()
override val component: JComponent = panel {
add(templatesSubStep.component, BorderLayout.CENTER)
add(buildSystemSubStep.component, BorderLayout.SOUTH)
}
}
class BuildSystemSubStep(valuesReadingContext: ValuesReadingContext) :
SubStep(valuesReadingContext) {
private val buildSystemSetting = BuildSystemTypeSettingComponent(valuesReadingContext).asSubComponent()
override fun buildContent(): JComponent = panel {
bordered(needBottomEmptyBorder = false)
add(buildSystemSetting.component, BorderLayout.CENTER)
}
}
class TemplatesSubStep(valuesReadingContext: ValuesReadingContext) :
SubStep(valuesReadingContext) {
private val projectTemplateSettingComponent =
ProjectTemplateSettingComponent(valuesReadingContext) { projectTemplate ->
templateDescriptionComponent.setTemplate(projectTemplate)
}.asSubComponent()
private val templateDescriptionComponent = TemplateDescriptionComponent().asSubComponent()
override fun buildContent(): JComponent = panel {
add(projectTemplateSettingComponent.component, BorderLayout.CENTER)
add(templateDescriptionComponent.component, BorderLayout.SOUTH)
}
override fun onInit() {
super.onInit()
applySelectedTemplate()
}
private fun applySelectedTemplate() {
projectTemplateSettingComponent.value?.setsValues?.forEach { (setting, value) ->
// TODO do not use settingContext directly
context.settingContext[setting] = value
}
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
if (reference == ProjectTemplatesPlugin::template.reference) {
applySelectedTemplate()
}
}
}
class TemplateDescriptionComponent : Component() {
private val descriptionPanel = DescriptionPanel()
fun setTemplate(template: ProjectTemplate) {
descriptionPanel.updateText(template.htmlDescription)
}
override val component: JComponent = panel {
bordered()
add(descriptionPanel, BorderLayout.CENTER)
}
}
@@ -0,0 +1,63 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.core.entity.DropDownSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.projectTemplates.ProjectTemplate
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.ImmutableSingleSelectableListWithIcon
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.UiConstants
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.bordered
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class ProjectTemplateSettingComponent(
valuesReadingContext: ValuesReadingContext,
private val onSelected: (ProjectTemplate) -> Unit
) : SettingComponent<ProjectTemplate, DropDownSettingType<ProjectTemplate>>(
ProjectTemplatesPlugin::template.reference,
valuesReadingContext
) {
override val validationIndicator: ValidationIndicator? get() = null
private val list = ImmutableSingleSelectableListWithIcon(
setting.type.values,
renderValue = { value ->
icon = KotlinIcons.SMALL_LOGO
append(value.title)
},
onValueSelected = {
onValueSelected(it!!)
}
).apply {
border = BorderFactory.createEmptyBorder(
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE
)
}
private fun onValueSelected(selected: ProjectTemplate) {
value = selected
onSelected(selected)
}
override val component: JComponent = panel {
bordered(needTopEmptyBorder = false, needInnerEmptyBorder = false)
add(list, BorderLayout.CENTER)
}
override fun onInit() {
super.onInit()
if (setting.type.values.isNotEmpty()) {
list.selectedIndex = 0
onValueSelected(setting.type.values.first())
}
}
}
@@ -0,0 +1,85 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.configuratorSettings
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
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.setting.SettingsList
import javax.swing.BorderFactory
import javax.swing.JComponent
class ModuleSettingsComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val validateModuleName =
StringValidators.shouldNotBeBlank("Module name") and
StringValidators.shouldBeValidIdentifier("Module Name")
private val moduleConfigurator = DropDownComponent<ModuleConfigurator>(
valuesReadingContext,
iconProvider = { it.icon },
labelText = "Consider as"
) { value ->
module?.configurator = value
}
private val moduleConfiguratorSettingsList = SettingsList(emptyList(), valuesReadingContext)
private val nameField = TextFieldComponent(
valuesReadingContext,
labelText = "Name",
onAnyValueUpdate = { value ->
module?.name = value
eventManager.fireListeners(null)
},
validator = validateModuleName
).asSubComponent()
override val component: JComponent = panel(VerticalLayout(5)) {
border = BorderFactory.createEmptyBorder(
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE
)
add(nameField.component)
add(moduleConfigurator.component)
add(moduleConfiguratorSettingsList.component)
}
var module: Module? = null
set(value) {
field = value
if (value != null) {
updateModule(value)
}
}
private fun updateModule(module: Module) {
nameField.value = module.name
val newValues = ModuleConfigurator.BY_MODULE_KIND.getValue(module.configurator.moduleKind)
.filter { configurator ->
if (configurator.moduleKind == ModuleKind.target)
configurator.moduleType == module.configurator.moduleType
else false
}
nameField.component.isVisible = module.kind != ModuleKind.target
|| module.configurator.moduleType != ModuleType.common
moduleConfiguratorSettingsList.setSettings(module.configuratorSettings)
moduleConfigurator.apply {
updateValues(newValues)
component.isVisible = newValues.size > 1
value = module.configurator
component.updateUI()
}
}
}
@@ -0,0 +1,23 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.util.ui.StatusText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import java.awt.Graphics
import javax.swing.JPanel
class NothingSelectedComponent : Component() {
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
init {
appendText("Neither Module nor Sourceset is selected")
}
}
}
@@ -0,0 +1,80 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
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.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) : WizardStepComponent(wizard.valuesReadingContext) {
private val moduleEditorSubStep =
ModulesEditorSubStep(wizard.valuesReadingContext, ::onNodeSelected).asSubComponent()
private val templatesSubStep = ModuleSettingsSubStep(wizard).asSubComponent()
override val component = splitterFor(
moduleEditorSubStep.component,
templatesSubStep.component
)
private fun onNodeSelected(data: DisplayableSettingItem?) {
templatesSubStep.selectedNode = data
}
}
class ModulesEditorSubStep(
valuesReadingContext: ValuesReadingContext,
onNodeSelected: (data: DisplayableSettingItem?) -> Unit
) : SubStep(valuesReadingContext) {
private val moduleSettingComponent = ModulesEditorComponent(
valuesReadingContext,
onNodeSelected
).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)
}
}
class ModuleSettingsSubStep(wizard: IdeWizard) : SubStep(wizard.valuesReadingContext) {
private val sourcesetSettingsComponent = SourcesetSettingsComponent(wizard.valuesReadingContext).asSubComponent()
private val moduleSettingsComponent = ModuleSettingsComponent(valuesReadingContext).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()
}
private fun changeComponent() {
panel.removeAll()
val component = when (selectedNode) {
is Sourceset -> sourcesetSettingsComponent
is Module -> moduleSettingsComponent
else -> nothingSelectedComponent
}
panel.add(component.component, BorderLayout.CENTER)
panel.updateUI()
}
override fun buildContent() = panel
}
@@ -0,0 +1,137 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.openapi.actionSystem.ActionToolbarPosition
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.ToolbarDecorator
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.AndroidSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.IOSSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.withAllSubModules
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.JComponent
class SourcesetDependenciesSettingsComponent(
valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
private val sourcesetDependenciesList = SourcesetDependenciesList()
private val toolbarDecorator: ToolbarDecorator = ToolbarDecorator.createDecorator(sourcesetDependenciesList).apply {
setToolbarPosition(ActionToolbarPosition.RIGHT)
setAddAction {
val dialog = ChooseModuleDialog(allModulesExcludingCurrent, component)
if (dialog.showAndGet()) {
dialog.selectedModule?.let(sourcesetDependenciesList::addDependency)
}
}
setRemoveAction {
sourcesetDependenciesList.removeSelected()
}
setMoveDownAction(null)
setMoveUpAction(null)
}
var sourceset: Sourceset? = null
set(value) {
field = value
sourcesetDependenciesList.sourceset = value
}
private val allModulesExcludingCurrent: List<Module>
get() {
val currentModule = sourceset?.parent
return KotlinPlugin::modules.value!!
.withAllSubModules()
.filter { module ->
if (sourceset in module.sourcesets) return@filter false
if (currentModule?.kind == ModuleKind.target && currentModule in module.subModules)
return@filter false
if (module.configurator == AndroidSinglePlatformModuleConfigurator
|| module.configurator == IOSSinglePlatformModuleConfigurator
) return@filter false
true
}
}
override val component = panel {
add(toolbarDecorator.createPanelWithPopupHandler(sourcesetDependenciesList), BorderLayout.CENTER)
}
}
private class ChooseModuleDialog(modules: List<Module>, parent: JComponent) : DialogWrapper(parent, false) {
private val list = ImmutableSingleSelectableListWithIcon(
values = modules,
emptyMessage = "There are no dependencies to add",
renderValue = { value ->
renderModule(value)
}
).bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
override fun createCenterPanel() = panel {
preferredSize = Dimension(preferredSize.width, 300)
add(
label("Please select the modules to add as dependencies:")
.bordered(
needLineBorder = false,
needInnerEmptyBorder = false,
needTopEmptyBorder = false
),
BorderLayout.NORTH
)
add(list, BorderLayout.CENTER)
}
val selectedModule: Module?
get() = list.selectedValue
init {
title = "Choose Modules"
init()
}
}
private class SourcesetDependenciesList : AbstractSingleSelectableListWithIcon<Module>() {
override fun ColoredListCellRenderer<Module>.render(value: Module) {
renderModule(value)
}
var sourceset: Sourceset? = null
set(value) {
field = value
updateValues(
value?.dependencies?.mapNotNull { it.safeAs<ModuleBasedSourcesetDependency>()?.module } ?: return
)
updateUI()
}
fun addDependency(dependency: Module) {
model.addElement(dependency)
sourceset?.let { it.dependencies += ModuleBasedSourcesetDependency(dependency) }
}
fun removeSelected() {
val index = selectedIndex
model.removeElementAt(selectedIndex)
sourceset?.let { it.dependencies = it.dependencies.toMutableList().also { it.removeAt(index) } }
if (model.size() > 0) {
selectedIndex = index.coerceAtMost(model.size - 1)
}
}
}
private fun ColoredListCellRenderer<Module>.renderModule(module: Module) {
append(module.path.asString())
append(" ")
append("(${module.greyText})", SimpleTextAttributes.GRAYED_ATTRIBUTES)
icon = module.icon
}
@@ -0,0 +1,22 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.ui.components.JBTabbedPane
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
class SourcesetSettingsComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val templatesComponent = TemplatesComponent(valuesReadingContext).asSubComponent()
private val dependenciesComponent = SourcesetDependenciesSettingsComponent(valuesReadingContext).asSubComponent()
override val component = JBTabbedPane().apply {
add("Template", templatesComponent.component)
add("Dependencies", dependenciesComponent.component)
}
var sourceset: Sourceset? = null
set(value) {
field = value
dependenciesComponent.sourceset = value
templatesComponent.sourceset = value
}
}
@@ -0,0 +1,264 @@
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.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
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.SettingsList
import java.awt.*
import javax.swing.JComponent
import javax.swing.JPanel
class TemplatesComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val chooseTemplateComponent: ChooseTemplateComponent =
ChooseTemplateComponent(valuesReadingContext) { template ->
sourceset?.template = template
switchState(template)
}
private val templateSettingsComponent = TemplateSettingsComponent(valuesReadingContext) {
if (MessagesEx.showOkCancelDialog(
component,
"Do you want to remove selected template from module",
"Remove selected template",
"Remove",
"Cancel",
null
) == Messages.OK
) {
switchState(null)
}
}
private fun switchState(selectedTemplate: Template?) {
panel.removeAll()
when (selectedTemplate) {
null -> panel.add(chooseTemplateComponent.component, BorderLayout.CENTER)
else -> {
if (sourceset != null) {
templateSettingsComponent.setTemplate(sourceset!!, selectedTemplate)
}
panel.add(templateSettingsComponent.component, BorderLayout.CENTER)
}
}
panel.updateUI()
}
val panel = panel {
add(chooseTemplateComponent.component, BorderLayout.CENTER)
}
override val component: JComponent = panel
var sourceset: Sourceset? = null
set(value) {
field = value
switchState(value?.template)
chooseTemplateComponent.selectedModule = value
}
}
class ChooseTemplateComponent(
private val valuesReadingContext: ValuesReadingContext,
private val onTemplateChosen: (Template) -> Unit
) : DynamicComponent(valuesReadingContext) {
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() = with(valuesReadingContext) {
TemplatesPlugin::templates.propertyValue
}
var selectedModule: Sourceset? = null
set(value) {
field = value
onStateUpdated()
}
init {
onStateUpdated()
}
private val availableTemplates
get() = allTemplates.values.filter { template ->
selectedModule!!.containingModuleType in template.moduleTypes
&& selectedModule!!.sourcesetType in template.sourcesetTypes
}
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(
valuesReadingContext: ValuesReadingContext,
removeTemplate: () -> Unit
) : DynamicComponent(valuesReadingContext) {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = true,
nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(),
onRemoveButtonClicked = removeTemplate
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val settings = SettingsList(emptyList(), valuesReadingContext).apply {
component.bordered()
}
fun setTemplate(sourceset: Sourceset, selectedTemplate: Template) {
settings.setSettings(selectedTemplate.settings(sourceset))
templateDescriptionComponent.updateSelectedTemplate(selectedTemplate)
}
override val component: JComponent = splitterFor(
templateDescriptionComponent.component,
settings.component,
vertical = true
).apply {
(this as JBSplitter).proportion = .7f
}
}
@@ -0,0 +1,118 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.ui.popup.PopupFactoryImpl
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.settings.fullText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.asHtml
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.htmlText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.Icon
class CreateModuleOrTargetPopup private constructor(
private val target: Module?,
private val allowMultiplatform: Boolean,
private val allowAndroid: Boolean,
private val allowIos: Boolean,
private val createTarget: (TargetConfigurator) -> Unit,
private val createModule: (ModuleConfigurator) -> Unit
) {
private fun TargetConfigurator.needToShow(): Boolean {
val allTargets = target?.subModules ?: return false
return canCoexistsWith(allTargets.map { it.configurator as TargetConfigurator })
}
private fun DisplayableSettingItem.needToShow(): Boolean = when (this) {
is TargetConfigurator -> needToShow()
is TargetConfiguratorGroupWithSubItems -> subItems.any { it.needToShow() }
else -> false
}
private inner class ChooseModuleOrMppModuleStep : BaseListPopupStep<ModuleConfigurator>(
"Module Type",
buildList {
if (allowMultiplatform) +MppModuleConfigurator
+JvmSinglePlatformModuleConfigurator
if (allowAndroid) +AndroidSinglePlatformModuleConfigurator
//todo ios support
//if (allowIos) +IOSSinglePlatformModuleConfigurator
}
) {
override fun getIconFor(value: ModuleConfigurator): Icon = value.icon
override fun getTextFor(value: ModuleConfigurator): String = value.htmlText
override fun onChosen(selectedValue: ModuleConfigurator?, finalChoice: Boolean): PopupStep<*>? =
when (selectedValue) {
null -> PopupStep.FINAL_CHOICE
else -> {
createModule(selectedValue)
PopupStep.FINAL_CHOICE
}
}
}
private inner class ChooseTargetTypeStep(
targetConfiguratorGroup: TargetConfiguratorGroupWithSubItems,
showTitle: Boolean
) : BaseListPopupStep<DisplayableSettingItem>(
"Target".takeIf { showTitle },
targetConfiguratorGroup.subItems.filter { it.needToShow() }
) {
override fun getIconFor(value: DisplayableSettingItem): Icon? = when (value) {
is DisplayableTargetConfiguratorGroup -> value.moduleType.icon
is ModuleConfigurator -> value.moduleType.icon
else -> null
}
override fun getTextFor(value: DisplayableSettingItem): String = value.htmlText
override fun onChosen(selectedValue: DisplayableSettingItem?, finalChoice: Boolean): PopupStep<*>? {
when {
finalChoice && selectedValue is TargetConfigurator -> createTarget(selectedValue)
selectedValue is TargetConfiguratorGroupWithSubItems ->
return ChooseTargetTypeStep(selectedValue, showTitle = false)
}
return PopupStep.FINAL_CHOICE
}
}
private fun create(): ListPopup? = when (target?.kind) {
ModuleKind.target -> null
ModuleKind.multiplatform -> ChooseTargetTypeStep(TargetConfigurationGroups.FIRST, showTitle = true)
else -> when {
allowMultiplatform || allowAndroid || allowIos -> ChooseModuleOrMppModuleStep()
else -> {
createModule(JvmSinglePlatformModuleConfigurator)
null
}
}
}?.let { PopupFactoryImpl.getInstance().createListPopup(it) }
companion object {
fun create(
target: Module?,
allowMultiplatform: Boolean,
allowAndroid: Boolean,
allowIos: Boolean,
createTarget: (TargetConfigurator) -> Unit,
createModule: (ModuleConfigurator) -> Unit
) = CreateModuleOrTargetPopup(
target = target,
allowMultiplatform = allowMultiplatform,
allowAndroid = allowAndroid,
allowIos = allowIos,
createTarget = createTarget,
createModule = createModule
).create()
}
}
@@ -0,0 +1,67 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.ui.JBColor
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ListSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class ModulesEditorComponent(
valuesReadingContext: ValuesReadingContext,
oneEntrySelected: (data: DisplayableSettingItem?) -> Unit
) : SettingComponent<List<Module>, ListSettingType<Module>>(KotlinPlugin::modules.reference, valuesReadingContext) {
private val tree: ModulesEditorTree =
ModulesEditorTree(
onSelected = { oneEntrySelected(it) },
addModule = { component ->
val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Multiplatform
moduleCreator.create(
target = null, // The empty tree case
allowMultiplatform = isMppProject,
allowAndroid = isMppProject,
allowIos = isMppProject,
allModules = value ?: emptyList(),
createModule = { model.add(it) }
)?.showInCenterOf(component)
}
)
private val model = TargetsModel(tree, ::value)
override fun onInit() {
super.onInit()
model.update()
}
private val moduleCreator = NewModuleCreator()
private val toolbarDecorator = ModulesEditorToolbarDecorator(
tree = tree,
moduleCreator = moduleCreator,
model = model,
getModules = { value ?: emptyList() },
isMultiplatformProject = { KotlinPlugin::projectKind.value == ProjectKind.Multiplatform }
)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
border = BorderFactory.createLineBorder(JBColor.border())
add(toolbarDecorator.createToolPanel(), BorderLayout.CENTER)
add(validationIndicator, BorderLayout.SOUTH)
}
}
override val validationIndicator = ValidationIndicator(showText = true).apply {
background = tree.background
}
}
@@ -0,0 +1,109 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionToolbarPosition
import com.intellij.openapi.ui.Messages
import com.intellij.ui.ToolbarDecorator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.AndroidSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.IOSSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.MppModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.withAllSubModules
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.createPanelWithPopupHandler
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent
class ModulesEditorToolbarDecorator(
private val tree: ModulesEditorTree,
private val moduleCreator: NewModuleCreator,
private val model: TargetsModel,
private val getModules: () -> List<Module>,
private val isMultiplatformProject: () -> Boolean
) {
private val toolbarDecorator: ToolbarDecorator = ToolbarDecorator.createDecorator(tree).apply {
setToolbarPosition(ActionToolbarPosition.TOP)
setAddAction { button ->
val allModules = getModules().withAllSubModules(includeSourcesets = false)
val target = tree.selectedSettingItem?.safeAs<Module>()
val isRootModule = target == null
val popup = moduleCreator.create(
target = tree.selectedSettingItem?.safeAs(),
allowMultiplatform = isRootModule
&& allModules.none { it.configurator == MppModuleConfigurator },
allowAndroid = isRootModule
&& isMultiplatformProject()
&& allModules.none { it.configurator == AndroidSinglePlatformModuleConfigurator },
allowIos = isRootModule
&& isMultiplatformProject()
&& allModules.none { it.configurator == IOSSinglePlatformModuleConfigurator },
allModules = allModules,
createModule = model::add
)
popup?.show(button.preferredPopupPoint!!)
}
setAddActionUpdater { event ->
event.presentation.apply {
isEnabled = when (val selected = tree.selectedSettingItem) {
is Module -> selected.configurator.canContainSubModules
is Sourceset -> false
null -> true
else -> false
}
text = "Add" + when (tree.selectedSettingItem?.safeAs<Module>()?.kind) {
ModuleKind.multiplatform -> " Target"
ModuleKind.singleplatform -> " Module"
ModuleKind.target -> ""
null -> ""
}
}
event.presentation.isEnabled
}
setRemoveAction {
val moduleKindText = selectedModuleKindText ?: "Module"
if (Messages.showOkCancelDialog(
tree,
buildString {
appendln("Do you want to remove selected $moduleKindText?")
if (tree.selectedSettingItem.safeAs<Module>()?.kind != ModuleKind.target) {
appendln("This will also remove all submodules.")
}
appendln()
appendln("This action cannot be undone.")
},
"Remove selected $moduleKindText?",
"Remove",
"Cancel",
AllIcons.General.QuestionDialog
) == Messages.OK
) {
model.removeSelected()
}
}
setRemoveActionUpdater { event ->
event.presentation.apply {
isEnabled = tree.selectedSettingItem is Module
text = "Remove" + selectedModuleKindText?.let { " $it" }.orEmpty()
}
event.presentation.isEnabled
}
setMoveDownAction(null)
setMoveUpAction(null)
}
private val selectedModuleKindText
get() = tree.selectedSettingItem.safeAs<Module>()?.kindText
fun createToolPanel(): JComponent = toolbarDecorator
.createPanelWithPopupHandler(tree)
.apply {
border = null
}
}
private val Module.kindText
get() = when (kind) {
ModuleKind.multiplatform -> "Module"
ModuleKind.singleplatform -> "Module"
ModuleKind.target -> "Target"
}
@@ -0,0 +1,91 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.icons.AllIcons
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.treeStructure.Tree
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.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreeSelectionModel
class ModulesEditorTree(
private val onSelected: (DisplayableSettingItem?) -> Unit,
private val addModule: (JComponent) -> Unit
) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) {
val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel
@Suppress("ClassName")
object PROJECT_USER_OBJECT
fun reload() {
val openPaths = (0 until rowCount).mapNotNull { row ->
getPathForRow(row).takeIf { isExpanded(it) }
}
model.reload()
openPaths.forEach(::expandPath)
}
val selectedSettingItem
get() = selectedNode?.userObject?.safeAs<DisplayableSettingItem>()
val selectedNode
get() = getSelectedNodes(DefaultMutableTreeNode::class.java) {
it.userObject is DisplayableSettingItem
}.singleOrNull()
init {
getSelectionModel().selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
setShowsRootHandles(true)
addTreeSelectionListener {
onSelected(selectedSettingItem)
}
emptyText.clear()
emptyText.appendText("No modules created")
emptyText.appendSecondaryText(
"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("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)
}
}
})
}
}
@@ -0,0 +1,75 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
class NewModuleCreator {
private fun suggestName(name: String, modules: List<Module>): String {
val names = modules.map(Module::name).toSet()
if (name !in names) return name
var index = 1
while ("${name}_$index" in names) {
index++
}
return "${name}_$index"
}
private fun newTarget(
configurator: TargetConfigurator,
allTargets: List<Module>
): Module = MultiplatformTargetModule(
suggestName(
configurator.suggestedModuleName ?: configurator.moduleType.name,
allTargets
),
configurator,
SourcesetType.values().map { sourcesetType ->
Sourceset(
sourcesetType,
configurator.moduleType,
template = null,
dependencies = emptyList()
)
}
)
fun create(
target: Module?,
allowMultiplatform: Boolean,
allowAndroid: Boolean,
allowIos: Boolean,
allModules: List<Module>,
createModule: (Module) -> Unit
) = CreateModuleOrTargetPopup.create(
target = target,
allowMultiplatform = allowMultiplatform,
allowAndroid = allowAndroid,
allowIos = allowIos,
createTarget = { targetConfigurator ->
createModule(newTarget(targetConfigurator, target?.subModules.orEmpty()))
},
createModule = { configurator ->
val name = suggestName(configurator.suggestedModuleName ?: "module", allModules)
val sourcesets = when (configurator.moduleKind) {
ModuleKind.multiplatform -> emptyList()
else -> SourcesetType.values().map { sourcesetType ->
Sourceset(
sourcesetType,
ModuleType.jvm,
template = null,
dependencies = emptyList()
)
}
}
val createdModule = Module(
name,
configurator.moduleKind,
configurator,
sourcesets,
emptyList()
)
createModule(createdModule)
}
)
}
@@ -0,0 +1,86 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.TreePath
import kotlin.reflect.KMutableProperty0
class TargetsModel(
private val tree: ModulesEditorTree,
private val value: KMutableProperty0<List<Module>?>
) {
private val root get() = tree.model.root as DefaultMutableTreeNode
private fun addToTheTree(module: Module, modifyValue: Boolean, parent: DefaultMutableTreeNode = root) {
val pathsToExpand = mutableListOf<TreePath>()
fun add(moduleToAdd: Module, parentToAdd: DefaultMutableTreeNode) {
DefaultMutableTreeNode(moduleToAdd).apply {
val userObject = parentToAdd.userObject
when {
userObject is Module && userObject.kind == ModuleKind.singleplatform -> {
val indexOfLastModule = parent.children()
.toList()
.indexOfLast {
it.safeAs<DefaultMutableTreeNode>()?.userObject is Module
}
if (indexOfLastModule == -1) parent.insert(this, 0)
else parent.insert(this, indexOfLastModule)
}
else -> parentToAdd.add(this)
}
pathsToExpand += TreePath(path)
moduleToAdd.subModules.forEach { subModule ->
add(subModule, this)
}
moduleToAdd.sourcesets.forEach { sourceset ->
val sourcesetNode = DefaultMutableTreeNode(sourceset)
add(sourcesetNode)
pathsToExpand += TreePath(sourcesetNode.path)
}
}
}
add(module, parent)
if (modifyValue) {
when (val parentModule = parent.userObject) {
ModulesEditorTree.PROJECT_USER_OBJECT -> value.set(value.get().orEmpty() + module)
is Module -> parentModule.subModules += module
}
}
tree.reload()
pathsToExpand.forEach(tree::expandPath)
}
fun add(module: Module) {
addToTheTree(module, modifyValue = true, parent = tree.selectedNode ?: root)
}
fun update() {
root.removeAllChildren()
value.get()?.forEach { addToTheTree(it, modifyValue = false) }
if (value.get()?.isEmpty() == true) {
tree.reload()
}
}
fun removeSelected() {
val selectedNode = tree.selectedNode?.takeIf { it.userObject is Module } ?: return
when (val parent = selectedNode.parent.safeAs<DefaultMutableTreeNode>()?.userObject) {
ModulesEditorTree.PROJECT_USER_OBJECT -> {
val index = selectedNode.parent.getIndex(selectedNode)
selectedNode.removeFromParent()
value.set(value.get()?.toMutableList().also { it?.removeAt(index) })
}
is Module -> {
parent.subModules = parent.subModules.filterNot { it === selectedNode.userObject }
selectedNode.removeFromParent()
}
}
tree.reload()
}
}
@@ -0,0 +1,200 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.components.JBCheckBox
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.PathFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import java.awt.BorderLayout
import java.awt.event.ItemEvent
import java.nio.file.Path
import java.nio.file.Paths
import javax.swing.DefaultComboBoxModel
import javax.swing.JComponent
sealed class DefaultSettingComponent<V : Any, T : SettingType<V>>(
reference: SettingReference<V, T>,
valuesReadingContext: ValuesReadingContext
) : SettingComponent<V, T>(reference, valuesReadingContext) {
companion object {
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> create(
setting: SettingReference<V, T>,
valuesReadingContext: ValuesReadingContext
): DefaultSettingComponent<V, T> = when (setting.type) {
VersionSettingType::class ->
VersionSettingComponent(
setting as SettingReference<Version, VersionSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
BooleanSettingType::class ->
BooleanSettingComponent(
setting as SettingReference<Boolean, BooleanSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
DropDownSettingType::class ->
DropdownSettingComponent(
setting as SettingReference<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
StringSettingType::class ->
StringSettingComponent(
setting as SettingReference<String, StringSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
PathSettingType::class ->
PathSettingComponent(
setting as SettingReference<Path, PathSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
else -> TODO(setting.type.qualifiedName!!)
}
}
}
class VersionSettingComponent(
reference: SettingReference<Version, VersionSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Version, VersionSettingType>(reference, valuesReadingContext) {
private val settingLabel = label(setting.title)
private val comboBox = ComboBox<Version>().apply {
addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
value = e.item as Version
}
}
}
override fun onInit() {
super.onInit()
val values = setting.defaultValue?.let(::listOf).orEmpty()
comboBox.model = DefaultComboBoxModel<Version>(values.toTypedArray())
if (values.isNotEmpty()) {
value = values.first()
}
}
override val validationIndicator = ValidationIndicator(showText = false)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
add(
panel {
add(validationIndicator, BorderLayout.WEST)
add(settingLabel, BorderLayout.CENTER)
},
BorderLayout.WEST
)
add(comboBox, BorderLayout.CENTER)
}
}
}
class DropdownSettingComponent(
reference: SettingReference<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>(
reference,
valuesReadingContext
) {
private val dropDownComponent = DropDownComponent(
valuesReadingContext,
setting.type.values,
labelText = setting.title,
onAnyValueUpdate = { newValue ->
value = newValue
}
).asSubComponent()
override fun onInit() {
super.onInit()
val valuesFiltered = setting.type.values.filter { setting.type.filter(reference, it) }
dropDownComponent.updateValues(valuesFiltered)
if (valuesFiltered.isNotEmpty()) {
value = valuesFiltered.first()
}
}
override val validationIndicator: ValidationIndicator? = null
override val component: JComponent = dropDownComponent.component
}
class BooleanSettingComponent(
reference: SettingReference<Boolean, BooleanSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Boolean, BooleanSettingType>(reference, valuesReadingContext) {
private val checkBox = JBCheckBox(setting.title).apply {
addChangeListener {
value = this@apply.isSelected
}
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
checkBox.isSelected = value ?: false
}
override val validationIndicator = ValidationIndicator(showText = false)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
// TODO add validation indicator
add(checkBox, BorderLayout.CENTER)
}
}
}
class StringSettingComponent(
reference: SettingReference<String, StringSettingType>,
valuesReadingContext: ValuesReadingContext,
showLabel: Boolean = true
) : DefaultSettingComponent<String, StringSettingType>(reference, valuesReadingContext) {
private val textFieldComponent = TextFieldComponent(
valuesReadingContext,
setting.defaultValue.orEmpty(),
labelText = if (showLabel) setting.title else null,
validator = setting.validator,
onAnyValueUpdate = {
value = it
}
).asSubComponent()
override val validationIndicator: ValidationIndicator? = null
override fun onInit() {
super.onInit()
textFieldComponent.value = value.orEmpty()
}
override val component = textFieldComponent.component
}
class PathSettingComponent(
reference: SettingReference<Path, PathSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Path, PathSettingType>(reference, valuesReadingContext) {
private val pathFieldComponent = PathFieldComponent(
valuesReadingContext,
"",
setting.title,
validator = settingValidator { path ->
setting.validator.validate(this, Paths.get(path))
},
onAnyValueUpdate = {
value = Paths.get(it)
}
).asSubComponent()
override val validationIndicator: ValidationIndicator? = null
override val component = pathFieldComponent.component
}
@@ -0,0 +1,41 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Displayable
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
abstract class SettingComponent<V : Any, T: SettingType<V>>(
val reference: SettingReference<V, T>,
private val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext), Displayable {
var value: V?
get() = reference.value
set(value) {
reference.value = value
}
val setting: Setting<V, T>
get() = with(valuesReadingContext.context) {
with(reference) { getSetting() }
}
abstract val validationIndicator: ValidationIndicator?
override fun onInit() {
super.onInit()
updateValidationState()
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
component.isVisible = setting.isActive(valuesReadingContext)
updateValidationState()
}
private fun updateValidationState() {
val value = value
if (validationIndicator != null && value != null) {
validationIndicator!!.validationState = setting.validator.validate(valuesReadingContext, value)
}
}
}
@@ -0,0 +1,37 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import javax.swing.JComponent
import javax.swing.JPanel
class SettingsList(
settings: List<SettingReference<*, *>>,
private val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
private val panel = JPanel(VerticalLayout(5))
private var settingComponents: List<Component> = emptyList()
init {
setSettings(settings)
}
override val component: JComponent = panel
override fun onInit() {
super.onInit()
settingComponents.forEach { it.onInit() }
}
fun setSettings(settings: List<SettingReference<*, *>>) {
panel.removeAll()
settingComponents = settings.map { setting ->
DefaultSettingComponent.create(setting, valuesReadingContext)
}
settingComponents.forEach { setting -> setting.onInit(); panel.add(setting.component) }
}
}
@@ -0,0 +1,52 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.icons.AllIcons
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.EmptyIcon
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.Icon
import javax.swing.JPanel
class ValidationIndicator(
defaultText: String? = null,
private val showText: Boolean
) : JPanel(BorderLayout()) {
private val textLabel = defaultText?.let { label("$it: ") }
private val errorLabel = label(" ").apply {
foreground = JBColor.red
}
init {
border = BorderFactory.createEmptyBorder(3, 3, 3, 3)
textLabel?.let { add(it, BorderLayout.WEST) }
add(errorLabel, BorderLayout.CENTER)
}
private fun setIcon(icon: Icon?) {
if (textLabel != null) textLabel.icon = icon
else errorLabel.icon = icon ?: EmptyIcon.ICON_16
}
var validationState: ValidationResult = ValidationResult.OK
set(value) {
field = value
when (value) {
ValidationResult.OK -> {
setIcon(null)
errorLabel.text = " "
toolTipText = null
}
is ValidationResult.ValidationError -> {
setIcon(AllIcons.General.Error)
val errors = value.messages.firstOrNull().orEmpty()
toolTipText = errors
if (showText) errorLabel.text = errors
}
}
}
}
@@ -0,0 +1,162 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.ui.JBColor
import com.intellij.ui.JBSplitter
import com.intellij.ui.PopupHandler
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBTextField
import com.intellij.util.ui.JBUI
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.UiConstants.GAP_BORDER_SIZE
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener
internal inline fun label(text: String, bold: Boolean = false, init: JBLabel.() -> Unit = {}) = JBLabel().apply {
font = UIUtil.getButtonFont().deriveFont(if (bold) Font.BOLD else Font.PLAIN)
this.text = text
init()
}
inline fun panel(layout: LayoutManager = BorderLayout(), init: JPanel.() -> Unit = {}) = JPanel(layout).apply(init)
fun textField(defaultValue: String?, onUpdated: (value: String) -> Unit) = JBTextField(defaultValue).apply {
document.addDocumentListener(object : DocumentListener {
override fun insertUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
override fun removeUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
override fun changedUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
})
}
internal fun hyperlinkLabel(
text: String,
onClick: () -> Unit
) = DescriptionPanel().apply {
cursor = Cursor(Cursor.HAND_CURSOR)
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent?) = onClick()
})
updateText("<a href='javascript:void(0)'>$text</a>".asHtml())
}
internal fun String.asHtml() = "<html><body>$this</body></html>"
val DisplayableSettingItem.htmlText
get() = (text + greyText?.let { " <i>($greyText)</i>" }.orEmpty()).asHtml()
fun splitterFor(
vararg components: JComponent,
vertical: Boolean = false
) = components.reduce { left, right ->
JBSplitter(vertical, 1f / components.size).apply {
firstComponent = left
secondComponent = right
dividerWidth = 1
}
}
val ModuleType.icon: Icon
get() = when (this) {
ModuleType.jvm -> KotlinIcons.SMALL_LOGO
ModuleType.js -> KotlinIcons.JS
ModuleType.native -> KotlinIcons.NATIVE
ModuleType.common -> KotlinIcons.SMALL_LOGO
}
val Module.icon: Icon
get() = when (kind) {
ModuleKind.target -> configurator.moduleType.icon
ModuleKind.multiplatform -> AllIcons.Nodes.Module
ModuleKind.singleplatform -> AllIcons.Nodes.Module
}
val ModuleSubType.icon: Icon
get() = moduleType.icon
val ModuleConfigurator.icon: Icon
get() = when (this) {
is SimpleTargetConfigurator -> moduleSubType.icon
is TargetConfigurator -> moduleType.icon
else -> AllIcons.Nodes.Module
}
fun ToolbarDecorator.createPanelWithPopupHandler(popupTarget: JComponent) = createPanel().apply toolbarApply@{
val actionGroup = DefaultActionGroup().apply {
ToolbarDecorator.findAddButton(this@toolbarApply)?.let(this::add)
ToolbarDecorator.findRemoveButton(this@toolbarApply)?.let(this::add)
}
PopupHandler.installPopupHandler(
popupTarget,
actionGroup,
ActionPlaces.UNKNOWN,
ActionManager.getInstance()
)
}
fun <C : JComponent> C.bordered(
needTopEmptyBorder: Boolean = true,
needBottomEmptyBorder: Boolean = true,
needInnerEmptyBorder: Boolean = true,
needLineBorder: Boolean = true
) = apply {
val lineBorder = if (needLineBorder) BorderFactory.createLineBorder(JBColor.border()) else null
border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(
if (needTopEmptyBorder) GAP_BORDER_SIZE else 0,
0,
if (needBottomEmptyBorder) GAP_BORDER_SIZE else 0,
0
),
when {
needInnerEmptyBorder -> BorderFactory.createCompoundBorder(
lineBorder,
BorderFactory.createEmptyBorder(GAP_BORDER_SIZE, GAP_BORDER_SIZE, GAP_BORDER_SIZE, GAP_BORDER_SIZE)
)
else -> lineBorder
}
)
}
// Copied from com.intellij.ide.projectWizard.ProjectSettingsStep.addField
fun JPanel.addFieldWithLabel(label: String, field: JComponent) {
val jLabel = JBLabel(label)
jLabel.labelFor = field
jLabel.verticalAlignment = SwingConstants.TOP
add(
jLabel,
GridBagConstraints(
0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
GridBagConstraints.VERTICAL, JBUI.insets(5, 0, 5, 0), 4, 0
)
)
add(
field,
GridBagConstraints(
1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, JBUI.insetsBottom(5), 0, 0
)
)
}
object UiConstants {
const val GAP_BORDER_SIZE = 5
}
@@ -0,0 +1,11 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.library.LibraryArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.NpmArtifact
fun LibraryArtifact.render() = when (this) {
is MavenArtifact -> "$groupId:$artifactId"
is NpmArtifact -> name
}