package com.intellij.tasks.redmine; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.tasks.config.BaseRepositoryEditor; import com.intellij.tasks.impl.TaskUiUtil; import com.intellij.tasks.redmine.model.RedmineProject; import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBLabel; import com.intellij.util.Consumer; import com.intellij.util.containers.Stack; import com.intellij.util.ui.FormBuilder; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; import java.util.List; /** * @author Mikhail Golubev * @author Dennis.Ushakov */ public class RedmineRepositoryEditor extends BaseRepositoryEditor { private ComboBox myProjectCombo; private JTextField myAPIKey; private JBLabel myProjectLabel; private JBLabel myAPIKeyLabel; public RedmineRepositoryEditor(final Project project, final RedmineRepository repository, Consumer changeListener) { super(project, repository, changeListener); myTestButton.setEnabled(myRepository.isConfigured()); myAPIKey.setText(repository.getAPIKey()); installListener(myProjectCombo); installListener(myAPIKey); toggleCredentialsVisibility(); UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { initialize(); } }); } @Override protected void afterTestConnection(boolean connectionSuccessful) { if (connectionSuccessful) { new FetchProjectsTask().queue(); } else { myProjectCombo.removeAllItems(); } } private void initialize() { final RedmineProject currentProject = myRepository.getCurrentProject(); if (currentProject != null && myRepository.isConfigured()) { new FetchProjectsTask().queue(); } else { myProjectCombo.removeAllItems(); } } @Override public void apply() { super.apply(); RedmineProjectItem selected = (RedmineProjectItem)myProjectCombo.getSelectedItem(); myRepository.setCurrentProject(selected != null ? selected.myProject : null); myRepository.setAPIKey(myAPIKey.getText().trim()); myTestButton.setEnabled(myRepository.isConfigured()); toggleCredentialsVisibility(); } private void toggleCredentialsVisibility() { myPasswordLabel.setVisible(myRepository.isUseHttpAuthentication()); myPasswordText.setVisible(myRepository.isUseHttpAuthentication()); myUsernameLabel.setVisible(myRepository.isUseHttpAuthentication()); myUserNameText.setVisible(myRepository.isUseHttpAuthentication()); myAPIKeyLabel.setVisible(!myRepository.isUseHttpAuthentication()); myAPIKey.setVisible(!myRepository.isUseHttpAuthentication()); } @Nullable @Override protected JComponent createCustomPanel() { myProjectLabel = new JBLabel("Project:", SwingConstants.RIGHT); myProjectCombo = new ComboBox(300); //myProjectCombo.setRenderer(new TaskUiUtil.SimpleComboBoxRenderer("Set URL and password/token first")); myProjectCombo.setRenderer(new ListCellRendererWrapper() { @Override public void customize(JList list, RedmineProjectItem value, int index, boolean selected, boolean hasFocus) { if (value == null) { setText("Set URL and password/token first"); } else { if (myProjectCombo.isPopupVisible()) { //if (value.myLevel == 0 && value.myProject != RedmineRepository.UNSPECIFIED_PROJECT) { //setFont(UIUtil.getListFont().deriveFont(Font.BOLD)); //} StringBuilder builder = new StringBuilder(); if (value.myLevel != 0) { for (int i = 0; i < value.myLevel; i++) { builder.append(" "); } } setText(builder.append(value.myProject.getIdentifier()).toString()); } else { setText(value.myProject.getIdentifier()); } } } }); myAPIKeyLabel = new JBLabel("API Token:", SwingConstants.RIGHT); myAPIKey = new JPasswordField(); return FormBuilder.createFormBuilder() .addLabeledComponent(myAPIKeyLabel, myAPIKey) .addLabeledComponent(myProjectLabel, myProjectCombo) .getPanel(); } @Override public void setAnchor(@Nullable final JComponent anchor) { super.setAnchor(anchor); myProjectLabel.setAnchor(anchor); myAPIKeyLabel.setAnchor(anchor); } private static class RedmineProjectItem { public final RedmineProject myProject; public final int myLevel; public RedmineProjectItem(@NotNull RedmineProject project, int level) { myProject = project; myLevel = level; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (o instanceof RedmineProject) { return myProject.equals(o); } else if (o instanceof RedmineProjectItem) { return myProject.equals(((RedmineProjectItem)o).myProject); } return false; } @Override public int hashCode() { return myProject.hashCode(); } } private class FetchProjectsTask extends TaskUiUtil.ComboBoxUpdater { private FetchProjectsTask() { super(RedmineRepositoryEditor.this.myProject, "Downloading Redmine projects...", myProjectCombo); } @Override public RedmineProjectItem getExtraItem() { return new RedmineProjectItem(RedmineRepository.UNSPECIFIED_PROJECT, 0); } @Nullable @Override public RedmineProjectItem getSelectedItem() { RedmineProject currentProject = myRepository.getCurrentProject(); return currentProject != null ? new RedmineProjectItem(currentProject, -1) : null; } @NotNull @Override protected List fetch(@NotNull ProgressIndicator indicator) throws Exception { // Seems that Redmine always return its project hierarchy in DFS order. // So it's easy to find level of each project using stack of parents. Stack parents = new Stack(); List items = new ArrayList(); for (RedmineProject project : myRepository.fetchProjects()) { RedmineProject parentProject = project.getParent(); if (parentProject == null) { items.add(new RedmineProjectItem(project, 0)); parents.clear(); } else { while (!parents.isEmpty() && !parents.peek().equals(parentProject)) { parents.pop(); } items.add(new RedmineProjectItem(project, parents.size())); } parents.push(project); } return items; } } }