forgot to check length()
[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.util.text.StringUtil;
19 import com.intellij.ui.TreeSpeedSearch;
20 import com.intellij.ui.treeStructure.Tree;
21 import com.intellij.util.containers.ContainerUtil;
22 import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import javax.swing.tree.*;
27 import java.util.*;
28
29 /**
30  * @author Rustam Vishnyakov
31  */
32 public class ColorOptionsTree extends Tree {
33   private final String myCategoryName;
34   private final DefaultTreeModel myTreeModel;
35
36   public final static String NAME_SEPARATOR = "//";
37
38   private static final Comparator<EditorSchemeAttributeDescriptor> ATTR_COMPARATOR =
39     (o1, o2) -> StringUtil.naturalCompare(o1.toString(), o2.toString());
40
41   public ColorOptionsTree(@NotNull String categoryName) {
42     super(createTreeModel());
43     myTreeModel = (DefaultTreeModel)getModel();
44     setRootVisible(false);
45     getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
46     myCategoryName = categoryName;
47     new TreeSpeedSearch(this, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true);
48   }
49
50   public void fillOptions(@NotNull ColorAndFontOptions options) {
51     DefaultMutableTreeNode root = new DefaultMutableTreeNode();
52     for (EditorSchemeAttributeDescriptor description : getOrderedDescriptors(options)) {
53       if (!description.getGroup().equals(myCategoryName)) continue;
54       List<String> path = extractPath(description);
55       if (path != null && path.size() > 1) {
56         MyTreeNode groupNode = ensureGroup(root, path, 0);
57         groupNode.add(new MyTreeNode(description, path.get(path.size() - 1)));
58       }
59       else {
60         root.add(new MyTreeNode(description));
61       }
62     }
63     myTreeModel.setRoot(root);
64   }
65
66   private static TreeModel createTreeModel()  {
67     return new DefaultTreeModel(new DefaultMutableTreeTableNode());
68   }
69
70   private Collection<EditorSchemeAttributeDescriptor> getOrderedDescriptors(@NotNull ColorAndFontOptions options) {
71     ArrayList<EditorSchemeAttributeDescriptor> list = ContainerUtil.newArrayList();
72     for (EditorSchemeAttributeDescriptor description : options.getCurrentDescriptions()) {
73       if (!description.getGroup().equals(myCategoryName)) continue;
74       list.add(description);
75     }
76     Collections.sort(list, ATTR_COMPARATOR);
77     return list;
78   }
79
80   @Nullable
81   public ColorAndFontDescription getSelectedDescriptor() {
82     Object selectedValue = getSelectedValue();
83     return selectedValue instanceof ColorAndFontDescription ? (ColorAndFontDescription)selectedValue : null;
84   }
85
86   @Nullable
87   public Object getSelectedValue() {
88     Object selectedNode = getLastSelectedPathComponent();
89     if (selectedNode instanceof DefaultMutableTreeNode) {
90       return ((DefaultMutableTreeNode)selectedNode).getUserObject();
91     }
92     return null;
93   }
94
95   public void selectOptionByType(@NotNull final String attributeType) {
96     selectPath(findOption(myTreeModel.getRoot(), new DescriptorMatcher() {
97       @Override
98       public boolean matches(@NotNull Object data) {
99         if (data instanceof EditorSchemeAttributeDescriptor) {
100           return attributeType.equals(((EditorSchemeAttributeDescriptor)data).getType());
101         }
102         return false;
103       }
104     }));
105   }
106
107   public void selectOptionByName(@NotNull final String optionName) {
108     selectPath(findOption(myTreeModel.getRoot(), new DescriptorMatcher() {
109       @Override
110       public boolean matches(@NotNull Object data) {
111         return !optionName.isEmpty() &&  StringUtil.containsIgnoreCase(data.toString(), optionName);
112       }
113     }));
114   }
115
116   @Nullable
117   private TreePath findOption(@NotNull Object nodeObject, @NotNull DescriptorMatcher matcher) {
118     for (int i = 0; i < myTreeModel.getChildCount(nodeObject); i ++) {
119       Object childObject = myTreeModel.getChild(nodeObject, i);
120       if (childObject instanceof MyTreeNode) {
121         Object data = ((MyTreeNode)childObject).getUserObject();
122         if (matcher.matches(data)) {
123           return new TreePath(myTreeModel.getPathToRoot((MyTreeNode)childObject));
124         }
125       }
126       TreePath pathInChild = findOption(childObject, matcher);
127       if (pathInChild != null) return pathInChild;
128     }
129     return null;
130   }
131
132   private void selectPath(@Nullable TreePath path) {
133     if (path != null) {
134       setSelectionPath(path);
135       scrollPathToVisible(path);
136     }
137   }
138
139   @Nullable
140   private static List<String> extractPath(@NotNull EditorSchemeAttributeDescriptor descriptor) {
141     if (descriptor instanceof ColorAndFontDescription) {
142       String name = descriptor.toString();
143       List<String> path = new ArrayList<>();
144       int separatorStart = name.indexOf(NAME_SEPARATOR);
145       int nextChunkStart = 0;
146       while(separatorStart > 0) {
147         path.add(name.substring(nextChunkStart, separatorStart));
148         nextChunkStart = separatorStart + NAME_SEPARATOR.length();
149         separatorStart = name.indexOf(NAME_SEPARATOR, nextChunkStart);
150       }
151       if (nextChunkStart < name.length()) {
152         path.add(name.substring(nextChunkStart));
153       }
154       return path;
155     }
156     return null;
157   }
158
159   private static class MyTreeNode extends DefaultMutableTreeNode {
160     private final String myName;
161
162     public MyTreeNode(@NotNull EditorSchemeAttributeDescriptor descriptor, @NotNull String name) {
163       super(descriptor);
164       myName = name;
165     }
166
167     public MyTreeNode(@NotNull EditorSchemeAttributeDescriptor descriptor) {
168       super(descriptor);
169       myName = descriptor.toString();
170     }
171
172     public MyTreeNode(@NotNull String groupName) {
173       super(groupName);
174       myName = groupName;
175     }
176
177     @Override
178     public String toString() {
179       return myName;
180     }
181
182   }
183
184   private interface DescriptorMatcher {
185     boolean matches(@NotNull Object data);
186   }
187
188   private static MyTreeNode ensureGroup(@NotNull DefaultMutableTreeNode root, @NotNull List<String> path, int index) {
189     String groupName = path.get(index ++);
190     for (int i = 0; i < root.getChildCount(); i ++) {
191       TreeNode child = root.getChildAt(i);
192       if (child instanceof MyTreeNode && groupName.equals(child.toString())) {
193         return index < path.size() - 1 ? ensureGroup((MyTreeNode)child, path, index) : (MyTreeNode)child;
194       }
195     }
196     MyTreeNode groupNode = new MyTreeNode(groupName);
197     root.add(groupNode);
198     return index < path.size() - 1 ? ensureGroup(groupNode, path, index) : groupNode;
199   }
200 }