45c6df8fbcada1748a62b4b02b797122fac66a55
[idea/community.git] / platform / platform-api / src / com / intellij / ui / CheckboxTreeBase.java
1 /*
2  * Copyright 2000-2017 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.ui;
17
18 import com.intellij.ui.treeStructure.Tree;
19 import com.intellij.util.EventDispatcher;
20 import com.intellij.util.ui.ThreeStateCheckBox;
21 import com.intellij.util.ui.UIUtil;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24
25 import javax.swing.*;
26 import javax.swing.tree.DefaultTreeModel;
27 import javax.swing.tree.TreeCellRenderer;
28 import javax.swing.tree.TreeNode;
29 import java.awt.*;
30
31 public class CheckboxTreeBase extends Tree {
32   private final CheckboxTreeHelper myHelper;
33   private final EventDispatcher<CheckboxTreeListener> myEventDispatcher = EventDispatcher.create(CheckboxTreeListener.class);
34
35   public CheckboxTreeBase() {
36     this(new CheckboxTreeCellRendererBase(), null);
37   }
38
39   public CheckboxTreeBase(final CheckboxTreeCellRendererBase cellRenderer, CheckedTreeNode root) {
40     this(cellRenderer, root, CheckboxTreeHelper.DEFAULT_POLICY);
41   }
42
43   public CheckboxTreeBase(CheckboxTreeCellRendererBase cellRenderer, @Nullable CheckedTreeNode root, CheckPolicy checkPolicy) {
44     myHelper = new CheckboxTreeHelper(checkPolicy, myEventDispatcher);
45     if (root != null) {
46       // override default model ("colors", etc.) ASAP to avoid CCE in renderers
47       setModel(new DefaultTreeModel(root));
48       setSelectionRow(0);
49     }
50     myEventDispatcher.addListener(new CheckboxTreeListener() {
51       @Override
52       public void mouseDoubleClicked(@NotNull CheckedTreeNode node) {
53         onDoubleClick(node);
54       }
55
56       @Override
57       public void nodeStateChanged(@NotNull CheckedTreeNode node) {
58         CheckboxTreeBase.this.onNodeStateChanged(node);
59       }
60
61       @Override
62       public void beforeNodeStateChanged(@NotNull CheckedTreeNode node) {
63         CheckboxTreeBase.this.nodeStateWillChange(node);
64       }
65     });
66     myHelper.initTree(this, this, cellRenderer);
67   }
68
69   @Deprecated
70   public void installRenderer(final CheckboxTreeCellRendererBase cellRenderer) {
71     setCellRenderer(cellRenderer);
72   }
73
74   /**
75    * @deprecated use {@link #setNodeState} to change node state or subscribe to {@link #addCheckboxTreeListener} to get notifications about state changes
76    */
77   @Deprecated
78   protected boolean toggleNode(CheckedTreeNode node) {
79     setNodeState(node, !node.isChecked());
80     return node.isChecked();
81   }
82
83   /**
84    * @deprecated use {@link #setNodeState} to change node state or subscribe to {@link #addCheckboxTreeListener} to get notifications about state changes
85    */
86   @Deprecated
87   protected void checkNode(CheckedTreeNode node, boolean checked) {
88     setNodeState(node, checked);
89   }
90
91   public void setNodeState(@NotNull CheckedTreeNode node, boolean checked) {
92     myHelper.setNodeState(this, node, checked);
93   }
94
95   public void addCheckboxTreeListener(@NotNull CheckboxTreeListener listener) {
96     myEventDispatcher.addListener(listener);
97   }
98
99   protected void onDoubleClick(final CheckedTreeNode node) {
100   }
101
102   /**
103    * Collect checked leaf nodes of the type {@code nodeType} and that are accepted by
104    * {@code filter}
105    *
106    * @param nodeType the type of userobject to consider
107    * @param filter   the filter (if null all nodes are accepted)
108    * @param <T>      the type of the node
109    * @return an array of collected nodes
110    */
111   public <T> T[] getCheckedNodes(final Class<T> nodeType, @Nullable final NodeFilter<T> filter) {
112     return CheckboxTreeHelper.getCheckedNodes(nodeType, filter, getModel());
113   }
114
115
116   public int getToggleClickCount() {
117     // to prevent node expanding/collapsing on checkbox toggling
118     return -1;
119   }
120
121   protected void onNodeStateChanged(CheckedTreeNode node) {
122   }
123
124   protected void nodeStateWillChange(CheckedTreeNode node) {
125   }
126
127   @Deprecated
128   protected void adjustParents(final CheckedTreeNode node, final boolean checked) {
129   }
130
131   public static class CheckboxTreeCellRendererBase extends JPanel implements TreeCellRenderer {
132     private final ColoredTreeCellRenderer myTextRenderer;
133     public final ThreeStateCheckBox myCheckbox;
134     private final boolean myUsePartialStatusForParentNodes;
135     protected boolean myIgnoreInheritance;
136
137     public CheckboxTreeCellRendererBase(boolean opaque) {
138       this(opaque, true);
139     }
140
141     public CheckboxTreeCellRendererBase(boolean opaque, final boolean usePartialStatusForParentNodes) {
142       super(new BorderLayout());
143       myUsePartialStatusForParentNodes = usePartialStatusForParentNodes;
144       myCheckbox = new ThreeStateCheckBox();
145       myCheckbox.setSelected(false);
146       myCheckbox.setThirdStateEnabled(false);
147       myTextRenderer = new ColoredTreeCellRenderer() {
148         public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { }
149       };
150       myTextRenderer.setOpaque(opaque);
151       add(myCheckbox, BorderLayout.WEST);
152       add(myTextRenderer, BorderLayout.CENTER);
153     }
154
155     public CheckboxTreeCellRendererBase() {
156       this(true);
157     }
158
159     public final Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
160       invalidate();
161       if (value instanceof CheckedTreeNode) {
162         CheckedTreeNode node = (CheckedTreeNode)value;
163
164         NodeState state = getNodeStatus(node);
165         myCheckbox.setVisible(true);
166         myCheckbox.setSelected(state != NodeState.CLEAR);
167         myCheckbox.setEnabled(node.isEnabled() && state != NodeState.PARTIAL);
168         myCheckbox.setOpaque(false);
169         myCheckbox.setBackground(null);
170         setBackground(null);
171
172         if (UIUtil.isUnderWin10LookAndFeel()) {
173           Object hoverValue = getClientProperty(UIUtil.CHECKBOX_ROLLOVER_PROPERTY);
174           myCheckbox.getModel().setRollover(hoverValue == value);
175
176           Object pressedValue = getClientProperty(UIUtil.CHECKBOX_PRESSED_PROPERTY);
177           myCheckbox.getModel().setPressed(pressedValue == value);
178         }
179       }
180       else {
181         myCheckbox.setVisible(false);
182       }
183       myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
184
185       if (UIUtil.isUnderGTKLookAndFeel()) {
186         final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground();
187         UIUtil.changeBackGround(this, background);
188       }
189       else if (UIUtil.isUnderNimbusLookAndFeel()) {
190         UIUtil.changeBackGround(this, UIUtil.TRANSPARENT_COLOR);
191       }
192       customizeRenderer(tree, value, selected, expanded, leaf, row, hasFocus);
193       revalidate();
194
195       return this;
196     }
197
198     private NodeState getNodeStatus(final CheckedTreeNode node) {
199       if (myIgnoreInheritance) return node.isChecked() ? NodeState.FULL : NodeState.CLEAR;
200       final boolean checked = node.isChecked();
201       if (node.getChildCount() == 0 || !myUsePartialStatusForParentNodes) return checked ? NodeState.FULL : NodeState.CLEAR;
202
203       NodeState result = null;
204
205       for (int i = 0; i < node.getChildCount(); i++) {
206         TreeNode child = node.getChildAt(i);
207         NodeState childStatus = child instanceof CheckedTreeNode? getNodeStatus((CheckedTreeNode)child) :
208                 checked? NodeState.FULL : NodeState.CLEAR;
209         if (childStatus == NodeState.PARTIAL) return NodeState.PARTIAL;
210         if (result == null) {
211           result = childStatus;
212         }
213         else if (result != childStatus) {
214           return NodeState.PARTIAL;
215         }
216       }
217
218       return result == null ? NodeState.CLEAR : result;
219     }
220
221     /**
222      * Should be implemented by concrete implementations.
223      * This method is invoked only for customization of component.
224      * All component attributes are cleared when this method is being invoked.
225      * Note that in general case <code>value</code> is not an instance of CheckedTreeNode.
226      */
227     public void customizeRenderer(JTree tree,
228                                   Object value,
229                                   boolean selected,
230                                   boolean expanded,
231                                   boolean leaf,
232                                   int row,
233                                   boolean hasFocus) {
234       if (value instanceof CheckedTreeNode) {
235         customizeCellRenderer(tree, value, selected, expanded, leaf, row, hasFocus);
236       }
237     }
238
239     /**
240      * @see CheckboxTreeCellRendererBase#customizeRenderer(JTree, Object, boolean, boolean, boolean, int, boolean)
241      * @deprecated
242      */
243     @Deprecated
244     public void customizeCellRenderer(JTree tree,
245                                       Object value,
246                                       boolean selected,
247                                       boolean expanded,
248                                       boolean leaf,
249                                       int row,
250                                       boolean hasFocus) {
251     }
252
253     public ColoredTreeCellRenderer getTextRenderer() {
254       return myTextRenderer;
255     }
256
257     public JCheckBox getCheckbox() {
258       return myCheckbox;
259     }
260   }
261
262
263   public enum NodeState {
264     FULL, CLEAR, PARTIAL
265   }
266
267   public static class CheckPolicy {
268     final boolean checkChildrenWithCheckedParent;
269     final boolean uncheckChildrenWithUncheckedParent;
270     final boolean checkParentWithCheckedChild;
271     final boolean uncheckParentWithUncheckedChild;
272
273     public CheckPolicy(final boolean checkChildrenWithCheckedParent,
274                        final boolean uncheckChildrenWithUncheckedParent,
275                        final boolean checkParentWithCheckedChild,
276                        final boolean uncheckParentWithUncheckedChild) {
277       this.checkChildrenWithCheckedParent = checkChildrenWithCheckedParent;
278       this.uncheckChildrenWithUncheckedParent = uncheckChildrenWithUncheckedParent;
279       this.checkParentWithCheckedChild = checkParentWithCheckedChild;
280       this.uncheckParentWithUncheckedChild = uncheckParentWithUncheckedChild;
281     }
282   }
283 }