mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Kotlin UI DSL — align fields in the nested grid
This commit is contained in:
@@ -145,7 +145,7 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
|
||||
|
||||
keePassMasterPassword.setPasswordIsStored(true)
|
||||
|
||||
val panel = panel {
|
||||
return panel {
|
||||
row { label("Save passwords:") }
|
||||
|
||||
buttonGroup({ updateEnabledState() }) {
|
||||
@@ -165,46 +165,46 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
|
||||
fileChooserDescriptor = fileChooserDescriptor,
|
||||
fileChosen = ::normalizeSelectedFile)
|
||||
gearButton(
|
||||
object : AnAction("Clear") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
if (MessageDialogBuilder.yesNo("Clear Passwords", "Are you sure want to remove all passwords?").yesText("Remove Passwords").isYes) {
|
||||
passwordSafe.clearPasswords()
|
||||
}
|
||||
object : AnAction("Clear") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
if (MessageDialogBuilder.yesNo("Clear Passwords", "Are you sure want to remove all passwords?").yesText("Remove Passwords").isYes) {
|
||||
passwordSafe.clearPasswords()
|
||||
}
|
||||
},
|
||||
object : AnAction("Import") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
chooseFile(fileChooserDescriptor, event) {
|
||||
val wantedDbFile = Paths.get(normalizeSelectedFile(it))
|
||||
val dbFile = getCurrentDbFile()
|
||||
if (dbFile != wantedDbFile) {
|
||||
val contextComponent = event.getData(PlatformDataKeys.CONTEXT_COMPONENT) as Component
|
||||
Messages.showInputDialog(
|
||||
contextComponent, "Master Password:", "Specify Master Password", null)?.trim().nullize()?.let { masterPassword ->
|
||||
try {
|
||||
Files.copy(wantedDbFile, dbFile, StandardCopyOption.REPLACE_EXISTING)
|
||||
passwordSafe.currentProvider = KeePassCredentialStore(existingMasterPassword = masterPassword.toByteArray(),
|
||||
dbFile = getCurrentDbFile())
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error(e)
|
||||
if (e.message == "Inconsistent stream bytes") {
|
||||
Messages.showMessageDialog(contextComponent, if (e.message == "Inconsistent stream bytes") "Password is not correct" else "Internal error", "Cannot Import", Messages.getErrorIcon())
|
||||
}
|
||||
}
|
||||
keePassMasterPassword.text = ""
|
||||
}
|
||||
},
|
||||
object : AnAction("Import") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
chooseFile(fileChooserDescriptor, event) {
|
||||
val wantedDbFile = Paths.get(normalizeSelectedFile(it))
|
||||
val dbFile = getCurrentDbFile()
|
||||
if (dbFile != wantedDbFile) {
|
||||
val contextComponent = event.getData(PlatformDataKeys.CONTEXT_COMPONENT) as Component
|
||||
Messages.showInputDialog(
|
||||
contextComponent, "Master Password:", "Specify Master Password", null)?.trim().nullize()?.let { masterPassword ->
|
||||
try {
|
||||
Files.copy(wantedDbFile, dbFile, StandardCopyOption.REPLACE_EXISTING)
|
||||
passwordSafe.currentProvider = KeePassCredentialStore(existingMasterPassword = masterPassword.toByteArray(),
|
||||
dbFile = getCurrentDbFile())
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error(e)
|
||||
if (e.message == "Inconsistent stream bytes") {
|
||||
Messages.showMessageDialog(contextComponent, if (e.message == "Inconsistent stream bytes") "Password is not correct" else "Internal error", "Cannot Import", Messages.getErrorIcon())
|
||||
}
|
||||
}
|
||||
keePassMasterPassword.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
row("Master Password:") {
|
||||
keePassMasterPassword(growPolicy = GrowPolicy.SHORT_TEXT)
|
||||
}
|
||||
if (!SystemInfo.isWindows) {
|
||||
row { hint("Stored using weak encryption. It is recommended to store password database on encrypted volume for additional security.") }
|
||||
row { hint("Stored using weak encryption. It is recommended to store on encrypted volume for additional security.") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +218,6 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun getProviderType(): ProviderType {
|
||||
|
||||
@@ -12,14 +12,16 @@ import com.intellij.openapi.ui.OnePixelDivider
|
||||
import com.intellij.ui.SeparatorComponent
|
||||
import com.intellij.ui.TextFieldWithHistory
|
||||
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton
|
||||
import com.intellij.ui.components.Label
|
||||
import com.intellij.ui.components.noteComponent
|
||||
import com.intellij.util.SmartList
|
||||
import net.miginfocom.layout.*
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import java.awt.Component
|
||||
import java.awt.Container
|
||||
import javax.swing.*
|
||||
import javax.swing.ButtonGroup
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.text.JTextComponent
|
||||
|
||||
/**
|
||||
@@ -31,18 +33,21 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
|
||||
private val componentConstraints: MutableMap<Component, CC> = SmartHashMap()
|
||||
|
||||
override fun newRow(label: JLabel?, buttonGroup: ButtonGroup?, separated: Boolean, indented: Boolean): Row {
|
||||
override fun newRow(label: JLabel?, buttonGroup: ButtonGroup?, separated: Boolean): Row {
|
||||
return newRow(rows, label, buttonGroup, separated)
|
||||
}
|
||||
|
||||
internal fun newRow(rowList: MutableList<MigLayoutRow>, label: JLabel?, buttonGroup: ButtonGroup? = null, separated: Boolean = false): Row {
|
||||
if (separated) {
|
||||
val row = MigLayoutRow(componentConstraints, this, noGrid = true, separated = true)
|
||||
rows.add(row)
|
||||
rowList.add(row)
|
||||
row.apply { SeparatorComponent(0, OnePixelDivider.BACKGROUND, null)() }
|
||||
}
|
||||
|
||||
val row = MigLayoutRow(componentConstraints, this, label != null, buttonGroup = buttonGroup, indented = indented)
|
||||
rows.add(row)
|
||||
val row = MigLayoutRow(componentConstraints, this, label != null, buttonGroup = buttonGroup)
|
||||
rowList.add(row)
|
||||
|
||||
if (label != null) {
|
||||
// componentConstraints.put(label, CC().growPrioX(-1))
|
||||
row.apply { label() }
|
||||
}
|
||||
|
||||
@@ -59,10 +64,9 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
}
|
||||
|
||||
override fun build(container: Container, layoutConstraints: Array<out LCFlags>) {
|
||||
val isLabeled = rows.firstOrNull({ it.labeled && !it.indented }) != null
|
||||
var gapTop = -1
|
||||
|
||||
val lc = c()
|
||||
val lc = createLayoutConstraints()
|
||||
if (layoutConstraints.isEmpty()) {
|
||||
lc.fillX()
|
||||
// not fillY because it leads to enormously large cells - we use cc `push` in addition to cc `grow` as a more robust and easy solution
|
||||
@@ -74,22 +78,18 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
lc.noVisualPadding()
|
||||
lc.hideMode = 3
|
||||
|
||||
val columnConstraints = if (isLabeled) AC() else null
|
||||
val columnConstraints = AC()
|
||||
var columnIndex = 0
|
||||
if (isLabeled) {
|
||||
columnConstraints!!.grow(0f, columnIndex++)
|
||||
}
|
||||
|
||||
container.layout = MigLayout(lc, columnConstraints)
|
||||
|
||||
val isNoGrid = layoutConstraints.contains(LCFlags.noGrid)
|
||||
|
||||
for (row in rows) {
|
||||
fun configureComponents(row: MigLayoutRow, prevRow: MigLayoutRow?, isSubRow: Boolean, isLabeled: Boolean) {
|
||||
val lastComponent = row.components.lastOrNull()
|
||||
if (lastComponent == null) {
|
||||
if (row === rows.first()) {
|
||||
if (prevRow == null) {
|
||||
// do not add gap for the first row
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// https://goo.gl/LDylKm
|
||||
@@ -109,63 +109,94 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
|
||||
addGrowIfNeed(cc, component)
|
||||
|
||||
if (!isNoGrid) {
|
||||
if (component === lastComponent) {
|
||||
isSplitRequired = false
|
||||
cc.wrap()
|
||||
if (isNoGrid) {
|
||||
container.add(component, cc)
|
||||
continue
|
||||
}
|
||||
|
||||
columnConstraints?.grow(100f, columnIndex++)
|
||||
if (component === lastComponent) {
|
||||
isSplitRequired = false
|
||||
cc.wrap()
|
||||
|
||||
if (isLabeled) {
|
||||
columnConstraints.grow(100f, columnIndex++)
|
||||
}
|
||||
}
|
||||
|
||||
if (row.noGrid) {
|
||||
if (component === row.components.first()) {
|
||||
// rowConstraints.noGrid() doesn't work correctly
|
||||
if (row.noGrid) {
|
||||
if (component === row.components.first()) {
|
||||
// rowConstraints.noGrid() doesn't work correctly
|
||||
cc.spanX()
|
||||
if (row.separated) {
|
||||
cc.vertical.gapBefore = gapToBoundSize(VERTICAL_GAP * 3, false)
|
||||
cc.vertical.gapAfter = gapToBoundSize(VERTICAL_GAP * 2, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var isSkippableComponent = true
|
||||
if (component === row.components.first()) {
|
||||
val isHintComponent = component.getClientProperty(COMPONENT_TAG_HINT) == true
|
||||
if ((isSubRow && !isHintComponent) || (isHintComponent && prevRow != null && !prevRow.labeled)) {
|
||||
cc.horizontal.gapBefore = gapToBoundSize(HORIZONTAL_GAP * 3, true)
|
||||
}
|
||||
|
||||
if (isLabeled) {
|
||||
if (row.labeled) {
|
||||
isSkippableComponent = false
|
||||
}
|
||||
else {
|
||||
cc.skip()
|
||||
}
|
||||
}
|
||||
|
||||
if (row.components.size == 1) {
|
||||
cc.spanX()
|
||||
if (row.separated) {
|
||||
cc.vertical.gapBefore = gapToBoundSize(VERTICAL_GAP * 3, false)
|
||||
cc.vertical.gapAfter = gapToBoundSize(VERTICAL_GAP * 2, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var isSkippableComponent = true
|
||||
if (component === row.components.first()) {
|
||||
if (row.indented) {
|
||||
cc.horizontal.gapBefore = gapToBoundSize(HORIZONTAL_GAP * 3, true)
|
||||
}
|
||||
|
||||
if (isLabeled) {
|
||||
if (row.labeled) {
|
||||
isSkippableComponent = false
|
||||
}
|
||||
else {
|
||||
cc.skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSkippableComponent) {
|
||||
if (isSplitRequired) {
|
||||
isSplitRequired = false
|
||||
cc.split()
|
||||
}
|
||||
|
||||
// do not add gap if next component is gear action button
|
||||
if (component !== lastComponent && !row.components.get(index + 1).let { it is JLabel && it.icon === AllIcons.General.Gear }) {
|
||||
cc.horizontal.gapAfter = gapToBoundSize(HORIZONTAL_GAP * 2, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= row.rightIndex) {
|
||||
cc.horizontal.gapBefore = BoundSize(null, null, null, true, null)
|
||||
if (isSkippableComponent) {
|
||||
if (isSplitRequired) {
|
||||
isSplitRequired = false
|
||||
cc.split()
|
||||
}
|
||||
|
||||
// do not add gap if next component is gear action button
|
||||
if (component !== lastComponent && !row.components.get(index + 1).let { it is JLabel && it.icon === AllIcons.General.Gear }) {
|
||||
cc.horizontal.gapAfter = gapToBoundSize(HORIZONTAL_GAP * 2, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= row.rightIndex) {
|
||||
cc.horizontal.gapBefore = BoundSize(null, null, null, true, null)
|
||||
}
|
||||
|
||||
container.add(component, cc)
|
||||
}
|
||||
}
|
||||
|
||||
fun processRows(rows: List<MigLayoutRow>, isSubRow: Boolean) {
|
||||
val isLabeled = rows.firstOrNull(MigLayoutRow::labeled) != null
|
||||
var prevRow: MigLayoutRow? = null
|
||||
for (row in rows) {
|
||||
columnIndex = 0
|
||||
|
||||
if (isLabeled) {
|
||||
columnConstraints.grow(0f, columnIndex++)
|
||||
}
|
||||
|
||||
configureComponents(row, prevRow, isSubRow, isLabeled)
|
||||
row._subRows?.let {
|
||||
processRows(it, true)
|
||||
}
|
||||
|
||||
prevRow = row
|
||||
}
|
||||
}
|
||||
|
||||
processRows(rows, false)
|
||||
|
||||
// do not hold components
|
||||
componentConstraints.clear()
|
||||
}
|
||||
@@ -185,161 +216,23 @@ private fun addGrowIfNeed(cc: CC, component: Component) {
|
||||
}
|
||||
}
|
||||
|
||||
private class MigLayoutRow(private val componentConstraints: MutableMap<Component, CC>,
|
||||
override val builder: LayoutBuilderImpl,
|
||||
val labeled: Boolean = false,
|
||||
val noGrid: Boolean = false,
|
||||
private val buttonGroup: ButtonGroup? = null,
|
||||
val separated: Boolean = false,
|
||||
val indented: Boolean = false) : Row() {
|
||||
val components = SmartList<Component>()
|
||||
var rightIndex = Int.MAX_VALUE
|
||||
|
||||
private var _subRows: MutableList<Row>? = null
|
||||
|
||||
override val subRows: List<Row>
|
||||
get() = _subRows ?: emptyList()
|
||||
|
||||
override var enabled: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
for (c in components) {
|
||||
c.isEnabled = value
|
||||
}
|
||||
}
|
||||
|
||||
override var visible: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
for (c in components) {
|
||||
c.isVisible = value
|
||||
}
|
||||
}
|
||||
|
||||
override var subRowsEnabled: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
_subRows?.forEach { it.enabled = value }
|
||||
}
|
||||
|
||||
override var subRowsVisible: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
_subRows?.forEach { it.visible = value }
|
||||
}
|
||||
|
||||
override operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int, growPolicy: GrowPolicy?) {
|
||||
addComponent(this, constraints, gapLeft = gapLeft, growPolicy = growPolicy)
|
||||
}
|
||||
|
||||
private fun addComponent(component: Component, constraints: Array<out CCFlags>, gapLeft: Int, growPolicy: GrowPolicy?) {
|
||||
if (buttonGroup != null && component is JToggleButton) {
|
||||
buttonGroup.add(component)
|
||||
}
|
||||
|
||||
createComponentConstraints(constraints, gapLeft = gapLeft, growPolicy = growPolicy)?.let {
|
||||
componentConstraints.put(component, it)
|
||||
}
|
||||
components.add(component)
|
||||
}
|
||||
|
||||
override fun alignRight() {
|
||||
if (rightIndex != Int.MAX_VALUE) {
|
||||
throw IllegalStateException("right allowed only once")
|
||||
}
|
||||
rightIndex = components.size
|
||||
}
|
||||
|
||||
override fun createRow(label: String?): Row {
|
||||
val row = builder.newRow(label?.let { Label(it) }, indented = true)
|
||||
if (_subRows == null) {
|
||||
_subRows = SmartList()
|
||||
}
|
||||
_subRows!!.add(row)
|
||||
return row
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComponentConstraints(constraints: Array<out CCFlags>? = null,
|
||||
gapLeft: Int = 0,
|
||||
gapAfter: Int = 0,
|
||||
gapTop: Int = 0,
|
||||
gapBottom: Int = 0,
|
||||
split: Int = -1,
|
||||
growPolicy: GrowPolicy?): CC? {
|
||||
@Suppress("LocalVariableName")
|
||||
var _cc = constraints?.create()
|
||||
fun cc(): CC {
|
||||
if (_cc == null) {
|
||||
_cc = CC()
|
||||
}
|
||||
return _cc!!
|
||||
}
|
||||
|
||||
if (gapLeft != 0) {
|
||||
cc().horizontal.gapBefore = gapToBoundSize(gapLeft, true)
|
||||
}
|
||||
if (gapAfter != 0) {
|
||||
cc().horizontal.gapAfter = gapToBoundSize(gapAfter, true)
|
||||
}
|
||||
|
||||
if (gapTop != 0) {
|
||||
cc().vertical.gapBefore = gapToBoundSize(gapTop, false)
|
||||
}
|
||||
if (gapBottom != 0) {
|
||||
cc().vertical.gapAfter = gapToBoundSize(gapBottom, false)
|
||||
}
|
||||
|
||||
if (split != -1) {
|
||||
cc().split = split
|
||||
}
|
||||
|
||||
if (growPolicy == GrowPolicy.SHORT_TEXT) {
|
||||
cc().maxWidth("210")
|
||||
}
|
||||
else if (growPolicy == GrowPolicy.MEDIUM_TEXT) {
|
||||
cc().minWidth("210")
|
||||
cc().maxWidth("350")
|
||||
}
|
||||
|
||||
return _cc
|
||||
}
|
||||
|
||||
private fun gapToBoundSize(value: Int, isHorizontal: Boolean): BoundSize {
|
||||
internal fun gapToBoundSize(value: Int, isHorizontal: Boolean): BoundSize {
|
||||
val unitValue = UnitValue(value.toFloat(), "", isHorizontal, UnitValue.STATIC, null)
|
||||
return BoundSize(unitValue, unitValue, null, false, null)
|
||||
}
|
||||
|
||||
// default values differs to MigLayout - IntelliJ Platform defaults are used
|
||||
// see com.intellij.uiDesigner.core.AbstractLayout.DEFAULT_HGAP and DEFAULT_VGAP (multiplied by 2 to achieve the same look (it seems in terms of MigLayout gap is both left and right space))
|
||||
private fun c(insets: String? = "0", gridGapX: Int = HORIZONTAL_GAP * 2, gridGapY: Int = VERTICAL_GAP): LC {
|
||||
private fun createLayoutConstraints(gridGapX: Int = HORIZONTAL_GAP * 2, gridGapY: Int = VERTICAL_GAP): LC {
|
||||
// no setter for gap, so, create string to parse
|
||||
val lc = LC()
|
||||
lc.gridGapX = gapToBoundSize(gridGapX, true)
|
||||
lc.gridGapY = gapToBoundSize(gridGapY, false)
|
||||
insets?.let {
|
||||
lc.insets(it)
|
||||
}
|
||||
lc.insets = ConstraintParser.parseInsets("0", true)
|
||||
return lc
|
||||
}
|
||||
|
||||
private fun Array<out CCFlags>.create() = if (isEmpty()) null else CC().apply(this)
|
||||
internal fun Array<out CCFlags>.create() = if (isEmpty()) null else CC().apply(this)
|
||||
|
||||
private fun CC.apply(flags: Array<out CCFlags>): CC {
|
||||
for (flag in flags) {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2000-2018 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 com.intellij.ui.layout
|
||||
|
||||
import com.intellij.ui.components.Label
|
||||
import com.intellij.util.SmartList
|
||||
import net.miginfocom.layout.CC
|
||||
import java.awt.Component
|
||||
import javax.swing.ButtonGroup
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JToggleButton
|
||||
|
||||
internal class MigLayoutRow(private val componentConstraints: MutableMap<Component, CC>,
|
||||
override val builder: MigLayoutBuilder,
|
||||
val labeled: Boolean = false,
|
||||
val noGrid: Boolean = false,
|
||||
private val buttonGroup: ButtonGroup? = null,
|
||||
val separated: Boolean = false) : Row() {
|
||||
val components = SmartList<JComponent>()
|
||||
var rightIndex = Int.MAX_VALUE
|
||||
|
||||
internal var _subRows: MutableList<MigLayoutRow>? = null
|
||||
|
||||
override val subRows: List<Row>
|
||||
get() = _subRows ?: emptyList()
|
||||
|
||||
override var enabled: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
for (c in components) {
|
||||
c.isEnabled = value
|
||||
}
|
||||
}
|
||||
|
||||
override var visible: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
for (c in components) {
|
||||
c.isVisible = value
|
||||
}
|
||||
}
|
||||
|
||||
override var subRowsEnabled: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
_subRows?.forEach { it.enabled = value }
|
||||
}
|
||||
|
||||
override var subRowsVisible: Boolean = true
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
_subRows?.forEach { it.visible = value }
|
||||
}
|
||||
|
||||
override operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int, growPolicy: GrowPolicy?) {
|
||||
addComponent(this, constraints, gapLeft = gapLeft, growPolicy = growPolicy)
|
||||
}
|
||||
|
||||
private fun addComponent(component: JComponent, constraints: Array<out CCFlags>, gapLeft: Int, growPolicy: GrowPolicy?) {
|
||||
if (buttonGroup != null && component is JToggleButton) {
|
||||
buttonGroup.add(component)
|
||||
}
|
||||
|
||||
createComponentConstraints(constraints, gapLeft = gapLeft, growPolicy = growPolicy)?.let {
|
||||
componentConstraints.put(component, it)
|
||||
}
|
||||
components.add(component)
|
||||
}
|
||||
|
||||
override fun alignRight() {
|
||||
if (rightIndex != Int.MAX_VALUE) {
|
||||
throw IllegalStateException("right allowed only once")
|
||||
}
|
||||
rightIndex = components.size
|
||||
}
|
||||
|
||||
override fun createRow(label: String?): Row {
|
||||
if (_subRows == null) {
|
||||
_subRows = SmartList()
|
||||
}
|
||||
return builder.newRow(rowList = _subRows!!, label = label?.let { Label(it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComponentConstraints(constraints: Array<out CCFlags>? = null,
|
||||
gapLeft: Int = 0,
|
||||
gapAfter: Int = 0,
|
||||
gapTop: Int = 0,
|
||||
gapBottom: Int = 0,
|
||||
split: Int = -1,
|
||||
growPolicy: GrowPolicy?): CC? {
|
||||
@Suppress("LocalVariableName")
|
||||
var _cc = constraints?.create()
|
||||
fun cc(): CC {
|
||||
if (_cc == null) {
|
||||
_cc = CC()
|
||||
}
|
||||
return _cc!!
|
||||
}
|
||||
|
||||
if (gapLeft != 0) {
|
||||
cc().horizontal.gapBefore = gapToBoundSize(gapLeft, true)
|
||||
}
|
||||
if (gapAfter != 0) {
|
||||
cc().horizontal.gapAfter = gapToBoundSize(gapAfter, true)
|
||||
}
|
||||
|
||||
if (gapTop != 0) {
|
||||
cc().vertical.gapBefore = gapToBoundSize(gapTop, false)
|
||||
}
|
||||
if (gapBottom != 0) {
|
||||
cc().vertical.gapAfter = gapToBoundSize(gapBottom, false)
|
||||
}
|
||||
|
||||
if (split != -1) {
|
||||
cc().split = split
|
||||
}
|
||||
|
||||
if (growPolicy == GrowPolicy.SHORT_TEXT) {
|
||||
cc().maxWidth("210")
|
||||
}
|
||||
else if (growPolicy == GrowPolicy.MEDIUM_TEXT) {
|
||||
cc().minWidth("210")
|
||||
cc().maxWidth("350")
|
||||
}
|
||||
|
||||
return _cc
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import javax.swing.JButton
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
|
||||
internal const val COMPONENT_TAG_HINT = "kotlin.dsl.hint.component"
|
||||
|
||||
abstract class Row {
|
||||
abstract var enabled: Boolean
|
||||
|
||||
@@ -41,8 +43,10 @@ abstract class Row {
|
||||
|
||||
protected abstract val builder: LayoutBuilderImpl
|
||||
|
||||
fun label(text: String, gapLeft: Int = 0, style: ComponentStyle? = null, fontColor: FontColor? = null, bold: Boolean = false) {
|
||||
Label(text, style, fontColor, bold)(gapLeft = gapLeft)
|
||||
fun label(text: String, gapLeft: Int = 0, style: ComponentStyle? = null, fontColor: FontColor? = null, bold: Boolean = false): JLabel {
|
||||
val label = Label(text, style, fontColor, bold)
|
||||
label(gapLeft = gapLeft)
|
||||
return label
|
||||
}
|
||||
|
||||
fun link(text: String, style: ComponentStyle? = null, action: () -> Unit) {
|
||||
@@ -91,7 +95,8 @@ abstract class Row {
|
||||
}
|
||||
|
||||
fun hint(text: String) {
|
||||
label(text, style = ComponentStyle.SMALL, fontColor = FontColor.BRIGHTER, gapLeft = 3 * HORIZONTAL_GAP)
|
||||
val component = label(text, style = ComponentStyle.SMALL, fontColor = FontColor.BRIGHTER)
|
||||
component.putClientProperty(COMPONENT_TAG_HINT, true)
|
||||
}
|
||||
|
||||
fun panel(title: String, wrappedComponent: Component, vararg constraints: CCFlags) {
|
||||
@@ -116,7 +121,6 @@ abstract class Row {
|
||||
return row
|
||||
}
|
||||
|
||||
|
||||
inline fun row(init: Row.() -> Unit): Row {
|
||||
val row = createRow(null)
|
||||
row.init()
|
||||
|
||||
@@ -14,7 +14,7 @@ internal const val VERTICAL_GAP = 5
|
||||
fun createLayoutBuilder() = LayoutBuilder(MigLayoutBuilder())
|
||||
|
||||
interface LayoutBuilderImpl {
|
||||
fun newRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false, indented: Boolean = false): Row
|
||||
fun newRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false): Row
|
||||
|
||||
fun build(container: Container, layoutConstraints: Array<out LCFlags>)
|
||||
|
||||
|
||||
@@ -36,5 +36,6 @@
|
||||
<orderEntry type="library" scope="TEST" name="fest-swing" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="assertj-swing" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="jackson" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="snakeyaml" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,26 @@
|
||||
rowConstraints: ''
|
||||
columnConstraints:
|
||||
count: 2.0
|
||||
constraints:
|
||||
- {}
|
||||
- {grow: 100.0}
|
||||
componentConstrains:
|
||||
'JRadioButton #0': {spanX: 2097051.0, wrap: true}
|
||||
'JLabel #1':
|
||||
horizontal:
|
||||
gapBefore:
|
||||
min: {value: 30.0}
|
||||
preferred: {value: 30.0}
|
||||
'JTextField #2': {split: 2097051.0}
|
||||
'JLabel #3': {wrap: true}
|
||||
'JLabel #4':
|
||||
horizontal:
|
||||
gapBefore:
|
||||
min: {value: 30.0}
|
||||
preferred: {value: 30.0}
|
||||
'JBPasswordField #5':
|
||||
wrap: true
|
||||
horizontal:
|
||||
size:
|
||||
max: {value: 210.0}
|
||||
'JBLabel #6': {skip: 1.0, spanX: 2097051.0, wrap: true}
|
||||
@@ -0,0 +1,11 @@
|
||||
rowConstraints: ''
|
||||
columnConstraints:
|
||||
count: 2.0
|
||||
constraints:
|
||||
- {}
|
||||
- {grow: 100.0}
|
||||
componentConstrains:
|
||||
'JLabel #0': {}
|
||||
'JCheckBox #1': {wrap: true}
|
||||
'JLabel #2': {}
|
||||
'JTextField #3': {wrap: true}
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2000-2018 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 com.intellij.ui.layout
|
||||
|
||||
import com.intellij.ui.components.CheckBox
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.ui.components.JBPasswordField
|
||||
import com.intellij.ui.components.RadioButton
|
||||
import net.miginfocom.layout.LayoutUtil
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.Dimension
|
||||
import javax.swing.JFrame
|
||||
@@ -13,21 +18,52 @@ object MigLayoutTestApp {
|
||||
fun main(args: Array<String>) {
|
||||
LayoutUtil.setGlobalDebugMillis(1000)
|
||||
|
||||
val androidModuleNameComponent = JTextField("input")
|
||||
val androidCheckBox = CheckBox("Android module name:")
|
||||
val panel = panel {
|
||||
row("Create Android module") { androidCheckBox() }
|
||||
row("Android module name:") { androidModuleNameComponent() }
|
||||
row { label("Save passwords:") }
|
||||
|
||||
buttonGroup {
|
||||
row {
|
||||
RadioButton("In KeePass")()
|
||||
row("Database:") {
|
||||
JTextField()()
|
||||
gearButton(
|
||||
object : AnAction("Clear") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
}
|
||||
},
|
||||
object : AnAction("Import") {
|
||||
override fun actionPerformed(event: AnActionEvent) {
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
row("Master Password:") {
|
||||
JBPasswordField()(growPolicy = GrowPolicy.SHORT_TEXT)
|
||||
}
|
||||
row {
|
||||
hint("Stored using weak encryption.")
|
||||
}
|
||||
}
|
||||
|
||||
row {
|
||||
RadioButton("Do not save, forget passwords after restart")()
|
||||
}
|
||||
row {
|
||||
hint("Existing KeePass file will be removed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val frame = JFrame()
|
||||
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||
frame.contentPane = panel
|
||||
frame.contentPane.add(panel, BorderLayout.CENTER)
|
||||
frame.contentPane.background = Color.WHITE
|
||||
frame.background = Color.WHITE
|
||||
frame.pack()
|
||||
frame.setLocationRelativeTo(null)
|
||||
frame.minimumSize = Dimension(512, 256)
|
||||
frame.isVisible = true
|
||||
|
||||
System.out.println(configurationToJson(panel, panel.layout as MigLayout, false))
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,32 @@
|
||||
// Copyright 2000-2018 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 com.intellij.ui.layout
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeed
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.ui.components.CheckBox
|
||||
import com.intellij.ui.components.JBPasswordField
|
||||
import com.intellij.ui.components.RadioButton
|
||||
import com.intellij.util.io.exists
|
||||
import com.intellij.util.io.outputStream
|
||||
import com.intellij.util.io.sanitizeFileName
|
||||
import com.intellij.util.io.write
|
||||
import net.miginfocom.layout.Grid
|
||||
import net.miginfocom.layout.LayoutUtil
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import org.assertj.core.data.Offset
|
||||
import org.assertj.swing.assertions.Assertions.assertThat
|
||||
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager
|
||||
import org.assertj.swing.edt.GuiActionRunner
|
||||
import org.assertj.swing.fixture.FrameFixture
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.*
|
||||
import org.junit.After
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestName
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.JFrame
|
||||
@@ -48,33 +51,6 @@ class UiDslTest {
|
||||
@JvmField
|
||||
val testName = TestName()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val frame = GuiActionRunner.execute(Callable {
|
||||
LayoutUtil.setGlobalDebugMillis(1000)
|
||||
|
||||
val androidModuleNameComponent = JTextField("input")
|
||||
val androidCheckBox = CheckBox("Android module name:")
|
||||
val panel = panel {
|
||||
row("Create Android module") { androidCheckBox() }
|
||||
row("Android module name:") { androidModuleNameComponent() }
|
||||
}
|
||||
|
||||
panel.background = Color.WHITE
|
||||
panel.name = "test"
|
||||
|
||||
val frame = JFrame("wrapper")
|
||||
frame.isUndecorated = true
|
||||
frame.contentPane.background = Color.WHITE
|
||||
frame.background = Color.WHITE
|
||||
frame.contentPane.add(panel, BorderLayout.CENTER)
|
||||
frame.minimumSize = Dimension(512, 256)
|
||||
frame
|
||||
})
|
||||
window = FrameFixture(frame)
|
||||
window.show()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
window.cleanUp()
|
||||
@@ -90,171 +66,102 @@ class UiDslTest {
|
||||
return window.target()
|
||||
}
|
||||
|
||||
private fun assertLayout(@Language("JSON") jsonData: String) {
|
||||
val component = window.panel("test").target()
|
||||
assertThat(configurationToJson(component, (component as JPanel).layout as MigLayout, false)).isEqualTo(jsonData.trimIndent())
|
||||
@Test
|
||||
fun `align fields in the nested grid`() {
|
||||
doTest(panel {
|
||||
buttonGroup {
|
||||
row {
|
||||
RadioButton("In KeePass")()
|
||||
row("Database:") {
|
||||
JTextField()()
|
||||
gearButton()
|
||||
}
|
||||
row("Master Password:") {
|
||||
JBPasswordField()(growPolicy = GrowPolicy.SHORT_TEXT)
|
||||
}
|
||||
row {
|
||||
hint("Stored using weak encryption.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "[0, 0, 512, 23], [0, 28, 139, 26], [159, 28, 353, 26], [159, 28, 353, 26], [0, 59, 139, 26], [159, 59, 353, 26], [159, 90, 353, 14]")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val component = window.panel("test").target()
|
||||
val layout = (component as JPanel).layout as MigLayout
|
||||
|
||||
assertLayout("""
|
||||
{
|
||||
"rowConstraints": "",
|
||||
"columnConstraints": {
|
||||
"count": 3.0,
|
||||
"constraints": [
|
||||
{},
|
||||
{
|
||||
"grow": 100.0
|
||||
},
|
||||
{
|
||||
"grow": 100.0
|
||||
}
|
||||
]
|
||||
},
|
||||
"componentConstrains": {
|
||||
"JLabel #0": {},
|
||||
"JCheckBox #1": {
|
||||
"wrap": true
|
||||
},
|
||||
"JLabel #2": {},
|
||||
"JTextField #3": {
|
||||
"wrap": true
|
||||
}
|
||||
fun `align fields`() {
|
||||
doTest(panel {
|
||||
row("Create Android module") { CheckBox("Android module name:")() }
|
||||
row("Android module name:") { JTextField("input")() }
|
||||
}, "[0, 0, 145, 23], [165, 0, 347, 23], [0, 28, 145, 26], [165, 28, 347, 26]")
|
||||
}
|
||||
}""")
|
||||
|
||||
val gridField = MigLayout::class.java.getDeclaredField("grid")
|
||||
gridField.isAccessible = true
|
||||
val grid = gridField.get(layout) as Grid
|
||||
val rectangles = MigLayoutTestUtil.getRectangles(grid)
|
||||
private fun doTest(panel: JPanel, expectedLocations: String) {
|
||||
val frame = GuiActionRunner.execute(Callable {
|
||||
LayoutUtil.setGlobalDebugMillis(1000)
|
||||
|
||||
panel.background = Color.WHITE
|
||||
panel.name = "test"
|
||||
|
||||
val frame = JFrame("wrapper")
|
||||
frame.isUndecorated = true
|
||||
frame.contentPane.background = Color.WHITE
|
||||
frame.background = Color.WHITE
|
||||
frame.contentPane.add(panel, BorderLayout.CENTER)
|
||||
frame.minimumSize = Dimension(512, 256)
|
||||
frame
|
||||
})
|
||||
window = FrameFixture(frame)
|
||||
window.show()
|
||||
|
||||
val component = window.panel("test").target() as JPanel
|
||||
val layout = component.layout as MigLayout
|
||||
val imageName = sanitizeFileName(testName.methodName)
|
||||
val actualLayoutJson = configurationToJson(component, component.layout as MigLayout, false)
|
||||
try {
|
||||
assertThat(rectangles.joinToString(",") { "[${it.joinToString(",")}]" }).isEqualTo(
|
||||
"[0,0,145,23],[165,0,251,23],[0,28,145,26],[165,28,251,26]")
|
||||
val expectedLayoutDataFile = Paths.get(PlatformTestUtil.getPlatformTestDataPath(), "ui", "layout", "$imageName.yml")
|
||||
if (expectedLayoutDataFile.exists()) {
|
||||
com.intellij.testFramework.assertions.Assertions.assertThat(actualLayoutJson).isEqualTo(expectedLayoutDataFile)
|
||||
}
|
||||
else {
|
||||
expectedLayoutDataFile.write(actualLayoutJson)
|
||||
}
|
||||
|
||||
val gridField = MigLayout::class.java.getDeclaredField("grid")
|
||||
gridField.isAccessible = true
|
||||
val grid = gridField.get(layout) as Grid
|
||||
val rectangles = MigLayoutTestUtil.getRectangles(grid)
|
||||
assertThat(rectangles.joinToString(", ") { "[${it.joinToString(", ")}]" }).isEqualTo(expectedLocations)
|
||||
|
||||
if (imageDir.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val imagePath = Paths.get(imageDir, "${sanitizeFileName(testName.methodName)}.png")
|
||||
val imagePath = Paths.get(imageDir, "$imageName.png")
|
||||
if (!imagePath.exists()) {
|
||||
System.out.println("Write a new snapshot image ${imagePath.fileName}")
|
||||
saveImage(imagePath)
|
||||
return
|
||||
}
|
||||
|
||||
assertThat(componentToImage(getContentPane())).isEqualTo(ImageIO.read(imagePath.toFile()))
|
||||
assertThat(componentToImage(getContentPane())).isEqualTo(ImageIO.read(imagePath.toFile()), Offset.offset(32))
|
||||
}
|
||||
catch (e: AssertionError) {
|
||||
if (!imageDir.isNullOrEmpty()) {
|
||||
saveImage(Paths.get(imageDir, "${sanitizeFileName(testName.methodName)}-NEW.png"))
|
||||
Paths.get(imageDir, "$imageName-NEW.yml").write(actualLayoutJson)
|
||||
saveImage(Paths.get(imageDir, "$imageName-NEW.png"))
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun configurationToJson(component: JPanel, layout: MigLayout, isIncludeLayoutConstraints: Boolean): String {
|
||||
val objectMapper = ObjectMapper()
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT)
|
||||
|
||||
val componentConstrains = LinkedHashMap<String, Any>()
|
||||
for ((index, c) in component.components.withIndex()) {
|
||||
componentConstrains.put("${c.javaClass.simpleName} #${index}", layout.getComponentConstraints(c))
|
||||
}
|
||||
|
||||
val json = objectMapper
|
||||
.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(linkedMapOf(
|
||||
"layoutConstraints" to if (isIncludeLayoutConstraints) layout.layoutConstraints else null,
|
||||
"rowConstraints" to layout.rowConstraints,
|
||||
"columnConstraints" to layout.columnConstraints,
|
||||
"componentConstrains" to componentConstrains
|
||||
))
|
||||
// *** *** jackson has ugly API and not clear how to write custom filter, so, GSON is used
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val map = gson.fromJson(StringReader(json), MutableMap::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (cc in (map.get("componentConstrains") as MutableMap<String, MutableMap<String, Any?>>).values) {
|
||||
removeDefaultCc(cc)
|
||||
cc.remove("animSpec")
|
||||
|
||||
for (name in arrayOf("horizontal", "vertical")) {
|
||||
val p = cc.get(name) as MutableMap<String, Any?>? ?: continue
|
||||
val size = p.get("size") as MutableMap<*, *>?
|
||||
if (size != null && size.get("unset") == true) {
|
||||
size.remove("unset")
|
||||
if (size.isEmpty()) {
|
||||
p.remove("size")
|
||||
}
|
||||
}
|
||||
|
||||
removeDefaultCc(p)
|
||||
if (p.isEmpty()) {
|
||||
cc.remove(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val columnConstraints = map.get("columnConstraints") as MutableMap<String, Any>
|
||||
val acList = columnConstraints.remove("constaints")!! as List<MutableMap<String, Any>>
|
||||
columnConstraints.put("constraints", acList)
|
||||
for (ac in acList) {
|
||||
val size = ac.get("size") as MutableMap<*, *>?
|
||||
if (size != null && size.get("unset") == true) {
|
||||
size.remove("unset")
|
||||
if (size.isEmpty()) {
|
||||
ac.remove("size")
|
||||
}
|
||||
|
||||
if (ac.get("shrinkPriority") == 100.0) {
|
||||
ac.remove("shrinkPriority")
|
||||
}
|
||||
if (ac.get("shrink") == 100.0) {
|
||||
ac.remove("shrink")
|
||||
}
|
||||
if (ac.get("growPriority") == 100.0) {
|
||||
ac.remove("growPriority")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gson.toJson(map)
|
||||
}
|
||||
|
||||
private fun removeDefaultCc(cc: MutableMap<String, Any?>) {
|
||||
for ((name, value) in DEFAULT_CC) {
|
||||
if (cc.get(name) == value) {
|
||||
cc.remove(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun componentToImage(component: Component): BufferedImage {
|
||||
// we don't need retina image
|
||||
val image = BufferedImage(component.width, component.height, BufferedImage.TYPE_BYTE_GRAY)
|
||||
val g = image.graphics
|
||||
component.paint(g)
|
||||
g.dispose()
|
||||
return image
|
||||
}
|
||||
|
||||
private val DEFAULT_CC = mapOf(
|
||||
"dockSide" to -1.0,
|
||||
"split" to 1.0,
|
||||
"spanX" to 1.0,
|
||||
"spanY" to 1.0,
|
||||
"cellX" to -1.0,
|
||||
"cellY" to -1.0,
|
||||
"hideMode" to -1.0,
|
||||
"growPriority" to 100.0,
|
||||
"shrinkPriority" to 100.0,
|
||||
"shrink" to 100.0,
|
||||
"grow" to 100.0,
|
||||
"boundsInGrid" to true,
|
||||
"" to ""
|
||||
)
|
||||
private fun componentToImage(component: Component, type: Int = BufferedImage.TYPE_BYTE_GRAY): BufferedImage {
|
||||
return invokeAndWaitIfNeed {
|
||||
// we don't need retina image
|
||||
val image = BufferedImage(component.width, component.height, type)
|
||||
val g = image.graphics
|
||||
component.paint(g)
|
||||
g.dispose()
|
||||
image
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2000-2018 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 com.intellij.ui.layout
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.intellij.testFramework.assertions.dumpData
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
import javax.swing.JPanel
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun configurationToJson(component: JPanel, layout: MigLayout, isIncludeLayoutConstraints: Boolean): String {
|
||||
val objectMapper = ObjectMapper()
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT)
|
||||
|
||||
val componentConstrains = LinkedHashMap<String, Any>()
|
||||
for ((index, c) in component.components.withIndex()) {
|
||||
componentConstrains.put("${c.javaClass.simpleName} #${index}", layout.getComponentConstraints(c))
|
||||
}
|
||||
|
||||
val json = objectMapper
|
||||
.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(linkedMapOf(
|
||||
"layoutConstraints" to if (isIncludeLayoutConstraints) layout.layoutConstraints else null,
|
||||
"rowConstraints" to layout.rowConstraints,
|
||||
"columnConstraints" to layout.columnConstraints,
|
||||
"componentConstrains" to componentConstrains
|
||||
))
|
||||
// *** *** jackson has ugly API and not clear how to write custom filter, so, GSON is used
|
||||
val gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.create()
|
||||
val map = gson.fromJson(StringReader(json), MutableMap::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (cc in (map.get("componentConstrains") as MutableMap<String, MutableMap<String, Any?>>).values) {
|
||||
removeDefaultCc(cc)
|
||||
cc.remove("animSpec")
|
||||
|
||||
for (axisName in arrayOf("horizontal", "vertical")) {
|
||||
val p = cc.get(axisName) as MutableMap<String, Any?>? ?: continue
|
||||
val size = p.get("size") as MutableMap<*, *>?
|
||||
if (size != null && size.get("unset") == true) {
|
||||
size.remove("unset")
|
||||
if (size.isEmpty()) {
|
||||
p.remove("size")
|
||||
}
|
||||
}
|
||||
|
||||
for (name2 in arrayOf("size", "gapBefore", "gapAfter")) {
|
||||
val v = p.get(name2) as? MutableMap<*, *>? ?: continue
|
||||
for (name in arrayOf("min", "max", "preferred")) {
|
||||
val p2 = v.get(name) as? MutableMap<*, *> ?: continue
|
||||
if (p2.get("unit") == 1.0) {
|
||||
p2.remove("unit")
|
||||
}
|
||||
if (p2.get("operation") == 100.0) {
|
||||
p2.remove("operation")
|
||||
}
|
||||
if (p2.get(axisName) == true) {
|
||||
p2.remove(axisName)
|
||||
}
|
||||
|
||||
if (p2.isEmpty()) {
|
||||
v.remove(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeDefaultCc(p)
|
||||
if (p.isEmpty()) {
|
||||
cc.remove(axisName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(map.get("columnConstraints") as? MutableMap<String, Any>)?.let { cleanupColumnConstraints(it) }
|
||||
return dumpData(map)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun cleanupColumnConstraints(columnConstraints: MutableMap<String, Any>) {
|
||||
val acList = columnConstraints.remove("constaints")!! as List<MutableMap<String, Any>>
|
||||
columnConstraints.put("constraints", acList)
|
||||
for (ac in acList) {
|
||||
val size = ac.get("size") as MutableMap<*, *>?
|
||||
if (size != null && size.get("unset") == true) {
|
||||
size.remove("unset")
|
||||
if (size.isEmpty()) {
|
||||
ac.remove("size")
|
||||
}
|
||||
|
||||
if (ac.get("shrinkPriority") == 100.0) {
|
||||
ac.remove("shrinkPriority")
|
||||
}
|
||||
if (ac.get("shrink") == 100.0) {
|
||||
ac.remove("shrink")
|
||||
}
|
||||
if (ac.get("growPriority") == 100.0) {
|
||||
ac.remove("growPriority")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeDefaultCc(cc: MutableMap<String, Any?>) {
|
||||
for ((name, value) in DEFAULT_CC) {
|
||||
if (cc.get(name) == value) {
|
||||
cc.remove(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_CC = mapOf(
|
||||
"dockSide" to -1.0,
|
||||
"split" to 1.0,
|
||||
"spanX" to 1.0,
|
||||
"spanY" to 1.0,
|
||||
"cellX" to -1.0,
|
||||
"cellY" to -1.0,
|
||||
"hideMode" to -1.0,
|
||||
"growPriority" to 100.0,
|
||||
"shrinkPriority" to 100.0,
|
||||
"shrink" to 100.0,
|
||||
"grow" to 100.0,
|
||||
"boundsInGrid" to true,
|
||||
"" to ""
|
||||
)
|
||||
+4
-21
@@ -1,18 +1,4 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Copyright 2000-2018 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 com.intellij.testFramework.assertions
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtilRt
|
||||
@@ -25,7 +11,6 @@ import org.yaml.snakeyaml.nodes.Node
|
||||
import org.yaml.snakeyaml.nodes.Tag
|
||||
import org.yaml.snakeyaml.representer.Represent
|
||||
import org.yaml.snakeyaml.representer.Representer
|
||||
import java.io.StringWriter
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -37,14 +22,12 @@ class ListAssertEx<ELEMENT>(actual: List<ELEMENT>) : ListAssert<ELEMENT>(actual)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpData(data: Any): String {
|
||||
fun dumpData(data: Any): String {
|
||||
val dumperOptions = DumperOptions()
|
||||
dumperOptions.isAllowReadOnlyProperties = true
|
||||
dumperOptions.lineBreak = DumperOptions.LineBreak.UNIX
|
||||
val yaml = Yaml(DumpRepresenter(), dumperOptions)
|
||||
val writer = StringWriter()
|
||||
yaml.dump(data, writer)
|
||||
return writer.toString()
|
||||
return yaml.dump(data)
|
||||
}
|
||||
|
||||
private class DumpRepresenter : Representer() {
|
||||
@@ -59,7 +42,7 @@ private class DumpRepresenter : Representer() {
|
||||
|
||||
internal fun compareFileContent(actual: Any, snapshotFile: Path) {
|
||||
val expectedContent = StringUtilRt.convertLineSeparators(snapshotFile.readText())
|
||||
val actualContent = dumpData(actual)
|
||||
val actualContent = if (actual is String) actual else dumpData(actual)
|
||||
if (actualContent != expectedContent) {
|
||||
throw FileComparisonFailure(null, expectedContent, actualContent, snapshotFile.toString())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user