From 40c59725c0ed5c2e218161fec4f8e35f48b7f50e Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Wed, 3 Oct 2012 18:59:40 +0400 Subject: [PATCH] project templates shown as tree --- .../newProjectWizard/SelectTemplateStep.form | 11 +- .../newProjectWizard/SelectTemplateStep.java | 232 ++++++++++++++---- .../projectRoot/FacetsTreeCellRenderer.java | 3 +- .../templates/ArchivedTemplatesFactory.java | 8 +- .../intellij/ui/GroupedElementsRenderer.java | 3 +- .../ui/speedSearch/ElementFilter.java | 2 +- .../filtered/FilteringTreeBuilder.java | 2 +- .../options/newEditor/OptionsTree.java | 3 +- .../platform/ProjectTemplatesFactory.java | 5 +- .../util/src/com/intellij/util/ui/UIUtil.java | 4 + 10 files changed, 212 insertions(+), 61 deletions(-) diff --git a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.form b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.form index 9d55be75fa91..6c58d0011214 100644 --- a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.form +++ b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.form @@ -24,11 +24,12 @@ - + - - - + + + + @@ -68,7 +69,7 @@ - + diff --git a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.java b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.java index 99b710e74f4f..5c611dc7d6e0 100644 --- a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.java +++ b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/SelectTemplateStep.java @@ -18,27 +18,37 @@ package com.intellij.ide.util.newProjectWizard; import com.intellij.ide.util.projectWizard.ModuleWizardStep; import com.intellij.ide.util.projectWizard.WizardContext; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.platform.ProjectTemplate; import com.intellij.platform.ProjectTemplatesFactory; import com.intellij.psi.codeStyle.MinusculeMatcher; import com.intellij.psi.codeStyle.NameUtil; -import com.intellij.ui.CollectionListModel; -import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.SearchTextField; -import com.intellij.ui.components.JBList; +import com.intellij.ui.speedSearch.ElementFilter; +import com.intellij.ui.treeStructure.*; +import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; +import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; +import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; import java.awt.*; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Map; /** * @author Dmitry Avdeev @@ -47,34 +57,76 @@ import java.util.List; public class SelectTemplateStep extends ModuleWizardStep { private JPanel myPanel; - private JBList myTemplatesList; + private SimpleTree myTemplatesTree; private JPanel mySettingsPanel; private SearchTextField mySearchField; private JTextPane myDescriptionPane; private JPanel myDescriptionPanel; + private final WizardContext myContext; + private final ElementFilter.Active.Impl myFilter; + private final FilteringTreeBuilder myBuilder; + private MinusculeMatcher myMatcher; + public SelectTemplateStep(WizardContext context) { - final List templates = new ArrayList(); - ProjectTemplatesFactory[] factories = ProjectTemplatesFactory.EP_NAME.getExtensions(); - for (ProjectTemplatesFactory factory : factories) { - templates.addAll(Arrays.asList(factory.createTemplates(context))); - } - + myContext = context; Messages.installHyperlinkSupport(myDescriptionPane); - myTemplatesList.setModel(new CollectionListModel(templates)); - myTemplatesList.setCellRenderer(new ColoredListCellRenderer() { + ProjectTemplatesFactory[] factories = ProjectTemplatesFactory.EP_NAME.getExtensions(); + final MultiMap groups = new MultiMap(); + for (ProjectTemplatesFactory factory : factories) { + for (String string : factory.getGroups()) { + groups.putValue(string, factory); + } + } + + SimpleTreeStructure.Impl structure = new SimpleTreeStructure.Impl(new SimpleNode() { @Override - protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - ProjectTemplate template = (ProjectTemplate)value; - append(template.getName()); + public SimpleNode[] getChildren() { + return ContainerUtil.map2Array(groups.entrySet(), NO_CHILDREN, new Function>, SimpleNode>() { + @Override + public SimpleNode fun(Map.Entry> entry) { + return new GroupNode(entry.getKey(), entry.getValue()); + } + }); } }); - myTemplatesList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + buildMatcher(); + myFilter = new ElementFilter.Active.Impl() { @Override - public void valueChanged(ListSelectionEvent e) { + public boolean shouldBeShowing(SimpleNode template) { + return matches(template); + } + }; + myBuilder = new FilteringTreeBuilder(myTemplatesTree, myFilter, structure, null); + + myTemplatesTree.setRootVisible(false); + myTemplatesTree.setShowsRootHandles(false); + myTemplatesTree.setCellRenderer(new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { +// setIpad(new Insets()); + SimpleNode node = getSimpleNode(value); + if (node != null) { + append(node.getName()); + } + if (node instanceof GroupNode) { + setIcon(UIUtil.getTreeIcon(expanded)); + } + } + }); + + myTemplatesTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { if (mySettingsPanel.getComponentCount() > 0) { mySettingsPanel.remove(0); } @@ -89,44 +141,83 @@ public class SelectTemplateStep extends ModuleWizardStep { myDescriptionPane.setText(description); myDescriptionPanel.setVisible(StringUtil.isNotEmpty(description)); } + else { + mySettingsPanel.setVisible(false); + myDescriptionPanel.setVisible(false); + } mySettingsPanel.revalidate(); mySettingsPanel.repaint(); } }); - if (myTemplatesList.getModel().getSize() > 0) { - myTemplatesList.setSelectedIndex(0); - } + + //if (myTemplatesTree.getModel().getSize() > 0) { + // myTemplatesTree.setSelectedIndex(0); + //} mySearchField.addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent e) { - final MinusculeMatcher matcher = NameUtil.buildMatcher(mySearchField.getText(), NameUtil.MatchingCaseSensitivity.NONE); - ProjectTemplate selectedTemplate = getSelectedTemplate(); - List list = ContainerUtil.filter(templates, new Condition() { - @Override - public boolean value(ProjectTemplate template) { - String name = template.getName(); - String[] words = NameUtil.nameToWords(name); - for (String word : words) { - if (matcher.matches(word)) return true; - } - return false; - } - }); - myTemplatesList.setModel(new CollectionListModel(list)); - if (!list.isEmpty()) { - if (list.contains(selectedTemplate)) { - myTemplatesList.setSelectedValue(selectedTemplate, true); - } - else { - myTemplatesList.setSelectedIndex(0); - } - } + doFilter(); } }); + doFilter(); } + private void doFilter() { + buildMatcher(); + SimpleNode selectedNode = getSelectedNode(); + final Ref node = new Ref(); + if (!(selectedNode instanceof TemplateNode) || !matches(selectedNode)) { + myTemplatesTree.accept(myBuilder, new SimpleNodeVisitor() { + @Override + public boolean accept(SimpleNode simpleNode) { + FilteringTreeStructure.FilteringNode wrapper = (FilteringTreeStructure.FilteringNode)simpleNode; + Object delegate = wrapper.getDelegate(); + if (delegate instanceof TemplateNode && matches((SimpleNode)delegate)) { + node.set((SimpleNode)delegate); + return true; + } + return false; + } + }); + } + + myFilter.fireUpdate(node.get(), true, false); + } + + private boolean matches(SimpleNode template) { + String name = template.getName(); + if (name == null) return false; + String[] words = NameUtil.nameToWords(name); + for (String word : words) { + if (myMatcher.matches(word)) return true; + } + return false; + } + + private void buildMatcher() { + myMatcher = NameUtil.buildMatcher(mySearchField.getText(), NameUtil.MatchingCaseSensitivity.NONE); + } + + @Nullable public ProjectTemplate getSelectedTemplate() { - return (ProjectTemplate)myTemplatesList.getSelectedValue(); + SimpleNode delegate = getSelectedNode(); + return delegate instanceof TemplateNode ? ((TemplateNode)delegate).myTemplate : null; + } + + @Nullable + private SimpleNode getSelectedNode() { + TreePath path = myTemplatesTree.getSelectionPath(); + if (path == null) return null; + return getSimpleNode(path.getLastPathComponent()); + } + + @Nullable + private SimpleNode getSimpleNode(Object component) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)component; + Object userObject = node.getUserObject(); + if (!(userObject instanceof FilteringTreeStructure.FilteringNode)) return null; + FilteringTreeStructure.FilteringNode object = (FilteringTreeStructure.FilteringNode)userObject; + return (SimpleNode)object.getDelegate(); } @Override @@ -142,4 +233,51 @@ public class SelectTemplateStep extends ModuleWizardStep { @Override public void updateDataModel() { } + + @Override + public void disposeUIResources() { + Disposer.dispose(myBuilder); + } + + private class GroupNode extends SimpleNode { + private final String myGroup; + private final Collection myFactories; + + public GroupNode(String group, Collection factories) { + myGroup = group; + myFactories = factories; + } + + @Override + public SimpleNode[] getChildren() { + List children = new ArrayList(); + for (ProjectTemplatesFactory factory : myFactories) { + ProjectTemplate[] templates = factory.createTemplates(myGroup, myContext); + for (ProjectTemplate template : templates) { + children.add(new TemplateNode(template)); + } + } + return children.toArray(new SimpleNode[children.size()]); + } + + + @Override + public String getName() { + return myGroup; + } + } + + private static class TemplateNode extends NullNode { + + private final ProjectTemplate myTemplate; + + public TemplateNode(ProjectTemplate template) { + myTemplate = template; + } + + @Override + public String getName() { + return myTemplate.getName(); + } + } } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetsTreeCellRenderer.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetsTreeCellRenderer.java index 67b89765c300..d89fe3797478 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetsTreeCellRenderer.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetsTreeCellRenderer.java @@ -21,13 +21,12 @@ import com.intellij.ui.ErrorLabel; import com.intellij.ui.GroupedElementsRenderer; import javax.swing.*; -import javax.swing.tree.TreeCellRenderer; import java.awt.*; /** * @author nik */ -public class FacetsTreeCellRenderer extends GroupedElementsRenderer.Tree implements TreeCellRenderer { +public class FacetsTreeCellRenderer extends GroupedElementsRenderer.Tree { @Override protected JComponent createItemComponent() { myTextLabel = new ErrorLabel(); diff --git a/java/idea-ui/src/com/intellij/platform/templates/ArchivedTemplatesFactory.java b/java/idea-ui/src/com/intellij/platform/templates/ArchivedTemplatesFactory.java index 0cbca9eb0086..9ce6014d93c3 100644 --- a/java/idea-ui/src/com/intellij/platform/templates/ArchivedTemplatesFactory.java +++ b/java/idea-ui/src/com/intellij/platform/templates/ArchivedTemplatesFactory.java @@ -38,7 +38,13 @@ public class ArchivedTemplatesFactory implements ProjectTemplatesFactory { @NotNull @Override - public ProjectTemplate[] createTemplates(WizardContext context) { + public String[] getGroups() { + return new String[] {"Standard"}; + } + + @NotNull + @Override + public ProjectTemplate[] createTemplates(String group, WizardContext context) { IdeaPluginDescriptor[] plugins = PluginManager.getPlugins(); Set urls = new HashSet(); diff --git a/platform/platform-api/src/com/intellij/ui/GroupedElementsRenderer.java b/platform/platform-api/src/com/intellij/ui/GroupedElementsRenderer.java index 7223a36514af..314dfbbc0fc1 100644 --- a/platform/platform-api/src/com/intellij/ui/GroupedElementsRenderer.java +++ b/platform/platform-api/src/com/intellij/ui/GroupedElementsRenderer.java @@ -21,6 +21,7 @@ import com.intellij.util.ui.UIUtil; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import javax.swing.tree.TreeCellRenderer; import java.awt.*; public abstract class GroupedElementsRenderer { @@ -135,7 +136,7 @@ public abstract class GroupedElementsRenderer { } } - public static abstract class Tree extends GroupedElementsRenderer { + public static abstract class Tree extends GroupedElementsRenderer implements TreeCellRenderer { protected void layout() { myRendererComponent.add(mySeparatorComponent, BorderLayout.NORTH); diff --git a/platform/platform-api/src/com/intellij/ui/speedSearch/ElementFilter.java b/platform/platform-api/src/com/intellij/ui/speedSearch/ElementFilter.java index 503c9c91eb60..1d568967b6e5 100644 --- a/platform/platform-api/src/com/intellij/ui/speedSearch/ElementFilter.java +++ b/platform/platform-api/src/com/intellij/ui/speedSearch/ElementFilter.java @@ -41,7 +41,7 @@ public interface ElementFilter { abstract class Impl implements Active { Set> myListeners = new CopyOnWriteArraySet>(); - public ActionCallback fireUpdate(final T preferredSelection, final boolean adjustSelection, final boolean now) { + public ActionCallback fireUpdate(@Nullable final T preferredSelection, final boolean adjustSelection, final boolean now) { final ActionCallback result = new ActionCallback(myListeners.size()); for (final Listener myListener : myListeners) { myListener.update(preferredSelection, adjustSelection, now).doWhenProcessed(result.createSetDoneRunnable()); diff --git a/platform/platform-api/src/com/intellij/ui/treeStructure/filtered/FilteringTreeBuilder.java b/platform/platform-api/src/com/intellij/ui/treeStructure/filtered/FilteringTreeBuilder.java index b84b12d258db..d6048bfbbe35 100644 --- a/platform/platform-api/src/com/intellij/ui/treeStructure/filtered/FilteringTreeBuilder.java +++ b/platform/platform-api/src/com/intellij/ui/treeStructure/filtered/FilteringTreeBuilder.java @@ -48,7 +48,7 @@ public class FilteringTreeBuilder extends AbstractTreeBuilder { public FilteringTreeBuilder(Tree tree, ElementFilter filter, AbstractTreeStructure structure, - Comparator comparator) { + @Nullable Comparator comparator) { super(tree, (DefaultTreeModel)tree.getModel(), structure instanceof FilteringTreeStructure ? structure diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java index 4006716e9f48..be3330db1bf5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java @@ -45,7 +45,6 @@ import javax.swing.event.TreeSelectionListener; import javax.swing.plaf.TreeUI; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.*; @@ -283,7 +282,7 @@ public class OptionsTree extends JPanel implements Disposable, OptionsEditorColl return null; } - class Renderer extends GroupedElementsRenderer.Tree implements TreeCellRenderer { + class Renderer extends GroupedElementsRenderer.Tree { private JLabel myHandle; diff --git a/platform/platform-impl/src/com/intellij/platform/ProjectTemplatesFactory.java b/platform/platform-impl/src/com/intellij/platform/ProjectTemplatesFactory.java index f1e7f041e8bc..e64e4ae47800 100644 --- a/platform/platform-impl/src/com/intellij/platform/ProjectTemplatesFactory.java +++ b/platform/platform-impl/src/com/intellij/platform/ProjectTemplatesFactory.java @@ -28,5 +28,8 @@ public interface ProjectTemplatesFactory { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.projectTemplatesFactory"); @NotNull - ProjectTemplate[] createTemplates(WizardContext context); + String[] getGroups(); + + @NotNull + ProjectTemplate[] createTemplates(String group, WizardContext context); } diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index fbe23b9c0155..25236800c7b0 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -840,6 +840,10 @@ public class UIUtil { return UIManager.getIcon("Tree.expandedIcon"); } + public static Icon getTreeIcon(boolean expanded) { + return expanded ? getTreeExpandedIcon() : getTreeCollapsedIcon(); + } + public static Icon getTreeSelectedCollapsedIcon() { return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() ? AllIcons.Mac.Tree_white_right_arrow : getTreeCollapsedIcon();