| /* |
| * Copyright 2000-2010 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.ui; |
| |
| import com.intellij.openapi.util.Iconable; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.ui.SimpleColoredComponent; |
| import com.intellij.ui.SimpleColoredRenderer; |
| import com.intellij.ui.SimpleTextAttributes; |
| import com.intellij.util.containers.Stack; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import javax.swing.tree.TreeModel; |
| import javax.swing.tree.TreeNode; |
| import javax.swing.tree.TreePath; |
| import java.awt.*; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| |
| /** |
| * User: spLeaner |
| */ |
| public class TreeComboBox extends ComboBoxWithWidePopup { |
| private static final int INDENT = UIUtil.getTreeLeftChildIndent(); |
| private TreeModel myTreeModel; |
| private final String myDefaultText; |
| private final boolean myShowRootNode; |
| |
| public TreeComboBox(@NotNull final TreeModel model) { |
| this(model, true); |
| } |
| |
| public TreeComboBox(@NotNull final TreeModel model, final boolean showRootNode) { |
| this(model, showRootNode, null); |
| } |
| |
| public TreeComboBox(@NotNull final TreeModel model, final boolean showRootNode, final String defaultText) { |
| myTreeModel = model; |
| myDefaultText = defaultText; |
| myShowRootNode = showRootNode; |
| setModel(new TreeModelWrapper(myTreeModel, showRootNode)); |
| setRenderer(new TreeListCellRenderer(this, showRootNode, defaultText)); |
| if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); |
| } |
| |
| public void setTreeModel(@NotNull final TreeModel model, final boolean showRootNode) { |
| myTreeModel = model; |
| setModel(new TreeModelWrapper(model, showRootNode)); |
| } |
| |
| public TreeModel getTreeModel() { |
| return myTreeModel; |
| } |
| |
| public JTree createFakeTree() { |
| final JTree tree = new JTree(getTreeModel()); |
| tree.setRootVisible(myShowRootNode); |
| return tree; |
| } |
| |
| private static class TreeListCellRenderer extends SimpleColoredRenderer implements ListCellRenderer { |
| private static final Border SELECTION_PAINTER = (Border)UIManager.get("MenuItem.selectedBackgroundPainter"); |
| |
| private boolean mySelected; |
| private boolean myInList; |
| private final JComboBox myComboBox; |
| private boolean myChecked; |
| private boolean myEditable; |
| private final boolean myUnderAquaLookAndFeel; |
| private final boolean myShowRootNode; |
| private final String myDefaultText; |
| |
| private TreeListCellRenderer(@NotNull final JComboBox comboBox, final boolean showRootNode, @Nullable final String defaultText) { |
| myComboBox = comboBox; |
| myShowRootNode = showRootNode; |
| myDefaultText = defaultText; |
| myUnderAquaLookAndFeel = UIUtil.isUnderAquaLookAndFeel(); |
| setOpaque(!myUnderAquaLookAndFeel); |
| } |
| |
| private static Icon getValueIcon(final Object value, final int index) { |
| if (value instanceof CustomPresentation) { |
| return ((CustomPresentation)value).getIcon(index, 0); |
| } |
| if (value instanceof Iconable) { |
| return ((Iconable)value).getIcon(0); |
| } |
| |
| return null; |
| } |
| |
| private TreeModelWrapper getTreeModelWrapper() { |
| return (TreeModelWrapper)myComboBox.getModel(); |
| } |
| |
| @Override |
| public Component getListCellRendererComponent(final JList list, |
| final Object value, |
| final int index, |
| final boolean isSelected, |
| final boolean cellHasFocus) { |
| clear(); |
| |
| myInList = index >= 0; |
| if (index >= 0) { |
| Object obj1 = myComboBox.getItemAt(index); |
| myChecked = obj1 != null && obj1.equals(myComboBox.getSelectedItem()); |
| } |
| else { |
| myChecked = false; |
| } |
| |
| int indent = 0; |
| if (myInList) { |
| final TreePath path = getTreeModelWrapper().getPathForRow(index); |
| indent = path == null ? 0 : (path.getPathCount() - 1 - (myShowRootNode ? 0 : 1)) * |
| (UIUtil.isUnderAquaLookAndFeel() ? 2 : 1) * INDENT; |
| } |
| |
| setIpad(new Insets(1, !myInList || myEditable ? myUnderAquaLookAndFeel ? 0 : 5 : (myUnderAquaLookAndFeel ? 23 : 5) + indent, 1, 5)); |
| |
| setIcon(getValueIcon(value, index)); |
| setIconOpaque(!myUnderAquaLookAndFeel); |
| |
| myEditable = myComboBox.isEditable(); |
| |
| setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); |
| if (!myUnderAquaLookAndFeel) setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); |
| |
| if (value instanceof CustomPresentation) { |
| ((CustomPresentation)value).append(this, index); |
| } else { |
| if (value == null) { |
| if (index == -1 && myDefaultText != null) { |
| append(myDefaultText, SimpleTextAttributes.GRAY_ATTRIBUTES); |
| } else { |
| append(""); |
| } |
| } else { |
| append(value.toString()); |
| } |
| } |
| |
| |
| setSelected(isSelected); |
| setFont(list.getFont()); |
| |
| return this; |
| } |
| |
| private void setSelected(final boolean selected) { |
| mySelected = selected; |
| } |
| |
| @Override |
| protected boolean shouldPaintBackground() { |
| return !myUnderAquaLookAndFeel; |
| } |
| |
| @Override |
| protected void paintComponent(final Graphics g) { |
| if (myUnderAquaLookAndFeel) { |
| if (mySelected) { |
| SELECTION_PAINTER.paintBorder(this, g, 0, 0, getWidth(), getHeight()); |
| } |
| |
| if (SystemInfo.isMac && myChecked && !myEditable) { |
| int i = getHeight() - 4; |
| g.setColor(getForeground()); |
| g.drawString("\u2713", 6, i); |
| } |
| } |
| |
| super.paintComponent(g); |
| } |
| } |
| |
| private static class TreeModelWrapper extends AbstractListModel implements ComboBoxModel { |
| private final TreeModel myTreeModel; |
| private Object mySelectedItem; |
| private final boolean myShowRootNode; |
| private final List<TreeNode> myTreeModelAsList = new ArrayList<TreeNode>(); |
| |
| private TreeModelWrapper(@NotNull final TreeModel treeModel, final boolean showRootNode) { |
| myTreeModel = treeModel; |
| myShowRootNode = showRootNode; |
| accumulateChildren((TreeNode) treeModel.getRoot(), myTreeModelAsList, showRootNode); |
| } |
| |
| public TreeModel getTreeModel() { |
| return myTreeModel; |
| } |
| |
| @Override |
| public void setSelectedItem(final Object obj) { |
| if (mySelectedItem != null && !mySelectedItem.equals(obj) || mySelectedItem == null && obj != null) { |
| mySelectedItem = obj; |
| fireContentsChanged(this, -1, -1); |
| } |
| } |
| |
| private static void accumulateChildren(@NotNull final TreeNode node, @NotNull final List<TreeNode> list, final boolean showRoot) { |
| if (showRoot || node.getParent() != null) list.add(node); |
| |
| final int count = node.getChildCount(); |
| for (int i = 0; i < count; i++) { |
| accumulateChildren(node.getChildAt(i), list, showRoot); |
| } |
| } |
| |
| private TreePath getPathForRow(final int row) { |
| TreeNode node = myTreeModelAsList.get(row); |
| final List<TreeNode> path = new ArrayList<TreeNode>(); |
| while (node != null) { |
| path.add(0, node); |
| node = node.getParent(); |
| } |
| |
| return new TreePath(path.toArray(new TreeNode[path.size()])); |
| } |
| |
| @Override |
| public Object getSelectedItem() { |
| return mySelectedItem; |
| } |
| |
| public int getSize() { |
| int count = 0; |
| Enumeration e = new PreorderEnumeration(myTreeModel); |
| while (e.hasMoreElements()) { |
| e.nextElement(); |
| count++; |
| } |
| |
| return count - (myShowRootNode ? 0 : 1); |
| } |
| |
| public Object getElementAt(int index) { |
| Enumeration e = new PreorderEnumeration(myTreeModel); |
| if (!myShowRootNode) index++; |
| for (int i = 0; i < index; i++) { |
| e.nextElement(); |
| } |
| |
| return e.nextElement(); |
| } |
| } |
| |
| private static class ChildrenEnumeration implements Enumeration { |
| private final TreeModel myTreeModel; |
| private final Object myNode; |
| private int myIndex = -1; |
| |
| public ChildrenEnumeration(@NotNull final TreeModel treeModel, @NotNull final Object node) { |
| myTreeModel = treeModel; |
| myNode = node; |
| } |
| |
| public boolean hasMoreElements() { |
| return myIndex < myTreeModel.getChildCount(myNode) - 1; |
| } |
| |
| public Object nextElement() { |
| return myTreeModel.getChild(myNode, ++myIndex); |
| } |
| } |
| |
| private static class PreorderEnumeration implements Enumeration { |
| private final TreeModel myTreeModel; |
| private final Stack<Enumeration> myStack; |
| |
| public PreorderEnumeration(@NotNull final TreeModel treeModel) { |
| myTreeModel = treeModel; |
| myStack = new Stack<Enumeration>(); |
| myStack.push(Collections.enumeration(Collections.singleton(treeModel.getRoot()))); |
| } |
| |
| public boolean hasMoreElements() { |
| return !myStack.empty() && |
| myStack.peek().hasMoreElements(); |
| } |
| |
| public Object nextElement() { |
| Enumeration e = myStack.peek(); |
| Object node = e.nextElement(); |
| if (!e.hasMoreElements()) { |
| myStack.pop(); |
| } |
| Enumeration children = new ChildrenEnumeration(myTreeModel, node); |
| if (children.hasMoreElements()) { |
| myStack.push(children); |
| } |
| return node; |
| } |
| } |
| |
| public interface CustomPresentation { |
| void append(SimpleColoredComponent component, int index); |
| Icon getIcon(int index, @Iconable.IconFlags int flags); |
| } |
| |
| } |