From 3a1476e482b7e3bb6641ce3df76631fb14008eff Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Tue, 13 Sep 2016 12:59:56 +0200 Subject: [PATCH] =?UTF-8?q?kotlin=20UI=20DSL=20variant=202=20=E2=80=94=20m?= =?UTF-8?q?ore=20readable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make setOKActionEnabled public init to fix ask password dialog --- .../src/EnterPasswordComponent.java | 5 +- .../src/PasswordSafeConfigurable.kt | 23 ++- .../analysis/BaseAnalysisActionDialog.java | 5 - .../impl/EditConfigurationsDialog.java | 7 +- .../intellij/execution/impl/RunDialog.java | 7 +- .../intellij/openapi/application/actions.kt | 4 +- .../intellij/openapi/ui/DialogWrapper.java | 18 +- .../com/intellij/openapi/ui/dialogBuilder.kt | 49 ------ .../src/com/intellij/util/text/string.kt | 4 +- .../diagnostic/JetBrainsAccountDialog.kt | 38 ++--- .../ui/PasswordSafePromptDialog.java | 141 ++++------------ .../ide/passwordSafe/ui/credentialPromt.kt | 76 +++++++++ .../fileChooser/ex/FileSaverDialogImpl.java | 4 +- .../src/com/intellij/ui/components.kt | 66 -------- .../com/intellij/ui/components/components.kt | 159 ++++++++++++++++++ .../com/intellij/ui/layout/LayoutBuilder.kt | 50 ++++++ .../intellij/ui/layout/MigLayoutBuilder.kt | 95 +++++++++++ .../src/com/intellij/ui/layout/Row.kt | 76 +++++++++ .../src/com/intellij/ui/layout/flags.kt | 15 +- .../src/com/intellij/ui/layout/layout.kt | 75 +++------ .../src/com/intellij/ui/layout/layoutImpl.kt | 44 +++-- .../src/com/intellij/ui/layout/readme.md | 38 +++-- .../settings/DataViewsConfigurableUi.java | 4 +- .../git4idea/commands/GitSSHGUIHandler.java | 13 +- .../src/git4idea/i18n/GitBundle.properties | 2 - .../resources/messages/IcsBundle.properties | 3 +- .../src/IcsConfigurable.kt | 3 +- plugins/settings-repository/src/authForm.kt | 31 ++-- .../src/repositoryListEditor.kt | 7 +- .../learning/twitter/StudyTwitterUtils.java | 4 - 30 files changed, 660 insertions(+), 406 deletions(-) delete mode 100644 platform/platform-api/src/com/intellij/openapi/ui/dialogBuilder.kt create mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/credentialPromt.kt delete mode 100644 platform/platform-impl/src/com/intellij/ui/components.kt create mode 100644 platform/platform-impl/src/com/intellij/ui/components/components.kt create mode 100644 platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt create mode 100644 platform/platform-impl/src/com/intellij/ui/layout/MigLayoutBuilder.kt create mode 100644 platform/platform-impl/src/com/intellij/ui/layout/Row.kt diff --git a/platform/credential-store/src/EnterPasswordComponent.java b/platform/credential-store/src/EnterPasswordComponent.java index 7be5b8faaa7d..915aaa2caa2e 100644 --- a/platform/credential-store/src/EnterPasswordComponent.java +++ b/platform/credential-store/src/EnterPasswordComponent.java @@ -19,7 +19,7 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.SystemInfo; -import com.intellij.ui.ComponentsKt; +import com.intellij.ui.components.ComponentsKt; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -95,6 +95,7 @@ class EnterPasswordComponent { note = "The passwords will be stored in IDE configuration files with weak protection
(" + subNote + ")."; } - myPromptLabel = ComponentsKt.htmlComponent("Master password is required to convert saved passwords.
" + note, UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); + myPromptLabel = ComponentsKt + .htmlComponent("Master password is required to convert saved passwords.
" + note, UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); } } diff --git a/platform/credential-store/src/PasswordSafeConfigurable.kt b/platform/credential-store/src/PasswordSafeConfigurable.kt index ac971e19748f..d2e7d64d5074 100644 --- a/platform/credential-store/src/PasswordSafeConfigurable.kt +++ b/platform/credential-store/src/PasswordSafeConfigurable.kt @@ -21,9 +21,9 @@ import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl import com.intellij.openapi.options.ConfigurableBase import com.intellij.openapi.options.ConfigurableUi import com.intellij.openapi.ui.Messages +import com.intellij.ui.components.RadioButton import com.intellij.ui.layout.* -import com.intellij.ui.layout.CCFlags.* -import com.intellij.ui.layout.LCFlags.* +import java.awt.Component class PasswordSafeConfigurable(private val settings: PasswordSafeSettings) : ConfigurableBase("application.passwordSafe", "Passwords", "reference.ide.settings.password.safe") { override fun getSettings() = settings @@ -49,16 +49,23 @@ class PasswordSafeConfigurableUi : ConfigurableUi { settings.providerType = getProviderType() } - override fun getComponent() = panel(noGrid, flowY, fillX) { + override fun getComponent() = panel { val passwordSafe = PasswordSafe.getInstance() as PasswordSafeImpl - buttonGroup(saveOnDisk, rememberPasswordsUntilClosing) + buttonGroup { + row { saveOnDisk() } + row { rememberPasswordsUntilClosing() } + } if (!passwordSafe.isNativeCredentialStoreUsed) - button("Clear Passwords", right) { - passwordSafe.clearPasswords() - Messages.showInfoMessage(this@panel, "Passwords were cleared", "Clear Passwords") - } + row { + right { + button("Clear Passwords") { event -> + passwordSafe.clearPasswords() + Messages.showInfoMessage(event.source as Component, "Passwords were cleared", "Clear Passwords") + } + } + } } private fun getProviderType(): ProviderType { diff --git a/platform/lang-impl/src/com/intellij/analysis/BaseAnalysisActionDialog.java b/platform/lang-impl/src/com/intellij/analysis/BaseAnalysisActionDialog.java index a76bc96e00a1..42d2fcc010e6 100644 --- a/platform/lang-impl/src/com/intellij/analysis/BaseAnalysisActionDialog.java +++ b/platform/lang-impl/src/com/intellij/analysis/BaseAnalysisActionDialog.java @@ -101,11 +101,6 @@ public class BaseAnalysisActionDialog extends DialogWrapper { onScopeRadioButtonPressed(); } - @Override - public void setOKActionEnabled(boolean isEnabled) { - super.setOKActionEnabled(isEnabled); - } - @Override protected JComponent createCenterPanel() { myTitledSeparator.setText(myAnalysisNoon); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java b/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java index 0c8c6b4152b9..6b4a0e99b096 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -86,9 +86,4 @@ public class EditConfigurationsDialog extends SingleConfigurableEditor implement public Executor getExecutor() { return myExecutor; } - - @Override - public void setOKActionEnabled(boolean isEnabled) { - super.setOKActionEnabled(isEnabled); - } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunDialog.java b/platform/lang-impl/src/com/intellij/execution/impl/RunDialog.java index a8269a5c0c8f..5143fb9b87c9 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunDialog.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -97,11 +97,6 @@ public class RunDialog extends DialogWrapper implements RunConfigurable.RunDialo return myCenterPanel; } - @Override - public void setOKActionEnabled(final boolean isEnabled){ - super.setOKActionEnabled(isEnabled); - } - @Override protected void dispose() { myConfigurable.disposeUIResources(); diff --git a/platform/platform-api/src/com/intellij/openapi/application/actions.kt b/platform/platform-api/src/com/intellij/openapi/application/actions.kt index 236502bc92c3..e4351dc56c9b 100644 --- a/platform/platform-api/src/com/intellij/openapi/application/actions.kt +++ b/platform/platform-api/src/com/intellij/openapi/application/actions.kt @@ -25,7 +25,7 @@ inline fun runReadAction(crossinline runnable: () -> T): T = ApplicationMana /** * @exclude Internal use only */ -fun invokeAndWaitIfNeed(runnable: () -> T): T { +fun invokeAndWaitIfNeed(modalityState: ModalityState? = null, runnable: () -> T): T { val app = ApplicationManager.getApplication() if (app == null) { if (SwingUtilities.isEventDispatchThread()) { @@ -39,7 +39,7 @@ fun invokeAndWaitIfNeed(runnable: () -> T): T { } else { var result: T? = null - app.invokeAndWait { result = runnable() } + app.invokeAndWait({ result = runnable() }, modalityState ?: ModalityState.defaultModalityState()) return result as T } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java index db4a3b7b0911..0dac1f802ed1 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -213,7 +213,11 @@ public abstract class DialogWrapper { } protected DialogWrapper(@Nullable Project project, boolean canBeParent, @NotNull IdeModalityType ideModalityType) { - myPeer = createPeer(project, canBeParent, ideModalityType); + this(project, null, canBeParent, ideModalityType); + } + + protected DialogWrapper(@Nullable Project project, @Nullable Component parentComponent, boolean canBeParent, @NotNull IdeModalityType ideModalityType) { + myPeer = parentComponent == null ? createPeer(project, canBeParent, project == null ? IdeModalityType.IDE : ideModalityType) : createPeer(parentComponent, canBeParent); final Window window = myPeer.getWindow(); if (window != null) { myResizeListener = new ComponentAdapter() { @@ -1480,7 +1484,11 @@ public abstract class DialogWrapper { return myPeer.isModal(); } - protected void setOKActionEnabled(boolean isEnabled) { + public boolean isOKActionEnabled() { + return myOKAction.isEnabled(); + } + + public void setOKActionEnabled(boolean isEnabled) { myOKAction.setEnabled(isEnabled); } @@ -1538,10 +1546,6 @@ public abstract class DialogWrapper { return getExitCode() == OK_EXIT_CODE; } - public boolean isOKActionEnabled() { - return myOKAction.isEnabled(); - } - /** * @return true if and only if visible * @see Component#isVisible @@ -1860,7 +1864,7 @@ public abstract class DialogWrapper { if (info.component != null && info.component.isVisible()) { IdeFocusManager.getInstance(null).requestFocus(info.component, true); } - DialogEarthquakeShaker.shake((JDialog)getPeer().getWindow()); + DialogEarthquakeShaker.shake(getPeer().getWindow()); startTrackingValidation(); return; } diff --git a/platform/platform-api/src/com/intellij/openapi/ui/dialogBuilder.kt b/platform/platform-api/src/com/intellij/openapi/ui/dialogBuilder.kt deleted file mode 100644 index 9790d75fa561..000000000000 --- a/platform/platform-api/src/com/intellij/openapi/ui/dialogBuilder.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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. - */ -package com.intellij.openapi.ui - -import com.intellij.openapi.project.Project -import java.awt.Component -import javax.swing.JComponent - -fun dialog(title: String, - centerPanel: JComponent, - resizable: Boolean = false, - preferedFocusComponent: JComponent? = null, - okActionEnabled: Boolean = true, - project: Project? = null, - parent: Component? = null, - ok: (() -> Unit)? = null): DialogBuilder { - val builder = if (parent == null) DialogBuilder(project) else DialogBuilder(parent) - builder - .title(title) - .centerPanel(centerPanel) - .setPreferredFocusComponent(preferedFocusComponent) - builder.resizable(resizable) - if (!okActionEnabled) { - builder.okActionEnabled(false) - } - - if (ok != null) { - builder.setOkOperation { - if (builder.dialogWrapper.okAction.isEnabled) { - ok() - builder.dialogWrapper.close(DialogWrapper.OK_EXIT_CODE) - } - } - } - return builder -} \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/util/text/string.kt b/platform/platform-api/src/com/intellij/util/text/string.kt index 75a4c4b94249..5f4939937120 100644 --- a/platform/platform-api/src/com/intellij/util/text/string.kt +++ b/platform/platform-api/src/com/intellij/util/text/string.kt @@ -19,4 +19,6 @@ import com.intellij.openapi.util.text.StringUtil fun String?.nullize(nullizeSpaces: Boolean = false): String? = StringUtil.nullize(this, nullizeSpaces) -fun String.trimMiddle(maxLength: Int): String? = StringUtil.trimMiddle(this, maxLength) \ No newline at end of file +fun String.trimMiddle(maxLength: Int): String? = StringUtil.trimMiddle(this, maxLength) + +fun CharArray.nullize() = if (isEmpty()) null else this diff --git a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.kt b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.kt index 50f0c65319fd..a54a237752f2 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.kt @@ -21,45 +21,41 @@ import com.intellij.credentialStore.Credentials import com.intellij.ide.BrowserUtil import com.intellij.ide.passwordSafe.PasswordSafe import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogBuilder -import com.intellij.openapi.ui.dialog -import com.intellij.ui.components.JBCheckBox +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.components.CheckBox +import com.intellij.ui.components.dialog import com.intellij.ui.layout.* -import com.intellij.ui.layout.CCFlags.* -import com.intellij.ui.layout.LCFlags.* import com.intellij.util.io.encodeUrlQueryParameter import java.awt.Component import javax.swing.JPasswordField import javax.swing.JTextField @JvmOverloads -fun showJetBrainsAccountDialog(parent: Component, project: Project? = null): DialogBuilder { +fun showJetBrainsAccountDialog(parent: Component, project: Project? = null): DialogWrapper { val credentials = ErrorReportConfigurable.getCredentials() val userField = JTextField(credentials?.userName) val passwordField = JPasswordField(credentials?.password?.toString()) // if no user name - never stored and so, defaults to remember. if user name set, but no password, so, previously was stored without password - val rememberCheckBox = JBCheckBox(CommonBundle.message("checkbox.remember.password"), credentials?.userName == null || !credentials?.password.isNullOrEmpty()) + val rememberCheckBox = CheckBox(CommonBundle.message("checkbox.remember.password"), selected = credentials?.userName == null || !credentials?.password.isNullOrEmpty()) - val panel = panel(fillX) { - label("Login to JetBrains Account to get notified when the submitted\nexceptions are fixed.", span, wrap) - label("Username:") - userField(grow, wrap) - - label("Password:") - passwordField(grow, wrap) - rememberCheckBox(skip, split, grow) - link("Forgot password?", wrap, right) { - BrowserUtil.browse("https://account.jetbrains.com/forgot-password?username=${userField.text.trim().encodeUrlQueryParameter()}") + val panel = panel() { + noteRow("Login to JetBrains Account to get notified when the submitted\nexceptions are fixed.") + row("Username:") { userField() } + row("Password:") { passwordField() } + row { + rememberCheckBox() + right { + link("Forgot password?") { BrowserUtil.browse("https://account.jetbrains.com/forgot-password?username=${userField.text.trim().encodeUrlQueryParameter()}") } + } } - - note("""Do not have an account? Sign Up""", span, wrap) + noteRow("""Do not have an account? Sign Up""") } return dialog( title = DiagnosticBundle.message("error.report.title"), - centerPanel = panel, - preferedFocusComponent = if (credentials?.userName == null) userField else passwordField, + panel = panel, + focusedComponent = if (credentials?.userName == null) userField else passwordField, project = project, parent = if (parent.isShowing) parent else null) { val userName = userField.text diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java index 74f338d089d5..4415a54c9205 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java @@ -15,144 +15,65 @@ */ package com.intellij.ide.passwordSafe.ui; -import com.intellij.credentialStore.Credentials; -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; +import com.intellij.credentialStore.CredentialAttributes; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Ref; -import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; - import static com.intellij.credentialStore.CredentialAttributesKt.CredentialAttributes; /** * The generic password dialog. Use it to ask a password from user with option to remember it. */ -public class PasswordSafePromptDialog extends DialogWrapper { - private final PasswordPromptComponent myComponent; - - private PasswordSafePromptDialog(@Nullable Project project, @NotNull String title, @NotNull PasswordPromptComponent component) { - super(project, true); - - setTitle(title); - myComponent = component; - setResizable(false); - init(); - } - - @Override - protected JComponent createCenterPanel() { - return myComponent.getComponent(); - } - - @Override - public JComponent getPreferredFocusedComponent() { - return myComponent.getPreferredFocusedComponent(); - } - +public class PasswordSafePromptDialog { /** - * Ask password possibly asking password database first. The method could be invoked from any thread. If UI needs to be shown, - * the method invokes {@link UIUtil#invokeAndWaitIfNeeded(Runnable)} * @param project the context project * @param title the dialog title * @param message the message describing a resource for which password is asked * @param requestor the password requestor * @param key the password key * @param resetPassword if true, the old password is removed from database and new password will be asked. - * @param error the error to show in the dialog @return null if dialog was cancelled or password (stored in database or a entered by user) - */ - @Nullable - public static String askPassword(final Project project, - final String title, - final String message, - @NotNull final Class requestor, - final String key, - boolean resetPassword, String error) { - return askPassword(project, title, message, requestor, key, resetPassword, error, null); - } - - /** - * Ask password possibly asking password database first. The method could be invoked from any thread. If UI needs to be shown, - * the method invokes {@link UIUtil#invokeAndWaitIfNeeded(Runnable)} - * - * @param title the dialog title - * @param message the message describing a resource for which password is asked - * @param requestor the password requestor - * @param key the password key - * @param resetPassword if true, the old password is removed from database and new password will be asked. + * @param error the error to show in the dialog * @return null if dialog was cancelled or password (stored in database or a entered by user) */ @Nullable - public static String askPassword(final String title, - final String message, - @NotNull final Class requestor, - final String key, - boolean resetPassword) { - return askPassword(null, title, message, requestor, key, resetPassword, null); + public static String askPassword(@Nullable Project project, + String title, + String message, + @NotNull Class requestor, + String key, + boolean resetPassword, + String error) { + return CredentialPromtKt.askPassword(project, title, message, CredentialAttributes(requestor, key), resetPassword, error); + } + + /** + * @param dialogTitle The dialog title + * @param passwordFieldLabel The password field label, describing a resource, for which password is asked + * @return null if dialog was cancelled or password (stored in database or a entered by user) + */ + @Nullable + public static String askPassword(@NotNull String dialogTitle, @NotNull String passwordFieldLabel, @NotNull CredentialAttributes credentialAttributes) { + return CredentialPromtKt.askPassword(null, dialogTitle, passwordFieldLabel, credentialAttributes); } /** - * Ask passphrase possibly asking password database first. The method could be invoked from any thread. If UI needs to be shown, - * the method invokes {@link UIUtil#invokeAndWaitIfNeeded(Runnable)} - * @param project the context project (might be null) - * @param title the dialog title - * @param message the message describing a resource for which password is asked - * @param requestor the password requestor - * @param key the password key + * @param project The context project (might be null) + * @param dialogTitle The dialog title + * @param passwordFieldLabel the message describing a resource for which password is asked * @param resetPassword if true, the old password is removed from database and new password will be asked. - * @param error the error to show in the dialog @return null if dialog was cancelled or password (stored in database or a entered by user) + * @param error the error to show in the dialog + * @return null if dialog was cancelled or password (stored in database or a entered by user) */ @Nullable - public static String askPassphrase(final Project project, - final String title, - final String message, - @NotNull final Class requestor, - final String key, + public static String askPassword(@Nullable Project project, + String dialogTitle, + @NotNull String passwordFieldLabel, + @NotNull CredentialAttributes credentialAttributes, boolean resetPassword, String error) { - return askPassword(project, title, message, requestor, key, resetPassword, error, "Passphrase:"); - } - - @Nullable - private static String askPassword(Project project, - String title, - String message, - @NotNull Class requestor, - String accountName, - boolean resetPassword, - String error, - String promptLabel) { - PasswordSafe ps = PasswordSafe.getInstance(); - if (resetPassword) { - ps.set(CredentialAttributes(requestor, accountName), null); - } - else { - String pw = ps.getPassword(requestor, accountName); - if (pw != null) { - return pw; - } - } - - Ref ref = Ref.create(); - ApplicationManager.getApplication().invokeAndWait(() -> { - final PasswordPromptComponent component = new PasswordPromptComponent(ps.isMemoryOnly(), message, false, promptLabel); - PasswordSafePromptDialog d = new PasswordSafePromptDialog(project, title, component); - - d.setErrorText(error); - if (d.showAndGet()) { - Credentials credentials = new Credentials(component.getUserName(), component.getPassword()); - ref.set(credentials); - ps.set(CredentialAttributes(requestor, accountName), credentials, !component.isRememberSelected()); - } - }, ModalityState.any()); - Credentials credentials = ref.get(); - return credentials == null ? null : credentials.getPasswordAsString(); + return CredentialPromtKt.askPassword(project, dialogTitle, passwordFieldLabel, credentialAttributes, resetPassword, error); } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/credentialPromt.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/credentialPromt.kt new file mode 100644 index 000000000000..6a614a800a3d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/credentialPromt.kt @@ -0,0 +1,76 @@ +/* + * 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. + */ +package com.intellij.ide.passwordSafe.ui + +import com.intellij.CommonBundle +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.Credentials +import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.invokeAndWaitIfNeed +import com.intellij.openapi.project.Project +import com.intellij.ui.components.CheckBox +import com.intellij.ui.components.dialog +import com.intellij.ui.layout.* +import com.intellij.util.text.nullize +import javax.swing.JPasswordField + +@JvmOverloads +fun askPassword(project: Project?, + dialogTitle: String, + passwordFieldLabel: String, + attributes: CredentialAttributes, + resetPassword: Boolean = false, + error: String? = null): String? { + val store = PasswordSafe.getInstance() + if (resetPassword) { + store.set(attributes, null) + } + else { + store.get(attributes)?.getPasswordAsString()?.nullize()?.let { + return it + } + } + + return invokeAndWaitIfNeed(ModalityState.any()) { + val passwordField = JPasswordField() + val rememberCheckBox = if (store.isMemoryOnly) { + null + } + else { + CheckBox(CommonBundle.message("checkbox.remember.password"), + selected = true, + toolTip = "The password will be stored between application sessions.") + } + + val panel = panel { + row { label(passwordFieldLabel) } + row { passwordField() } + rememberCheckBox?.let { + row { it() } + } + } + + if (dialog(dialogTitle, project = project, panel = panel, focusedComponent = passwordField, errorText = error).showAndGet()) { + val credentials = Credentials(attributes.userName, passwordField.password.nullize()) + store.set(attributes, credentials, store.isMemoryOnly || rememberCheckBox!!.isSelected) + credentials.getPasswordAsString() + } + else { + null + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java index afb2c7df929c..4c38051e523e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSaverDialogImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -179,7 +179,7 @@ public class FileSaverDialogImpl extends FileChooserDialogImpl implements FileSa } @Override - protected void setOKActionEnabled(boolean isEnabled) { + public void setOKActionEnabled(boolean isEnabled) { //double check. FileChooserFactoryImpl sets enable ok button super.setOKActionEnabled(isFileNameExist()); } diff --git a/platform/platform-impl/src/com/intellij/ui/components.kt b/platform/platform-impl/src/com/intellij/ui/components.kt deleted file mode 100644 index d60685d0f804..000000000000 --- a/platform/platform-impl/src/com/intellij/ui/components.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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. - */ -package com.intellij.ui - -import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase -import com.intellij.util.ui.SwingHelper -import com.intellij.util.ui.UIUtil -import java.awt.Color -import java.awt.Font -import java.util.regex.Pattern -import javax.swing.JEditorPane - -private val HREF_PATTERN = Pattern.compile("([^<]*)") - -private val LINK_TEXT_ATTRIBUTES: SimpleTextAttributes - get() = SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, UI.getColor("link.foreground")) - -fun noteComponent(note: String): SimpleColoredComponent { - val noteComponent = SimpleColoredComponent() - - val matcher = HREF_PATTERN.matcher(note) - var prev = 0 - if (matcher.find()) { - do { - if (matcher.start() != prev) { - noteComponent.append(note.substring(prev, matcher.start())) - } - noteComponent.append(matcher.group(2), LINK_TEXT_ATTRIBUTES, SimpleColoredComponent.BrowserLauncherTag(matcher.group(1))) - prev = matcher.end() - } - while (matcher.find()) - - LinkMouseListenerBase.installSingleTagOn(noteComponent) - } - - if (prev < note.length) { - noteComponent.append(note.substring(prev)) - } - - return noteComponent -} - -@JvmOverloads -fun htmlComponent(text: String = "", font: Font = UIUtil.getLabelFont(), background: Color? = null, foreground: Color? = null, lineWrap: Boolean = false): JEditorPane { - val pane = SwingHelper.createHtmlViewer(lineWrap, font, background, foreground) - if (!text.isNullOrEmpty()) { - pane.text = "${UIUtil.getCssFontDeclaration(font, UIUtil.getLabelForeground(), null, null)}$text" - } - pane.border = null - pane.disabledTextColor = UIUtil.getLabelDisabledForeground() - pane.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE) - return pane -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/components/components.kt b/platform/platform-impl/src/com/intellij/ui/components/components.kt new file mode 100644 index 000000000000..97bf7c173e89 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/components/components.kt @@ -0,0 +1,159 @@ +/* + * 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. + */ +package com.intellij.ui.components + +import com.intellij.BundleBase +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.ex.MultiLineLabel +import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase +import com.intellij.ui.BrowserHyperlinkListener +import com.intellij.ui.SimpleColoredComponent +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.UI +import com.intellij.ui.components.labels.LinkLabel +import com.intellij.ui.layout.* +import com.intellij.util.ui.SwingHelper +import com.intellij.util.ui.UIUtil +import java.awt.* +import java.util.regex.Pattern +import javax.swing.* + +private val HREF_PATTERN = Pattern.compile("([^<]*)") + +private val LINK_TEXT_ATTRIBUTES: SimpleTextAttributes + get() = SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, UI.getColor("link.foreground")) + +fun Label(text: String, style: UIUtil.ComponentStyle? = null, fontColor: UIUtil.FontColor? = null, bold: Boolean = false): JLabel { + val finalText = BundleBase.replaceMnemonicAmpersand(text) + val label: JLabel + if (fontColor == null) { + label = if (finalText.contains('\n')) MultiLineLabel(finalText) else JLabel(finalText) + style?.let { UIUtil.applyStyle(it, label) } + } + else { + label = JBLabel(finalText, style ?: UIUtil.ComponentStyle.REGULAR, fontColor) + } + + if (bold) { + label.font = label.font.deriveFont(Font.BOLD) + } + + return label +} + +fun Link(text: String, style: UIUtil.ComponentStyle? = null, action: () -> Unit): JComponent { + val result = LinkLabel.create(text, action) + style?.let { UIUtil.applyStyle(it, result) } + return result +} + +fun noteComponent(note: String): JComponent { + val matcher = HREF_PATTERN.matcher(note) + if (!matcher.find()) { + return Label(note) + } + + val noteComponent = SimpleColoredComponent() + var prev = 0 + do { + if (matcher.start() != prev) { + noteComponent.append(note.substring(prev, matcher.start())) + } + noteComponent.append(matcher.group(2), LINK_TEXT_ATTRIBUTES, SimpleColoredComponent.BrowserLauncherTag(matcher.group(1))) + prev = matcher.end() + } + while (matcher.find()) + + LinkMouseListenerBase.installSingleTagOn(noteComponent) + + if (prev < note.length) { + noteComponent.append(note.substring(prev)) + } + + return noteComponent +} + +@JvmOverloads +fun htmlComponent(text: String = "", font: Font = UIUtil.getLabelFont(), background: Color? = null, foreground: Color? = null, lineWrap: Boolean = false): JEditorPane { + val pane = SwingHelper.createHtmlViewer(lineWrap, font, background, foreground) + if (!text.isNullOrEmpty()) { + pane.text = "${UIUtil.getCssFontDeclaration(font, UIUtil.getLabelForeground(), null, null)}$text" + } + pane.border = null + pane.disabledTextColor = UIUtil.getLabelDisabledForeground() + pane.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE) + return pane +} + +fun RadioButton(text: String) = JRadioButton(BundleBase.replaceMnemonicAmpersand(text)) + +fun CheckBox(text: String, selected: Boolean = false, toolTip: String? = null): JCheckBox { + val component = JCheckBox(BundleBase.replaceMnemonicAmpersand(text), selected) + toolTip?.let { component.toolTipText = it } + return component +} + +@JvmOverloads +fun Panel(title: String? = null, layout: LayoutManager2 = BorderLayout()): JPanel { + val panel = JPanel(layout) + title?.let { setTitledBorder(it, panel) } + return panel +} + +fun dialog(title: String, + panel: JComponent, + resizable: Boolean = false, + focusedComponent: JComponent? = null, + okActionEnabled: Boolean = true, + project: Project? = null, + parent: Component? = null, + errorText: String? = null, + ok: (() -> Unit)? = null): DialogWrapper { + return object: DialogWrapper(project, parent, true, IdeModalityType.PROJECT) { +// var okActionEnabled: Boolean +// get() = okAction.isEnabled +// set(value) { +// okAction.isEnabled = value +// } + + init { + setTitle(title) + setResizable(resizable) + + if (!okActionEnabled) { + this.okAction.isEnabled = false + } + + setErrorText(errorText) + + init() + } + + override fun createCenterPanel() = panel + + override fun getPreferredFocusedComponent() = focusedComponent + + override fun doOKAction() { + ok?.let { + if (okAction.isEnabled) { + it() + } + } + super.doOKAction() + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt b/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt new file mode 100644 index 000000000000..2d80cbff1171 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt @@ -0,0 +1,50 @@ +/* + * 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. + */ +package com.intellij.ui.layout + +import com.intellij.ui.components.Label +import java.awt.Container +import javax.swing.ButtonGroup +import javax.swing.JLabel + +class LayoutBuilder(val `$`: LayoutBuilderImpl, val buttonGroup: ButtonGroup? = null) { + inline fun row(label: String, init: Row.() -> Unit) { + row(Label(label), init) + } + + inline fun row(label: JLabel? = null, init: Row.() -> Unit) { + val row = Row(buttonGroup, label != null) + label?.let { row.apply { label() } } + row.init() + `$`.addRow(row) + } + + fun noteRow(text: String) { + `$`.noteRow(text) + } + + inline fun buttonGroup(init: LayoutBuilder.() -> Unit) { + LayoutBuilder(`$`, buttonGroup).init() + } +} + +interface LayoutBuilderImpl { + fun addRow(row: Row) + + fun build(container: Container) + + fun noteRow(text: String) +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/MigLayoutBuilder.kt b/platform/platform-impl/src/com/intellij/ui/layout/MigLayoutBuilder.kt new file mode 100644 index 000000000000..3c4e7672a4cd --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/layout/MigLayoutBuilder.kt @@ -0,0 +1,95 @@ +/* + * 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. + */ +package com.intellij.ui.layout + +import com.intellij.ui.components.noteComponent +import com.intellij.util.SmartList +import net.miginfocom.layout.BoundSize +import net.miginfocom.layout.CC +import java.awt.Container +import javax.swing.JComponent +import javax.swing.text.JTextComponent + +/** + * Automatically add `grow` to text components (see isAddGrowX). + */ +internal class MigLayoutBuilder : LayoutBuilderImpl { + private val rows = SmartList() + + override fun addRow(row: Row) { + rows.add(row) + } + + override fun noteRow(text: String) { + // add empty row + addRow(Row(null)) + + val row = Row(null, spanned = true) + row.apply { (noteComponent(text))() } + addRow(row) + } + + override fun build(container: Container) { + val labeled = rows.firstOrNull(Row::labeled) != null + var gapTop = -1 + for (row in rows) { + val lastComponent = row.components.lastOrNull() + if (lastComponent == null) { + if (row === rows.first()) { + // do not add gap for the first row + continue + } + + // https://docs.google.com/document/d/1DKnLkO-7_onA7_NCw669aeMH5ltNvw-QMiQHnXu8k_Y/edit#heading=h.c3849zu3vjhq + // gap = 10u where u = 4px + gapTop = VERTICAL_GAP * 3 + } + + for ((index, component) in row.components.withIndex()) { + // MigLayout in any case always creates CC, so, create instance even if it is not required + val cc = CC() + + if (gapTop != -1) { + cc.vertical.gapBefore = gapToBoundSize(gapTop, false) + gapTop = -1 + } + + if (isAddGrowX(component)) { + cc.growX() + } + + if (component === lastComponent) { + cc.wrap() + // Set span for last component because cell count in other rows may be greater — but we expect that last component + // Spanned row always contains only one component, so, handled as general case. + cc.spanX() + } + + if (labeled && !row.spanned && !row.labeled && component === row.components.first()) { + cc.skip() + } + + if (index >= row.rightIndex) { + cc.horizontal.gapBefore = BoundSize(null, null, null, true, null) + } + + container.add(component, cc) + } + } + } +} + +private fun isAddGrowX(component: JComponent) = component is JTextComponent \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/Row.kt b/platform/platform-impl/src/com/intellij/ui/layout/Row.kt new file mode 100644 index 000000000000..2244ea51eed3 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/layout/Row.kt @@ -0,0 +1,76 @@ +/* + * 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. + */ +package com.intellij.ui.layout + +import com.intellij.BundleBase +import com.intellij.ui.components.Label +import com.intellij.ui.components.Link +import com.intellij.util.SmartList +import com.intellij.util.ui.UIUtil +import java.awt.event.ActionEvent +import javax.swing.ButtonGroup +import javax.swing.JButton +import javax.swing.JComponent +import javax.swing.JLabel + +class Row(private val buttonGroup: ButtonGroup?, internal val labeled: Boolean = false, internal val spanned: Boolean = false) { + var rightIndex = Int.MAX_VALUE + val components = SmartList() + + fun label(text: String, style: UIUtil.ComponentStyle? = null, fontColor: UIUtil.FontColor? = null, bold: Boolean = false) { + Label(text, style, fontColor, bold)() + } + + fun link(text: String, style: UIUtil.ComponentStyle? = null, action: () -> Unit) { + val result = Link(text, action = action) + style?.let { UIUtil.applyStyle(it, result) } + result() + } + + fun button(text: String, actionListener: (event: ActionEvent) -> Unit) { + val button = JButton(BundleBase.replaceMnemonicAmpersand(text)) + button.addActionListener(actionListener) + button() + } + + operator fun JComponent.invoke() { + if (buttonGroup != null && this is JButton) { + buttonGroup.add(this) + } + this@Row.components.add(this) + } + + inline fun right(init: Row.() -> Unit) { + if (rightIndex != Int.MAX_VALUE) { + throw IllegalStateException("right allowed only once") + } + rightIndex = components.size + + init() + } + + @Deprecated(message = "Nested row is prohibited", level = DeprecationLevel.ERROR) + fun row(label: String, init: Row.() -> Unit) { + } + + @Deprecated(message = "Nested row is prohibited", level = DeprecationLevel.ERROR) + fun row(label: JLabel? = null, init: Row.() -> Unit) { + } + + @Deprecated(message = "Nested noteRow is prohibited", level = DeprecationLevel.ERROR) + fun noteRow(text: String) { + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/flags.kt b/platform/platform-impl/src/com/intellij/ui/layout/flags.kt index 05cb4ef9f746..459c11a96689 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/flags.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/flags.kt @@ -57,7 +57,20 @@ enum class CCFlags { */ span, spanX, spanY, + /** + * Splits the cell in a number of sub cells. Basically this means that the next count number of components will be put in the same cell, next to each other with default gaps. + * Only the first component in a cell can set the split, any subsequent "split" keywords in the cell will be ignored. + * "count" defaults to infinite if not specified, which means that "split" alone will put all subsequent components in the same cell. + * "skip", "wrap" and "newline" will break out of the split cell. The latter two will move to a new row/column as usual. + * "skip" will skip out if the splitting and continue in the next cell. + */ split, - grow, push, pushY, pushX, right, skip + grow, push, pushY, pushX, right, + + /** + * Skips a number of cells in the flow. This is used to jump over a number of cells before the next free cell is looked for. + * The skipping is done before this component is put in a cell and thus this cells is affected by it. "count" defaults to 1 if not specified. + */ + skip } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/layout.kt b/platform/platform-impl/src/com/intellij/ui/layout/layout.kt index 55d6ad1b381f..286c0ec9f158 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/layout.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/layout.kt @@ -15,104 +15,69 @@ */ package com.intellij.ui.layout -import com.intellij.BundleBase import com.intellij.ide.BrowserUtil -import com.intellij.openapi.ui.ex.MultiLineLabel -import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.Label +import com.intellij.ui.components.RadioButton import com.intellij.ui.components.labels.LinkLabel -import com.intellij.ui.noteComponent +import com.intellij.ui.components.noteComponent +import com.intellij.ui.layout.LCFlags.* import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil.ComponentStyle +import com.intellij.util.ui.UIUtil.FontColor import net.miginfocom.layout.BoundSize import net.miginfocom.layout.CC import net.miginfocom.layout.UnitValue import java.awt.Component -import java.awt.Font -import java.awt.event.ActionEvent -import javax.swing.* -inline fun panel(vararg layoutConstraints: LCFlags, title: String? = null, init: Panel.() -> Unit) = createPanel(title, layoutConstraints, init) +inline fun panel(title: String? = null, init: LayoutBuilder.() -> Unit) = createPanel2(title, init) -fun JPanel.label(text: String, +inline fun verticalPanel(init: Panel.() -> Unit) = createUnsafePanel(layoutConstraints = arrayOf(noGrid, flowY, fillX), init = init) + +fun Panel.label(text: String, vararg constraints: CCFlags, style: ComponentStyle? = null, - fontColor: UIUtil.FontColor? = null, + fontColor: FontColor? = null, bold: Boolean = false, gapLeft: Int = 0, gapBottom: Int = 0, gapAfter: Int = 0, split: Int = -1) { - val finalText = BundleBase.replaceMnemonicAmpersand(text) - val label: JLabel - if (fontColor == null) { - label = if (finalText.contains('\n')) MultiLineLabel(finalText) else JLabel(finalText) - style?.let { UIUtil.applyStyle(it, label) } - } - else { - label = JBLabel(finalText, style ?: ComponentStyle.REGULAR, fontColor) - } - - if (bold) { - label.font = label.font.deriveFont(Font.BOLD) - } - - add(label, createComponentConstraints(constraints, gapLeft = gapLeft, gapBottom = gapBottom, gapAfter = gapAfter, split = split)) + add(Label(text, style, fontColor, bold), createComponentConstraints(constraints, gapLeft = gapLeft, gapBottom = gapBottom, gapAfter = gapAfter, split = split)) } -fun JPanel.link(text: String, vararg constraints: CCFlags, style: ComponentStyle? = null, action: () -> Unit) { +fun Panel.link(text: String, vararg constraints: CCFlags, style: ComponentStyle? = null, action: () -> Unit) { val result = LinkLabel.create(text, action) style?.let { UIUtil.applyStyle(it, result) } add(result, constraints.create()) } -fun JPanel.link(text: String, url: String, vararg constraints: CCFlags, style: ComponentStyle? = null) { +fun Panel.link(text: String, url: String, vararg constraints: CCFlags, style: ComponentStyle? = null) { val result = LinkLabel.create(text, { BrowserUtil.browse(url) }) style?.let { UIUtil.applyStyle(it, result) } add(result, constraints.create()) } - -fun JPanel.hint(text: String, vararg constraints: CCFlags) { - label(text, style = ComponentStyle.SMALL, fontColor = UIUtil.FontColor.BRIGHTER, constraints = *constraints, gapLeft = 3 * GAP) +fun Panel.hint(text: String, vararg constraints: CCFlags) { + label(text, style = ComponentStyle.SMALL, fontColor = FontColor.BRIGHTER, constraints = *constraints, gapLeft = 3 * GAP) } /** * Hyperlinks are supported (``), new lines and
are not supported. */ -fun JPanel.note(text: String, vararg constraints: CCFlags) { +fun Panel.note(text: String, vararg constraints: CCFlags) { add(noteComponent(text), createComponentConstraints(constraints, gapTop = GAP)) } -fun JPanel.radioButton(text: String, vararg constraints: CCFlags) { +fun Panel.radioButton(text: String, vararg constraints: CCFlags) { add(RadioButton(text), constraints.create()) } -fun JPanel.buttonGroup(vararg buttons: AbstractButton) { - val group = ButtonGroup() - buttons.forEach { - group.add(it) - add(it) - } -} - -fun JPanel.button(text: String, vararg constraints: CCFlags, actionListener: (event: ActionEvent) -> Unit) { - val button = JButton(BundleBase.replaceMnemonicAmpersand(text)) - button.addActionListener(actionListener) - add(button, constraints.create()) -} - -inline fun Panel.panel(title: String, layoutConstraints: Array = emptyArray(), vararg constraints: CCFlags, init: Panel.() -> Unit) { - add(createPanel(title, layoutConstraints, init), constraints.create()) -} - -fun JPanel.panel(title: String, wrappedComponent: Component, vararg constraints: CCFlags) { - val panel = TitledPanel(title) +fun Panel.panel(title: String, wrappedComponent: Component, vararg constraints: CCFlags) { + val panel = com.intellij.ui.components.Panel(title) panel.add(wrappedComponent) add(panel, constraints.create()) } -fun RadioButton(text: String) = JRadioButton(BundleBase.replaceMnemonicAmpersand(text)) - -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) } diff --git a/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt b/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt index 378f619bad9b..295db41c53b2 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt @@ -16,11 +16,11 @@ package com.intellij.ui.layout import com.intellij.ui.IdeBorderFactory +import net.miginfocom.layout.BoundSize import net.miginfocom.layout.CC import net.miginfocom.layout.ConstraintParser import net.miginfocom.layout.LC import net.miginfocom.swing.MigLayout -import java.awt.BorderLayout import java.awt.Component import java.awt.LayoutManager import javax.swing.JPanel @@ -29,10 +29,11 @@ import javax.swing.JPanel // https://docs.google.com/document/d/1DKnLkO-7_onA7_NCw669aeMH5ltNvw-QMiQHnXu8k_Y/edit internal const val GAP = 10 +internal const val VERTICAL_GAP = 5 // 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)) -fun c(insets: String? = "0", gap: String? = "20 5"): LC { +fun c(insets: String? = "0", gap: String? = "20 $VERTICAL_GAP"): LC { // no setter for gap, so, create string to parse val lc = if (gap == null) LC() else ConstraintParser.parseLayoutConstraint("gap ${gap}") insets?.let { @@ -42,8 +43,13 @@ fun c(insets: String? = "0", gap: String? = "20 5"): LC { } // do not use directly -inline fun createPanel(title: String?, layoutConstraints: Array, init: Panel.() -> Unit): JPanel { - val panel = Panel(MigLayout(c().apply(layoutConstraints))) +inline fun createUnsafePanel(title: String? = null, wrap: Int = 0, layoutConstraints: Array, init: Panel.() -> Unit): JPanel { + val constraints = c().apply(layoutConstraints) + if (wrap != 0) { + constraints.wrapAfter(wrap) + } + + val panel = Panel(MigLayout(constraints)) if (title != null) { setTitledBorder(title, panel) } @@ -52,6 +58,17 @@ inline fun createPanel(title: String?, layoutConstraints: Array, in return panel } +inline fun createPanel2(title: String?, init: LayoutBuilder.() -> Unit): JPanel { + val builder = createLayoutBuilder() + builder.init() + + val panel = com.intellij.ui.components.Panel(title, MigLayout(c().fillX())) + builder.`$`.build(panel) + return panel +} + +fun createLayoutBuilder() = LayoutBuilder(MigLayoutBuilder()) + fun setTitledBorder(title: String, panel: JPanel) { val border = IdeBorderFactory.createTitledBorder(title, false) panel.border = border @@ -61,6 +78,8 @@ fun setTitledBorder(title: String, panel: JPanel) { // we have to use own class because we want to use method `add`, but Kotlin cannot select proper method implementation (not Kotlin bug, but intentional change) // and it is required to add invoke operator fun to Component, but use JPanel as receiver class Panel(layout: LayoutManager) : JPanel(layout) { + internal var beforeAdd: ((Component, CC?) -> CC?)? = null + operator fun Component.invoke(vararg constraints: CCFlags) { add(this, constraints.create()) } @@ -72,6 +91,10 @@ class Panel(layout: LayoutManager) : JPanel(layout) { fun add(component: Component, vararg constraints: CCFlags) { add(component, constraints.create()) } + + override fun addImpl(component: Component, constraints: Any?, index: Int) { + super.addImpl(component, beforeAdd?.invoke(component, constraints as? CC) ?: constraints, index) + } } fun Array.create() = if (isEmpty()) null else CC().apply(this) @@ -81,7 +104,12 @@ fun CC.apply(flags: Array): CC { when (flag) { CCFlags.wrap -> isWrap = true CCFlags.grow -> grow() - CCFlags.right -> alignX("right") + + // If you have more than one component in a cell the alignment keywords will not work since the behavior would be indeterministic. + // You can however accomplish the same thing by setting a gap before and/or after the components. + // That gap may have a minimum size of 0 and a preferred size of a really large value to create a "pushing" gap. + // There is even a keyword for this: "push". So "gapleft push" will be the same as "align right" and work for multi-component cells as well. + CCFlags.right -> horizontal.gapBefore = BoundSize(null, null, null, true, null) CCFlags.push -> push() CCFlags.pushX -> pushX() @@ -120,10 +148,4 @@ fun LC.apply(flags: Array): LC { fun JPanel.add(component: Component, vararg constraints: CCFlags) { add(component, constraints.create()) -} - -fun TitledPanel(title: String): JPanel { - val panel = JPanel(BorderLayout()) - setTitledBorder(title, panel) - return panel } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/readme.md b/platform/platform-impl/src/com/intellij/ui/layout/readme.md index 6a89a48e5743..76ed4f462a80 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/readme.md +++ b/platform/platform-impl/src/com/intellij/ui/layout/readme.md @@ -1,12 +1,18 @@ Use `panel` to create UI: ```kotlin -panel(fillX) { - // child components +panel { + row { + // child components + } } ``` + +Rows are created vertically from top to bottom, in the same order as lines of code that call `row`. +Inside one row, you add components from left to right in the same order calls to factory method or `()` appear in each row. + There are two ways to add child components: -* Using factory methods `label`, `button`, `radioButton`, `hint`, `link`, `note`, `panel`, etc. It allows you to create consistent UI and reuse common patterns (for example, `note` automatically adds required top gap). +* Using factory methods `label`, `button`, `radioButton`, `hint`, `link`, etc. It allows you to create consistent UI and reuse common patterns. ```kotlin note("""Do not have an account? Sign Up""", span, wrap) ``` @@ -14,7 +20,7 @@ There are two ways to add child components: ```kotlin val userField = JTextField(credentials?.userName) panel() { - userField(grow, wrap) + row { userField(grow, wrap) } } // use userField variable somehow ``` @@ -23,21 +29,19 @@ There are two ways to add child components: ```kotlin JTextField(credentials?.userName)(grow, wrap) ``` - + Example: ```kotlin -val panel = panel(fillX) { - label("Login to get notified when the submitted\nexceptions are fixed.", span, wrap) - label("Username:") - userField(grow, wrap) - - label("Password:") - passwordField(grow, wrap) - rememberCheckBox(skip, split, grow) - link("Forgot password?", wrap, right) { - // custom action +val panel = panel { + noteRow("Login to get notified when the submitted\nexceptions are fixed.") + row("Username:") { userField() } + row("Password:") { passwordField() } + row { + rememberCheckBox() + right { + link("Forgot password?") { /* custom action */ } + } } - - note("""Do not have an account? Sign Up""", span, wrap) + noteRow("""Do not have an account? Sign Up""") } ``` \ No newline at end of file diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/DataViewsConfigurableUi.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/DataViewsConfigurableUi.java index 3bf09df6835d..ac97a72738bd 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/DataViewsConfigurableUi.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/settings/DataViewsConfigurableUi.java @@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; -import static com.intellij.ui.layout.LayoutImplKt.TitledPanel; +import static com.intellij.ui.components.ComponentsKt.Panel; public class DataViewsConfigurableUi { public static final String DEBUGGER_VALUE_TOOLTIP_AUTO_SHOW_KEY = "debugger.valueTooltipAutoShow"; @@ -91,6 +91,6 @@ public class DataViewsConfigurableUi { } private void createUIComponents() { - myEditorSettingsPanel = TitledPanel("Editor"); + myEditorSettingsPanel = Panel("Editor"); } } \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java b/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java index 89fc1498ec2f..0d74f8042183 100644 --- a/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java @@ -21,6 +21,7 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.util.PathUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import git4idea.config.SSHConnectionSettings; @@ -35,6 +36,8 @@ import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import static com.intellij.credentialStore.CredentialAttributesKt.CredentialAttributes; + /** * Swing GUI handler for the SSH events */ @@ -69,10 +72,12 @@ public class GitSSHGUIHandler { @Nullable public String askPassphrase(final String username, final String keyPath, boolean resetPassword, final String lastError) { String error = processLastError(resetPassword, lastError); - return PasswordSafePromptDialog.askPassphrase(myProject, GitBundle.getString("ssh.ask.passphrase.title"), - GitBundle.message("ssh.askPassphrase.message", keyPath, username), - GitSSHGUIHandler.class, "PASSPHRASE:" + keyPath, resetPassword, error - ); + return PasswordSafePromptDialog.askPassword(myProject, + GitBundle.getString("ssh.ask.passphrase.title"), + "Password for the SSH key \"" + PathUtil.getFileName(keyPath) + "\":", + CredentialAttributes(GitSSHGUIHandler.class, "PASSPHRASE:" + keyPath), + resetPassword, + error); } /** diff --git a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties index f4597806bfa6..180e14c96e2b 100644 --- a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties +++ b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties @@ -349,8 +349,6 @@ root.tracker.message.title=Invalid Git Roots root.tracker.message=

Some configured Git VCS roots are not under Git or have Git repositories in subdirectories without a configured VCS root. Configure.

select.branch.to.checkout=Select branch to checkout show.all.paths.affected.action.name=Show All Affected Paths -ssh.ask.passphrase.title=SSH Key Passphrase -ssh.askPassphrase.message=Please enter passphrase for the private key {0} (the user name is {1}) ssh.changed.host.key=The server host key for the host {0}:{1} has changed to {2} (type {3}).\nDo you want to accept the changed key? ssh.confirm.key.titile=Confirm SSH Server Key ssh.error.title=Authentication error diff --git a/plugins/settings-repository/resources/messages/IcsBundle.properties b/plugins/settings-repository/resources/messages/IcsBundle.properties index 10c2767d7351..0147c44bf099 100644 --- a/plugins/settings-repository/resources/messages/IcsBundle.properties +++ b/plugins/settings-repository/resources/messages/IcsBundle.properties @@ -38,5 +38,4 @@ login.other.git.provider.note=Consider to configure ("ics", icsMessage("ics.settings"), "reference.settings.ics") { @@ -56,7 +55,7 @@ internal class IcsConfigurableUi : ConfigurableUi, Disposable { saveSettings(settings, icsManager.settingsFile) } - override fun getComponent() = panel(noGrid, flowY) { + override fun getComponent() = verticalPanel { editors.get(0).component() autoSync() hint("Use VCS -> Sync Settings to sync when you want") diff --git a/plugins/settings-repository/src/authForm.kt b/plugins/settings-repository/src/authForm.kt index f55e87cb97dc..2175e40f4a61 100644 --- a/plugins/settings-repository/src/authForm.kt +++ b/plugins/settings-repository/src/authForm.kt @@ -19,11 +19,9 @@ import com.intellij.credentialStore.Credentials import com.intellij.credentialStore.OneTimeString import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.invokeAndWaitIfNeed -import com.intellij.openapi.ui.dialog import com.intellij.ui.DocumentAdapter +import com.intellij.ui.components.dialog import com.intellij.ui.layout.* -import com.intellij.ui.layout.CCFlags.* -import com.intellij.ui.layout.LCFlags.* import com.intellij.util.PathUtilRt import com.intellij.util.text.nullize import com.intellij.util.text.trimMiddle @@ -45,43 +43,40 @@ fun showAuthenticationForm(credentials: Credentials?, uri: String, host: String? username = path.substring(1, if (firstSlashIndex == -1) path.length else firstSlashIndex) } - val message = if (sshKeyFile == null) icsMessage("log.in.to", uri.trimMiddle(50)) else icsMessage("enter.your.password.for.ssh.key", PathUtilRt.getFileName(sshKeyFile)) + val message = if (sshKeyFile == null) icsMessage("log.in.to", uri.trimMiddle(50)) else "Enter your password for the SSH key \"${PathUtilRt.getFileName(sshKeyFile)}\":" return invokeAndWaitIfNeed { val userField = JTextField(username) val passwordField = JPasswordField(credentials?.password?.toString()) - val centerPanel = panel(fillX) { - label(message, wrap, span, bold = true, gapBottom = 10) - + val centerPanel = panel { + noteRow(message) if (sshKeyFile == null && !isGitHub) { - label("Username:") - userField(grow, wrap) + row("Username:") { userField() } } - label(if (sshKeyFile == null && isGitHub) "Token:" else "Password:") - passwordField(grow, wrap) + row(if (sshKeyFile == null && isGitHub) "Token:" else "Password:") { passwordField() } - note?.let { note(it, skip) } + note?.let { noteRow(it) } } val authenticationForm = dialog( title = "Settings Repository", - centerPanel = centerPanel, - preferedFocusComponent = if (userField.parent == null) passwordField else userField, + panel = centerPanel, + focusedComponent = if (userField.parent == null) passwordField else userField, okActionEnabled = false) passwordField.document.addDocumentListener(object : DocumentAdapter() { override fun textChanged(e: DocumentEvent) { - authenticationForm.okActionEnabled(e.document.length != 0) + authenticationForm.isOKActionEnabled = e.document.length != 0 } }) - authenticationForm.okActionEnabled(false) + authenticationForm.isOKActionEnabled = false if (authenticationForm.showAndGet()) { username = sshKeyFile ?: userField.text.nullize(true) - val passwordChars = passwordField.password - Credentials(username, if (passwordChars == null || passwordChars.isEmpty()) (if (username == null) null else OneTimeString("x-oauth-basic")) else OneTimeString(passwordChars)) + val passwordChars = passwordField.password.nullize() + Credentials(username, if (passwordChars == null) (if (username == null) null else OneTimeString("x-oauth-basic")) else OneTimeString(passwordChars)) } else { null diff --git a/plugins/settings-repository/src/repositoryListEditor.kt b/plugins/settings-repository/src/repositoryListEditor.kt index 9c52b96d8601..dbfa024bbbbb 100644 --- a/plugins/settings-repository/src/repositoryListEditor.kt +++ b/plugins/settings-repository/src/repositoryListEditor.kt @@ -53,9 +53,10 @@ internal fun createRepositoryListEditor(): ConfigurableUi { override fun isModified(settings: IcsSettings) = editor.isModified override fun getComponent() = panel { - label("Repository:") - editor.comboBox() - deleteButton() + row("Repository:") { + editor.comboBox() + deleteButton() + } } override fun apply(settings: IcsSettings) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java index 8c6d8668066d..ebfa4f238389 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java @@ -257,10 +257,6 @@ public class StudyTwitterUtils { init(); } - public void setOKActionEnabled(boolean isEnabled) { - super.setOKActionEnabled(isEnabled); - } - @Nullable @Override protected JComponent createCenterPanel() {