From 2dcb0d6ae0fc478f26b29d68be42dc0be176f64a Mon Sep 17 00:00:00 2001 From: Evgeny Zakrevsky Date: Tue, 9 Oct 2012 01:28:22 +0400 Subject: [PATCH] IDEA-68253 Issue trackers integration. Basic implementation. --- .../src/com/intellij/util/ui/FormBuilder.java | 34 ++- .../util/src/com/intellij/util/ui/UIUtil.java | 4 +- .../tasks/config/BaseRepositoryEditor.form | 4 +- .../tasks/config/BaseRepositoryEditor.java | 2 +- .../tasks/tasks-core/src/META-INF/plugin.xml | 1 + .../tasks/connector/WebRepository.java | 252 ++++++++++++++++++ .../tasks/connector/WebRepositoryEditor.java | 77 ++++++ .../tasks/connector/WebRepositoryType.java | 47 ++++ .../tasks-core/src/icons/TasksIcons.java | 17 +- 9 files changed, 408 insertions(+), 30 deletions(-) create mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepository.java create mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryEditor.java create mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryType.java diff --git a/platform/util/src/com/intellij/util/ui/FormBuilder.java b/platform/util/src/com/intellij/util/ui/FormBuilder.java index fe190d15fb0e..5b1088debd19 100644 --- a/platform/util/src/com/intellij/util/ui/FormBuilder.java +++ b/platform/util/src/com/intellij/util/ui/FormBuilder.java @@ -19,6 +19,7 @@ */ package com.intellij.util.ui; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -52,49 +53,46 @@ public class FormBuilder { return new FormBuilder(); } - public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component) { + public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component) { return addLabeledComponent(label, component, myVerticalGap, false); } - public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, final int topInset) { + public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, final int topInset) { return addLabeledComponent(label, component, topInset, false); } - public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, boolean labelOnTop) { + public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, boolean labelOnTop) { return addLabeledComponent(label, component, myVerticalGap, labelOnTop); } - public FormBuilder addLabeledComponent(String labelText, JComponent component) { + public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component) { return addLabeledComponent(labelText, component, myVerticalGap, false); } - public FormBuilder addLabeledComponent(String labelText, JComponent component, final int topInset) { + public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, final int topInset) { return addLabeledComponent(labelText, component, topInset, false); } - public FormBuilder addLabeledComponent(String labelText, JComponent component, boolean labelOnTop) { + public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, boolean labelOnTop) { return addLabeledComponent(labelText, component, myVerticalGap, labelOnTop); } - public FormBuilder addLabeledComponent(String labelText, JComponent component, final int topInset, boolean labelOnTop) { - JLabel label = null; - if (labelText != null) { - label = new JLabel(UIUtil.removeMnemonic(labelText)); - final int index = UIUtil.getDisplayMnemonicIndex(labelText); - if (index != -1) { - label.setDisplayedMnemonic(labelText.charAt(index + 1)); - } - label.setLabelFor(component); + public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, final int topInset, boolean labelOnTop) { + JLabel label = new JLabel(UIUtil.removeMnemonic(labelText)); + final int index = UIUtil.getDisplayMnemonicIndex(labelText); + if (index != -1) { + label.setDisplayedMnemonic(labelText.charAt(index + 1)); } + label.setLabelFor(component); return addLabeledComponent(label, component, topInset, labelOnTop); } - public FormBuilder addComponent(JComponent component) { + public FormBuilder addComponent(@NotNull JComponent component) { return addLabeledComponent((JLabel)null, component, myVerticalGap, false); } - public FormBuilder addComponent(JComponent component, final int topInset) { + public FormBuilder addComponent(@NotNull JComponent component, final int topInset) { return addLabeledComponent((JLabel)null, component, topInset, false); } @@ -111,7 +109,7 @@ public class FormBuilder { new Box.Filler(new Dimension(0, height), new Dimension(0, height), new Dimension(Short.MAX_VALUE, height))); } - public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, int topInset, boolean labelOnTop) { + public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, int topInset, boolean labelOnTop) { GridBagConstraints c = new GridBagConstraints(); topInset = myLineCount > 0 ? topInset : 0; diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index 25236800c7b0..876a33516e50 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -388,9 +388,9 @@ public class UIUtil { int defSize = getLabelFont().getSize(); switch (size) { case SMALL: - return Math.max(defSize - 2f, 11f); + return Math.max(defSize - 2f, 10f); case MINI: - return Math.max(defSize - 4f, 9f); + return Math.max(defSize - 4f, 8f); default: return defSize; } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.form b/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.form index 9acd06059d54..074ebcfa1acd 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.form +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.form @@ -146,11 +146,13 @@ - + + + diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.java index bc4d8dc39ff4..94e67421b8f0 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/config/BaseRepositoryEditor.java @@ -62,7 +62,7 @@ public class BaseRepositoryEditor extends TaskReposito private JPanel myCustomPanel; private JBCheckBox myAddCommitMessage; - private JLabel myComment; + private JBLabel myComment; private JPanel myEditorPanel; private boolean myApplying; diff --git a/plugins/tasks/tasks-core/src/META-INF/plugin.xml b/plugins/tasks/tasks-core/src/META-INF/plugin.xml index 4e74c57fc7e8..84346733e975 100644 --- a/plugins/tasks/tasks-core/src/META-INF/plugin.xml +++ b/plugins/tasks/tasks-core/src/META-INF/plugin.xml @@ -116,6 +116,7 @@ + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepository.java new file mode 100644 index 000000000000..bc2b0115b8fd --- /dev/null +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepository.java @@ -0,0 +1,252 @@ +package com.intellij.tasks.connector; + +import com.intellij.openapi.util.Comparing; +import com.intellij.tasks.Comment; +import com.intellij.tasks.Task; +import com.intellij.tasks.TaskRepositoryType; +import com.intellij.tasks.TaskType; +import com.intellij.tasks.impl.BaseRepository; +import com.intellij.tasks.impl.BaseRepositoryImpl; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.annotations.Tag; +import icons.TasksIcons; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * User: Evgeny.Zakrevsky + * Date: 10/4/12 + */ +@Tag("Web") +public class WebRepository extends BaseRepositoryImpl { + private String myTasksListURL; + private String myTaskPattern; + private String myLoginURL; + + final static String SERVER_URL_PLACEHOLDER = "{serverUrl}"; + final static String USERNAME_PLACEHOLDER = "{username}"; + final static String PASSWORD_PLACEHOLDER = "{password}"; + final static String ID_PLACEHOLDER = "{id}"; + final static String SUMMARY_PLACEHOLDER = "{summary}"; + final static String DESCRIPTION_PLACEHOLDER = "{description}"; + final static String PAGE_PLACEHOLDER = "{page}"; + + //final Map myPlaceholder2Value = new HashMap(); + + @SuppressWarnings({"UnusedDeclaration"}) + public WebRepository() { + } + + public WebRepository(final TaskRepositoryType type) { + super(type); + } + + public WebRepository(final WebRepository other) { + super(other); + myTasksListURL = other.getTasksListURL(); + myTaskPattern = other.getTaskPattern(); + myLoginURL = other.getLoginURL(); + } + + @Override + public Task[] getIssues(@Nullable final String query, final int max, final long since) throws Exception { + final HttpClient httpClient = getHttpClient(); + login(httpClient); + + final GetMethod getMethod = new GetMethod(getFullTasksUrl()); + httpClient.executeMethod(getMethod); + final String response = getMethod.getResponseBodyAsString(Integer.MAX_VALUE); + + final String taskPatternWithoutPlaceholders = myTaskPattern.replaceAll("\\{.+?\\}", ""); + Matcher matcher = Pattern + .compile(taskPatternWithoutPlaceholders, + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNICODE_CASE | Pattern.CANON_EQ) + .matcher(response); + + final List placeholders = getPlaceholders(myTaskPattern); + if (matcher.groupCount() != 2 || !placeholders.contains(ID_PLACEHOLDER) || !placeholders.contains(SUMMARY_PLACEHOLDER)) { + throw new Exception("Incorrect Task Pattern"); + } + + List tasks = new ArrayList(); + while (matcher.find()) { + final String id = matcher.group(placeholders.indexOf(ID_PLACEHOLDER) + 1); + final String description = matcher.group(placeholders.indexOf(SUMMARY_PLACEHOLDER) + 1); + tasks.add(new Task() { + @NotNull + @Override + public String getId() { + return id; + } + + @NotNull + @Override + public String getSummary() { + return description; + } + + @Nullable + @Override + public String getDescription() { + return null; + } + + @NotNull + @Override + public Comment[] getComments() { + return new Comment[0]; + } + + @Nullable + @Override + public Icon getIcon() { + return TasksIcons.Other; + } + + @NotNull + @Override + public TaskType getType() { + return TaskType.OTHER; + } + + @Nullable + @Override + public Date getUpdated() { + return null; + } + + @Nullable + @Override + public Date getCreated() { + return null; + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public boolean isIssue() { + return true; + } + + @Nullable + @Override + public String getIssueUrl() { + return null; + } + }); + } + + + return tasks.toArray(new Task[tasks.size()]); + } + + private void login(final HttpClient httpClient) throws IOException { + final GetMethod method = new GetMethod(getFullLoginUrl()); + httpClient.executeMethod(method); + } + + private static List getPlaceholders(String value) { + if (value == null) { + return ContainerUtil.emptyList(); + } + + List vars = new ArrayList(); + Matcher m = Pattern.compile("\\{(.+?)\\}").matcher(value); + while (m.find()) { + vars.add(m.group(0)); + } + return vars; + } + + private String getFullTasksUrl() { + return getTasksListURL() + .replaceAll(placeholder2regexp(SERVER_URL_PLACEHOLDER), getUrl()); + } + + private String getFullLoginUrl() { + return getLoginURL() + .replaceAll(placeholder2regexp(SERVER_URL_PLACEHOLDER), getUrl()) + .replaceAll(placeholder2regexp(USERNAME_PLACEHOLDER), getUsername()) + .replaceAll(placeholder2regexp(PASSWORD_PLACEHOLDER), getPassword()); + } + + private static String placeholder2regexp(String placeholder) { + return placeholder.replaceAll("\\{", "\\\\{"); + } + + @Nullable + @Override + public Task findTask(final String id) throws Exception { + return null; + } + + @Override + public BaseRepository clone() { + return new WebRepository(this); + } + + public String getTasksListURL() { + return myTasksListURL; + } + + public void setTasksListURL(final String tasksListURL) { + myTasksListURL = tasksListURL; + } + + public String getTaskPattern() { + return myTaskPattern; + } + + public void setTaskPattern(final String taskPattern) { + myTaskPattern = taskPattern; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof WebRepository)) return false; + if (!super.equals(o)) return false; + WebRepository that = (WebRepository)o; + if (!Comparing.equal(getTasksListURL(), that.getTasksListURL())) return false; + if (!Comparing.equal(getTaskPattern(), that.getTaskPattern())) return false; + if (!Comparing.equal(getLoginURL(), that.getLoginURL())) return false; + return true; + } + + @Nullable + @Override + public CancellableConnection createCancellableConnection() { + return new CancellableConnection() { + @Override + protected void doTest() throws Exception { + final Task[] issues = getIssues("", 0, 0); + if (issues.length == 0) throw new Exception("Tasks not found. Probably, you don't login."); + } + + @Override + public void cancel() { + } + }; + } + + public String getLoginURL() { + return myLoginURL; + } + + public void setLoginURL(final String loginURL) { + myLoginURL = loginURL; + } +} diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryEditor.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryEditor.java new file mode 100644 index 000000000000..3dabbcc8e26f --- /dev/null +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryEditor.java @@ -0,0 +1,77 @@ +package com.intellij.tasks.connector; + +import com.intellij.openapi.project.Project; +import com.intellij.tasks.config.BaseRepositoryEditor; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.Consumer; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * User: Evgeny.Zakrevsky + * Date: 10/4/12 + */ +public class WebRepositoryEditor extends BaseRepositoryEditor { + private JBLabel myTasksListURLLabel; + private JBLabel myTaskPatternLabel; + private JTextField myTasksListURLText; + private JTextField myTaskPatternText; + private JBLabel myLoginURLLabel; + private JTextField myLoginURLText; + + public WebRepositoryEditor(final Project project, final WebRepository repository, final Consumer changeListener) { + super(project, repository, changeListener); + myUrlLabel.setText("{serverUrl}:"); + myUsernameLabel.setText("{username}:"); + myPasswordLabel.setText("{password}:"); + + //setAnchor(myUsernameLabel); + } + + @Override + public void apply() { + myRepository.setTasksListURL(myTasksListURLText.getText()); + myRepository.setTaskPattern(myTaskPatternText.getText()); + myRepository.setLoginURL(myLoginURLText.getText()); + super.apply(); + } + + @Nullable + @Override + protected JComponent createCustomPanel() { + myTasksListURLLabel = new JBLabel("Tasks List URL:", SwingConstants.RIGHT); + myTasksListURLText = new JTextField(myRepository.getTasksListURL()); + installListener(myTasksListURLText); + myTaskPatternLabel = new JBLabel("Task Pattern:", SwingConstants.RIGHT); + myTaskPatternText = new JTextField(myRepository.getTaskPattern()); + installListener(myTaskPatternText); + myLoginURLLabel = new JBLabel("Login URL:", SwingConstants.RIGHT); + myLoginURLText = new JTextField(myRepository.getLoginURL()); + installListener(myLoginURLText); + return FormBuilder.createFormBuilder().setAlignLabelOnRight(true) + .addVerticalGap(8) + .addLabeledComponent(myTasksListURLLabel, myTasksListURLText) + .addLabeledComponent(new JLabel(), new JBLabel("Available placeholders: " + WebRepository.SERVER_URL_PLACEHOLDER, UIUtil.ComponentStyle.SMALL, + UIUtil.FontColor.BRIGHTER), 1) + .addLabeledComponent(myTaskPatternLabel, myTaskPatternText, 8) + .addLabeledComponent(new JLabel(), new JBLabel("Task pattern should be a regexp with two matching group: ({id}.+?) and ({summary}.+?)", + UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER), 1) + .addLabeledComponent(myLoginURLLabel, myLoginURLText, 8) + .addLabeledComponent(new JLabel(), new JBLabel("Available placeholders: " + WebRepository.SERVER_URL_PLACEHOLDER + ", " + + WebRepository.USERNAME_PLACEHOLDER + ", " + WebRepository.PASSWORD_PLACEHOLDER, UIUtil.ComponentStyle.SMALL, + UIUtil.FontColor.BRIGHTER), 1) + .addVerticalGap(8) + .getPanel(); + } + + @Override + public void setAnchor(@Nullable final JComponent anchor) { + super.setAnchor(anchor); + myTasksListURLLabel.setAnchor(anchor); + myTaskPatternLabel.setAnchor(anchor); + myLoginURLLabel.setAnchor(anchor); + } +} diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryType.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryType.java new file mode 100644 index 000000000000..21251bee3f02 --- /dev/null +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/connector/WebRepositoryType.java @@ -0,0 +1,47 @@ +package com.intellij.tasks.connector; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import com.intellij.tasks.TaskRepository; +import com.intellij.tasks.config.TaskRepositoryEditor; +import com.intellij.tasks.impl.BaseRepositoryType; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * User: Evgeny.Zakrevsky + * Date: 10/4/12 + */ +public class WebRepositoryType extends BaseRepositoryType { + @NotNull + @Override + public String getName() { + return "Web Repository"; + } + + @Override + public Icon getIcon() { + return AllIcons.General.Web; + } + + @NotNull + @Override + public TaskRepository createRepository() { + return new WebRepository(this); + } + + @Override + public Class getRepositoryClass() { + return WebRepository.class; + } + + @NotNull + @Override + public TaskRepositoryEditor createEditor(final WebRepository repository, + final Project project, + final Consumer changeListener) { + return new WebRepositoryEditor(project, repository, changeListener); + } +} diff --git a/plugins/tasks/tasks-core/src/icons/TasksIcons.java b/plugins/tasks/tasks-core/src/icons/TasksIcons.java index 3233faff2144..319c687910dc 100644 --- a/plugins/tasks/tasks-core/src/icons/TasksIcons.java +++ b/plugins/tasks/tasks-core/src/icons/TasksIcons.java @@ -16,12 +16,19 @@ public class TasksIcons { public static final Icon Bug = load("/icons/bug.png"); // 16x16 public static final Icon Exception = load("/icons/exception.png"); // 16x16 public static final Icon Feature = load("/icons/feature.png"); // 16x16 + public static final Icon Other = load("/icons/other.png"); // 16x16 + public static final Icon Unknown = load("/icons/unknown.png"); // 16x16 + public static final Icon SavedContext = load("/icons/savedContext.png"); // 16x16 + public static final Icon Fogbugz = load("/icons/fogbugz.png"); // 16x16 public static final Icon Github = load("/icons/github.png"); // 16x16 public static final Icon Lighthouse = load("/icons/lighthouse.gif"); // 16x16 public static final Icon Mantis = load("/icons/mantis.png"); // 16x16 - public static final Icon Other = load("/icons/other.png"); // 16x16 - + public static final Icon Pivotal = load("/icons/pivotal.png"); // 16x16 + public static final Icon Redmine = load("/icons/redmine.png"); // 16x16 + public static final Icon Trac = load("/icons/trac.png"); // 16x16 + public static final Icon Youtrack = load("/icons/youtrack.png"); // 16x16 + public static class Pivotal { public static final Icon Bug = load("/icons/pivotal/bug.png"); // 14x14 public static final Icon Chore = load("/icons/pivotal/chore.png"); // 14x14 @@ -29,10 +36,4 @@ public class TasksIcons { public static final Icon Release = load("/icons/pivotal/release.png"); // 14x14 } - public static final Icon Pivotal = load("/icons/pivotal.png"); // 16x16 - public static final Icon Redmine = load("/icons/redmine.png"); // 16x16 - public static final Icon SavedContext = load("/icons/savedContext.png"); // 16x16 - public static final Icon Trac = load("/icons/trac.png"); // 16x16 - public static final Icon Unknown = load("/icons/unknown.png"); // 16x16 - public static final Icon Youtrack = load("/icons/youtrack.png"); // 16x16 }