mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-161462: master password
This commit is contained in:
@@ -41,7 +41,7 @@ internal class KeePassCredentialStore(keyToValue: Map<CredentialAttributes, Cred
|
||||
baseDirectory: Path = Paths.get(PathManager.getConfigPath()),
|
||||
var memoryOnly: Boolean = false,
|
||||
private val dbFile: Path = baseDirectory.resolve(DB_FILE_NAME),
|
||||
existingMasterPassword: String? = null) : PasswordStorage, CredentialStore {
|
||||
existingMasterPassword: ByteArray? = null) : PasswordStorage, CredentialStore {
|
||||
private val db: KeePassDatabase
|
||||
|
||||
private val masterKeyStorage = MasterKeyFileStorage(baseDirectory)
|
||||
@@ -52,7 +52,7 @@ internal class KeePassCredentialStore(keyToValue: Map<CredentialAttributes, Cred
|
||||
if (keyToValue == null) {
|
||||
needToSave = AtomicBoolean(false)
|
||||
|
||||
val masterPassword = existingMasterPassword?.toByteArray() ?: masterKeyStorage.get()
|
||||
val masterPassword = existingMasterPassword ?: masterKeyStorage.get()
|
||||
if (masterPassword == null) {
|
||||
LOG.catchAndLog {
|
||||
if (dbFile.exists()) {
|
||||
@@ -66,7 +66,7 @@ internal class KeePassCredentialStore(keyToValue: Map<CredentialAttributes, Cred
|
||||
else {
|
||||
db = loadKdbx(dbFile, KdbxPassword(masterPassword)) ?: KeePassDatabase()
|
||||
if (existingMasterPassword != null) {
|
||||
masterKeyStorage.set(existingMasterPassword.toByteArray())
|
||||
masterKeyStorage.set(existingMasterPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,12 +178,11 @@ internal class KeePassCredentialStore(keyToValue: Map<CredentialAttributes, Cred
|
||||
}
|
||||
}
|
||||
|
||||
fun setMasterPassword(password: String) {
|
||||
fun setMasterPassword(masterPassword: ByteArray) {
|
||||
LOG.assertTrue(!memoryOnly)
|
||||
|
||||
val masterKey = password.toByteArray()
|
||||
masterKeyStorage.set(masterKey)
|
||||
dbFile.writeSafe { db.save(KdbxPassword(masterKey), it) }
|
||||
masterKeyStorage.set(masterPassword)
|
||||
dbFile.writeSafe { db.save(KdbxPassword(masterPassword), it) }
|
||||
dbFile.setOwnerPermissions()
|
||||
}
|
||||
}
|
||||
@@ -192,7 +191,7 @@ internal fun copyFileDatabase(path: Path, masterPassword: String, baseDirectory:
|
||||
val dbFile = baseDirectory.resolve(DB_FILE_NAME)
|
||||
Files.copy(path, dbFile, StandardCopyOption.REPLACE_EXISTING)
|
||||
dbFile.setOwnerPermissions()
|
||||
return KeePassCredentialStore(baseDirectory = baseDirectory, dbFile = dbFile, existingMasterPassword = masterPassword)
|
||||
return KeePassCredentialStore(baseDirectory = baseDirectory, dbFile = dbFile, existingMasterPassword = masterPassword.toByteArray())
|
||||
}
|
||||
|
||||
internal fun copyTo(from: Map<CredentialAttributes, Credentials>, store: PasswordStorage) {
|
||||
|
||||
@@ -15,16 +15,20 @@
|
||||
*/
|
||||
package com.intellij.credentialStore
|
||||
|
||||
import com.intellij.credentialStore.PasswordSafeSettings.ProviderType
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe
|
||||
import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl
|
||||
import com.intellij.ide.passwordSafe.impl.createPersistentCredentialStore
|
||||
import com.intellij.openapi.options.ConfigurableBase
|
||||
import com.intellij.openapi.options.ConfigurableUi
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.ui.components.JBPasswordField
|
||||
import com.intellij.ui.components.RadioButton
|
||||
import com.intellij.ui.layout.*
|
||||
import com.intellij.util.text.nullize
|
||||
import gnu.trove.THashMap
|
||||
import java.awt.Component
|
||||
import javax.swing.JPanel
|
||||
|
||||
class PasswordSafeConfigurable(private val settings: PasswordSafeSettings) : ConfigurableBase<PasswordSafeConfigurableUi, PasswordSafeSettings>("application.passwordSafe", "Passwords", "reference.ide.settings.password.safe") {
|
||||
override fun getSettings() = settings
|
||||
@@ -33,56 +37,139 @@ class PasswordSafeConfigurable(private val settings: PasswordSafeSettings) : Con
|
||||
}
|
||||
|
||||
class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings> {
|
||||
private val saveOnDisk = RadioButton("Save on &disk")
|
||||
private val rememberPasswordsUntilClosing = RadioButton("Remember passwords &until the application is closed")
|
||||
private val inKeychain = RadioButton("In Native Keychain")
|
||||
|
||||
private val inKeePass = RadioButton("In KeePass")
|
||||
private val keePassMasterPassword = JBPasswordField()
|
||||
|
||||
private val rememberPasswordsUntilClosing = RadioButton("Do not save, forget passwords after restart")
|
||||
|
||||
private val modeToRow = THashMap<ProviderType, Row>()
|
||||
|
||||
override fun reset(settings: PasswordSafeSettings) {
|
||||
when (settings.providerType) {
|
||||
ProviderType.MEMORY_ONLY -> rememberPasswordsUntilClosing.isSelected = true
|
||||
ProviderType.MASTER_PASSWORD -> saveOnDisk.isSelected = true
|
||||
ProviderType.KEYCHAIN -> inKeychain.isSelected = true
|
||||
ProviderType.KEEPASS -> inKeePass.isSelected = true
|
||||
else -> throw IllegalStateException("Unknown provider type: ${settings.providerType}")
|
||||
}
|
||||
|
||||
updateEnabledState()
|
||||
}
|
||||
|
||||
override fun isModified(settings: PasswordSafeSettings) = getProviderType() != settings.providerType
|
||||
override fun isModified(settings: PasswordSafeSettings): Boolean {
|
||||
if (getProviderType() != settings.providerType) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (getProviderType() == ProviderType.KEEPASS && String(keePassMasterPassword.password).nullize(true) != null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun apply(settings: PasswordSafeSettings) {
|
||||
settings.providerType = getProviderType()
|
||||
val providerType = getProviderType()
|
||||
val passwordSafe = PasswordSafe.getInstance() as PasswordSafeImpl
|
||||
var provider = passwordSafe.currentProvider
|
||||
|
||||
val masterPassword = String(keePassMasterPassword.password).nullize(true)?.toByteArray()
|
||||
|
||||
if (settings.providerType != providerType) {
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
when (providerType) {
|
||||
ProviderType.MEMORY_ONLY -> {
|
||||
if (provider is KeePassCredentialStore) {
|
||||
provider.memoryOnly = true
|
||||
provider.deleteFileStorage()
|
||||
}
|
||||
else {
|
||||
provider = KeePassCredentialStore(memoryOnly = true)
|
||||
}
|
||||
}
|
||||
|
||||
ProviderType.KEYCHAIN -> {
|
||||
provider = createPersistentCredentialStore(provider as? KeePassCredentialStore)
|
||||
}
|
||||
|
||||
ProviderType.KEEPASS -> {
|
||||
provider = KeePassCredentialStore(memoryOnly = true, existingMasterPassword = masterPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (providerType == ProviderType.KEEPASS) {
|
||||
if (provider === passwordSafe.currentProvider && masterPassword != null) {
|
||||
// so, provider is the same and we must change master password for existing database file
|
||||
(provider as KeePassCredentialStore).setMasterPassword(masterPassword)
|
||||
}
|
||||
}
|
||||
|
||||
settings.providerType = providerType
|
||||
}
|
||||
|
||||
override fun getComponent() = panel {
|
||||
val passwordSafe = PasswordSafe.getInstance() as PasswordSafeImpl
|
||||
fun updateEnabledState() {
|
||||
modeToRow[ProviderType.KEEPASS]?.enabled = getProviderType() == ProviderType.KEEPASS
|
||||
}
|
||||
|
||||
buttonGroup {
|
||||
row {
|
||||
saveOnDisk()
|
||||
if (!passwordSafe.isNativeCredentialStoreUsed) {
|
||||
button("Set Master Password") { event ->
|
||||
Messages.showInputDialog(event.source as Component, "Master Password:", "Set Master Password", null)?.trim().nullize()?.let {
|
||||
passwordSafe.setFileDatabaseMasterPassword(it)
|
||||
}
|
||||
override fun getComponent(): JPanel {
|
||||
val passwordSafe = PasswordSafe.getInstance() as PasswordSafeImpl
|
||||
val currentProvider = passwordSafe.currentProvider
|
||||
|
||||
keePassMasterPassword.setPasswordIsStored(true)
|
||||
|
||||
val panel = panel {
|
||||
row { label("Save passwords:") }
|
||||
|
||||
buttonGroup({ updateEnabledState() }) {
|
||||
if (SystemInfo.isLinux || isMacOsCredentialStoreSupported) {
|
||||
row {
|
||||
inKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
row {
|
||||
inKeePass()
|
||||
|
||||
modeToRow[ProviderType.KEEPASS] = row("Master Password:") {
|
||||
keePassMasterPassword(growPolicy = GrowPolicy.SHORT_TEXT)
|
||||
}
|
||||
}
|
||||
|
||||
row {
|
||||
rememberPasswordsUntilClosing()
|
||||
}
|
||||
if (currentProvider is KeePassCredentialStore && !currentProvider.memoryOnly) {
|
||||
row { hint("Existing KeePass file will be removed.") }
|
||||
}
|
||||
}
|
||||
|
||||
if (!passwordSafe.isNativeCredentialStoreUsed) {
|
||||
row(separated = true) {
|
||||
button("Clear Passwords") { event ->
|
||||
passwordSafe.clearPasswords()
|
||||
Messages.showInfoMessage(event.source as Component, "Passwords were cleared", "Clear Passwords")
|
||||
}
|
||||
}
|
||||
}
|
||||
row { rememberPasswordsUntilClosing() }
|
||||
}
|
||||
|
||||
if (!passwordSafe.isNativeCredentialStoreUsed) {
|
||||
row(separated = true) {
|
||||
button("Clear Passwords") { event ->
|
||||
passwordSafe.clearPasswords()
|
||||
Messages.showInfoMessage(event.source as Component, "Passwords were cleared", "Clear Passwords")
|
||||
}
|
||||
}
|
||||
}
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun getProviderType(): ProviderType {
|
||||
if (rememberPasswordsUntilClosing.isSelected) {
|
||||
return ProviderType.MEMORY_ONLY
|
||||
}
|
||||
else {
|
||||
return ProviderType.MASTER_PASSWORD
|
||||
return when {
|
||||
rememberPasswordsUntilClosing.isSelected -> ProviderType.MEMORY_ONLY
|
||||
inKeePass.isSelected -> ProviderType.KEEPASS
|
||||
else -> ProviderType.KEYCHAIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class ProviderType {
|
||||
MEMORY_ONLY, KEYCHAIN, KEEPASS,
|
||||
|
||||
// unused, but we cannot remove it because enum value maybe stored in the config and we must correctly deserialize it
|
||||
@Deprecated("")
|
||||
DO_NOT_STORE
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.intellij.ide.passwordSafe.impl
|
||||
|
||||
import com.intellij.credentialStore.*
|
||||
import com.intellij.credentialStore.PasswordSafeSettings.ProviderType
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe
|
||||
import com.intellij.ide.passwordSafe.PasswordStorage
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
@@ -28,7 +27,7 @@ import org.jetbrains.concurrency.runAsync
|
||||
import java.nio.file.Path
|
||||
|
||||
class PasswordSafeImpl(/* public - backward compatibility */val settings: PasswordSafeSettings) : PasswordSafe(), SettingsSavingComponent {
|
||||
private @Volatile var currentProvider: PasswordStorage
|
||||
internal @Volatile var currentProvider: PasswordStorage
|
||||
|
||||
// it is helper storage to support set password as memory-only (see setPassword memoryOnly flag)
|
||||
private val memoryHelperProvider = lazy { KeePassCredentialStore(emptyMap(), memoryOnly = true) }
|
||||
@@ -45,25 +44,23 @@ class PasswordSafeImpl(/* public - backward compatibility */val settings: Passwo
|
||||
else {
|
||||
currentProvider = createPersistentCredentialStore()
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().messageBus.connect().subscribe(PasswordSafeSettings.TOPIC, object: PasswordSafeSettingsListener {
|
||||
override fun typeChanged(oldValue: ProviderType, newValue: ProviderType) {
|
||||
val memoryOnly = newValue == ProviderType.MEMORY_ONLY
|
||||
if (memoryOnly) {
|
||||
val provider = currentProvider
|
||||
if (provider is KeePassCredentialStore) {
|
||||
provider.memoryOnly = true
|
||||
provider.deleteFileStorage()
|
||||
}
|
||||
else {
|
||||
currentProvider = KeePassCredentialStore(memoryOnly = true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentProvider = createPersistentCredentialStore(currentProvider as? KeePassCredentialStore)
|
||||
}
|
||||
internal fun setProvider(type: ProviderType) {
|
||||
val memoryOnly = type == ProviderType.MEMORY_ONLY
|
||||
if (memoryOnly) {
|
||||
val provider = currentProvider
|
||||
if (provider is KeePassCredentialStore) {
|
||||
provider.memoryOnly = true
|
||||
provider.deleteFileStorage()
|
||||
}
|
||||
})
|
||||
else {
|
||||
currentProvider = KeePassCredentialStore(memoryOnly = true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentProvider = createPersistentCredentialStore(currentProvider as? KeePassCredentialStore)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(attributes: CredentialAttributes): Credentials? {
|
||||
@@ -122,9 +119,7 @@ class PasswordSafeImpl(/* public - backward compatibility */val settings: Passwo
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(PasswordSafeSettings.TOPIC).credentialStoreCleared()
|
||||
}
|
||||
|
||||
fun setFileDatabaseMasterPassword(password: String) {
|
||||
(currentProvider as KeePassCredentialStore).setMasterPassword(password)
|
||||
}
|
||||
internal fun getKeePassCredentialStore() = currentProvider as KeePassCredentialStore
|
||||
|
||||
fun importFileDatabase(path: Path, masterPassword: String) {
|
||||
currentProvider = copyFileDatabase(path, masterPassword)
|
||||
@@ -157,7 +152,7 @@ class PasswordSafeImpl(/* public - backward compatibility */val settings: Passwo
|
||||
get() = memoryHelperProvider.value
|
||||
}
|
||||
|
||||
private fun createPersistentCredentialStore(existing: KeePassCredentialStore? = null, convertFileStore: Boolean = false): PasswordStorage {
|
||||
internal fun createPersistentCredentialStore(existing: KeePassCredentialStore? = null, convertFileStore: Boolean = false): PasswordStorage {
|
||||
LOG.catchAndLog {
|
||||
for (factory in CredentialStoreFactory.CREDENTIAL_STORE_FACTORY.extensions) {
|
||||
val store = factory.create() ?: continue
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class PasswordSafeSettings implements PersistentStateComponent<PasswordSafeSettings.State> {
|
||||
public static final Topic<PasswordSafeSettingsListener> TOPIC = Topic.create("PasswordSafeSettingsListener", PasswordSafeSettingsListener.class);
|
||||
|
||||
private ProviderType myProviderType = ProviderType.MASTER_PASSWORD;
|
||||
private ProviderType myProviderType = ProviderType.KEYCHAIN;
|
||||
|
||||
@NotNull
|
||||
public ProviderType getProviderType() {
|
||||
@@ -56,23 +56,10 @@ public class PasswordSafeSettings implements PersistentStateComponent<PasswordSa
|
||||
}
|
||||
|
||||
public void loadState(@NotNull State state) {
|
||||
setProviderType(ObjectUtils.chooseNotNull(state.PROVIDER, ProviderType.MASTER_PASSWORD));
|
||||
setProviderType(ObjectUtils.chooseNotNull(state.PROVIDER, ProviderType.KEYCHAIN));
|
||||
}
|
||||
|
||||
public static class State {
|
||||
public ProviderType PROVIDER = ProviderType.MASTER_PASSWORD;
|
||||
}
|
||||
|
||||
public enum ProviderType {
|
||||
@Deprecated
|
||||
DO_NOT_STORE,
|
||||
/**
|
||||
* The passwords are stored only in the memory
|
||||
*/
|
||||
MEMORY_ONLY,
|
||||
/**
|
||||
* The passwords are encrypted with master password
|
||||
*/
|
||||
MASTER_PASSWORD
|
||||
public ProviderType PROVIDER = ProviderType.KEYCHAIN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.intellij.credentialStore;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface PasswordSafeSettingsListener {
|
||||
default void typeChanged(@NotNull PasswordSafeSettings.ProviderType oldValue, @NotNull PasswordSafeSettings.ProviderType newValue) {
|
||||
default void typeChanged(@NotNull ProviderType oldValue, @NotNull ProviderType newValue) {
|
||||
}
|
||||
|
||||
default void credentialStoreCleared() {
|
||||
|
||||
@@ -112,7 +112,7 @@ internal class PasswordDatabaseConvertor : ApplicationLoadListener {
|
||||
val oldDbFile = Paths.get(PathManager.getConfigPath(), "options", "security.xml")
|
||||
if (oldDbFile.exists()) {
|
||||
val settings = ServiceManager.getService(PasswordSafeSettings::class.java)
|
||||
if (settings.providerType != PasswordSafeSettings.ProviderType.MASTER_PASSWORD) {
|
||||
if (settings.providerType != ProviderType.KEYCHAIN) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class FileCredentialStoreTest {
|
||||
credentialMap.put(attributes, credentials)
|
||||
}
|
||||
|
||||
provider.setMasterPassword("foo")
|
||||
provider.setMasterPassword("foo".toByteArray())
|
||||
|
||||
val pdbFile = baseDir.resolve("c.kdbx")
|
||||
val pdbPwdFile = baseDir.resolve("pdb.pwd")
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.ui.layout
|
||||
|
||||
import com.intellij.ui.components.Label
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.ButtonGroup
|
||||
import javax.swing.JLabel
|
||||
|
||||
@@ -38,4 +39,15 @@ class LayoutBuilder(val `$`: LayoutBuilderImpl, val buttonGroup: ButtonGroup? =
|
||||
inline fun buttonGroup(init: LayoutBuilder.() -> Unit) {
|
||||
LayoutBuilder(`$`, ButtonGroup()).init()
|
||||
}
|
||||
|
||||
inline fun buttonGroup(crossinline elementActionListener: () -> Unit, init: LayoutBuilder.() -> Unit): ButtonGroup {
|
||||
val group = ButtonGroup()
|
||||
LayoutBuilder(`$`, group).init()
|
||||
|
||||
val listener = ActionListener { elementActionListener() }
|
||||
for (button in group.elements) {
|
||||
button.addActionListener(listener)
|
||||
}
|
||||
return group
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.ToggleAction
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.ui.OnePixelDivider
|
||||
import com.intellij.ui.SeparatorComponent
|
||||
import com.intellij.ui.components.Label
|
||||
import com.intellij.ui.components.noteComponent
|
||||
import com.intellij.util.SmartList
|
||||
import net.miginfocom.layout.*
|
||||
@@ -40,14 +41,14 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
|
||||
private val componentConstraints: MutableMap<Component, CC> = SmartHashMap()
|
||||
|
||||
override fun newRow(label: JLabel?, buttonGroup: ButtonGroup?, separated: Boolean): Row {
|
||||
override fun newRow(label: JLabel?, buttonGroup: ButtonGroup?, separated: Boolean, indented: Boolean): Row {
|
||||
if (separated) {
|
||||
val row = MigLayoutRow(componentConstraints, noGrid = true, separated = true)
|
||||
val row = MigLayoutRow(componentConstraints, this, noGrid = true, separated = true)
|
||||
rows.add(row)
|
||||
row.apply { SeparatorComponent(0, OnePixelDivider.BACKGROUND, null)() }
|
||||
}
|
||||
|
||||
val row = MigLayoutRow(componentConstraints, label != null, buttonGroup = buttonGroup)
|
||||
val row = MigLayoutRow(componentConstraints, this, label != null, buttonGroup = buttonGroup, indented = indented)
|
||||
rows.add(row)
|
||||
|
||||
label?.let { row.apply { label() } }
|
||||
@@ -59,13 +60,13 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
// add empty row as top gap
|
||||
newRow()
|
||||
|
||||
val row = MigLayoutRow(componentConstraints, noGrid = true)
|
||||
val row = MigLayoutRow(componentConstraints, this, noGrid = true)
|
||||
rows.add(row)
|
||||
row.apply { noteComponent(text)() }
|
||||
}
|
||||
|
||||
override fun build(container: Container, layoutConstraints: Array<out LCFlags>) {
|
||||
val labeled = rows.firstOrNull(MigLayoutRow::labeled) != null
|
||||
val labeled = rows.firstOrNull({ it.labeled && !it.indented }) != null
|
||||
var gapTop = -1
|
||||
|
||||
val lc = c()
|
||||
@@ -77,6 +78,8 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
lc.apply(layoutConstraints)
|
||||
}
|
||||
|
||||
lc.noVisualPadding()
|
||||
|
||||
container.layout = MigLayout(lc)
|
||||
|
||||
val noGrid = layoutConstraints.contains(LCFlags.noGrid)
|
||||
@@ -124,12 +127,18 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
}
|
||||
else {
|
||||
var isSkippableComponent = true
|
||||
if (component === row.components.first() && labeled) {
|
||||
if (row.labeled) {
|
||||
isSkippableComponent = false
|
||||
if (component === row.components.first()) {
|
||||
if (row.indented) {
|
||||
cc.horizontal.gapBefore = gapToBoundSize(HORIZONTAL_GAP * 3, true)
|
||||
}
|
||||
else {
|
||||
cc.skip()
|
||||
|
||||
if (labeled) {
|
||||
if (row.labeled) {
|
||||
isSkippableComponent = false
|
||||
}
|
||||
else {
|
||||
cc.skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +147,10 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
|
||||
isSplitRequired = false
|
||||
cc.split()
|
||||
}
|
||||
cc.horizontal.gapAfter = gapToBoundSize(HORIZONTAL_GAP * 2, true)
|
||||
|
||||
if (component !== lastComponent) {
|
||||
cc.horizontal.gapAfter = gapToBoundSize(HORIZONTAL_GAP * 2, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,20 +178,39 @@ private fun addGrowIfNeed(cc: CC, component: Component) {
|
||||
}
|
||||
}
|
||||
|
||||
private class MigLayoutRow(private val componentConstraints: MutableMap<Component, CC>, val labeled: Boolean = false, val noGrid: Boolean = false, private val buttonGroup: ButtonGroup? = null, val separated: Boolean = false) : Row() {
|
||||
private class MigLayoutRow(private val componentConstraints: MutableMap<Component, CC>,
|
||||
private val builder: MigLayoutBuilder,
|
||||
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
|
||||
|
||||
override operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int) {
|
||||
addComponent(this, constraints, gapLeft = gapLeft)
|
||||
override var enabled: Boolean = true
|
||||
get() = field
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
|
||||
field = value
|
||||
for (c in components) {
|
||||
c.isEnabled = 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) {
|
||||
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)?.let {
|
||||
createComponentConstraints(constraints, gapLeft = gapLeft, growPolicy = growPolicy)?.let {
|
||||
componentConstraints.put(component, it)
|
||||
}
|
||||
components.add(component)
|
||||
@@ -191,6 +222,12 @@ private class MigLayoutRow(private val componentConstraints: MutableMap<Componen
|
||||
}
|
||||
rightIndex = components.size
|
||||
}
|
||||
|
||||
override fun row(label: String, init: Row.() -> Unit): Row {
|
||||
val row = builder.newRow(Label(label), indented = true)
|
||||
row.init()
|
||||
return row
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComponentConstraints(constraints: Array<out CCFlags>? = null,
|
||||
@@ -198,7 +235,8 @@ private fun createComponentConstraints(constraints: Array<out CCFlags>? = null,
|
||||
gapAfter: Int = 0,
|
||||
gapTop: Int = 0,
|
||||
gapBottom: Int = 0,
|
||||
split: Int = -1): CC? {
|
||||
split: Int = -1,
|
||||
growPolicy: GrowPolicy?): CC? {
|
||||
var _cc = constraints?.create()
|
||||
fun cc(): CC {
|
||||
if (_cc == null) {
|
||||
@@ -224,6 +262,12 @@ private fun createComponentConstraints(constraints: Array<out CCFlags>? = null,
|
||||
if (split != -1) {
|
||||
cc().split = split
|
||||
}
|
||||
|
||||
if (growPolicy == GrowPolicy.SHORT_TEXT) {
|
||||
// cc().minWidth("210")
|
||||
cc().maxWidth("210")
|
||||
}
|
||||
|
||||
return _cc
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
|
||||
abstract class Row() {
|
||||
abstract var enabled: Boolean
|
||||
|
||||
fun label(text: String, gapLeft: Int = 0, style: ComponentStyle? = null, fontColor: FontColor? = null, bold: Boolean = false) {
|
||||
Label(text, style, fontColor, bold)(gapLeft = gapLeft)
|
||||
}
|
||||
@@ -55,7 +57,7 @@ abstract class Row() {
|
||||
panel(*constraints)
|
||||
}
|
||||
|
||||
abstract operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int = 0)
|
||||
abstract operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int = 0, growPolicy: GrowPolicy? = null)
|
||||
|
||||
inline fun right(init: Row.() -> Unit) {
|
||||
alignRight()
|
||||
@@ -64,9 +66,7 @@ abstract class Row() {
|
||||
|
||||
protected abstract fun alignRight()
|
||||
|
||||
@Deprecated(message = "Nested row is prohibited", level = DeprecationLevel.ERROR)
|
||||
fun row(label: String, init: Row.() -> Unit) {
|
||||
}
|
||||
abstract fun row(label: String, init: Row.() -> Unit): Row
|
||||
|
||||
@Deprecated(message = "Nested row is prohibited", level = DeprecationLevel.ERROR)
|
||||
fun row(label: JLabel? = null, init: Row.() -> Unit) {
|
||||
@@ -75,4 +75,8 @@ abstract class Row() {
|
||||
@Deprecated(message = "Nested noteRow is prohibited", level = DeprecationLevel.ERROR)
|
||||
fun noteRow(text: String) {
|
||||
}
|
||||
}
|
||||
|
||||
enum class GrowPolicy {
|
||||
SHORT_TEXT
|
||||
}
|
||||
@@ -32,7 +32,7 @@ fun createLayoutBuilder() = LayoutBuilder(MigLayoutBuilder())
|
||||
// "When a protected member is accessed from an inline function, a public accessor method is created to provide an access to that protected member from the outside of the class where the function will be inlined to."
|
||||
// (https://youtrack.jetbrains.com/issue/KT-12215)
|
||||
interface LayoutBuilderImpl {
|
||||
fun newRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false): Row
|
||||
fun newRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false, indented: Boolean = false): Row
|
||||
|
||||
fun build(container: Container, layoutConstraints: Array<out LCFlags>)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user