1b4f09513de0b104e6774a3166cc907da1609f6c
[idea/community.git] / plugins / gradle / src / org / jetbrains / plugins / gradle / config / GradleColorAndFontPreviewPanel.java
1 package org.jetbrains.plugins.gradle.config;
2
3 import com.intellij.application.options.colors.ColorAndFontOptions;
4 import com.intellij.application.options.colors.ColorAndFontSettingsListener;
5 import com.intellij.application.options.colors.EditorSchemeAttributeDescriptor;
6 import com.intellij.application.options.colors.PreviewPanel;
7 import com.intellij.ide.projectView.PresentationData;
8 import com.intellij.ide.util.treeView.NodeRenderer;
9 import com.intellij.openapi.application.ApplicationNamesInfo;
10 import com.intellij.openapi.editor.colors.EditorColorsScheme;
11 import com.intellij.openapi.editor.colors.TextAttributesKey;
12 import com.intellij.openapi.util.Pair;
13 import com.intellij.openapi.util.Ref;
14 import com.intellij.ui.components.JBScrollPane;
15 import com.intellij.ui.treeStructure.Tree;
16 import com.intellij.util.containers.hash.HashMap;
17 import org.jetbrains.annotations.NotNull;
18 import org.jetbrains.annotations.Nullable;
19 import org.jetbrains.plugins.gradle.ui.GradleIcons;
20 import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNodeDescriptor;
21 import org.jetbrains.plugins.gradle.util.GradleBundle;
22
23 import javax.swing.*;
24 import javax.swing.event.TreeSelectionEvent;
25 import javax.swing.event.TreeSelectionListener;
26 import javax.swing.tree.*;
27 import java.awt.*;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.CopyOnWriteArrayList;
31
32 /**
33  * @author Denis Zhdanov
34  * @since 1/19/12 11:59 AM
35  */
36 public class GradleColorAndFontPreviewPanel implements PreviewPanel {
37   
38   private final Map<TextAttributesKey, DefaultMutableTreeNode> myNodes = new HashMap<TextAttributesKey, DefaultMutableTreeNode>();
39
40   private final List<ColorAndFontSettingsListener> myListeners                = new CopyOnWriteArrayList<ColorAndFontSettingsListener>();
41   private final JPanel                             myContent                  = new JPanel(new GridBagLayout());
42   private final JPanel                             myNodeRenderPanel          = new JPanel(new GridBagLayout());
43   private final Ref<Boolean>                       myAllowTreeExpansion       = new Ref<Boolean>(true);
44   private final ArrowPanel                         mySelectedElementSignPanel = new ArrowPanel();
45   
46   private final Tree             myTree;
47   private final DefaultTreeModel myTreeModel;
48
49   private ColorAndFontOptions myOptions;
50   private TreeNode            mySelectedNode;
51
52   public GradleColorAndFontPreviewPanel(@NotNull ColorAndFontOptions options) {
53     myOptions = options;
54     final Pair<Tree, DefaultTreeModel> pair = init();
55     myTree = pair.first;
56     myTreeModel = pair.second;
57   }
58
59   private Pair<Tree, DefaultTreeModel> init() {
60     myContent.removeAll();
61     String projectName = GradleBundle.message("gradle.settings.color.text.sample.conflict.node.name");
62     DefaultMutableTreeNode root = createNode(projectName, GradleIcons.PROJECT_ICON, GradleTextAttributes.GRADLE_CHANGE_CONFLICT);
63
64     String moduleName = GradleBundle.message("gradle.settings.color.text.sample.node.confirmed.name");
65     DefaultMutableTreeNode module = createNode(moduleName, GradleIcons.MODULE_ICON, GradleTextAttributes.GRADLE_CONFIRMED_CONFLICT);
66
67     String gradleLibraryName = GradleBundle.message("gradle.settings.color.text.sample.node.gradle.name");
68     DefaultMutableTreeNode gradleLibrary = createNode(
69       gradleLibraryName, GradleIcons.LIB_ICON, GradleTextAttributes.GRADLE_LOCAL_CHANGE
70     );
71
72     String intellijLibraryName = GradleBundle.message("gradle.settings.color.text.sample.node.intellij.name",
73                                                       ApplicationNamesInfo.getInstance().getProductName());
74     DefaultMutableTreeNode intellijLibrary = createNode(
75       intellijLibraryName, GradleIcons.LIB_ICON, GradleTextAttributes.INTELLIJ_LOCAL_CHANGE
76     );
77     
78     String syncLibraryName = GradleBundle.message("gradle.settings.color.text.sample.node.sync.name");
79     DefaultMutableTreeNode syncLibrary = createNode(syncLibraryName, GradleIcons.LIB_ICON, GradleTextAttributes.GRADLE_NO_CHANGE);
80     
81     module.add(gradleLibrary);
82     module.add(intellijLibrary);
83     module.add(syncLibrary);
84     root.add(module);
85     
86     mySelectedNode = root;
87     
88     DefaultTreeModel treeModel = new DefaultTreeModel(root);
89     Tree tree = buildTree(treeModel, module);
90     
91     GridBagConstraints constraints = new GridBagConstraints();
92     constraints.fill = GridBagConstraints.BOTH;
93     constraints.weightx = constraints.weighty = 1;
94     myContent.add(new JBScrollPane(tree), constraints);
95     return new Pair<Tree, DefaultTreeModel>(tree, treeModel);
96   }
97   
98   private Tree buildTree(@NotNull TreeModel model, DefaultMutableTreeNode ... nodesToExpand) {
99     final Tree tree = new Tree(model) {
100       @Override
101       protected void setExpandedState(TreePath path, boolean state) {
102         if (myAllowTreeExpansion.get()) {
103           super.setExpandedState(path, state);
104         }
105         // Ignore the expansion change events otherwise
106       }
107     };
108     
109     // Configure expansion
110     for (DefaultMutableTreeNode node : nodesToExpand) {
111       tree.expandPath(new TreePath(node.getPath()));
112     }
113     myAllowTreeExpansion.set(false);
114     
115     // Configure selection.
116     tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
117     tree.addTreeSelectionListener(new TreeSelectionListener() {
118       
119       private boolean myIgnore;
120       
121       @Override
122       public void valueChanged(TreeSelectionEvent e) {
123         if (myIgnore) {
124           return;
125         }
126         final TreePath path = e.getNewLeadSelectionPath();
127         if (path == null) {
128           clearSelection();
129           return;
130         }
131         final Object component = path.getLastPathComponent();
132         for (Map.Entry<TextAttributesKey, DefaultMutableTreeNode> entry : myNodes.entrySet()) {
133           if (entry.getValue().equals(component)) {
134             pointTo(entry.getValue());
135             for (ColorAndFontSettingsListener listener : myListeners) {
136               listener.selectionInPreviewChanged(entry.getKey().getExternalName());
137               clearSelection();
138             }
139             return;
140           }
141         }
142         clearSelection();
143       }
144
145       private void clearSelection() {
146         // Don't show selection at the 'preview' node.
147         myIgnore = true;
148         try {
149           tree.getSelectionModel().clearSelection();
150         }
151         finally {
152           myIgnore = false;
153         }
154       }
155     });
156     
157     // Bind rendering to the target colors scheme.
158     final NodeRenderer delegate = new NodeRenderer() {
159       @NotNull
160       @Override
161       protected EditorColorsScheme getColorsScheme() {
162         return myOptions.getSelectedScheme();
163       }
164     };
165     myNodeRenderPanel.setBackground(tree.getBackground());
166     tree.setCellRenderer(new TreeCellRenderer() {
167       @Override
168       public Component getTreeCellRendererComponent(JTree tree,
169                                                     Object value,
170                                                     boolean selected,
171                                                     boolean expanded,
172                                                     boolean leaf,
173                                                     int row,
174                                                     boolean hasFocus)
175       {
176         final Component component = delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
177         if (myNodeRenderPanel.getComponentCount() <= 0) {
178           GridBagConstraints constraints = new GridBagConstraints();
179           myNodeRenderPanel.add(component, constraints);
180           constraints.weightx = 1;
181           myNodeRenderPanel.add(mySelectedElementSignPanel, constraints);
182         }
183         
184         mySelectedElementSignPanel.setPaint(value == mySelectedNode);
185         return myNodeRenderPanel;
186       }
187     });
188     return tree;
189   }
190   
191   @Override
192   public void blinkSelectedHighlightType(Object selected) {
193     if (!(selected instanceof EditorSchemeAttributeDescriptor)) {
194       return;
195     }
196     final String type = ((EditorSchemeAttributeDescriptor)selected).getType();
197     for (Map.Entry<TextAttributesKey, DefaultMutableTreeNode> entry : myNodes.entrySet()) {
198       if (entry.getKey().getExternalName().equals(type)) {
199         pointTo(entry.getValue());
200         return;
201       }
202     }
203   }
204
205   /**
206    * Instructs the panel to show given node as selected.
207    * 
208    * @param node  node to show as 'selected'
209    */
210   private void pointTo(@NotNull TreeNode node) {
211     TreeNode oldSelectedNode = mySelectedNode;
212     mySelectedNode = node;
213     myTreeModel.nodeChanged(oldSelectedNode);
214     myTreeModel.nodeChanged(mySelectedNode);
215   }
216   
217   @Override
218   public void disposeUIResources() {
219     myListeners.clear();
220     myNodes.clear();
221     myContent.removeAll();
222     myNodeRenderPanel.removeAll();
223   }
224
225   @Override
226   public Component getPanel() {
227     return myContent;
228   }
229
230   @Override
231   public void updateView() {
232     repaintTree();
233   }
234
235   private void repaintTree() {
236     myTreeModel.reload();
237     myAllowTreeExpansion.set(true);
238     try {
239       for (DefaultMutableTreeNode node : myNodes.values()) {
240         myTree.expandPath(new TreePath(node.getPath()));
241       }
242     }
243     finally {
244       myAllowTreeExpansion.set(false);
245     }
246   }
247
248   @Override
249   public void addListener(@NotNull ColorAndFontSettingsListener listener) {
250     myListeners.add(listener);
251   }
252
253   private DefaultMutableTreeNode createNode(@NotNull String text, @NotNull Icon icon, @Nullable TextAttributesKey textAttributesKey) {
254     final GradleProjectStructureNodeDescriptor<String> descriptor = new GradleProjectStructureNodeDescriptor<String>(text, text, icon);
255     DefaultMutableTreeNode result = new DefaultMutableTreeNode(descriptor);
256     if (textAttributesKey != null) {
257       final PresentationData presentation = descriptor.getPresentation();
258       presentation.setAttributesKey(textAttributesKey);
259       myNodes.put(textAttributesKey, result);
260     }
261     return result;
262   }
263
264   /**
265    * Encapsulates logic of drawing 'selected element' sign.
266    */
267   private static class ArrowPanel extends JPanel {
268     
269     private boolean myPaint;
270     
271     ArrowPanel() {
272       super(new BorderLayout());
273       // Reserve enough horizontal space.
274       add(new JLabel("intellij"));
275     }
276
277     public void setPaint(boolean paint) {
278       myPaint = paint;
279     }
280
281     @Override
282     public void paint(Graphics g) {
283       if (!myPaint) {
284         return;
285       }
286       Graphics2D g2 = (Graphics2D)g;
287       g.setColor(Color.RED);
288       RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
289       renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
290       g2.setRenderingHints(renderHints);
291
292       FontMetrics fontMetrics = getFontMetrics(getFont());
293       int unit = fontMetrics.charWidth('a');
294       int q = unit / 4;
295       int[] x = {0, unit * 3, unit * 2, unit * 4, unit * 4, unit * 2, unit * 3, 0};
296       int[] y = {unit, 0, unit - q, unit - q, unit + q, unit + q, unit * 2, unit};
297       g2.fillPolygon(x, y, x.length);
298     }
299   }
300 }