kotlin UI DSL variant 2 — more readable

make setOKActionEnabled public

init to fix ask password dialog
This commit is contained in:
Vladimir Krivosheev
2016-09-13 18:33:37 +02:00
parent c7f3424ad1
commit 3a1476e482
30 changed files with 660 additions and 406 deletions
@@ -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<br>(" + subNote + ").";
}
myPromptLabel = ComponentsKt.htmlComponent("Master password is required to convert saved passwords.<br>" + note, UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
myPromptLabel = ComponentsKt
.htmlComponent("Master password is required to convert saved passwords.<br>" + note, UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
}
}
@@ -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<PasswordSafeConfigurableUi, PasswordSafeSettings>("application.passwordSafe", "Passwords", "reference.ide.settings.password.safe") {
override fun getSettings() = settings
@@ -49,16 +49,23 @@ class PasswordSafeConfigurableUi : ConfigurableUi<PasswordSafeSettings> {
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 {
@@ -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);
@@ -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);
}
}
@@ -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();
@@ -25,7 +25,7 @@ inline fun <T> runReadAction(crossinline runnable: () -> T): T = ApplicationMana
/**
* @exclude Internal use only
*/
fun <T> invokeAndWaitIfNeed(runnable: () -> T): T {
fun <T> invokeAndWaitIfNeed(modalityState: ModalityState? = null, runnable: () -> T): T {
val app = ApplicationManager.getApplication()
if (app == null) {
if (SwingUtilities.isEventDispatchThread()) {
@@ -39,7 +39,7 @@ fun <T> invokeAndWaitIfNeed(runnable: () -> T): T {
}
else {
var result: T? = null
app.invokeAndWait { result = runnable() }
app.invokeAndWait({ result = runnable() }, modalityState ?: ModalityState.defaultModalityState())
return result as T
}
}
@@ -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 <code>true</code> 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;
}
@@ -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
}
@@ -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)
fun String.trimMiddle(maxLength: Int): String? = StringUtil.trimMiddle(this, maxLength)
fun CharArray.nullize() = if (isEmpty()) null else this
@@ -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? <a href="https://account.jetbrains.com/login">Sign Up</a>""", span, wrap)
noteRow("""Do not have an account? <a href="https://account.jetbrains.com/login">Sign Up</a>""")
}
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
@@ -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<Credentials> 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);
}
}
@@ -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
}
}
}
@@ -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());
}
@@ -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("<a(?:\\s+href\\s*=\\s*[\"']([^\"']*)[\"'])?\\s*>([^<]*)</a>")
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 = "<html><head>${UIUtil.getCssFontDeclaration(font, UIUtil.getLabelForeground(), null, null)}</head><body>$text</body></html>"
}
pane.border = null
pane.disabledTextColor = UIUtil.getLabelDisabledForeground()
pane.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE)
return pane
}
@@ -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("<a(?:\\s+href\\s*=\\s*[\"']([^\"']*)[\"'])?\\s*>([^<]*)</a>")
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 = "<html><head>${UIUtil.getCssFontDeclaration(font, UIUtil.getLabelForeground(), null, null)}</head><body>$text</body></html>"
}
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()
}
}
}
@@ -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)
}
@@ -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<Row>()
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
@@ -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<JComponent>()
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) {
}
}
@@ -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
}
@@ -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 (`<a href=""></a>`), new lines and <br> 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<out LCFlags> = 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)
}
@@ -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<out LCFlags>, init: Panel.() -> Unit): JPanel {
val panel = Panel(MigLayout(c().apply(layoutConstraints)))
inline fun createUnsafePanel(title: String? = null, wrap: Int = 0, layoutConstraints: Array<out LCFlags>, 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<out LCFlags>, 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<out CCFlags>.create() = if (isEmpty()) null else CC().apply(this)
@@ -81,7 +104,12 @@ fun CC.apply(flags: Array<out CCFlags>): 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<out LCFlags>): 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
}
@@ -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? <a href="https://account.jetbrains.com/login">Sign Up</a>""", 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? <a href="https://example.com/login">Sign Up</a>""", span, wrap)
noteRow("""Do not have an account? <a href="https://account.jetbrains.com/login">Sign Up</a>""")
}
```
@@ -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");
}
}
@@ -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);
}
/**
@@ -349,8 +349,6 @@ root.tracker.message.title=Invalid Git Roots
root.tracker.message=<p>Some configured Git VCS roots are not under Git or have Git repositories in subdirectories without a configured VCS root. <a href="">Configure.</a></p>
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=<html>The server host key for the host {0}:{1} has changed to {2} (type {3}).\nDo you want to accept the changed key?</html>
ssh.confirm.key.titile=Confirm SSH Server Key
ssh.error.title=Authentication error
@@ -38,5 +38,4 @@ login.other.git.provider.note=Consider to configure <a href="https://help.github
settings.upstream.url=Upstream URL\:
log.in.to=Log in to {0}.
enter.your.password.for.ssh.key=Enter your password for the SSH key \"{0}\".
log.in.to=Log in to {0}.
@@ -20,7 +20,6 @@ import com.intellij.openapi.options.ConfigurableBase
import com.intellij.openapi.options.ConfigurableUi
import com.intellij.ui.layout.*
import com.intellij.ui.layout.CCFlags.*
import com.intellij.ui.layout.LCFlags.*
import javax.swing.JCheckBox
internal class IcsConfigurable : ConfigurableBase<IcsConfigurableUi, IcsSettings>("ics", icsMessage("ics.settings"), "reference.settings.ics") {
@@ -56,7 +55,7 @@ internal class IcsConfigurableUi : ConfigurableUi<IcsSettings>, 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")
+13 -18
View File
@@ -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
@@ -53,9 +53,10 @@ internal fun createRepositoryListEditor(): ConfigurableUi<IcsSettings> {
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) {
@@ -257,10 +257,6 @@ public class StudyTwitterUtils {
init();
}
public void setOKActionEnabled(boolean isEnabled) {
super.setOKActionEnabled(isEnabled);
}
@Nullable
@Override
protected JComponent createCenterPanel() {