diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/HttpUtil.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/HttpUtil.java index 8c45b35..a96eb29 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/core/HttpUtil.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/HttpUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 IBM Corporation and others. + * Copyright (c) 2018, 2020 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -12,7 +12,6 @@ package org.eclipse.codewind.intellij.core; import okhttp3.*; -import org.eclipse.codewind.intellij.core.cli.AuthToken; import org.json.JSONArray; import org.json.JSONObject; @@ -130,55 +129,55 @@ public static HttpResult get(URI uri) throws IOException { return get(uri, null); } - public static HttpResult get(URI uri, AuthToken auth) throws IOException { + public static HttpResult get(URI uri, IAuthInfo auth) throws IOException { return sendRequest("GET", uri, auth, null); } - public static HttpResult get(URI uri, AuthToken auth, int connectTimeoutMS, int readTimeoutMS) throws IOException { + public static HttpResult get(URI uri, IAuthInfo auth, int connectTimeoutMS, int readTimeoutMS) throws IOException { return sendRequest("GET", uri, auth, null, connectTimeoutMS, readTimeoutMS); } - public static HttpResult post(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("POST", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult post(URI uri, AuthToken auth, JSONObject payload, int readTimeoutSeconds) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth, JSONObject payload, int readTimeoutSeconds) throws IOException { return sendRequest("POST", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, readTimeoutSeconds * 1000); } - public static HttpResult post(URI uri, AuthToken auth) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth) throws IOException { return sendRequest("POST", uri, auth, null); } - public static HttpResult put(URI uri, AuthToken auth) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth) throws IOException { return sendRequest("PUT", uri, auth, null); } - public static HttpResult put(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("PUT", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult put(URI uri, AuthToken auth, JSONObject payload, int readTimoutSeconds) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth, JSONObject payload, int readTimoutSeconds) throws IOException { return sendRequest("PUT", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult head(URI uri, AuthToken auth) throws IOException { + public static HttpResult head(URI uri, IAuthInfo auth) throws IOException { return sendRequest("HEAD", uri, auth, null); } - public static HttpResult delete(URI uri, AuthToken auth) throws IOException { + public static HttpResult delete(URI uri, IAuthInfo auth) throws IOException { return delete(uri, auth, null); } - public static HttpResult delete(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult delete(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("DELETE", uri, auth, payload); } - public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult sendRequest(String method, URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest(method, uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSONObject payload, int connectTimeoutMS, int readTimeoutMS) throws IOException { + public static HttpResult sendRequest(String method, URI uri, IAuthInfo auth, JSONObject payload, int connectTimeoutMS, int readTimeoutMS) throws IOException { HttpURLConnection connection = null; if (payload != null) { Logger.log("Making a " + method + " request on " + uri + " with payload: " + payload.toString()); @@ -210,11 +209,11 @@ public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSO } } - private static void addAuthorization(HttpURLConnection connection, AuthToken auth) { - if (sslContext == null || auth == null || auth.getToken() == null || auth.getTokenType() == null || !(connection instanceof HttpsURLConnection)) { + private static void addAuthorization(HttpURLConnection connection, IAuthInfo auth) { + if (sslContext == null || auth == null || !auth.isValid() || !(connection instanceof HttpsURLConnection)) { return; } - connection.setRequestProperty("Authorization", auth.getTokenType() + " " + auth.getToken()); + connection.setRequestProperty("Authorization", auth.getHttpAuthorization()); ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory()); ((HttpsURLConnection)connection).setHostnameVerifier(hostnameVerifier); } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/IAuthInfo.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/IAuthInfo.java new file mode 100644 index 0000000..4396609 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/IAuthInfo.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.core; + +public interface IAuthInfo { + public boolean isValid(); + public String getHttpAuthorization(); +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/AuthToken.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/AuthToken.java index d1b64f2..04ce949 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/AuthToken.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/AuthToken.java @@ -11,10 +11,11 @@ package org.eclipse.codewind.intellij.core.cli; +import org.eclipse.codewind.intellij.core.IAuthInfo; import org.eclipse.codewind.intellij.core.connection.JSONObjectResult; import org.json.JSONObject; -public class AuthToken extends JSONObjectResult { +public class AuthToken extends JSONObjectResult implements IAuthInfo { private static final String ACCESS_TOKEN_KEY = "access_token"; private static final String TOKEN_TYPE_KEY = "token_type"; @@ -31,4 +32,15 @@ public String getTokenType() { return getString(TOKEN_TYPE_KEY); } + @Override + public boolean isValid() { + return getToken() != null && getTokenType() != null; + } + + @Override + public String getHttpAuthorization() { + return getTokenType() + " " + getToken(); + } + + } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/CLIUtil.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/CLIUtil.java index a4c119a..abdd53b 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/CLIUtil.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/CLIUtil.java @@ -98,7 +98,7 @@ public static ProcessBuilder createCWCTLProcess(String[] globalOptions, String[] addOptions(cmdList, cmd); addOptions(cmdList, options); addOptions(cmdList, args); - Logger.log(cmdList.stream().collect(Collectors.joining(" "))); +// Logger.log(cmdList.stream().collect(Collectors.joining(" "))); String[] command = cmdList.toArray(new String[cmdList.size()]); ProcessBuilder builder = new ProcessBuilder(command); if (PlatformUtil.getOS() == PlatformUtil.OperatingSystem.MAC) { @@ -182,13 +182,13 @@ public static void checkResult(String[] command, ProcessResult result, boolean c // system output. // Expected format: // {"error":"con_not_found","error_description":"Connection AGALJKAFD not found"} + String commandName = command.length > 0 ? command[0] : ""; // cwctl with no parameter; try { if (result.getOutput() != null && !result.getOutput().isEmpty()) { JSONObject obj = new JSONObject(result.getOutput()); if (obj.has(ERROR_KEY) && obj.has(ERROR_DESCRIPTION_KEY)) { - String msg = String.format("The cwctl '%s' command failed with error: %s", CoreUtil.formatString(command, " "), obj.getString(ERROR_DESCRIPTION_KEY)); //$NON-NLS-1$ - Logger.logWarning(msg); - throw new IOException(obj.getString(ERROR_DESCRIPTION_KEY)); + String msg = String.format("The cwctl '%s' command failed with error: %s", commandName, obj.getString(ERROR_DESCRIPTION_KEY)); //$NON-NLS-1$ + throw new CLIException(obj.getString(ERROR_KEY), obj.getString(ERROR_DESCRIPTION_KEY)); } } } catch (JSONException e) { @@ -199,19 +199,27 @@ public static void checkResult(String[] command, ProcessResult result, boolean c String msg; String error = result.getError() != null && !result.getError().isEmpty() ? result.getError() : result.getOutput(); if (error == null || error.isEmpty()) { - msg = String.format("The cwctl '%s' command exited with return code %d", CoreUtil.formatString(command, " "), result.getExitValue()); //$NON-NLS-1$ + msg = String.format("The cwctl '%s' command exited with return code %d", commandName, result.getExitValue()); } else { - msg = String.format("The cwctl '%s' command exited with return code %d and error: %s", CoreUtil.formatString(command, " "), result.getExitValue(), error); //$NON-NLS-1$ + msg = String.format("The cwctl '%s' command exited with return code %d and error: %s", commandName, result.getExitValue(), error); } - Logger.logWarning(msg); throw new IOException(msg); } else if (checkOutput && (result.getOutput() == null || result.getOutput().isEmpty())) { - String msg = String.format("The cwctl '%s' command exited with return code 0 but the output was empty", CoreUtil.formatString(command, " ")); //$NON-NLS-1$ - Logger.logWarning(msg); + String msg = String.format("The cwctl '%s' command exited with return code 0 but the output was empty", commandName); //$NON-NLS-1$ throw new IOException(msg); } - - Logger.log(String.format("Result of the cwctl '%s' command: \n%s", CoreUtil.formatString(command, " "), Optional.ofNullable(result.getOutput()).orElse(""))); } + @SuppressWarnings("serial") + public static class CLIException extends IOException { + + public final String errorId; + public final String errorMsg; + + public CLIException(String errorId, String errorMsg) { + super(errorMsg); + this.errorId = errorId; + this.errorMsg = errorMsg; + } + } } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/TemplateUtil.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/TemplateUtil.java index 57676a6..1167b5a 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/TemplateUtil.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/cli/TemplateUtil.java @@ -43,6 +43,10 @@ public class TemplateUtil { private static final String URL_OPTION = "--url"; private static final String NAME_OPTION = "--name"; private static final String DESCRIPTION_OPTION = "--description"; + private static final String USERNAME_OPTION = "--username"; + private static final String PASSWORD_OPTION = "--password"; + private static final String PERSONAL_ACCESS_TOKEN_OPTION = "--personalAccessToken"; + public static List listTemplates(boolean enabledOnly, String conid, ProgressIndicator monitor) throws IOException, JSONException, TimeoutException { monitor.setIndeterminate(true); @@ -84,7 +88,29 @@ public static List listTemplateSources(String conid, ProgressInd } } } - + + public static void addTemplateSource(String url, String username, String password, String accessToken, String name, String description, String conid, ProgressIndicator monitor) throws IOException, JSONException, TimeoutException { + List options = new ArrayList(); + options.add(URL_OPTION); + options.add(url); + if (username != null && password != null) { + options.add(USERNAME_OPTION); + options.add(username); + options.add(PASSWORD_OPTION); + options.add(password); + } else if (accessToken != null) { + options.add(PERSONAL_ACCESS_TOKEN_OPTION); + options.add(accessToken); + } + options.add(NAME_OPTION); + options.add(name); + options.add(DESCRIPTION_OPTION); + options.add(description); + options.add(CLIUtil.CON_ID_OPTION); + options.add(conid); + runTemplateSourceCmd(REPO_ADD_CMD, options.toArray(new String[options.size()]), null, monitor); + } + public static void addTemplateSource(String url, String name, String description, String conid, ProgressIndicator monitor) throws IOException, JSONException, TimeoutException { runTemplateSourceCmd(REPO_ADD_CMD, new String[] {URL_OPTION, url, NAME_OPTION, name, DESCRIPTION_OPTION, description, CLIUtil.CON_ID_OPTION, conid}, null, monitor); } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/core/connection/JSONObjectResult.java b/dev/src/main/java/org/eclipse/codewind/intellij/core/connection/JSONObjectResult.java index 18e8f33..cc929ee 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/core/connection/JSONObjectResult.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/core/connection/JSONObjectResult.java @@ -19,7 +19,7 @@ import org.json.JSONException; import org.json.JSONObject; -public abstract class JSONObjectResult { +public class JSONObjectResult { protected final JSONObject result; protected final String type; diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/CodewindToolWindow.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/CodewindToolWindow.java index c72951d..912114f 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/CodewindToolWindow.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/CodewindToolWindow.java @@ -76,6 +76,7 @@ public class CodewindToolWindow extends JBPanel { private final AnAction restartDebugModeAction; private final AnAction attachDebuggerAction; private final AnAction openShellAction; + private final AnAction manageReposAction; private final AnAction newProjectAction; @@ -113,6 +114,7 @@ public CodewindToolWindow() { restartDebugModeAction = new RestartDebugModeAction(); attachDebuggerAction = new AttachDebuggerAction(); openShellAction = new OpenContainerShellAction(); + manageReposAction = new ManageReposAction(); newProjectAction = new NewCodewindProjectAction(); @@ -275,6 +277,8 @@ private void handleLocalConnectionPopup(LocalConnection connection, Component co actions.add(newProjectAction); actions.add(addExistingProjectAction); actions.addSeparator(); + actions.add(manageReposAction); + actions.addSeparator(); InstallStatus status = CodewindManager.getManager().getInstallStatus(); if (status.isInstalled()) { // a supported version of Codewind is installed diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/IconCache.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/IconCache.java index 7034d10..5b03344 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/IconCache.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/IconCache.java @@ -32,6 +32,7 @@ public class IconCache { public static final String ICONS_THEMELESS_PROJECT_TYPES_SPRING_SVG = "/icons/themeless/project-types/spring.svg"; public static final String ICONS_THEMELESS_PROJECT_TYPES_SWIFT_SVG = "/icons/themeless/project-types/swift.svg"; public static final String ICONS_CODEWIND_13PX_SVG = "/META-INF/pluginIcon13x13.svg"; + public static final String ICONS_CODEWIND_BANNER_PNG = "/icons/codewindBanner.png"; private static final Map iconCache = new HashMap<>(); diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/actions/ManageReposAction.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/actions/ManageReposAction.java new file mode 100644 index 0000000..b076989 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/actions/ManageReposAction.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.ui.treeStructure.Tree; +import org.eclipse.codewind.intellij.core.CoreUtil; +import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.cli.TemplateUtil; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.core.connection.RepositoryInfo; +import org.eclipse.codewind.intellij.ui.templates.RepositoryManagementDialog; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.TreePath; +import java.util.List; + +import static com.intellij.openapi.actionSystem.PlatformDataKeys.CONTEXT_COMPONENT; +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + + +public class ManageReposAction extends AnAction { + + public ManageReposAction() { + super(message("RepoMgmtActionLabel")); + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + CodewindConnection connection = getSelection(e); + e.getPresentation().setEnabled(connection != null && connection.isConnected()); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + CodewindConnection connection = getSelection(e); + final List[] repoListArray = new List[1]; + try { + ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + try { + ProgressIndicator mon = new EmptyProgressIndicator(); + repoListArray[0] = TemplateUtil.listTemplateSources(connection.getConid(), mon); + } catch (Exception e1) { + Logger.logWarning("An error occurred trying to get the template sources for: " + connection.getName() + ": " + e1.getMessage()); + } + }, message("RepoListTask", "connection"), false, e.getProject()); + RepositoryManagementDialog dialog = new RepositoryManagementDialog(e.getProject(), connection, repoListArray[0]); + // Init must be done after + dialog.initForm(); + boolean rc = dialog.showAndGet(); + if (rc) { + dialog.updateRepos(); + } + } catch (Exception ex) { + CoreUtil.openDialog(true, message("RepoListErrorTitle"), message("RepoListErrorMsg", ex)); + } + } + + private CodewindConnection getSelection(@NotNull AnActionEvent e) { + Object data = e.getData(CONTEXT_COMPONENT); + if (!(data instanceof Tree)) { + Logger.logDebug("Unrecognized component for : " + data); + return null; + } + Tree tree = (Tree) data; + TreePath treePath = tree.getSelectionPath(); + if (treePath == null) { + Logger.logDebug("No selection path for ManageReposAction: " + tree); + return null; + } + Object node = treePath.getLastPathComponent(); + if (!(node instanceof CodewindConnection)) { + return null; + } + return (CodewindConnection) node; + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/AbstractCodewindDialogWrapper.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/AbstractCodewindDialogWrapper.java index c794c58..f9ad02c 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/AbstractCodewindDialogWrapper.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/AbstractCodewindDialogWrapper.java @@ -27,9 +27,15 @@ public abstract class AbstractCodewindDialogWrapper extends DialogWrapper { private String helpUrl; + /** + * Note: init() should be called after the dialog wrapper has been instantiated + * + * @param project + * @param title + * @param helpUrl + */ public AbstractCodewindDialogWrapper(Project project, String title, String helpUrl) { super(project,true); // Can use current dialog window as parent for other child dialogs - init(); setTitle(title); this.helpUrl = helpUrl; } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WidgetUtils.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WidgetUtils.java index f868818..966f06a 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WidgetUtils.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WidgetUtils.java @@ -12,24 +12,26 @@ import com.intellij.ide.browsers.BrowserLauncher; import com.intellij.ui.HyperlinkAdapter; +import org.eclipse.codewind.intellij.core.Logger; +import javax.annotation.Nonnull; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; +import javax.swing.JPasswordField; import javax.swing.JTextArea; import javax.swing.JTextField; +import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.event.HyperlinkEvent; import java.awt.Color; import java.awt.Cursor; -import java.awt.Font; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -52,6 +54,34 @@ public static JTextArea createJTextArea(String textValue) { return textArea; } + /** + * Create a readonly JTextPane with a border + * @param textValue + * @return + */ + public static JTextPane createTextPane(String textValue, boolean showBorder) { + JTextPane textPane = new JTextPane(); + JTextField tf = new JTextField(); + textPane.setContentType("text/string"); + textPane.setEditable(false); + textPane.setOpaque(false); + if (showBorder) { + textPane.setBorder( + BorderFactory.createCompoundBorder( + tf.getBorder(), + BorderFactory.createEmptyBorder(3, 6, 3, 6) // between border and text + ) + ); + } + textPane.setFont(tf.getFont()); // Set the font of the editorPane to be the same as a text field + textPane.setSelectedTextColor(tf.getSelectedTextColor()); + textPane.setSelectionColor(tf.getSelectionColor()); + textPane.setText(textValue); + textPane.setRequestFocusEnabled(true); + textPane.setFocusable(true); + return textPane; + } + public static JLabel createHyperlinkUsingLabel(String urlValue) { JLabel hyperlink = new JLabel(urlValue); hyperlink.setForeground(Color.BLUE.darker()); // TODO: Possible contrast issue? @@ -64,7 +94,7 @@ public void mouseClicked(MouseEvent mouseEvent) { BrowserLauncher.getInstance().browse(new URI(hyperlink.getText())); // Desktop.getDesktop().browse(new URI(hyperlink.getText())); } catch (URISyntaxException e) { - e.printStackTrace(); + Logger.logTrace(e); } } }); @@ -78,10 +108,54 @@ public void mouseClicked(MouseEvent mouseEvent) { return hyperlink; } - public static JEditorPane createHyperlink(String urlValue) { + public static JTextPane createHyperlinkUsingTextPane(String urlValue, boolean showBorder) { + JTextPane hyperlink = new JTextPane(); + JTextField tf = new JTextField(); + if (urlValue.length() == 0) { + hyperlink.setContentType("text/string"); + } else { + hyperlink.setContentType("text/html"); + } + hyperlink.setEditable(false); + hyperlink.setOpaque(false); + if (showBorder) { + hyperlink.setBorder( + BorderFactory.createCompoundBorder( + tf.getBorder(), + BorderFactory.createEmptyBorder(3, 6, 3, 6) // between border and text + ) + ); + } + hyperlink.setFont(tf.getFont()); // Set the font of the editorPane to be the same as a text field + hyperlink.setSelectedTextColor(tf.getSelectedTextColor()); + hyperlink.setSelectionColor(tf.getSelectionColor()); + hyperlink.setText(urlValue); + hyperlink.setRequestFocusEnabled(true); + hyperlink.setFocusable(true); + final InputMap inputMap = hyperlink.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + final KeyStroke goKey = KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_MASK); + inputMap.put(goKey, "activate-link-action"); + hyperlink.addHyperlinkListener(new HyperlinkAdapter() { + @Override + protected void hyperlinkActivated(HyperlinkEvent event) { + try { + BrowserLauncher.getInstance().browse(event.getURL().toURI()); + } catch (Exception e) { + Logger.logTrace(e); + } + } + }); + return hyperlink; + } + + public static JEditorPane createHyperlink(@Nonnull String urlValue) { JEditorPane editorPane = new JEditorPane(); JTextField tf = new JTextField(); - editorPane.setContentType("text/html"); + if (urlValue.length() == 0) { + editorPane.setContentType("text/string"); + } else { + editorPane.setContentType("text/html"); + } editorPane.setEditable(false); editorPane.setOpaque(false); editorPane.setBorder( BorderFactory.createCompoundBorder( @@ -100,12 +174,29 @@ public static JEditorPane createHyperlink(String urlValue) { protected void hyperlinkActivated(HyperlinkEvent event) { try { BrowserLauncher.getInstance().browse(event.getURL().toURI()); -// Desktop.getDesktop().browse(event.getURL().toURI()); } catch (Exception e) { - e.printStackTrace(); + Logger.logTrace(e); } } }); return editorPane; } + + public static String getTextValue(JTextField text) { + return text.getText() == null || text.getText().trim().isEmpty() ? "" : text.getText().trim(); + } + + /** + * Do not log password + * @param passwordField + * @return + */ + public static String getPassword(JPasswordField passwordField) { + char[] password = passwordField.getPassword(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < password.length; i++) { + sb.append(password[i]); + } + return sb.toString(); + } } \ No newline at end of file diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.form b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.form new file mode 100644 index 0000000..24e9f4c --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.form @@ -0,0 +1,85 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.java new file mode 100644 index 0000000..feea952 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/form/WizardHeaderForm.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.form; + +import com.intellij.ui.components.JBLabel; +import org.eclipse.codewind.intellij.ui.IconCache; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextPane; + +public class WizardHeaderForm { + private JPanel contentPane; + private JPanel iconPanel; + private JTextPane descriptionTextPane; + private JLabel icon; + private JFormattedTextField titleField; + private JPanel textPanel; + private JPanel headerPanel; + + private final String title, description; + + public WizardHeaderForm(String title, String description) { + this.title = title; + this.description = description; + } + + private void createUIComponents() { + titleField = new JFormattedTextField(); + titleField.setBorder(BorderFactory.createEmptyBorder()); + titleField.setText(title); + descriptionTextPane = WidgetUtils.createTextPane(description, false); + icon = new JBLabel(); + icon.setIcon(IconCache.getCachedIcon(IconCache.ICONS_CODEWIND_BANNER_PNG)); + } + + /** + * Can be customized + * @param customIcon + */ + public void setIcon(Icon customIcon) { + icon.setIcon(customIcon); + } + + public JPanel getContentPane() { + return this.contentPane; + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/CodewindModuleBuilder.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/CodewindModuleBuilder.java index 6e64287..33df2a8 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/CodewindModuleBuilder.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/CodewindModuleBuilder.java @@ -27,6 +27,7 @@ import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; @@ -39,8 +40,10 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import org.eclipse.codewind.intellij.core.CodewindApplication; +import org.eclipse.codewind.intellij.core.CoreUtil; import org.eclipse.codewind.intellij.core.FileUtil; import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.cli.ProjectUtil; import org.eclipse.codewind.intellij.core.connection.CodewindConnection; import org.eclipse.codewind.intellij.core.connection.ConnectionManager; import org.eclipse.codewind.intellij.core.connection.LocalConnection; @@ -54,6 +57,9 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.List; @@ -183,9 +189,15 @@ private Module postCommitModule(@NotNull Module module) throws ProcessCanceledEx SetupCodewindProjectRunnable setupProjectRunnable = new SetupCodewindProjectRunnable(path, name, url, language, projectType, conid, javaHome); // This MUST run synchronously, even if we have to wait for the image to download. If there is an issue, the user can cancel // True if operation completed successfully, or false if cancelled. - isSuccessful = ProgressManager.getInstance().runProcessWithProgressSynchronously(setupProjectRunnable, message("NewProjectWizard_ProgressTitle"), true, ideaProject); - if (!isSuccessful) { // Is Cancelled - throw new ProcessCanceledException(); + try { + // Handle error conditions. The template source repo might not be found or available. IOException (404) possible + ProgressManager.getInstance().runProcessWithProgressSynchronously(setupProjectRunnable, message("NewProjectWizard_ProgressTitle"), true, ideaProject); + } catch (Exception e) { + if (!(e instanceof ProcessCanceledException)) { // If the user cancelled it, don't log it + Throwable thrown = Logger.unwrap(e); + Logger.logWarning("An error occurred creating project " + name, thrown); + } + throw e; // rethrow so that a message dialog will appear } return module; } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/SetupCodewindProjectRunnable.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/SetupCodewindProjectRunnable.java index eceaf1b..c48279f 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/SetupCodewindProjectRunnable.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/module/SetupCodewindProjectRunnable.java @@ -10,14 +10,10 @@ *******************************************************************************/ package org.eclipse.codewind.intellij.ui.module; -import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import org.eclipse.codewind.intellij.core.CoreUtil; +import com.intellij.openapi.util.ThrowableComputable; import org.eclipse.codewind.intellij.core.FileUtil; -import org.eclipse.codewind.intellij.core.Logger; import org.eclipse.codewind.intellij.core.cli.ProjectUtil; import java.nio.file.Files; @@ -26,7 +22,7 @@ import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; -public class SetupCodewindProjectRunnable implements Runnable { +public class SetupCodewindProjectRunnable implements ThrowableComputable { private String path; private String name; @@ -55,37 +51,33 @@ public SetupCodewindProjectRunnable( }; @Override - public void run() throws ProcessCanceledException{ + public Object compute() throws Throwable { ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - try { - progressIndicator.setIndeterminate(false); - progressIndicator.setText2(message("PleaseWaitForBuild")); - Path projectPath = Paths.get(path); - progressIndicator.checkCanceled(); - Path tmpProjectPath = Files.createTempDirectory("codewind").resolve(projectPath.getFileName()); - progressIndicator.checkCanceled(); + progressIndicator.setIndeterminate(false); + progressIndicator.setText2(message("PleaseWaitForBuild")); + Path projectPath = Paths.get(path); + progressIndicator.checkCanceled(); - progressIndicator.setText(message("SettingUpProject")); - ProjectUtil.createProject(name, tmpProjectPath.toString(), url, conId, javaHome, progressIndicator); - progressIndicator.checkCanceled(); - FileUtil.copyDirectory(tmpProjectPath, projectPath); - progressIndicator.checkCanceled(); + Path tmpProjectPath = Files.createTempDirectory("codewind").resolve(projectPath.getFileName()); + progressIndicator.checkCanceled(); - progressIndicator.setText("Binding project"); - ProjectUtil.bindProject(name, path, language, projectType, conId, progressIndicator); - progressIndicator.checkCanceled(); + progressIndicator.setText(message("SettingUpProject")); + ProjectUtil.createProject(name, tmpProjectPath.toString(), url, conId, javaHome, progressIndicator); + progressIndicator.checkCanceled(); + FileUtil.copyDirectory(tmpProjectPath, projectPath); + progressIndicator.checkCanceled(); - FileUtil.deleteDirectory(tmpProjectPath.getParent().toString(), true); - progressIndicator.checkCanceled(); + progressIndicator.setText("Binding project"); + ProjectUtil.bindProject(name, path, language, projectType, conId, progressIndicator); + progressIndicator.checkCanceled(); - progressIndicator.stop(); - } catch (Exception error) { - if (!(error instanceof ProcessCanceledException)) { - Throwable thrown = Logger.unwrap(error); - Logger.logWarning("An error occurred creating project " + name, thrown); - CoreUtil.openDialog(CoreUtil.DialogType.ERROR, message("NewProjectPage_ProjectCreateErrorTitle"), message("StartBuildError", name, thrown.getLocalizedMessage())); - } - } + FileUtil.deleteDirectory(tmpProjectPath.getParent().toString(), true); + progressIndicator.checkCanceled(); + + progressIndicator.stop(); + // Since we don't have anything meaningful to return, just return true for success since no exceptions were + // thrown at this point + return true; } } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AbstractAddTemplateSourceWizardStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AbstractAddTemplateSourceWizardStep.java new file mode 100644 index 0000000..bd017da --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AbstractAddTemplateSourceWizardStep.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.wizard.AbstractCodewindWizardStep; +import org.eclipse.codewind.intellij.ui.wizard.CodewindCommitStepException; + +public abstract class AbstractAddTemplateSourceWizardStep extends AbstractCodewindWizardStep { + + protected CodewindConnection connection; + protected Project project; + protected AddTemplateSourceWizardModel wizardModel; + + public AbstractAddTemplateSourceWizardStep(String title) { + super(null); // set this to null to override behavior + } + + protected abstract void onStepEntering(); + + protected abstract void onStepLeaving() throws CodewindCommitStepException; + + protected abstract void postDoNextStep(); + + public abstract ValidationInfo doValidate(); +} \ No newline at end of file diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizard.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizard.java new file mode 100644 index 0000000..d70defa --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizard.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.ide.wizard.CommitStepException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import org.eclipse.codewind.intellij.core.CoreUtil; +import org.eclipse.codewind.intellij.ui.constants.UIConstants; +import org.eclipse.codewind.intellij.ui.templates.form.AuthForm; +import org.eclipse.codewind.intellij.ui.wizard.AbstractCodewindWizard; +import org.eclipse.codewind.intellij.ui.wizard.AbstractCodewindWizardStep; +import org.eclipse.codewind.intellij.ui.wizard.CodewindCommitStepException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; +import java.util.List; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class AddTemplateSourceWizard extends AbstractCodewindWizard { + + public AddTemplateSourceWizard(@Nullable Project project, List steps) { + super(message("AddRepoDialogTitle"), project, steps, UIConstants.TEMPLATES_INFO_URL); + } + + public AddTemplateSourceWizard(String title, @Nullable Project project, List steps) { + super(title, project, steps, UIConstants.TEMPLATES_INFO_URL); + } + + @Override + protected void doNextAction() { + try { + setErrorText(null); + AbstractAddTemplateSourceWizardStep currentStepObject = (AbstractAddTemplateSourceWizardStep) getCurrentStepObject(); + currentStepObject.onStepLeaving(); + // Get next step object reference for use after super.doNextAction(); + AbstractAddTemplateSourceWizardStep nextStepObject = (AbstractAddTemplateSourceWizardStep) getNextStepObject(); + nextStepObject.onStepEntering(); // Do calls prior to entering the page + // Now, switch pages and show content + super.doNextAction(); + // getNextStepObject() will return the next step, which isn't what we want + nextStepObject.postDoNextStep(); // This could be for validation right after the page appears + } catch (CodewindCommitStepException e) { + CoreUtil.openDialog(true, e.getTitle(), e.getMessage()); + if (e.getComponent() != null) { + setErrorText(e.getMessage(), e.getComponent()); + } + } + } + + @NotNull + @Override + protected List doValidateAll() { + return super.doValidateAll(); + } + + @Override + protected void doPreviousAction() { + // Thus far, only care about stepLeaving + try { + AbstractAddTemplateSourceWizardStep currentStepObject = (AbstractAddTemplateSourceWizardStep) getCurrentStepObject(); + currentStepObject.onStepLeaving(); + super.doPreviousAction(); + } catch (CommitStepException e) { + + } + } + + @Override + protected void doOKAction() { + super.doOKAction(); + } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + Object source = changeEvent.getSource(); + if (source instanceof JComponent) { + JComponent component = (JComponent) source; + if (hasErrors(component)) { + setErrorText(null, (JComponent) source); + } + } else if (source == AuthForm.AUTH_SUCCESSFUL) { + setErrorText(null); + } + } + + @NotNull + @Override + protected Action[] createActions() { + return super.createActions(); + } + + @Nullable + protected ValidationInfo doValidate() { + AbstractAddTemplateSourceWizardStep currentStepObject = (AbstractAddTemplateSourceWizardStep) getCurrentStepObject(); + return currentStepObject.doValidate(); + } + + public RepoEntry getRepoEntry() { + // We know the order of the steps. + UrlStep urlStep = (UrlStep)mySteps.get(0); + DetailsStep detailsStep = (DetailsStep)mySteps.get(1); + String url = urlStep.getTemplateSourceUrl(); + String name = detailsStep.getTemplateSourceName(); + String description = detailsStep.getTemplateSourceDescription(); + if (name != null && !name.isEmpty() && + description != null && !description.isEmpty() && + url != null && !url.isEmpty()) { + return new RepoEntry(url, urlStep.getUsername(), urlStep.getPassword(), urlStep.getToken(), name, description); + } + return null; + } + +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizardModel.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizardModel.java new file mode 100644 index 0000000..13b26c3 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/AddTemplateSourceWizardModel.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import org.eclipse.codewind.intellij.core.IAuthInfo; +import org.eclipse.codewind.intellij.ui.wizard.BaseCodewindWizardModel; + +import java.util.List; + +/** + * Values set in wizard and used by steps + */ +public class AddTemplateSourceWizardModel extends BaseCodewindWizardModel { + + private String urlValue; + private IAuthInfo authInfo; + private String nameValue, descriptionValue; + private List repoEntries; // Current list in the repo management table + private boolean isEdit = false; + + public void setUrlValue(String urlValue) { + this.urlValue = urlValue; + } + + public String getTemplateSourceURL() { + return urlValue; + } + + public void setAuthInfo(IAuthInfo authInfo) { + this.authInfo = authInfo; + } + + public IAuthInfo getAuthInfo() { + return authInfo; + } + + public void setNameValue(String nameValue) { + this.nameValue = nameValue; + } + + public String getNameValue() { + return nameValue; + } + + public void setDescriptionValue(String descriptionValue) { + this.descriptionValue = descriptionValue; + } + + public String getDescriptionValue() { + return descriptionValue; + } + + public void setRepoEntries(List repoEntries) { + this.repoEntries = repoEntries; + } + + public List getRepoEntries() { + return repoEntries; + } + + /** + * Is the wizard for edit or for add + * @return + */ + public boolean isEdit() { + return isEdit; + } + + /** + * Set whether the wizard is for edit or add + * @param isEdit + */ + public void setIsEdit(boolean isEdit) { + this.isEdit = isEdit; + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/DetailsStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/DetailsStep.java new file mode 100644 index 0000000..df0e61a --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/DetailsStep.java @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.ide.wizard.CommitStepException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import org.eclipse.codewind.intellij.core.HttpUtil; +import org.eclipse.codewind.intellij.core.HttpUtil.HttpResult; +import org.eclipse.codewind.intellij.core.IAuthInfo; +import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.templates.form.DetailsForm; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.json.JSONObject; + +import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.net.URL; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class DetailsStep extends AbstractAddTemplateSourceWizardStep { + public static String STEP_ID = "DetailsStep"; + private DetailsForm form; + + public static final String DETAILS_FILE_NAME = "templates.json"; + public static final String NAME_KEY = "name"; + public static final String DESCRIPTION_KEY = "description"; + + private String nameValue, descriptionValue; + private String[] defaultValues = new String[2]; + + public DetailsStep(Project project, CodewindConnection connection, AddTemplateSourceWizardModel wizardModel) { + this(project, connection, wizardModel, wizardModel.getNameValue(), wizardModel.getDescriptionValue()); + } + + public DetailsStep(Project project, CodewindConnection connection, AddTemplateSourceWizardModel wizardModel, String nameValue, String descriptionValue) { + super(null); //"Details"); + this.project = project; + this.connection = connection; + this.wizardModel = wizardModel; + this.nextStepId = null; // This won't change + this.previousStepId = UrlStep.STEP_ID; // By default, the auth checkbox is unchecked + this.nameValue = nameValue; + this.descriptionValue = descriptionValue; + } + + @NotNull + @Override + public Object getStepId() { + return STEP_ID; + } + + @Nullable + @Override + public Object getNextStepId() { + return null; + } // This is the last page + + @Nullable + @Override + public Object getPreviousStepId() { + return previousStepId; + } + + @Override + public boolean isComplete() { + return form.isComplete(); + } + + @Override + public void commit(CommitType commitType) throws CommitStepException { + // ignore + } + + @Override + public JComponent getComponent() { + if (form == null) { + form = new DetailsForm(project, connection, nameValue, descriptionValue); + form.addListener(this); + } + return form.getContentPane(); + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return null; + } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + fireStateChanged(); + } + + @Override + protected void onStepEntering() { + IAuthInfo authInfo = wizardModel.getAuthInfo(); + descriptionValue = wizardModel.getDescriptionValue(); + nameValue = wizardModel.getNameValue(); + + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + try { + String urlValue = wizardModel.getTemplateSourceURL(); + URL repoUrl = new URL(urlValue); + String path = repoUrl.getPath(); + path = path.substring(0, path.lastIndexOf("/") + 1) + DETAILS_FILE_NAME; + URL detailsUrl = new URL(repoUrl.getProtocol(), repoUrl.getHost(), path); + + // Try to get the template source details + HttpResult result = HttpUtil.get(detailsUrl.toURI(), authInfo); + if (result.isGoodResponse && result.response != null && !result.response.isEmpty()) { + JSONObject jsonObj = new JSONObject(result.response); + String name = jsonObj.has(NAME_KEY) ? jsonObj.getString(NAME_KEY) : null; + String description = jsonObj.has(DESCRIPTION_KEY) ? jsonObj.getString(DESCRIPTION_KEY) : null; + + // The name should at least be set + if (name == null || name.isEmpty()) { + Logger.logDebug("Found the template source information but the name is null or empty: " + detailsUrl); + } else { + defaultValues[0] = name; + defaultValues[1] = description; + form.setDefaultValues(defaultValues); + if (nameValue == null) { + form.setNameValue(name); + } else { + form.setNameValue(nameValue); + } + if (descriptionValue == null) { + form.setDescriptionValue(description == null ? "" : description); + } else { + form.setDescriptionValue(descriptionValue); + } + } + } else { + // Don't log this as an error as the template source may not provide details + Logger.logTrace("Got error code " + result.error + + " trying to retrieve the template source details for url: " + detailsUrl + + ", and error: " + result.error); + } + } catch (Exception e) { + + } + } + }, message("AddRepoDialogAutoFillTaskLabel"), true, project); + } + + @Override + protected void onStepLeaving() { + // empty no extra actions + } + + @Override + protected void postDoNextStep() { + // empty, last step + } + + @Override + public ValidationInfo doValidate() { + return null; + } + + String getTemplateSourceName() { + return form.getTemplateSourceName(); + } + + String getTemplateSourceDescription() { + return form.getTemplateSourceDescription(); + } + + @Override + public void addListener(ChangeListener listener) { + // empty + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/EditTemplateSourceWizard.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/EditTemplateSourceWizard.java new file mode 100644 index 0000000..620633a --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/EditTemplateSourceWizard.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.openapi.project.Project; +import org.eclipse.codewind.intellij.ui.wizard.AbstractCodewindWizardStep; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class EditTemplateSourceWizard extends AddTemplateSourceWizard { + + public EditTemplateSourceWizard(@Nullable Project project, List steps) { + super(message("EditRepoDialogTitle"), project, steps); + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepoEntry.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepoEntry.java new file mode 100644 index 0000000..df3fc74 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepoEntry.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import org.eclipse.codewind.intellij.core.connection.RepositoryInfo; + +import java.util.List; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class RepoEntry { + public final String url; + public final String username; + public final String password; + public final String accessToken; + public final String name; + public final String description; + public boolean enabled; + public RepositoryInfo info; + + public RepoEntry(String url, String username, String password, String accessToken, String name, String description) { + this.url = url; + this.username = username; + this.password = password; + this.accessToken = accessToken; + this.name = name; + this.description = description; + this.enabled = true; + } + + public RepoEntry(RepositoryInfo info) { + this.url = info.getURL(); + this.username = null; + this.password = null; + this.accessToken = null; + this.name = info.getName(); + this.description = info.getDescription(); + this.enabled = info.getEnabled(); + this.info = info; + } + + public boolean isProtected() { + if (info != null) { + return info.isProtected(); + } + return false; + } + + public String getStyles() { + if (info != null) { + List styles = info.getStyles(); + if (styles == null || styles.isEmpty()) { + return message("GenericNotAvailable"); + } + StringBuilder builder = new StringBuilder(); + boolean start = true; + for (String style : styles) { + if (!start) { + builder.append(", "); + } else { + start = false; + } + builder.append(style); + return builder.toString(); + } + } + return message("GenericNotAvailable"); + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepositoryManagementDialog.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepositoryManagementDialog.java new file mode 100644 index 0000000..b6dd288 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/RepositoryManagementDialog.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.core.connection.RepositoryInfo; +import org.eclipse.codewind.intellij.ui.constants.UIConstants; +import org.eclipse.codewind.intellij.ui.form.AbstractCodewindDialogWrapper; +import org.eclipse.codewind.intellij.ui.templates.form.RepositoryManagementForm; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JComponent; + +import java.util.List; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class RepositoryManagementDialog extends AbstractCodewindDialogWrapper { + + private RepositoryManagementForm form; + private List repoList; + private CodewindConnection connection; + private Project project; + + public RepositoryManagementDialog(Project project, CodewindConnection connection, List repoList) { + super(project, message("RepoMgmtDialogTitle"), UIConstants.TEMPLATES_INFO_URL); + this.project = project; + this.connection = connection; + this.repoList = repoList; + } + + /** + * Init must be called after the dialog has been instantiated + */ + public void initForm() { + init(); + form.initForm(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + if (form == null) { + form = new RepositoryManagementForm(project, connection, repoList); + } + return form.getContentPane(); + } + + @Override + protected ValidationInfo doValidate() { + return form.doValidate(); + } + + public void updateRepos() { + form.updateRepos(); + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/UrlStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/UrlStep.java new file mode 100644 index 0000000..84f3ad7 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/UrlStep.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates; + +import com.intellij.ide.wizard.CommitStepException; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.util.ThrowableComputable; +import org.eclipse.codewind.intellij.core.HttpUtil; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.templates.form.UrlForm; +import org.eclipse.codewind.intellij.ui.wizard.CodewindCommitStepException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JComponent; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class UrlStep extends AbstractAddTemplateSourceWizardStep { + public static String STEP_ID = "UrlStep"; + private UrlForm form; + + public UrlStep(Project project, CodewindConnection connection, AddTemplateSourceWizardModel wizardModel) { + super(null); // "Url of Template Source Repository"); + this.project = project; + this.connection = connection; + this.wizardModel = wizardModel; + this.nextStepId = DetailsStep.STEP_ID; // Auth Checkbox is unchecked by default + } + + @NotNull + @Override + public Object getStepId() { + return STEP_ID; + } + + @Nullable + @Override + public Object getNextStepId() { + return nextStepId; + } + + @Nullable + @Override + public Object getPreviousStepId() { + return null; + } + + @Override + public boolean isComplete() { + return form.isComplete(); + } + + @Override + public void commit(CommitType commitType) throws CommitStepException { + // empty + } + + @Override + public JComponent getComponent() { + if (form == null) { + form = new UrlForm(project, connection, false, wizardModel.getTemplateSourceURL()); + // Add this step as a listener to any form changes + form.addListener(this); + form.getAuthForm().addListener(this); + } + return form.getContentPane(); + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return null; + } + + /** + * The step is a listener to any form widget changes so that steps can change their next page target, + * amongst other things. Validation should not be done. + */ + @Override + public void stateChanged(ChangeEvent changeEvent) { + fireStateChanged(); + } + + @Override + protected void onStepEntering() { + // empty + } + + // Do necessary validation before going to the next page. If error, stay on current page + @Override + protected void onStepLeaving() throws CodewindCommitStepException { + String urlValue = form.getTemplateSourceUrl(); + JTextField urlTextField = form.getTemplateSourceTextField(); + if (urlTextField.getText().length() == 0) { + throw new CodewindCommitStepException(message("AddRepoDialogInvalidUrlTitle"), message("AddRepoDialogInvalidUrlMsg"), urlTextField); + } + try { + if (urlValue != null) { + URI uri = new URI(urlValue); + } + } catch (URISyntaxException e) { + throw new CodewindCommitStepException(message("AddRepoDialogInvalidUrlTitle"), message("AddRepoDialogInvalidUrlMsg"), urlTextField); + } + + wizardModel.setUrlValue(getTemplateSourceUrl()); + wizardModel.setAuthInfo(form.getAuthInfo()); + + if (!wizardModel.isEdit()) { + List repoEntries = wizardModel.getRepoEntries(); + for (RepoEntry entry : repoEntries) { + if (form.getTemplateSourceUrl().equals(entry.url)) { + throw new CodewindCommitStepException(message("AddRepoDialogInvalidUrlTitle"), + message("AddRepoDialogDuplicateUrlError"), + form.getTemplateSourceTextField()); + } + } + } + + // Test connection again in case the URL was changed + try { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new ThrowableComputable() { + @Override + public Object compute() throws Exception { + ProgressIndicator monitor = new EmptyProgressIndicator(); + HttpUtil.HttpResult result = HttpUtil.get(new URI(urlValue), form.getAuthInfo()); + if (!result.isGoodResponse) { + String errorMsg = result.error; + if (errorMsg == null || errorMsg.trim().isEmpty()) { + errorMsg = message("AddRepoDialogTestFailedDefaultMsg", result.responseCode); + } + throw new InvocationTargetException(new IOException(errorMsg)); + } + return result; + } + }, message("AddRepoDialogTestTaskLabel", urlValue), true, project); + } catch (Exception e) { + String msg = e instanceof InvocationTargetException ? ((InvocationTargetException)e).getCause().toString() : e.toString(); + throw new CodewindCommitStepException(message("AddRepoDialogTestFailedTitle"), message("AddRepoDialogTestFailedError", msg), urlTextField); + } + } + + @Override + protected void postDoNextStep() { + // empty + } + + @Override + public ValidationInfo doValidate() { + return form.doValidate(); + } + + public String getTemplateSourceUrl() { + return form.getTemplateSourceUrl(); + } + + String getUsername() { + return form.getUsername(); + } + + String getPassword() { + return form.getPassword(); + } + + String getToken() { + return form.getToken(); + } + + // Add any interested listener to any form changes + @Override + public void addListener(ChangeListener listener) { + form.addListener(listener); + form.getAuthForm().addListener(listener); + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.form b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.form new file mode 100644 index 0000000..7d4e80e --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.form @@ -0,0 +1,165 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.java new file mode 100644 index 0000000..50b7124 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/AuthForm.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates.form; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBPasswordField; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.ui.components.JBTextField; +import org.eclipse.codewind.intellij.core.CoreUtil; +import org.eclipse.codewind.intellij.core.HttpUtil; +import org.eclipse.codewind.intellij.core.IAuthInfo; +import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.form.WidgetUtils; +import org.eclipse.codewind.intellij.ui.form.WizardHeaderForm; +import org.eclipse.codewind.intellij.ui.wizard.BaseCodewindForm; +import org.jetbrains.annotations.NotNull; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.DocumentEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.util.Base64; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class AuthForm extends BaseCodewindForm { + private JPanel contentPane; + private JPanel authenticationMethodPanel; + private JRadioButton logonRadioButton; + private JPanel logonPanel; + private JRadioButton tokenRadioButton; + private JPanel tokenPanel; + private JLabel usernameLabel; + private JTextField usernameTextField; + private JLabel passwordLabel; + private JPasswordField passwordField; + private JLabel tokenLabel; + private JPanel headerPanel; + private JPanel mainContent; + private JPanel buttonRow; + private JButton testButton; + private JPasswordField tokenField; + + private CodewindConnection connection; + private Project project; + private String urlValue; + private boolean isLogonMethod; + private String usernameValue, passwordValue, tokenValue; + private boolean showHeader; + + public static String AUTH_FAILED = "AUTH_FAILED"; // not NLS + public static String AUTH_SUCCESSFUL = "AUTH_SUCCESSFUL"; // not NLS + + public AuthForm(Project project, CodewindConnection connection, boolean isLogonMethod, boolean showHeader) { + this.project = project; + this.connection = connection; + this.isLogonMethod = isLogonMethod; + this.showHeader = showHeader; + } + + private void createUIComponents() { + WizardHeaderForm headerForm = new WizardHeaderForm(message("AddRepoDialogAuthentication"), message("AddRepoAuthPageMessage")); + headerPanel = headerForm.getContentPane(); + headerPanel.setVisible(showHeader); + + authenticationMethodPanel = new JPanel(); + authenticationMethodPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Authentication method")); + + logonRadioButton = new JBRadioButton(); + logonRadioButton.setText(message("AddRepoDialogLogonAuthButton")); + + tokenRadioButton = new JBRadioButton(); + tokenRadioButton.setText(message("AddRepoDialogAccessTokenAuthButton")); + + usernameTextField = new JBTextField(); + passwordField = new JBPasswordField(); + tokenField = new JBPasswordField(); + + usernameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + updateAuthValues(); + } + }); + + passwordField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + updateAuthValues(); + } + }); + + tokenField.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent keyEvent) { + updateAuthValues(); + } + }); + + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(logonRadioButton); + buttonGroup.add(tokenRadioButton); + + if (isLogonMethod) { + logonRadioButton.setSelected(true); + tokenField.setEnabled(false); + usernameTextField.setEnabled(true); + passwordField.setEnabled(true); + + } else { + tokenRadioButton.setSelected(true); + tokenField.setEnabled(true); + usernameTextField.setEnabled(false); + passwordField.setEnabled(false); + } + + logonRadioButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + isLogonMethod = true; + tokenField.setEnabled(false); + usernameTextField.setEnabled(true); + passwordField.setEnabled(true); + } + }); + + tokenRadioButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + isLogonMethod = false; + tokenField.setEnabled(true); + usernameTextField.setEnabled(false); + passwordField.setEnabled(false); + } + }); + + testButton = new JButton(); + testButton.setText(message("AddRepoDialogTestButtonLabel")); + testButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + try { + ThrowableComputable computable = new ThrowableComputable() { + @Override + public Object compute() throws Exception { + HttpUtil.HttpResult result = null; + try { + result = HttpUtil.get(new URI(urlValue), getAuthInfo()); + if (!result.isGoodResponse) { + String errorMsg = result.error; + if (errorMsg == null || errorMsg.trim().isEmpty()) { + errorMsg = message("AddRepoDialogTestFailedDefaultMsg", result.responseCode); + } + throw new InvocationTargetException(new IOException(errorMsg)); + } + } catch (IOException e) { + throw new InvocationTargetException(e, e.toString()); + } + return result; + } + }; + ProgressManager.getInstance().runProcessWithProgressSynchronously(computable, message("AddRepoDialogAuthTestTaskLabel", urlValue), true, project); + CoreUtil.openDialog(false, message("AddRepoDialogAuthTestFailedTitle"), message("AddRepoDialogTestSuccessMsg")); + fireStateChanged(new ChangeEvent(AUTH_SUCCESSFUL)); + } catch (Exception e) { + String msg = e instanceof InvocationTargetException ? e.getCause().toString() : e.toString(); + CoreUtil.openDialog(true, message("AddRepoDialogTestFailedTitle"), message("AddRepoDialogAuthTestFailedError", urlValue, msg)); + fireStateChanged(new ChangeEvent(AUTH_FAILED)); + return; + } + } + }); + + } + + public IAuthInfo getAuthInfo() { + return isLogonMethod ? new LogonAuth(usernameValue, passwordValue) : new TokenAuth(tokenValue); + } + + public JComponent getContentPane() { + return this.contentPane; + } + + public String getUsername() { + if (logonRadioButton.isSelected()) { + return usernameTextField.getText(); + } + return null; + } + + public String getPassword() { + if (logonRadioButton.isSelected()) { + return WidgetUtils.getPassword(passwordField); + } + return null; + } + + public String getToken() { + if (tokenRadioButton.isSelected()) { + return WidgetUtils.getPassword(tokenField); + } + return null; + } + + public void setUrlValue(String url) { + this.urlValue = url; + } + + public void setEnabled(boolean isEnabled) { + authenticationMethodPanel.setEnabled(isEnabled); + buttonRow.setEnabled(isEnabled); + contentPane.setEnabled(isEnabled); + logonRadioButton.setEnabled(isEnabled); + tokenRadioButton.setEnabled(isEnabled); + usernameLabel.setEnabled(isEnabled); + passwordLabel.setEnabled(isEnabled); + tokenLabel.setEnabled(isEnabled); + usernameTextField.setEnabled(isEnabled && logonRadioButton.isSelected()); + passwordField.setEnabled(isEnabled && logonRadioButton.isSelected()); + tokenField.setEnabled(isEnabled && tokenRadioButton.isSelected()); + } + + private void updateAuthValues() { + isLogonMethod = logonRadioButton.isSelected(); + if (isLogonMethod) { + usernameValue = WidgetUtils.getTextValue(usernameTextField); + passwordValue = WidgetUtils.getTextValue(passwordField); + } else { + tokenValue = WidgetUtils.getTextValue(tokenField); + } + } + + public static class LogonAuth implements IAuthInfo { + + private final String username; + private final String password; + + public LogonAuth(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public boolean isValid() { + return username != null && password != null; + } + + @Override + public String getHttpAuthorization() { + try { + String auth = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes("UTF-8")); // NON NLS + } catch (UnsupportedEncodingException e) { + Logger.logWarning("An unsupported encoding exception occurred trying to encode the logon authentication."); // NON NLS + } + return null; + } + } + + public static class TokenAuth implements IAuthInfo { + + private final String token; + + public TokenAuth(String token) { + this.token = token; + } + + @Override + public boolean isValid() { + return token != null; + } + + @Override + public String getHttpAuthorization() { + return "bearer " + token; + } // NON NLS + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.form b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.form new file mode 100644 index 0000000..05bcf9e --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.form @@ -0,0 +1,100 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.java new file mode 100644 index 0000000..0ee2170 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/DetailsForm.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates.form; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBTextField; +import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.form.WidgetUtils; +import org.eclipse.codewind.intellij.ui.form.WizardHeaderForm; +import org.eclipse.codewind.intellij.ui.wizard.BaseCodewindForm; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.DocumentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class DetailsForm extends BaseCodewindForm { + private JPanel contentPane; + private JPanel headerPanel; + private JPanel mainContent; + private JPanel rowPanel; + private JLabel nameLabel; + private JTextField nameTextField; + private JLabel descriptionLabel; + private JTextField descriptionTextField; + private JPanel rowButtonPanel; + private JButton resetButton; + + private CodewindConnection connection; + private String nameValue, descriptionValue; + private Project project; + private String[] defaultValues = new String[2]; + + + public DetailsForm(Project project, CodewindConnection connection, String nameValue, String descriptionValue) { + this.project = project; + this.connection = connection; + this.nameValue = nameValue; + this.descriptionValue = descriptionValue; + }; + + private void createUIComponents() { + WizardHeaderForm headerForm = new WizardHeaderForm(message("AddRepoDetailsPage"), message("AddRepoDetailsPageMessage")); + headerPanel = headerForm.getContentPane(); + + nameTextField = new JBTextField(); + descriptionTextField = new JBTextField(); + + if (nameValue != null) { + nameTextField.setText(nameValue); + } + if (descriptionValue != null) { + descriptionTextField.setText(descriptionValue); + } + resetButton = new JButton(); + resetButton.setText(message("AddRepoDialogResetButtonLabel")); + + resetButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + try { + nameTextField.setText(defaultValues[0] == null ? "" : defaultValues[0]); + descriptionTextField.setText(defaultValues[1] == null ? "" : defaultValues[1]); + validate(false, resetButton); + } catch (Exception e) { + Logger.logDebug("An error occurred trying to get the template source details", e); + } + + } + }); + + nameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + validate(false, nameTextField); + } + }); + + descriptionTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + validate(false, descriptionTextField); + } + }); + } + + public ValidationInfo doValidate() { + if (nameTextField.getText().length() == 0) { + return new ValidationInfo(message("AddRepoDialogNoName")); + } else if (descriptionTextField.getText().length() == 0) { + return new ValidationInfo(message("AddRepoDialogNoDescription")); + } + return null; + } + + public boolean isComplete() { + return (nameTextField.getText().length() > 0 && descriptionTextField.getText().length() > 0); + } + + public JComponent getContentPane() { + return this.contentPane; + } + + public String getTemplateSourceName() { + return WidgetUtils.getTextValue(nameTextField); + } + + public String getTemplateSourceDescription() { + return WidgetUtils.getTextValue(descriptionTextField); + } + + private void validate(boolean init, JComponent component) { + String errorMsg = null; + nameValue = WidgetUtils.getTextValue(nameTextField); + descriptionValue = WidgetUtils.getTextValue(descriptionTextField); + resetButton.setVisible(defaultValues[0] != null); + if (resetButton.isVisible()) { + resetButton.setEnabled((nameValue == null && defaultValues[0] != null) || (nameValue != null && !nameValue.equals(defaultValues[0])) || + (descriptionValue == null && defaultValues[1] != null) || (descriptionValue != null && !descriptionValue.equals(defaultValues[1]))); + } + if (nameValue == null) { + errorMsg = message("AddRepoDialogNoName"); + } else if (descriptionValue == null) { + errorMsg = message("AddRepoDialogNoDescription"); + } + fireStateChanged(new ChangeEvent(component)); + } + + public void setDefaultValues(String [] defaultValues) { + this.defaultValues = defaultValues; + } + + public void setNameValue(String name) { + this.nameValue = name; + nameTextField.setText(name); + } + + public void setDescriptionValue(String description) { + this.descriptionValue = description; + descriptionTextField.setText(description); + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.form b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.form new file mode 100644 index 0000000..f9f821a --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.form @@ -0,0 +1,173 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.java new file mode 100644 index 0000000..e779dd3 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/RepositoryManagementForm.java @@ -0,0 +1,485 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates.form; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.components.JBScrollPane; +import org.eclipse.codewind.intellij.core.CoreUtil; +import org.eclipse.codewind.intellij.core.Logger; +import org.eclipse.codewind.intellij.core.cli.TemplateUtil; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.core.connection.RepositoryInfo; +import org.eclipse.codewind.intellij.ui.form.WidgetUtils; +import org.eclipse.codewind.intellij.ui.form.WizardHeaderForm; +import org.eclipse.codewind.intellij.ui.templates.AbstractAddTemplateSourceWizardStep; +import org.eclipse.codewind.intellij.ui.templates.AddTemplateSourceWizardModel; +import org.eclipse.codewind.intellij.ui.templates.DetailsStep; +import org.eclipse.codewind.intellij.ui.templates.EditTemplateSourceWizard; +import org.eclipse.codewind.intellij.ui.templates.RepoEntry; +import org.eclipse.codewind.intellij.ui.templates.AddTemplateSourceWizard; +import org.eclipse.codewind.intellij.ui.templates.UrlStep; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumn; +import java.awt.Dimension; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class RepositoryManagementForm { + private JPanel contentPane; + private JPanel rowPanel; + private JTable repoViewer; + private JButton addButton; + private JButton removeButton; + private JScrollPane tableScrollPane; + private JPanel buttonPanel; + private JLabel descLabel; + private JTextPane descTextPane; + private JLabel styleLabel; + private JLabel urlLabel; + private JPanel detailsPanel; + private JTextPane styleTextPane; + private JTextPane urlTextPane; + private JPanel headerPanel; + private JPanel mainContent; + private JTextPane descriptionTextPane; + private JButton editButton; + + private List repoList; // Actual list of repo from PFE + private CodewindConnection connection; + private Project project; + private List repoEntries; // Current edited set of repos (content of the table) + + private RepoViewerModel repoViewerModel; + + public RepositoryManagementForm(Project project, CodewindConnection connection, List repoList) { + this.project = project; + this.connection = connection; + this.repoList = repoList; + } + + public void initForm() { + this.repoEntries = getRepoEntries(repoList); + if (repoViewer instanceof RepoViewer) { + ((RepoViewer)repoViewer).init(); + ((RepoViewer)repoViewer).setRepoEntries(this.repoEntries); + if (repoEntries.size() > 0) { + repoViewer.setRowSelectionInterval(0, 0); + } + } + } + + private void createUIComponents() { + WizardHeaderForm headerForm = new WizardHeaderForm(message("RepoMgmtDialogTitle"), message("RepoMgmtDialogMessage")); + headerPanel = headerForm.getContentPane(); + descriptionTextPane = new JTextPane(); + repoViewer = new RepoViewer(); + tableScrollPane = new JBScrollPane(repoViewer); + tableScrollPane.setPreferredSize(new Dimension(600, 200)); + addButton = new JButton(); + editButton = new JButton(); + removeButton = new JButton(); + + descriptionTextPane.setText(message("RepoMgmtDescription")); + addButton.setText(message("RepoMgmtAddButton")); + editButton.setText(message("RepoMgmtEditButton")); + removeButton.setText(message("RepoMgmtRemoveButton")); + + detailsPanel = new JPanel(); + descTextPane = WidgetUtils.createTextPane("", true); + styleTextPane = WidgetUtils.createTextPane("", true); + urlTextPane = WidgetUtils.createHyperlinkUsingTextPane("", true); + + addButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + List steps = new ArrayList<>(); + AddTemplateSourceWizardModel wizardModel = new AddTemplateSourceWizardModel(); + wizardModel.setRepoEntries(repoEntries); + UrlStep urlStep = new UrlStep(project, connection, wizardModel); + steps.add(urlStep); + steps.add(new DetailsStep(project, connection, wizardModel)); + wizardModel.addSteps(steps); + AddTemplateSourceWizard wizard = new AddTemplateSourceWizard(project, steps); + urlStep.addListener(wizard); + boolean rc = wizard.showAndGet(); + if (rc) { + RepoEntry repoEntry = wizard.getRepoEntry(); + if (repoEntry != null) { + repoEntries.add(repoEntry); + repoViewerModel.fireTableDataChanged(); + } + } + + } + }); + + editButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int selectedIndex = repoViewer.getSelectedRow(); + RepoEntry selectedRepo = repoEntries.get(selectedIndex); + List steps = new ArrayList<>(); + AddTemplateSourceWizardModel wizardModel = new AddTemplateSourceWizardModel(); + wizardModel.setRepoEntries(repoEntries); + wizardModel.setNameValue(selectedRepo.name); + wizardModel.setDescriptionValue(selectedRepo.description); + wizardModel.setIsEdit(true); + wizardModel.setUrlValue(selectedRepo.url); + UrlStep urlStep = new UrlStep(project, connection, wizardModel); + steps.add(urlStep); + steps.add(new DetailsStep(project, connection, wizardModel)); + wizardModel.addSteps(steps); + EditTemplateSourceWizard wizard = new EditTemplateSourceWizard(project, steps); + urlStep.addListener(wizard); + boolean rc = wizard.showAndGet(); + if (rc) { + RepoEntry newRepoEntry = wizard.getRepoEntry(); + if (newRepoEntry != null) { + newRepoEntry.enabled = selectedRepo.enabled; + repoEntries.set(selectedIndex, newRepoEntry); + repoViewerModel.fireTableDataChanged(); + repoViewer.setRowSelectionInterval(selectedIndex, selectedIndex); + } + } + } + }); + + removeButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int[] selectedIndices = repoViewer.getSelectedRows(); + if (selectedIndices.length > 0) { + List itemsToRemove = new ArrayList<>(); + for (int i = 0; i < selectedIndices.length; i++) { + itemsToRemove.add(repoEntries.get(selectedIndices[i])); + } + Arrays.stream(itemsToRemove.toArray()).forEach(item -> { + repoEntries.remove(item); + }); + repoViewerModel.fireTableDataChanged(); + } + } + }); + } + + private void updateDetails() { + String desc = ""; + String styles = ""; + String url = ""; + boolean enabled = false; + if (repoViewer.getSelectedRowCount() == 1) { + RepoEntry repoEntry = repoEntries.get(repoViewer.getSelectedRow()); + enabled = repoEntry != null; + if (enabled) { + desc = repoEntry.description; + styles = repoEntry.getStyles(); + url = repoEntry.url; + } + } + descLabel.setEnabled(enabled); + descTextPane.setText(desc); + styleLabel.setEnabled(enabled); + styleTextPane.setText(styles); + urlLabel.setEnabled(enabled); + if (url.length() == 0) { + urlTextPane.setContentType("text/string"); // Not NLS + urlTextPane.setText(url); + } else { + urlTextPane.setContentType("text/html"); // Not NLS + urlTextPane.setText("" + url + ""); // Not NLS + } + } + + private void updateButtons() { + boolean enabled = true; + if (repoViewer.getSelectedRowCount() > 0) { + int[] selectedRows = repoViewer.getSelectedRows(); + for (int i = 0; i < selectedRows.length; i++) { + RepoEntry repoEntry = repoEntries.get(selectedRows[i]); + if (repoEntry.isProtected()) { + enabled = false; + break; + } + } + } else { + enabled = false; + } + removeButton.setEnabled(enabled); + editButton.setEnabled(repoViewer.getSelectedRowCount() == 1 && enabled); + } + + + public ValidationInfo doValidate() { + return null; + } + + public JComponent getContentPane() { + return this.contentPane; + } + + private List getRepoEntries(List infos) { + if (infos == null) return new ArrayList<>(0); + List entries = new ArrayList<>(infos.size()); + for (RepositoryInfo info : infos) { + entries.add(new RepoEntry(info)); + } + return entries; + } + + // This should only be called once the user has made all of their changes + // and indicated they want to update (clicked OK rather than Cancel). + public void updateRepos() { + + if (!hasChanges()) { + return; + } + ApplicationManager.getApplication().invokeLater(() -> { + ProgressManager.getInstance().run(new Task.Backgroundable(project, message("RepoUpdateTask"), true) { + @Override + public void run(@NotNull ProgressIndicator mon) { + // Check for the differences between the original repo set and the new set + for (RepositoryInfo info : repoList) { + Optional entry = getRepoEntry(info); + if (!entry.isPresent()) { + // The new set does not contain the original repo so remove it + try { + TemplateUtil.removeTemplateSource(info.getURL(), connection.getConid(), mon); + } catch (Exception e) { + Logger.logWarning("Failed to remove repository: " + info.getURL(), e); // Not NLS + } + } else if (info.getEnabled() != entry.get().enabled) { + // The new set contains the original repo but the enablement does not match so update it + try { + TemplateUtil.enableTemplateSource(entry.get().enabled, info.getURL(), connection.getConid(), mon); + } catch (Exception e) { + Logger.logWarning("Failed to update repository: " + info.getURL(), e); // Not NLS + } + } + if (mon.isCanceled()) { + return; + } + } + // Check for new entries (RepositoryInfo is null) and add them + for (RepoEntry entry : repoEntries) { + if (entry.info == null) { + // Add the repository + try { + TemplateUtil.addTemplateSource(entry.url, entry.username, entry.password, entry.accessToken, entry.name, entry.description, connection.getConid(), mon); + } catch (Exception e) { + Logger.logWarning("Failed to add repository: " + entry.url, e); // Not NLS + CoreUtil.openDialog(true, message("RepoMgmtAddFailed", entry.url), e.getLocalizedMessage()); + } + } + if (mon.isCanceled()) { + return; + } + } + } + }); + }); + } + + public boolean hasChanges() { + // For all pre-existing repos, if not in the current list then they + // need to be removed. If in the current list but the enablement is + // different then they need to be updated. + for (RepositoryInfo info : repoList) { + Optional entry = getRepoEntry(info); + if (!entry.isPresent()) { + return true; + } else if (info.getEnabled() != entry.get().enabled) { + return true; + } + } + // For all entries that are not pre-existing (RepositoryInfo is null), + // they need to be added. + for (RepoEntry entry : repoEntries) { + if (entry.info == null) { + return true; + } + } + return false; + } + + private Optional getRepoEntry(RepositoryInfo info) { + return repoEntries.stream().filter(entry -> entry.info == info).findFirst(); + } + + + + private class RepoViewerModel extends AbstractTableModel { + private List repoEntries; + + public RepoViewerModel() { + super(); + } + + public void setRepoEntries(List repoEntries) { + this.repoEntries = repoEntries; + } + + @Override + public int getRowCount() { + if (repoEntries != null) { + return repoEntries.size(); + } + return 0; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Class getColumnClass(int column) { + switch (column) { + case 0: + return Boolean.class; + default: + return String.class; + } + } + + @Override + public boolean isCellEditable(int row, int col) { + if (col == 0) { + return true; + } + return false; + } + + @Override + public void setValueAt(Object o, int row, int col) { + super.setValueAt(o, row, col); + if (o instanceof Boolean) { + RepoEntry repoEntry = repoEntries.get(row); + repoEntry.enabled = ((Boolean) o).booleanValue(); + } + fireTableCellUpdated(row, col); + } + + @Override + public Object getValueAt(int row, int col) { + RepoEntry repoEntry = repoEntries.get(row); + if (col ==0) { + return repoEntry.enabled; + } + if (col == 1) { + String name = repoEntry.name; + if (name == null || name.isEmpty()) { + name = repoEntry.description; + } + return name; + } + return null; + } + } + + private class RepoViewer extends JTable { + private List selectionListeners = new ArrayList<>(); + + public RepoViewer() { + super(); + } + + public void setRepoEntries(List repoEntries) { + repoViewerModel.setRepoEntries(repoEntries); + } + + /** + * Call init() after instantiation + */ + public void init() { + setFillsViewportHeight(true); + + setShowGrid(true); + setColumnSelectionAllowed(false); + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + JTableHeader tableHeader = new JTableHeader(); + tableHeader.setTable(this); + tableHeader.setReorderingAllowed(false); + setTableHeader(tableHeader); + + DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); + + TableColumn checkboxColumn = new TableColumn(); + checkboxColumn.setPreferredWidth(50); + checkboxColumn.setMaxWidth(100); + checkboxColumn.setModelIndex(0); + checkboxColumn.setHeaderValue(null); + columnModel.addColumn(checkboxColumn); + + TableColumn nameColumn = new TableColumn(); + nameColumn.setModelIndex(1); + nameColumn.setHeaderValue(null); + columnModel.addColumn(nameColumn); + + repoViewerModel = new RepoViewerModel(); + setModel(repoViewerModel); +// repoViewerModel.addTableModelListener(new TableModelListener() { +// @Override +// public void tableChanged(TableModelEvent event) { +// Object source = event.getSource(); +// +// int row = event.getFirstRow(); +// int column = event.getColumn(); +// if (source instanceof RepoViewerModel) { +// RepoViewerModel model = (RepoViewerModel)source; +// } +// } +// }); + + setColumnModel(columnModel); + setTableHeader(null); + setRowHeight(getRowHeight() + 5); + + ListSelectionModel selectionModel = getSelectionModel(); + selectionModel.addListSelectionListener(listSelectionEvent -> { + updateDetails(); + updateButtons(); + if (selectionListeners.size() > 0) { + for (ListSelectionListener listener : selectionListeners) { + listener.valueChanged(listSelectionEvent); + } + } + }); + } + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.form b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.form new file mode 100644 index 0000000..ff6886e --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.form @@ -0,0 +1,86 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.java new file mode 100644 index 0000000..95bdd46 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/templates/form/UrlForm.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.templates.form; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBTextField; +import org.eclipse.codewind.intellij.core.IAuthInfo; +import org.eclipse.codewind.intellij.core.connection.CodewindConnection; +import org.eclipse.codewind.intellij.ui.form.WidgetUtils; +import org.eclipse.codewind.intellij.ui.form.WizardHeaderForm; +import org.eclipse.codewind.intellij.ui.wizard.BaseCodewindForm; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; + +public class UrlForm extends BaseCodewindForm implements ChangeListener { + private JPanel contentPane; + private JPanel rowPanel; + private JLabel urlLabel; + private JTextField urlTextField; + private JCheckBox authCheckBox; + private JPanel headerPanel; + private JPanel mainContent; + private JPanel authPanel; + private AuthForm authForm; + + private CodewindConnection connection; + private Project project; + private String urlValue = ""; + private boolean isAuth = false; + + public UrlForm(Project project, CodewindConnection connection, boolean isAuth, String urlValue) { + this.project = project; + this.connection = connection; + this.isAuth = isAuth; + this.urlValue = urlValue; + } + + private void createUIComponents() { + WizardHeaderForm headerForm = new WizardHeaderForm(message("AddRepoDialogUrlLocation"), message("AddRepoURLPageMessage")); + headerPanel = headerForm.getContentPane(); + urlTextField = new JBTextField(); + authCheckBox = new JBCheckBox(); + + authCheckBox.setSelected(isAuth); + + authCheckBox.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent mouseEvent) { + if (mouseEvent.getSource() == authCheckBox) { + if (!mouseEvent.isPopupTrigger()) { + isAuth = !isAuth; + authForm.setEnabled(isAuth); + fireStateChanged(new ChangeEvent(authCheckBox)); + } + } + } + }); + + urlTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent e) { + urlValue = WidgetUtils.getTextValue(urlTextField); + authForm.setUrlValue(urlValue); + fireStateChanged(new ChangeEvent(urlTextField)); + } + }); + + authPanel = new JPanel(); + authForm = new AuthForm(project, connection, true, false); + authForm.addListener(this); + + if (urlValue != null) { + urlTextField.setText(urlValue); + } + + authForm.setEnabled(isAuth); + } + + public String getTemplateSourceUrl() { + return WidgetUtils.getTextValue(urlTextField); + } + + public JTextField getTemplateSourceTextField() { + return urlTextField; + } + + /** + * Minimal validation to disable/enable the Next button. Eg. URL cannot be blank. + * @return + */ + public ValidationInfo doValidate() { + return null; + } + + public boolean isComplete() { + return urlValue != null ? urlValue.length() > 0 : false; + } + + public AuthForm getAuthForm() { + return authForm; + } + + public JComponent getContentPane() { + return this.contentPane; + } + + public IAuthInfo getAuthInfo() { + if (isAuth) { + return authForm.getAuthInfo(); + } + return null; // if auth checkbox is unchecked, return null + } + public String getUsername() { + return authForm.getUsername(); + } + + public String getPassword() { + return authForm.getPassword(); + } + + public String getToken() { + return authForm.getToken(); + } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + // Empty + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractBindProjectWizardStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractBindProjectWizardStep.java index 3cd3fa7..d7a6eb3 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractBindProjectWizardStep.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractBindProjectWizardStep.java @@ -10,10 +10,9 @@ *******************************************************************************/ package org.eclipse.codewind.intellij.ui.wizard; -import com.intellij.ide.wizard.AbstractWizardStepEx; import org.jetbrains.annotations.Nullable; -public abstract class AbstractBindProjectWizardStep extends AbstractWizardStepEx { +public abstract class AbstractBindProjectWizardStep extends AbstractCodewindWizardStep { public AbstractBindProjectWizardStep(@Nullable String title) { super(title); diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizard.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizard.java new file mode 100644 index 0000000..89152b1 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizard.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.wizard; + +import com.intellij.ide.browsers.BrowserLauncher; +import com.intellij.ide.wizard.AbstractWizardEx; +import com.intellij.openapi.project.Project; +import org.eclipse.codewind.intellij.core.Logger; +import org.jetbrains.annotations.Nullable; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +public abstract class AbstractCodewindWizard extends AbstractWizardEx implements ChangeListener { + protected String helpUrl; + + public AbstractCodewindWizard(String title, Project project, List steps, String helpUrl) { + super(title, project, steps); + this.helpUrl = helpUrl; + } + + @Nullable + @Override + protected String getHelpID() { + return null; + } + + @Override + protected void doOKAction() { + super.doOKAction(); + } + + @Override + protected boolean canFinish() { + return super.canFinish(); + } + + @Override + protected void helpAction() { + try { + BrowserLauncher.getInstance().browse(new URI(helpUrl)); + } catch (URISyntaxException ex) { + Logger.logWarning(ex); + } + } + + @Override + public abstract void stateChanged(ChangeEvent changeEvent); +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizardStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizardStep.java new file mode 100644 index 0000000..38e209d --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AbstractCodewindWizardStep.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.wizard; + +import com.intellij.ide.wizard.AbstractWizardStepEx; + +import javax.swing.event.ChangeListener; + +/** + * The step is a change listener to any form or widget changes so that steps can change their next page target, + * or other things. + */ +public abstract class AbstractCodewindWizardStep extends AbstractWizardStepEx implements ChangeListener { + + public AbstractCodewindWizardStep(String title) { + super(title); + } + // These can change + protected Object nextStepId; + protected Object previousStepId; + + /** + * This is intended to be called only by the wizard model + */ + public void setNextStepId(Object id) { + this.nextStepId = id; + } + + /** + * This is intended to be called only by the wizard model + */ + public void setPreviousStepId(Object id) { + this.previousStepId = id; + } + + /** + * Listeners can be added to be informed of any form or widget changes + * @param listener + */ + public abstract void addListener(ChangeListener listener); +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AddExistingProjectWizard.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AddExistingProjectWizard.java index e0c5dd1..14d10e1 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AddExistingProjectWizard.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/AddExistingProjectWizard.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.codewind.intellij.ui.wizard; -import com.intellij.ide.wizard.AbstractWizardEx; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -26,16 +25,18 @@ import org.eclipse.codewind.intellij.core.connection.ProjectTypeInfo.ProjectSubtypeInfo; import org.eclipse.codewind.intellij.core.constants.ProjectInfo; import org.eclipse.codewind.intellij.core.constants.ProjectLanguage; +import org.eclipse.codewind.intellij.ui.constants.UIConstants; import org.eclipse.codewind.intellij.ui.tree.CodewindToolWindowHelper; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.SystemIndependent; import javax.swing.JFrame; +import javax.swing.event.ChangeEvent; import java.util.List; import static org.eclipse.codewind.intellij.ui.messages.CodewindUIBundle.message; -public class AddExistingProjectWizard extends AbstractWizardEx { +public class AddExistingProjectWizard extends AbstractCodewindWizard { private BindProjectModel model; private CodewindConnection connection; @@ -44,18 +45,13 @@ public class AddExistingProjectWizard extends AbstractWizardEx { private Exception validationException = null; private Exception bindException = null; - public AddExistingProjectWizard(String title, @Nullable Project project, List steps, @Nullable CodewindConnection connection) { - super(title, project, steps); + public AddExistingProjectWizard(String title, @Nullable Project project, List steps, @Nullable CodewindConnection connection) { + super(title, project, steps, UIConstants.GETTING_STARTED_INFO_URL); this.connection = connection; this.intellijProject = project; model = new BindProjectModel(); } - @Override - protected boolean canFinish() { - return super.canFinish(); - } - @Override protected void doNextAction() { AbstractBindProjectWizardStep currentStepObject = (AbstractBindProjectWizardStep) getCurrentStepObject(); @@ -154,6 +150,11 @@ public void run() { } + @Override + public void stateChanged(ChangeEvent changeEvent) { + // ignore + } + private void continueWithBind(String name, String path, String language, ProjectTypeInfo typeInfo) { try { // Todo - make all of these processes in this wizard cancellable @@ -209,10 +210,4 @@ public void run() { Logger.log(ex); } } - - @Nullable - @Override - protected String getHelpID() { - return null; - } } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindForm.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindForm.java new file mode 100644 index 0000000..33a1561 --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindForm.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.wizard; + +import com.intellij.util.EventDispatcher; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class BaseCodewindForm { + + protected final EventDispatcher eventDispatcher = EventDispatcher.create(ChangeListener.class); + + public void addListener(ChangeListener listener) { + eventDispatcher.addListener(listener); + } + + public void fireStateChanged(ChangeEvent event) { + eventDispatcher.getMulticaster().stateChanged(event); + } + +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindWizardModel.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindWizardModel.java new file mode 100644 index 0000000..c58071e --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/BaseCodewindWizardModel.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.wizard; + +import java.util.ArrayList; +import java.util.List; + +/** + * This wizard model will handle the logic to determine the page ordering. The subclass can define + * the behavior based on listening to the change events from each step + * + * Each step should know which page is next based on the context they know about. eg. a checkbox has + * been selected or not. The model will just facilitate updating all the steps' next and previous step IDs. + */ +public class BaseCodewindWizardModel { + + private List steps = new ArrayList<>(); + + // List of all steps. Each step must have a valid initial prev and next step ID. + // Not all steps have to be on the wizard path sequence but can be added to the path depending + // on each step's logic. + public void addSteps(List steps) { + this.steps = steps; + } + + /** + * There should be no invalid paths initially when the wizard is initialized. + * For example + * Initial main path: A - C, where A is always the first page* + * Anext = C, Aprev = null (The first page is where Xprev = null) + * Cnext = null, Cprev = A (The last page is where Xnext = null) + * Other steps that are not on the initial wizard path + * Bnext = C, Bprev = A - Valid new path: A - B - C, essentially inserting step B between A - C. + * Bnext = C, Bprev = C - is invalid (If the page must be re-used, then a new step ID should be used) + * When a new path is inserted, only one Xnext and one Yprev need to be changed in order to have a valid path. + * The reason is that the context/widget/value changes on the current page determines what the next page will be. + * It will not affect the previous page. So if Xnext changes, then only one Yprev value changes in order to complete + * the wizard path back to the current page. + * + * For example, when B is inserted like so: A - B - C + * then the state changes are: + * Anext = B (NEW change, since original Anext = C) + * Bprev = A (originally defined in step B) + * Bnext = C (originally defined in step B) + * Cprev = B (NEW change, since original Cprev = A) + * + * + * @param step + * @param nextStepId + */ + public void updateNextStep(AbstractCodewindWizardStep step, Object nextStepId) { + Object currentStepId = step.getStepId(); // A, nextStepId = B + Object currentNextStepId = step.getNextStepId(); // A's current next step is C + // Because A's next step is going to change, the original next step's prev pointer must change + // Find that step now. + AbstractCodewindWizardStep oldStepPrevToUpdate = null; + for (AbstractCodewindWizardStep aStep : steps) { + if (aStep.getStepId() == currentNextStepId) { // C + oldStepPrevToUpdate = aStep; // C's prev pointer needs to be changed. But to what? We need to find the step that has C as the next step + } + } + + for (AbstractCodewindWizardStep aStep : steps) { + if (aStep.getStepId() == nextStepId) { // B + aStep.setPreviousStepId(currentStepId); // Bprev = A - Ensure Bprev is to A + } + if (oldStepPrevToUpdate != null && aStep.getNextStepId() == oldStepPrevToUpdate.getStepId()) { // C + oldStepPrevToUpdate.setPreviousStepId(aStep.getStepId()); + } + } + step.setNextStepId(nextStepId); // Anext = B + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/CodewindCommitStepException.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/CodewindCommitStepException.java new file mode 100644 index 0000000..be81eef --- /dev/null +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/CodewindCommitStepException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.codewind.intellij.ui.wizard; + +import com.intellij.ide.wizard.CommitStepException; + +import javax.swing.JComponent; + +public class CodewindCommitStepException extends CommitStepException { + + private String title; + private JComponent component; + + public CodewindCommitStepException(final String title, final String message, final JComponent component) { + super(message); + this.title = title; + this.component = component; + } + + public String getTitle() { + return title; + } + + public JComponent getComponent() { + return component; + } +} diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ConfirmProjectTypeStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ConfirmProjectTypeStep.java index 33232b7..8ee60d9 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ConfirmProjectTypeStep.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ConfirmProjectTypeStep.java @@ -32,6 +32,8 @@ import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -238,4 +240,14 @@ protected void postDoNextStep(BindProjectModel model) { CoreUtil.openDialog(true, message("ProjectValidationPageTitle"), message("ProjectValidationPageFailMsg")); } } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + // ignore + } + + @Override + public void addListener(ChangeListener listener) { + // ignore + } } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectFolderStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectFolderStep.java index 6773a94..9c18ae4 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectFolderStep.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectFolderStep.java @@ -18,6 +18,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; public class ProjectFolderStep extends AbstractBindProjectWizardStep { public static String STEP_ID = "ProjectFolderStep"; @@ -67,7 +69,7 @@ public JComponent getComponent() { } form = new AddExistingProjectForm(this, this.project, connection); // Attempt to initialize the path with the currently opened project's path - String basePath = project.getBasePath().toString(); + String basePath = project.getBasePath(); boolean isCodewindProject = form.isCodewindProject(basePath); if (!isCodewindProject) { form.setProjectPath(basePath); @@ -103,4 +105,14 @@ protected void onStepLeaving(BindProjectModel model) { protected void postDoNextStep(BindProjectModel model) { // empty } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + // ignore + } + + @Override + public void addListener(ChangeListener listener) { + // ignore + } } diff --git a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectTypeSelectionStep.java b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectTypeSelectionStep.java index 805d12a..bfcaed8 100644 --- a/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectTypeSelectionStep.java +++ b/dev/src/main/java/org/eclipse/codewind/intellij/ui/wizard/ProjectTypeSelectionStep.java @@ -23,6 +23,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -154,4 +156,14 @@ protected void onStepLeaving(BindProjectModel model) { protected void postDoNextStep(BindProjectModel model) { // empty } + + @Override + public void stateChanged(ChangeEvent changeEvent) { + // ignore + } + + @Override + public void addListener(ChangeListener listener) { + // ignore + } } diff --git a/dev/src/main/resources/icons/codewindBanner.png b/dev/src/main/resources/icons/codewindBanner.png new file mode 100644 index 0000000..ccbce8d Binary files /dev/null and b/dev/src/main/resources/icons/codewindBanner.png differ diff --git a/dev/src/main/resources/messages/ui/CodewindUIBundle.properties b/dev/src/main/resources/messages/ui/CodewindUIBundle.properties index 6938f4c..c656859 100644 --- a/dev/src/main/resources/messages/ui/CodewindUIBundle.properties +++ b/dev/src/main/resources/messages/ui/CodewindUIBundle.properties @@ -250,6 +250,7 @@ RefreshProjectJobLabel=Refreshing project: {0} ImportProjectError=An error occurred while importing the {0} project. StartBuildError=An error occurred while starting a build for the {0} project: {1}. +ProjectCreateError=An error occurred while creating the project. The error is: {0}. SettingUpProject=Setting up project and dependencies. PleaseWaitForBuild=Please wait while dependencies are downloaded. NewProjectSetupProcessCanceledMessage=The project setup process was canceled. The project was not created. @@ -376,7 +377,8 @@ GenericActionNotSupported=Action not supported AppMonitorNotSupported=This project does not use AppMetrics, and so does not support the metrics dashboard. PerfDashboardNotSupported=This project does not use AppMetrics, and so does not support the performance dashboard. -RepoMgmtActionLabel=&Manage Template Sources +ManagePreferencesMenu=Preferences +RepoMgmtActionLabel=&Manage Template Sources... RepoMgmtDialogTitle=Manage Template Sources RepoMgmtDialogMessage=Add, remove, enable and disable template sources @@ -384,6 +386,7 @@ RepoMgmtDialogMessage=Add, remove, enable and disable template sources RepoMgmtDescription=Enable or disable template sources using the check boxes. Use the buttons to add and remove template sources. Default template sources cannot be removed. RepoMgmtTableLabel=Available template sources: RepoMgmtAddButton=&Add... +RepoMgmtEditButton=&Edit... RepoMgmtRemoveButton=&Remove RepoMgmtDescriptionLabel=Description: RepoMgmtStylesLabel=Styles: @@ -393,13 +396,67 @@ RepoMgmtRemoveFailed=An error occurred trying to remove template source: {0} RepoMgmtUpdateFailed=An error occurred trying to update template source: {0} RepoMgmtAddFailed=An error occurred trying to add template source: {0} -AddRepoDialogTitle=Add Template Source -AddRepoDialogMessage=Supply a description and a URL for the new template source -AddRepoDialogDescriptionLabel=Description: -AddRepoDialogUrlLabel=URL: -AddRepoDialogNoDescription=Please fill in a description for the template source -AddRepoDialogNoUrl=Please fill in a URL for the template source - +AddRepoDialogShell=Add Template Source +AddRepoDialogTitle=Add a Template Source +EditRepoDialogTitle=Edit a Template Source +AddRepoURLPageMessage=Enter the URL of the template source and supply authentication details if needed. +AddRepoAuthPageMessage=Provide authorization details for the template source. +AddRepoDetailsPage=Template Details +AddRepoDetailsPageMessage=Provide the name and description for the template source. +AddRepoDialogNameLabel=&Name: +AddRepoDialogDescriptionLabel=&Description: +AddRepoDialogUrlLabel=&URL: +AddRepoDialogUrlLocation=Template Source Location +AddRepoDialogInvalidUrlTitle=Invalid URL +AddRepoDialogInvalidUrlError=The specified URL is not valid:\n\n{0} +AddRepoDialogDuplicateUrlError=The specified URL is already added +AddRepoDialogInvalidUrlMsg=The specified URL is not valid. +# AddRepoDialogUrlPingTask=Pinging: {0} +# AddRepoDialogUrlPingFailedDefaultMsg=The ping failed with error code: {0} +# AddRepoDialogUrlPingFailedTitle=Ping Error +# AddRepoDialogUrlPing401FailedError=A 401 error response occurred while trying to ping the URL. +# AddRepoDialogUrlPingFailedError=The following error occurred while trying to ping the URL: \n\n{0}. +# AddRepoDialogUrlPingFailedMsg=Ping of the URL was unsuccessful. +AddRepoDialogAuthentication=Secure Authentication +AddRepoDialogAuthRequiredCheckboxLabel=&Authentication required for this URL +AddRepoDialogAuthRequiredCheckboxTooltip=Select if authentication is required for this URL. +AddRepoDialogTestButtonLabel=Te&st Template Source +AddRepoDialogTestTaskLabel=Testing template source: {0} +AddRepoDialogTestFailedTitle=Template Source Test +AddRepoDialogTestFailedDefaultMsg=The template source test failed with error code: {0} +AddRepoDialogTestFailedError=The following error occurred while testing the template source: \n\n{0} +AddRepoDialogTestSuccessMsg=The template source test was successful. +AddRepoDialogTestFailedMsg=The template source test was not successful. +AddRepoDialogAuthGroup=Authentication method +AddRepoDialogLogonAuthButton=&Logon authentication +AddRepoDialogAccessTokenAuthButton=&Access token authentication +AddRepoDialogUsernameLabel=&Username: +AddRepoDialogPasswordLabel=&Password: +AddRepoDialogAccessTokenLabel=Access &token: +AddRepoDialogAuthTestButtonLabel=Te&st Authentication +AddRepoDialogAuthTestTaskLabel=Testing authentication for: {0} +AddRepoDialogAuthTestFailedTitle=Authentication Test +AddRepoDialogAuthTestFailedDefaultMsg=The authentication test failed with error code: {0} +AddRepoDialogAuthTestFailedError=The following error occurred while testing authentication for URL: {0}\n\n{1} +AddRepoDialogAuthTestFailed=The following error occurred while testing authentication. Response code is {0} and the response message is: {1} +AddRepoDialogAuthTestSuccessMsg=The authentication test was successful. +AddRepoDialogAuthTestFailedMsg=The authentication test was not successful. +AddRepoDialogResetButtonLabel=&Restore Defaults +AddRepoDialogResetButtonTooltip=Reset to the defaults provided by the URL +AddRepoDialogAutoFillTaskLabel=Retrieving template source details +AddRepoDialogAutoFillNotAvailableMsg=The template source details could not be retrieved from the URL location. +AddRepoDialogNoName=Fill in a name for the template source. +AddRepoDialogNoDescription=Fill in a description for the template source. +AddRepoDialogNoUrl=Fill in a URL for the template source. +AddRepoDialogNoUsername=Fill in a username for template source authorization. +AddRepoDialogNoPassword=Fill in a password for template source authorization. +AddRepoDialogNoAccessToken=Fill in an access token for template source authorization. +RepoAuthDialogShell=Invalid Credentials +RepoAuthDialogTitle=Update Template Source Authentication Details +RepoAuthDialogMsg=Project create failed because of invalid credentials. Update the authentication details and click OK to try again. +RepoAuthDialogDescription=Update the authentication details for url: {0} + +RepoListTask=Retrieving template sources for: {0} RepoUpdateTask=Updating template sources RepoUpdateErrorTitle=Template Source Update Error RepoListErrorTitle=Template Source List Error