Merge branch 'master' into uta/rainbow
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / colors / ColorOptionsTree.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.application.options.colors;
17
18 import com.intellij.openapi.editor.colors.EditorSchemeAttributeDescriptor;
19 import com.intellij.openapi.editor.colors.EditorSchemeAttributeDescriptorWithPath;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.ui.TreeSpeedSearch;
22 import com.intellij.ui.treeStructure.Tree;
23 import com.intellij.util.containers.ContainerUtil;
24 import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27
28 import javax.swing.tree.*;
29 import java.util.*;
30
31 import static com.intellij.openapi.editor.colors.EditorSchemeAttributeDescriptorWithPath.NAME_SEPARATOR;
32
33 /**
34  * @author Rustam Vishnyakov
35  */
36 public class ColorOptionsTree extends Tree {
37   private final String myCategoryName;
38   private final DefaultTreeModel myTreeModel;
39
40   private static final Comparator<EditorSchemeAttributeDescriptor> ATTR_COMPARATOR =
41     (o1, o2) -> StringUtil.naturalCompare(o1.toString(), o2.toString());
42
43   public ColorOptionsTree(@NotNull String categoryName) {
44     super(createTreeModel());
45     myTreeModel = (DefaultTreeModel)getModel();
46     setRootVisible(false);
47     getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
48     myCategoryName = categoryName;
49     new TreeSpeedSearch(this, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true);
50   }
51
52   public void fillOptions(@NotNull ColorAndFontOptions options) {
53     DefaultMutableTreeNode root = new DefaultMutableTreeNode();
54     for (EditorSchemeAttributeDescriptor description : getOrderedDescriptors(options)) {
55       if (!description.getGroup().equals(myCategoryName)) continue;
56       List<String> path = extractPath(description);
57       if (path != null && path.size() > 1) {
58         MyTreeNode groupNode = ensureGroup(root, path, 0);
59         groupNode.add(new MyTreeNode(description, path.get(path.size() - 1)));
60       }
61       else {
62         root.add(new MyTreeNode(description));
63       }
64     }
65     myTreeModel.setRoot(root);
66   }
67
68   private static TreeModel createTreeModel()  {
69     return new DefaultTreeModel(new DefaultMutableTreeTableNode());
70   }
71
72   private Collection<EditorSchemeAttributeDescriptor> getOrderedDescriptors(@NotNull ColorAndFontOptions options) {
73     ArrayList<EditorSchemeAttributeDescriptor> list = ContainerUtil.newArrayList();
74     for (EditorSchemeAttributeDescriptor description : options.getCurrentDescriptions()) {
75       if (!description.getGroup().equals(myCategoryName)) continue;
76       list.add(description);
77     }
78     Collections.sort(list, ATTR_COMPARATOR);
79     return list;
80   }
81
82   @Nullable
83   public EditorSchemeAttributeDescriptor getSelectedDescriptor() {
84     Object selectedValue = getSelectedValue();
85     return selectedValue instanceof EditorSchemeAttributeDescriptor ? (EditorSchemeAttributeDescriptor)selectedValue : null;
86   }
87
88   @Nullable
89   public Object getSelectedValue() {
90     Object selectedNode = getLastSelectedPathComponent();
91     if (selectedNode instanceof DefaultMutableTreeNode) {
92       return ((DefaultMutableTreeNode)selectedNode).getUserObject();
93     }
94     return null;
95   }
96
97   public void selectOptionByType(@NotNull final String attributeType) {
98     selectPath(findOption(myTreeModel.getRoot(), new DescriptorMatcher() {
99       @Override
100       public boolean matches(@NotNull Object data) {
101         if (data instanceof EditorSchemeAttributeDescriptor) {
102           return attributeType.equals(((EditorSchemeAttributeDescriptor)data).getType());
103         }
104         return false;
105       }
106     }));
107   }
108
109   public void selectOptionByName(@NotNull final String optionName) {
110     selectPath(findOption(myTreeModel.getRoot(), new DescriptorMatcher() {
111       @Override
112       public boolean matches(@NotNull Object data) {
113         return !optionName.isEmpty() &&  StringUtil.containsIgnoreCase(data.toString(), optionName);
114       }
115     }));
116   }
117
118   @Nullable
119   private TreePath findOption(@NotNull Object nodeObject, @NotNull DescriptorMatcher matcher) {
120     for (int i = 0; i < myTreeModel.getChildCount(nodeObject); i ++) {
121       Object childObject = myTreeModel.getChild(nodeObject, i);
122       if (childObject instanceof MyTreeNode) {
123         Object data = ((MyTreeNode)childObject).getUserObject();
124         if (matcher.matches(data)) {
125           return new TreePath(myTreeModel.getPathToRoot((MyTreeNode)childObject));
126         }
127       }
128       TreePath pathInChild = findOption(childObject, matcher);
129       if (pathInChild != null) return pathInChild;
130     }
131     return null;
132   }
133
134   private void selectPath(@Nullable TreePath path) {
135     if (path != null) {
136       setSelectionPath(path);
137       scrollPathToVisible(path);
138     }
139   }
140
141   @Nullable
142   private static List<String> extractPath(@NotNull EditorSchemeAttributeDescriptor descriptor) {
143     if (descriptor instanceof EditorSchemeAttributeDescriptorWithPath) {
144       String name = descriptor.toString();
145       List<String> path = new ArrayList<>();
146       int separatorStart = name.indexOf(NAME_SEPARATOR);
147       int nextChunkStart = 0;
148       while(separatorStart > 0) {
149         path.add(name.substring(nextChunkStart, separatorStart));
150         nextChunkStart = separatorStart + NAME_SEPARATOR.length();
151         separatorStart = name.indexOf(NAME_SEPARATOR, nextChunkStart);
152       }
153       if (nextChunkStart < name.length()) {
154         path.add(name.substring(nextChunkStart));
155       }
156       return path;
157     }
158     return null;
159   }
160
161   private static class MyTreeNode extends DefaultMutableTreeNode {
162     private final String myName;
163
164     public MyTreeNode(@NotNull EditorSchemeAttributeDescriptor descriptor, @NotNull String name) {
165       super(descriptor);
166       myName = name;
167     }
168
169     public MyTreeNode(@NotNull EditorSchemeAttributeDescriptor descriptor) {
170       super(descriptor);
171       myName = descriptor.toString();
172     }
173
174     public MyTreeNode(@NotNull String groupName) {
175       super(groupName);
176       myName = groupName;
177     }
178
179     @Override
180     public String toString() {
181       return myName;
182     }
183
184   }
185
186   private interface DescriptorMatcher {
187     boolean matches(@NotNull Object data);
188   }
189
190   private static MyTreeNode ensureGroup(@NotNull DefaultMutableTreeNode root, @NotNull List<String> path, int index) {
191     String groupName = path.get(index ++);
192     for (int i = 0; i < root.getChildCount(); i ++) {
193       TreeNode child = root.getChildAt(i);
194       if (child instanceof MyTreeNode && groupName.equals(child.toString())) {
195         return index < path.size() - 1 ? ensureGroup((MyTreeNode)child, path, index) : (MyTreeNode)child;
196       }
197     }
198     MyTreeNode groupNode = new MyTreeNode(groupName);
199     root.add(groupNode);
200     return index < path.size() - 1 ? ensureGroup(groupNode, path, index) : groupNode;
201   }
202 }