mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Form to UI DSL converter: generate property bindings, bunch of other fixes
GitOrigin-RevId: 911ba4f93f72b404ca2cc3adc1f77c520ff0a0cc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
acc1771712
commit
3cec4c138f
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.formConversion
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.ui.EditorTextFieldWithBrowseButton
|
||||
import com.intellij.ui.layout.*
|
||||
import javax.swing.JComponent
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
class ConvertFormDialog(val project: Project, var className: String) : DialogWrapper(project) {
|
||||
init {
|
||||
init()
|
||||
title = "Convert Form to UI DSL"
|
||||
}
|
||||
|
||||
var boundInstanceType: String = ""
|
||||
|
||||
override fun createCenterPanel(): JComponent? {
|
||||
return panel {
|
||||
row("Target class name:") {
|
||||
textField(::className, columns = 40).focused()
|
||||
}
|
||||
row("Bound instance type:") {
|
||||
EditorTextFieldWithBrowseButton(project, true)()
|
||||
.withBinding(EditorTextFieldWithBrowseButton::getText, EditorTextFieldWithBrowseButton::setText,
|
||||
::boundInstanceType.toBinding())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,12 @@ import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiFileFactory
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.codeStyle.NameUtil
|
||||
import com.intellij.psi.search.ProjectScope
|
||||
import com.intellij.psi.search.PsiShortNamesCache
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.uiDesigner.PsiPropertiesProvider
|
||||
@@ -22,6 +22,11 @@ import com.intellij.uiDesigner.binding.FormClassIndex
|
||||
import com.intellij.uiDesigner.compiler.Utils.getRootContainer
|
||||
import com.intellij.uiDesigner.core.GridLayoutManager
|
||||
import com.intellij.uiDesigner.lw.*
|
||||
import org.jetbrains.uast.UClass
|
||||
import org.jetbrains.uast.UastVisibility
|
||||
import org.jetbrains.uast.toUElement
|
||||
import java.util.*
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -43,29 +48,44 @@ class ConvertFormToDslAction : AnAction() {
|
||||
return
|
||||
}
|
||||
|
||||
val dialog = ConvertFormDialog(project, "${psiClass.name}Ui")
|
||||
if (!dialog.showAndGet()) return
|
||||
|
||||
val boundInstanceUClass = findBoundInstanceUClass(project, dialog.boundInstanceType)
|
||||
|
||||
val rootContainer = getRootContainer(formFile.text, PsiPropertiesProvider(module))
|
||||
val form = convertRootContainer(module, rootContainer)
|
||||
val form = convertRootContainer(module, rootContainer, boundInstanceUClass)
|
||||
|
||||
val imports = LinkedHashSet(form.imports)
|
||||
boundInstanceUClass?.qualifiedName?.let {
|
||||
imports.add(it)
|
||||
}
|
||||
|
||||
val formText = buildString {
|
||||
append("val panel = panel {\n")
|
||||
form.root.render(this)
|
||||
append("}\n")
|
||||
}
|
||||
val uiName = "${psiClass.name}Ui"
|
||||
val uiName = dialog.className
|
||||
val ktFileType = FileTypeRegistry.getInstance().getFileTypeByExtension("kt")
|
||||
val ktFileText = buildString {
|
||||
if (psiFile.packageName.isNotEmpty()) {
|
||||
append("package ${psiFile.packageName}\n\n")
|
||||
}
|
||||
append("import com.intellij.ui.layout.*\n")
|
||||
for (usedImport in form.imports) {
|
||||
for (usedImport in imports) {
|
||||
append("import $usedImport\n")
|
||||
}
|
||||
append("\n")
|
||||
append("class $uiName {")
|
||||
append("class $uiName")
|
||||
if (boundInstanceUClass != null) {
|
||||
append("(val model: ${dialog.boundInstanceType.substringAfterLast('.')})")
|
||||
}
|
||||
append(" {")
|
||||
|
||||
for (binding in form.bindings) {
|
||||
append("lateinit var ${binding.name}: ${binding.type.substringAfterLast('.')}\n")
|
||||
for (binding in form.componentProperties) {
|
||||
val typeParameters = buildTypeParametersString(module, binding.type)
|
||||
append("lateinit var ${binding.name}: ${binding.type.substringAfterLast('.')}$typeParameters\n")
|
||||
}
|
||||
|
||||
append(formText)
|
||||
@@ -79,8 +99,16 @@ class ConvertFormToDslAction : AnAction() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertRootContainer(module: Module, rootContainer: LwRootContainer): UiForm {
|
||||
val call = convertContainer(rootContainer)
|
||||
private fun findBoundInstanceUClass(project: Project, boundInstanceType: String): UClass? {
|
||||
val psiClass = JavaPsiFacade.getInstance(project).findClass(boundInstanceType, ProjectScope.getAllScope(project))
|
||||
if (psiClass == null) return null
|
||||
return psiClass.navigationElement.toUElement(UClass::class.java)
|
||||
}
|
||||
|
||||
private fun convertRootContainer(module: Module,
|
||||
rootContainer: LwRootContainer,
|
||||
boundInstanceUClass: UClass?): UiForm {
|
||||
val call = FormToDslConverter(module, boundInstanceUClass).convertContainer(rootContainer)
|
||||
for (buttonGroup in rootContainer.buttonGroups) {
|
||||
call.checkConvertButtonGroup(buttonGroup.componentIds)
|
||||
}
|
||||
@@ -105,6 +133,23 @@ class FormCall(
|
||||
this.args.addAll(args.toList())
|
||||
}
|
||||
|
||||
fun addArgIfPresent(arg: Array<String>?): FormCall {
|
||||
if (arg != null) {
|
||||
Collections.addAll(args, *arg)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addArgOrDefault(arg: Array<String>?, vararg default: String): FormCall {
|
||||
if (arg != null) {
|
||||
Collections.addAll(args, *arg)
|
||||
}
|
||||
else {
|
||||
Collections.addAll(args, *default)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun render(builder: StringBuilder) {
|
||||
if (callee == "row" && args.isEmpty() && contents.all { it.callee == "row" }) {
|
||||
for (content in contents) {
|
||||
@@ -131,14 +176,14 @@ class FormCall(
|
||||
}
|
||||
}
|
||||
|
||||
data class Binding(val name: String, val type: String)
|
||||
data class ComponentProperty(val name: String, val type: String)
|
||||
|
||||
class UiForm(module: Module, val root: FormCall) {
|
||||
private val _imports = sortedSetOf<String>()
|
||||
private val _bindings = mutableListOf<Binding>()
|
||||
private val _componentProperties = mutableListOf<ComponentProperty>()
|
||||
|
||||
val imports: Collection<String> get() { return _imports }
|
||||
val bindings: Collection<Binding> get() { return _bindings }
|
||||
val componentProperties: Collection<ComponentProperty> get() { return _componentProperties }
|
||||
|
||||
init {
|
||||
collectUsedImportsAndBindings(module, root)
|
||||
@@ -160,7 +205,7 @@ class UiForm(module: Module, val root: FormCall) {
|
||||
}
|
||||
formCall.bindingType?.let { bindingType ->
|
||||
_imports.add(bindingType)
|
||||
formCall.binding?.let { bindingName -> _bindings.add(Binding(bindingName, bindingType))}
|
||||
formCall.binding?.let { bindingName -> _componentProperties.add(ComponentProperty(bindingName, bindingType))}
|
||||
}
|
||||
|
||||
for (content in formCall.contents) {
|
||||
@@ -169,104 +214,161 @@ class UiForm(module: Module, val root: FormCall) {
|
||||
}
|
||||
}
|
||||
|
||||
internal class PropertyBinding(val type: PsiType?, val bindingCallParameters: Array<String>)
|
||||
|
||||
private fun convertContainer(container: LwContainer): FormCall {
|
||||
val row: FormCall
|
||||
class FormToDslConverter(private val module: Module, private val boundInstanceUClass: UClass?) {
|
||||
fun convertContainer(container: LwContainer): FormCall {
|
||||
val row: FormCall
|
||||
|
||||
val borderTitle = container.borderTitle
|
||||
if (borderTitle != null) {
|
||||
row = FormCall("titledRow", origin = container)
|
||||
row.args.add(convertStringDescriptor(borderTitle))
|
||||
}
|
||||
else {
|
||||
row = FormCall("row", origin = container)
|
||||
val borderTitle = container.borderTitle
|
||||
if (borderTitle != null) {
|
||||
row = FormCall("titledRow", origin = container)
|
||||
row.args.add(convertStringDescriptor(borderTitle))
|
||||
}
|
||||
else {
|
||||
row = FormCall("row", origin = container)
|
||||
}
|
||||
|
||||
val layoutManager = container.layout
|
||||
if (layoutManager is GridLayoutManager) {
|
||||
for (rowIndex in 0 until layoutManager.rowCount) {
|
||||
row.appendGridRow(container, layoutManager, rowIndex)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (index in 0 until container.componentCount) {
|
||||
row.contents.add(convertComponentOrContainer(container.getComponent(index)))
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
val layoutManager = container.layout
|
||||
if (layoutManager is GridLayoutManager) {
|
||||
for (rowIndex in 0 until layoutManager.rowCount) {
|
||||
row.appendGridRow(container, layoutManager, rowIndex)
|
||||
private fun FormCall.appendGridRow(container: LwContainer, layoutManager: GridLayoutManager, rowIndex: Int) {
|
||||
val allComponents = container.collectComponentsInRow(rowIndex, layoutManager.columnCount)
|
||||
val components = allComponents.filter { it !is LwHSpacer && it !is LwVSpacer }
|
||||
if (components.isEmpty()) return
|
||||
|
||||
val row = FormCall("row", origin = container)
|
||||
contents.add(row)
|
||||
if (components.first().componentClassName == "javax.swing.JLabel") {
|
||||
row.args.add(convertComponentText(components.first()))
|
||||
for (component in components.drop(1)) {
|
||||
row.contents.add(convertComponentOrContainer(component))
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (component in components) {
|
||||
row.contents.add(convertComponentOrContainer((component)))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (index in 0 until container.componentCount) {
|
||||
row.contents.add(convertComponentOrContainer(container.getComponent(index)))
|
||||
|
||||
private fun LwContainer.collectComponentsInRow(row: Int, columnCount: Int): List<IComponent> {
|
||||
val result = arrayOfNulls<IComponent>(columnCount)
|
||||
for (i in 0 until componentCount) {
|
||||
val component = getComponent(i)
|
||||
|
||||
val constraints = component.constraints
|
||||
if (constraints.row == row) {
|
||||
result[constraints.column] = component
|
||||
}
|
||||
}
|
||||
return result.toList().filterNotNull()
|
||||
}
|
||||
|
||||
private fun convertComponentOrContainer(component: IComponent): FormCall {
|
||||
if (component is LwContainer) {
|
||||
return convertContainer(component)
|
||||
}
|
||||
else {
|
||||
return convertComponent(component).also { it.origin = component }
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
fun FormCall.appendGridRow(container: LwContainer, layoutManager: GridLayoutManager, rowIndex: Int) {
|
||||
val allComponents = container.collectComponentsInRow(rowIndex, layoutManager.columnCount)
|
||||
val components = allComponents.filter { it !is LwHSpacer && it !is LwVSpacer }
|
||||
if (components.isEmpty()) return
|
||||
private fun convertComponent(component: IComponent): FormCall {
|
||||
val propertyBinding = convertBinding(component.binding)
|
||||
return when (component.componentClassName) {
|
||||
"javax.swing.JCheckBox",
|
||||
"com.intellij.ui.components.JBCheckBox" ->
|
||||
FormCall("checkBox", convertComponentText(component))
|
||||
.addArgIfPresent(propertyBinding?.bindingCallParameters)
|
||||
|
||||
val row = FormCall("row", origin = container)
|
||||
contents.add(row)
|
||||
if (components.first().componentClassName == "javax.swing.JLabel") {
|
||||
row.args.add(convertComponentText(components.first()))
|
||||
for (component in components.drop(1)) {
|
||||
row.contents.add(convertComponentOrContainer(component))
|
||||
"javax.swing.JTextField" -> {
|
||||
val methodName = if (propertyBinding?.type?.canonicalText == "int") "intTextField" else "textField"
|
||||
FormCall(methodName)
|
||||
.addArgOrDefault(propertyBinding?.bindingCallParameters, "{ \"\" }", "{}")
|
||||
}
|
||||
|
||||
"javax.swing.JRadioButton",
|
||||
"com.intellij.ui.components.JBRadioButton"->
|
||||
FormCall("radioButton", convertComponentText(component))
|
||||
|
||||
"javax.swing.JButton" ->
|
||||
FormCall("button", convertComponentText(component), "actionListener = { TODO() }")
|
||||
|
||||
"javax.swing.JLabel",
|
||||
"com.intellij.ui.components.JBLabel" ->
|
||||
FormCall("label", convertComponentText(component))
|
||||
|
||||
else -> {
|
||||
val typeParameters = buildTypeParametersString(module, component.componentClassName)
|
||||
|
||||
val classShortName = component.componentClassName.substringAfterLast('.')
|
||||
FormCall("$classShortName$typeParameters()",
|
||||
binding = component.binding,
|
||||
bindingType = component.componentClassName)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (component in components) {
|
||||
row.contents.add(convertComponentOrContainer((component)))
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun LwContainer.collectComponentsInRow(row: Int, columnCount: Int): List<IComponent> {
|
||||
val result = arrayOfNulls<IComponent>(columnCount)
|
||||
for (i in 0 until componentCount) {
|
||||
val component = getComponent(i)
|
||||
|
||||
val constraints = component.constraints
|
||||
if (constraints.row == row) {
|
||||
result[constraints.column] = component
|
||||
private fun convertBinding(binding: String?): PropertyBinding? {
|
||||
if (binding == null || boundInstanceUClass == null) return null
|
||||
val field = boundInstanceUClass.fields.find { it.matchesBinding(binding) }
|
||||
if (field != null && !field.isStatic && field.visibility != UastVisibility.PRIVATE) {
|
||||
return PropertyBinding(field.type, arrayOf("model::${field.name}"))
|
||||
}
|
||||
}
|
||||
return result.toList().filterNotNull()
|
||||
}
|
||||
|
||||
private fun convertComponentOrContainer(component: IComponent): FormCall {
|
||||
if (component is LwContainer) {
|
||||
return convertContainer(component)
|
||||
val getter = boundInstanceUClass.methods.find {
|
||||
!it.name.startsWith("set") && it.matchesBinding(binding)
|
||||
}
|
||||
val setter = boundInstanceUClass.methods.find {
|
||||
it.name.startsWith("set") && it.matchesBinding(binding)
|
||||
}
|
||||
if (getter != null && setter != null) {
|
||||
return PropertyBinding(getter.returnType, arrayOf("model::${getter.name}", "model::${setter.name}"))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
else {
|
||||
return convertComponent(component).also { it.origin = component }
|
||||
|
||||
private fun PsiNamedElement.matchesBinding(binding: String): Boolean {
|
||||
val bindingWords = NameUtil.nameToWordsLowerCase(binding.removePrefix("my"))
|
||||
val elementWords = NameUtil.nameToWordsLowerCase(name?.removePrefix("my") ?: "")
|
||||
if (bindingWords.size == 1 && elementWords.size == 1) {
|
||||
return bindingWords[0] == elementWords[0]
|
||||
}
|
||||
return bindingWords.count { it in elementWords } > 1
|
||||
}
|
||||
|
||||
private fun convertComponentText(component: IComponent) =
|
||||
convertStringDescriptor(component.getPropertyValue("text") as StringDescriptor)
|
||||
|
||||
private fun convertStringDescriptor(text: StringDescriptor): String {
|
||||
text.value?.let {
|
||||
return "\"${StringUtil.escapeQuotes(it)}\""
|
||||
}
|
||||
return "${text.bundleName.substringAfterLast('/')}.message(\"${text.key}\")"
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertComponent(component: IComponent): FormCall {
|
||||
return when (component.componentClassName) {
|
||||
"javax.swing.JCheckBox" ->
|
||||
FormCall("checkBox", convertComponentText(component))
|
||||
|
||||
"javax.swing.JTextField" ->
|
||||
FormCall("textField", "{ \"\" }", "{}")
|
||||
|
||||
"javax.swing.JRadioButton" ->
|
||||
FormCall("radioButton", convertComponentText(component))
|
||||
|
||||
"javax.swing.JButton" ->
|
||||
FormCall("button", convertComponentText(component), "actionListener = {}")
|
||||
|
||||
else -> {
|
||||
FormCall("${component.componentClassName.substringAfterLast('.')}()",
|
||||
binding = component.binding,
|
||||
bindingType = component.componentClassName)
|
||||
}
|
||||
private fun buildTypeParametersString(module: Module, className: String): String {
|
||||
val javaPsiFacade = JavaPsiFacade.getInstance(module.project)
|
||||
val componentClass = javaPsiFacade.findClass(className, module.moduleWithDependenciesScope)
|
||||
return if (componentClass != null && componentClass.typeParameters.isNotEmpty()) {
|
||||
Array(componentClass.typeParameters.size) { "Any" }.joinToString(prefix = "<", postfix = ">", separator = ", ")
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertComponentText(component: IComponent) =
|
||||
convertStringDescriptor(component.getPropertyValue("text") as StringDescriptor)
|
||||
|
||||
private fun convertStringDescriptor(text: StringDescriptor): String {
|
||||
text.value?.let {
|
||||
return "\"${StringUtil.escapeQuotes(it)}\""
|
||||
}
|
||||
return "${text.bundleName.substringAfterLast('/')}.message(\"${text.key}\")"
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
private fun IComponent.getPropertyValue(name: String): Any? {
|
||||
|
||||
Reference in New Issue
Block a user