Merge branch 'master' into east825/tasks-configurable-issue-states
[idea/community.git] / plugins / tasks / tasks-core / src / com / intellij / tasks / impl / TaskUiUtil.java
1 package com.intellij.tasks.impl;
2
3 import com.intellij.openapi.application.ApplicationManager;
4 import com.intellij.openapi.application.ModalityState;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.progress.ProgressIndicator;
7 import com.intellij.openapi.progress.Task;
8 import com.intellij.openapi.project.Project;
9 import com.intellij.openapi.ui.ComboBox;
10 import com.intellij.ui.ListCellRendererWrapper;
11 import com.intellij.util.ArrayUtil;
12 import org.jetbrains.annotations.NotNull;
13 import org.jetbrains.annotations.Nullable;
14
15 import javax.swing.*;
16 import java.util.Collection;
17
18 /**
19  * @author Mikhail Golubev
20  */
21 public class TaskUiUtil {
22
23   private static Logger LOG = Logger.getInstance(TaskUiUtil.class);
24
25   private TaskUiUtil() {
26     // Utility class
27   }
28
29   /**
30    * Special kind of backgroundable task tailored to update UI in modal dialogs, which is very common for
31    * task repository editors in settings.
32    */
33   public abstract static class RemoteFetchTask<T> extends Task.Backgroundable {
34     protected T myResult;
35     protected Exception myException;
36     private final ModalityState myModalityState;
37
38     /**
39      * Should be called only from EDT, so current modality state can be captured.
40      */
41     protected RemoteFetchTask(@Nullable Project project, @NotNull String title) {
42       this(project, title, ModalityState.current());
43     }
44
45     protected RemoteFetchTask(@Nullable Project project, @NotNull String title, @NotNull ModalityState modalityState) {
46       super(project, title);
47       myModalityState = modalityState;
48     }
49
50     @Override
51     public final void run(@NotNull ProgressIndicator indicator) {
52       try {
53         myResult = fetch(indicator);
54       }
55       catch (Exception e) {
56         LOG.error(e);
57         myException = e;
58       }
59     }
60
61     /**
62      * {@link #onSuccess()} can't be used for this purpose, because it doesn't consider current modality state
63      * which will prevent UI updating in modal dialog (e.g. in {@link com.intellij.tasks.config.TaskRepositoryEditor}).
64      */
65     @Nullable
66     @Override
67     public final NotificationInfo notifyFinished() {
68       ApplicationManager.getApplication().invokeLater(new Runnable() {
69         @Override
70         public void run() {
71           updateUI();
72         }
73       }, myModalityState);
74       return null;
75     }
76
77     @NotNull
78     protected abstract T fetch(@NotNull ProgressIndicator indicator) throws Exception;
79
80     protected abstract void updateUI();
81   }
82
83   /**
84    * Auxiliary remote fetcher designed to simplify updating of combo boxes in repository editors, which is
85    * indeed a rather common task.
86    */
87   public static abstract class ComboBoxUpdater<T> extends RemoteFetchTask<Collection<T>> {
88     protected final ComboBox myComboBox;
89
90     public ComboBoxUpdater(@Nullable Project project, @NotNull String title, @NotNull ComboBox comboBox) {
91       super(project, title, ModalityState.any());
92       myComboBox = comboBox;
93     }
94
95     /**
96      * Return extra item like "All projects", which will be added as first item after every combo box update.
97      *
98      * @return extra first combo box item
99      */
100     @Nullable
101     public T getExtraItem() {
102       return null;
103     }
104
105     /**
106      * Return item to select after every combo box update. Default implementation select item, returned by {@link #getExtraItem()}.
107      *
108      * @return selected combo box item
109      */
110     @Nullable
111     public T getSelectedItem() {
112       return getExtraItem();
113     }
114
115     @SuppressWarnings("unchecked")
116     @Override
117     protected void updateUI() {
118       if (myResult != null) {
119         myComboBox.setModel(new DefaultComboBoxModel(ArrayUtil.toObjectArray(myResult)));
120         final T extra = getExtraItem();
121         if (extra != null) {
122           myComboBox.insertItemAt(extra, 0);
123         }
124         // ensure that selected ItemEvent will be fired, even if first item of the model
125         // is the same as the next selected
126         myComboBox.setSelectedItem(null);
127
128         final T selected = getSelectedItem();
129         if (selected != null) {
130           if (!selected.equals(extra) && !myResult.contains(selected)) {
131             myComboBox.addItem(selected);
132           }
133           myComboBox.setSelectedItem(selected);
134         }
135         else if (myComboBox.getItemCount() > 0) {
136           myComboBox.setSelectedIndex(0);
137         }
138       }
139       else {
140         handleError();
141       }
142     }
143
144     protected void handleError() {
145       myComboBox.removeAllItems();
146     }
147   }
148
149   /**
150    * Very simple wrapper around {@link com.intellij.ui.ListCellRendererWrapper} useful for
151    * combo boxes where each item has plain text representation with special message for
152    * {@code null} value.
153    */
154   public static class SimpleComboBoxRenderer<T> extends ListCellRendererWrapper<T> {
155     private final String myNullDescription;
156     public SimpleComboBoxRenderer(@NotNull String nullDescription) {
157       myNullDescription = nullDescription;
158     }
159
160     @Override
161     public final void customize(JList list, T value, int index, boolean selected, boolean hasFocus) {
162       setText(value == null ? myNullDescription : getDescription(value));
163     }
164
165     @NotNull
166     protected String getDescription(@NotNull T item) {
167       return item.toString();
168     }
169   }
170 }