diff --git a/build.gradle.kts b/build.gradle.kts index c0c1ed3da9e..8d80a0913ad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -621,11 +621,20 @@ tasks { ) } + register("idea-new-project-wizard-tests") { + dependsOn("dist") + dependsOn( + ":libraries:tools:new-project-wizard:test", + ":libraries:tools:new-project-wizard:new-project-wizard-cli:test" + ) + } + register("idea-plugin-tests") { dependsOn("dist") dependsOn( "idea-plugin-main-tests", - "idea-plugin-additional-tests" + "idea-plugin-additional-tests", + "idea-new-project-wizard-tests" ) } diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts index 06e5fe9b4c5..22946113308 100644 --- a/generators/build.gradle.kts +++ b/generators/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { compile(projectTests(":idea:idea-maven")) compile(projectTests(":j2k")) compile(projectTests(":nj2k")) + compile(projectTests(":libraries:tools:new-project-wizard:new-project-wizard-cli")) compile(projectTests(":idea:idea-android")) compile(projectTests(":idea:scripting-support")) compile(projectTests(":jps-plugin")) diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 547af9a4ba9..c107b02810d 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -160,6 +160,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest @@ -1066,6 +1067,15 @@ fun main(args: Array) { } } + testGroup( + "libraries/tools/new-project-wizard/new-project-wizard-cli/tests", + "libraries/tools/new-project-wizard/new-project-wizard-cli/testData" + ) { + testClass { + model("buildFileGeneration", recursive = false, extension = null) + } + } + //TODO: move these tests into idea-completion module testGroup("idea/tests", "idea/idea-completion/testData") { testClass { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 index f998a0873c3..b0674ae9669 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 @@ -167,6 +167,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest @@ -1005,6 +1006,15 @@ fun main(args: Array) { } } + testGroup( + "libraries/tools/new-project-wizard/new-project-wizard-cli/tests", + "libraries/tools/new-project-wizard/new-project-wizard-cli/testData" + ) { + testClass { + model("buildFileGeneration", recursive = false, extension = null) + } + } + //TODO: move these tests into idea-completion module testGroup("idea/tests", "idea/idea-completion/testData") { testClass { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 index 7d922ef244c..399e7858931 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 @@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest @@ -984,6 +985,15 @@ fun main(args: Array) { } } + testGroup( + "libraries/tools/new-project-wizard/new-project-wizard-cli/tests", + "libraries/tools/new-project-wizard/new-project-wizard-cli/testData" + ) { + testClass { + model("buildFileGeneration", recursive = false, extension = null) + } + } + //TODO: move these tests into idea-completion module testGroup("idea/tests", "idea/idea-completion/testData") { testClass { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 index 7d922ef244c..399e7858931 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 @@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest @@ -984,6 +985,15 @@ fun main(args: Array) { } } + testGroup( + "libraries/tools/new-project-wizard/new-project-wizard-cli/tests", + "libraries/tools/new-project-wizard/new-project-wizard-cli/testData" + ) { + testClass { + model("buildFileGeneration", recursive = false, extension = null) + } + } + //TODO: move these tests into idea-completion module testGroup("idea/tests", "idea/idea-completion/testData") { testClass { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as36 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as36 index 7d922ef244c..399e7858931 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as36 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as36 @@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest @@ -984,6 +985,15 @@ fun main(args: Array) { } } + testGroup( + "libraries/tools/new-project-wizard/new-project-wizard-cli/tests", + "libraries/tools/new-project-wizard/new-project-wizard-cli/testData" + ) { + testClass { + model("buildFileGeneration", recursive = false, extension = null) + } + } + //TODO: move these tests into idea-completion module testGroup("idea/tests", "idea/idea-completion/testData") { testClass { diff --git a/idea/idea-new-project-wizard/build.gradle.kts b/idea/idea-new-project-wizard/build.gradle.kts new file mode 100644 index 00000000000..74bc3106b33 --- /dev/null +++ b/idea/idea-new-project-wizard/build.gradle.kts @@ -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 +} diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeWizard.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeWizard.kt new file mode 100644 index 00000000000..1559ce4c266 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeWizard.kt @@ -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 +) : 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 > setting(reference: SettingReference) = + object : ReadWriteProperty { + 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] + } +} + diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeaJpsService.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeaJpsService.kt new file mode 100644 index 00000000000..0d47649c550 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/IdeaJpsService.kt @@ -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): TaskResult = 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 +) { + private val librariesPath: Path + get() = path / "libs" + + fun import() = modulesIrs.mapSequence { convertModule(it) } andThen + safe { modulesModel.commit() } + + + private fun convertModule(moduleIr: ModuleIR): TaskResult { + 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()) \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleBuilder.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleBuilder.kt new file mode 100644 index 00000000000..fe7d4821c04 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleBuilder.kt @@ -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 { + this.wizardContext = wizardContext + return arrayOf(ModuleNewWizardSecondStep(wizard)) + } + + override fun commit( + project: Project, + model: ModifiableModuleModel?, + modulesProvider: ModulesProvider? + ): List? { + 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()?.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() + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleType.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleType.kt new file mode 100644 index 00000000000..fc6990c703b --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/NewProjectWizardModuleType.kt @@ -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.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() +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ideaServices.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ideaServices.kt new file mode 100644 index 00000000000..fe5ef2823a9 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ideaServices.kt @@ -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 = safe { + runWriteAction { + VfsUtil.createDirectoryIfMissing(path.toString()) + } + } + + override fun createFile(path: Path, text: String): TaskResult = 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 + ): TaskResult = 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 + ): TaskResult = safe { + val mavenProjectManager = MavenProjectsManager.getInstance(project) + + val rootFile = LocalFileSystem.getInstance().findFileByPath(path.toString())!! + mavenProjectManager.addManagedFilesOrUnignore(rootFile.findAllPomFiles()) + } + + private fun VirtualFile.findAllPomFiles(): List { + val result = mutableListOf() + + 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) +} + diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Component.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Component.kt new file mode 100644 index 00000000000..1ad77c918ac --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Component.kt @@ -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() + + open fun onInit() { + subComponents.forEach(Component::onInit) + } + + protected fun 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 > SettingReference.value: V? + get() = with(valuesReadingContext) { notRequiredSettingValue() } + set(value) { + with(context) { settingContext[this@value] = value!! } + } + + inline val > PluginSettingPropertyReference.value: V? + get() = reference.value + + init { + valuesReadingContext.context.eventManager.addSettingUpdaterEventListener { reference -> + if (isInitialized) onValueUpdated(reference) + } + } + open fun onValueUpdated(reference: SettingReference<*, *>?) {} +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/DescriptionPanel.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/DescriptionPanel.kt new file mode 100644 index 00000000000..b6534ac3d9c --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/DescriptionPanel.kt @@ -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) +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Displayable.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Displayable.kt new file mode 100644 index 00000000000..09afd38edb2 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/Displayable.kt @@ -0,0 +1,7 @@ +package org.jetbrains.kotlin.tools.projectWizard.wizard.ui + +import javax.swing.JComponent + +interface Displayable { + val component: JComponent +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/PomWizardStepComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/PomWizardStepComponent.kt new file mode 100644 index 00000000000..7e56d5fcc06 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/PomWizardStepComponent.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SingleSelectableListWithIcon.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SingleSelectableListWithIcon.kt new file mode 100644 index 00000000000..c4133d1b6bb --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SingleSelectableListWithIcon.kt @@ -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(initialValues: List = emptyList()) : JBList() { + protected abstract fun ColoredListCellRenderer.render(value: V) + protected open fun onSelected(value: V?) {} + + protected val model: DefaultListModel + get() = super.getModel() as DefaultListModel + + fun updateValues(newValues: List) { + 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() { + override fun customizeCellRenderer( + list: JList, + value: V?, + index: Int, + selected: Boolean, + hasFocus: Boolean + ) { + render(value ?: return) + } + } + } +} + +class ImmutableSingleSelectableListWithIcon( + values: List, + private val renderValue: ColoredListCellRenderer.(V) -> Unit, + emptyMessage: String? = null, + private val onValueSelected: (V?) -> Unit = {} +) : AbstractSingleSelectableListWithIcon(values) { + override fun ColoredListCellRenderer.render(value: V) = renderValue(value) + override fun onSelected(value: V?) = onValueSelected(value) + + init { + emptyMessage?.let { text -> + emptyText.text = text + } + } +} diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SubStep.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SubStep.kt new file mode 100644 index 00000000000..5b457f9ca61 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/SubStep.kt @@ -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) + } + } +} + diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/WizardStepComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/WizardStepComponent.kt new file mode 100644 index 00000000000..afbaff44bde --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/WizardStepComponent.kt @@ -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) \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/DropDownComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/DropDownComponent.kt new file mode 100644 index 00000000000..e4ef00d7d77 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/DropDownComponent.kt @@ -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( + private val valuesReadingContext: ValuesReadingContext, + initialValues: List = emptyList(), + labelText: String? = null, + private val validator: SettingValidator = 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(initialValues.toTypedArray() as Array).apply { + renderer = object : ColoredListCellRenderer() { + override fun customizeCellRenderer( + list: JList, + 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() + ?.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) = withoutActionFiring { + val oldValue = comboBox.selectedItem + @Suppress("UNCHECKED_CAST") + comboBox.model = DefaultComboBoxModel(newValues.toTypedArray() as Array) + + 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) + } +} + diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/PathFieldComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/PathFieldComponent.kt new file mode 100644 index 00000000000..9f0ed99c39d --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/PathFieldComponent.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/TextFieldComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/TextFieldComponent.kt new file mode 100644 index 00000000000..b06a8df6520 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/components/TextFieldComponent.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/BuildSystemTypeSettingComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/BuildSystemTypeSettingComponent.kt new file mode 100644 index 00000000000..c498df8e2f0 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/BuildSystemTypeSettingComponent.kt @@ -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>( + 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 + } \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/FirstWizardStepComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/FirstWizardStepComponent.kt new file mode 100644 index 00000000000..1c34b09f1b9 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/FirstWizardStepComponent.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/ProjectTemplateSettingComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/ProjectTemplateSettingComponent.kt new file mode 100644 index 00000000000..e0e2f8d2ee3 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/firstStep/ProjectTemplateSettingComponent.kt @@ -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>( + 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()) + } + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/ModuleSettingsComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/ModuleSettingsComponent.kt new file mode 100644 index 00000000000..de861e5526e --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/ModuleSettingsComponent.kt @@ -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( + 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() + } + } +} + diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/NothingSelectedComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/NothingSelectedComponent.kt new file mode 100644 index 00000000000..5abfb914b58 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/NothingSelectedComponent.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SecondStepWizardComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SecondStepWizardComponent.kt new file mode 100644 index 00000000000..c536aeb84c2 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SecondStepWizardComponent.kt @@ -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 +} diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetDependenciesSettingsComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetDependenciesSettingsComponent.kt new file mode 100644 index 00000000000..abcc820484b --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetDependenciesSettingsComponent.kt @@ -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 + 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, 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() { + override fun ColoredListCellRenderer.render(value: Module) { + renderModule(value) + } + + var sourceset: Sourceset? = null + set(value) { + field = value + updateValues( + value?.dependencies?.mapNotNull { it.safeAs()?.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.renderModule(module: Module) { + append(module.path.asString()) + append(" ") + append("(${module.greyText})", SimpleTextAttributes.GRAYED_ATTRIBUTES) + icon = module.icon +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetSettingsComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetSettingsComponent.kt new file mode 100644 index 00000000000..5d792e49e20 --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/SourcesetSettingsComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/TemplatesComponent.kt b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/TemplatesComponent.kt new file mode 100644 index 00000000000..1f4bef3d25c --- /dev/null +++ b/idea/idea-new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/wizard/ui/secondStep/TemplatesComponent.kt @@ -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