From 0c99b50a598b6741db7e13f6606b557d692cc1fc Thu, 16 Feb 2012 22:04:36 +0100 From: zour Date: Thu, 16 Feb 2012 21:59:48 +0100 Subject: [PATCH] Bug 360894 - Extensions tree viewer should also be filtered by leaf item's attributes diff --git a/ui/org.eclipse.pde.ui/icons/dlcl16/filter_related.gif b/ui/org.eclipse.pde.ui/icons/dlcl16/filter_related.gif new file mode 100644 index 0000000..f07bf77 --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/dlcl16/filter_related.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/dlcl16/search_extensions.gif b/ui/org.eclipse.pde.ui/icons/dlcl16/search_extensions.gif new file mode 100644 index 0000000..8f7cbab --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/dlcl16/search_extensions.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/dlcl16/toggle_expand_state.gif b/ui/org.eclipse.pde.ui/icons/dlcl16/toggle_expand_state.gif new file mode 100644 index 0000000..e868b95 --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/dlcl16/toggle_expand_state.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/elcl16/filter_related.gif b/ui/org.eclipse.pde.ui/icons/elcl16/filter_related.gif new file mode 100644 index 0000000..b983f27 --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/elcl16/filter_related.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/elcl16/search_extensions.gif b/ui/org.eclipse.pde.ui/icons/elcl16/search_extensions.gif new file mode 100644 index 0000000..6d1094e --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/elcl16/search_extensions.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/elcl16/toggle_expand_state.gif b/ui/org.eclipse.pde.ui/icons/elcl16/toggle_expand_state.gif new file mode 100644 index 0000000..f61d0b9 --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/elcl16/toggle_expand_state.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/icons/obj16/esearch_obj.gif b/ui/org.eclipse.pde.ui/icons/obj16/esearch_obj.gif new file mode 100644 index 0000000..32860cb --- /dev/null +++ b/ui/org.eclipse.pde.ui/icons/obj16/esearch_obj.gif Binary files differ diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java index 88c9ab2..c22b465 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java @@ -153,6 +153,7 @@ public static final ImageDescriptor DESC_INFO_ST_OBJ = create(PATH_OBJ, "info_st_obj.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_CATEGORY_OBJ = create(PATH_OBJ, "category_obj.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_PSEARCH_OBJ = create(PATH_OBJ, "psearch_obj.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_ESEARCH_OBJ = create(PATH_OBJ, "esearch_obj.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_SITE_OBJ = create(PATH_OBJ, "site_obj.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_JUNIT_MAIN_TAB = create(PATH_OBJ, "test.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_OUTPUT_FOLDER_OBJ = create(PATH_OBJ, "output_folder_attrib.gif"); //$NON-NLS-1$ @@ -232,6 +233,7 @@ public static final ImageDescriptor DESC_VERTICAL = create(PATH_LCL, "th_vertical.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_COLLAPSE_ALL = create(PATH_LCL, "collapseall.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_COLLAPSE_ALL_MINI = create(PATH_LCL, "collapse_all_mini.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_TOGGLE_EXPAND_STATE = create(PATH_LCL, "toggle_expand_state.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_HELP = create(PATH_LCL, "help.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_LINK_WITH_EDITOR = create(PATH_LCL, "synced.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_CALLEES = create(PATH_LCL, "ch_callees.gif"); //$NON-NLS-1$ @@ -242,6 +244,8 @@ public static final ImageDescriptor DESC_HISTORY_LIST = create(PATH_LCL, "history_list.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_CLEAR = create(PATH_LCL, "clear.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_FILTER = create(PATH_LCL, "filter_ps.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_FILTER_RELATED = create(PATH_LCL, "filter_related.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_SEARCH_EXTENSIONS = create(PATH_LCL, "search_extensions.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_ADD_ATT_DISABLED = create(PATH_LCL_DISABLED, "add_att.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_ALPHAB_SORT_CO_DISABLED = create(PATH_LCL_DISABLED, "alphab_sort_co.gif"); //$NON-NLS-1$ @@ -255,6 +259,7 @@ public static final ImageDescriptor DESC_HORIZONTAL_DISABLED = create(PATH_LCL_DISABLED, "th_horizontal.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_VERTICAL_DISABLED = create(PATH_LCL_DISABLED, "th_vertical.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_COLLAPSE_ALL_DISABLED = create(PATH_LCL_DISABLED, "collapseall.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_TOGGLE_EXPAND_STATE_DISABLED = create(PATH_LCL_DISABLED, "toggle_expand_state.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_LINK_WITH_EDITOR_DISABLED = create(PATH_LCL_DISABLED, "synced.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_CALLEES_DISABLED = create(PATH_LCL_DISABLED, "ch_callees.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_CALLERS_DISABLED = create(PATH_LCL_DISABLED, "ch_callers.gif"); //$NON-NLS-1$ @@ -264,6 +269,8 @@ public static final ImageDescriptor DESC_HISTORY_LIST_DISABLED = create(PATH_LCL_DISABLED, "history_list.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_DCLEAR = create(PATH_LCL_DISABLED, "clear.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_FILTER_DISABLED = create(PATH_LCL_DISABLED, "filter_ps.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_FILTER_RELATED_DISABLED = create(PATH_LCL_DISABLED, "filter_related.gif"); //$NON-NLS-1$ + public static final ImageDescriptor DESC_SEARCH_EXTENSIONS_DISABLED = create(PATH_LCL_DISABLED, "search_extensions.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_RUN_EXC = create(PATH_OBJ, "run_exc.gif"); //$NON-NLS-1$ public static final ImageDescriptor DESC_DEBUG_EXC = create(PATH_OBJ, "debug_exc.gif"); //$NON-NLS-1$ diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java index cfae1ea7..978d594 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java @@ -54,6 +54,8 @@ public static String DependencyPropertiesDialog_exportGroupText; + public static String ExtensionsPage_toggleExpandState; + public static String ExtensionsPage_searchWithExtensionsFilter; public static String ExternalizeStringsOperation_editNames_addComment; public static String ExternalizeStringsOperation_editNames_insertProperty; @@ -1425,6 +1427,8 @@ public static String EditorActions_revert; public static String Actions_open_label; public static String Actions_delete_label; + public static String Actions_filter_relatedPluginElements; + public static String Actions_search_relatedPluginElements; public static String Actions_synchronizeVersions_label; public static String Menus_new_label; @@ -1501,6 +1505,8 @@ public static String SearchAction_Declaration; public static String ShowDescriptionAction_label; public static String ShowDescriptionAction_title; + public static String ShowAllExtensionsAction_label; + public static String HideUnfilteredExtensionsAction_label; public static String ShowSampleAction_installing; public static String ShowSampleAction_title; public static String ShowSampleAction_msgDesc; diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/FilterRelatedExtensionsAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/FilterRelatedExtensionsAction.java new file mode 100644 index 0000000..40e2721 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/FilterRelatedExtensionsAction.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.actions; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.pde.internal.ui.PDEPluginImages; +import org.eclipse.pde.internal.ui.PDEUIMessages; +import org.eclipse.pde.internal.ui.editor.plugin.ExtensionsSection; +import org.eclipse.pde.internal.ui.editor.plugin.FormFilteredTree; +import org.eclipse.pde.internal.ui.util.ExtensionsFilterUtil; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.keys.IBindingService; + +/** + * Set the search pattern text to all values found by attribute list {@link ExtensionsFilterUtil#RELATED_ATTRIBUTES} + * + * @author Sascha Becher + */ +public class FilterRelatedExtensionsAction extends Action { + + protected ExtensionsSection fSection; + protected TreeViewer fExtensionTree; + protected FormFilteredTree fFilteredTree; + + public FilterRelatedExtensionsAction(TreeViewer treeViewer, FormFilteredTree filteredTree, ExtensionsSection section, boolean inContextMenu) { + setImageDescriptor(PDEPluginImages.DESC_FILTER_RELATED); + setDisabledImageDescriptor(PDEPluginImages.DESC_FILTER_RELATED_DISABLED); + String filterBinding = ((IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class)).getBestActiveBindingFormattedFor(ActionFactory.FIND.getCommandId()); + String title = PDEUIMessages.Actions_filter_relatedPluginElements + ((filterBinding != null) ? "\t" + filterBinding : ""); //$NON-NLS-1$ //$NON-NLS-2$ + String toolTip = PDEUIMessages.Actions_filter_relatedPluginElements + ((filterBinding != null) ? " (" + filterBinding + ")" : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (inContextMenu) { + setText(title); + } + setToolTipText(toolTip); + fSection = section; + fExtensionTree = treeViewer; + fFilteredTree = filteredTree; + } + + public void run() { + String filterPattern = ExtensionsFilterUtil.getFilterRelatedPattern((IStructuredSelection) fExtensionTree.getSelection()); + Text filterControl = fFilteredTree.getFilterControl(); + if (filterControl != null && filterPattern.length() > 0) { + fSection.setBypassFilterDelay(true); // force immediate job run + filterControl.setText(filterPattern); + } + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/SearchExtensionsAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/SearchExtensionsAction.java new file mode 100644 index 0000000..aec84c0 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/SearchExtensionsAction.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.actions; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.pde.internal.core.search.PluginSearchInput; +import org.eclipse.pde.internal.core.search.PluginSearchScope; +import org.eclipse.pde.internal.ui.PDEPluginImages; +import org.eclipse.pde.internal.ui.editor.plugin.FormFilteredTree; +import org.eclipse.pde.internal.ui.search.FindExtensionsByAttributeQuery; +import org.eclipse.pde.internal.ui.util.ExtensionsFilterUtil; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.NewSearchUI; + +/** + * Search in workspace plugins for occurences of either the current filter text or filter related attributes + * using the ExtensionsPatternFilter search behaviour. + * + * @author Sascha Becher + */ +public class SearchExtensionsAction extends Action { + + protected FormFilteredTree fFilteredTree; + + private IStructuredSelection fSelection; + private String fFilterRelatedText; + + public SearchExtensionsAction(FormFilteredTree filteredTree, String actionText) { + this(filteredTree.getViewer().getSelection(), actionText); + fFilteredTree = filteredTree; + } + + public SearchExtensionsAction(ISelection selection, String actionText) { + setImageDescriptor(PDEPluginImages.DESC_SEARCH_EXTENSIONS); + setDisabledImageDescriptor(PDEPluginImages.DESC_SEARCH_EXTENSIONS_DISABLED); + setText(actionText); + if (selection != null && selection instanceof IStructuredSelection) { + fSelection = (IStructuredSelection) selection; + } + } + + public void run() { + if (fSelection != null) { + this.fFilterRelatedText = ExtensionsFilterUtil.getFilterRelatedPattern(fSelection); + NewSearchUI.activateSearchResultView(); + NewSearchUI.runQueryInBackground(createSearchQuery()); + } + } + + protected ISearchQuery createSearchQuery() { + PluginSearchInput input = new PluginSearchInput(); + input.setSearchElement(PluginSearchInput.ELEMENT_PLUGIN); + input.setSearchLimit(PluginSearchInput.LIMIT_ALL); + input.setSearchString(getFilterText()); + input.setSearchScope(new PluginSearchScope(PluginSearchScope.SCOPE_WORKSPACE, PluginSearchScope.EXTERNAL_SCOPE_ALL, null)); + input.setCaseSensitive(false); + return new FindExtensionsByAttributeQuery(input); + } + + private String getFilterText() { + if (fFilterRelatedText != null && fFilterRelatedText.length() > 0) { + return fFilterRelatedText; + } + if (fFilteredTree != null) { + return fFilteredTree.getFilterControl().getText(); + } + return new String(); + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ShowAllExtensionsAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ShowAllExtensionsAction.java new file mode 100644 index 0000000..25b6a58 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ShowAllExtensionsAction.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.actions; + +import java.util.*; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.pde.core.IBaseModel; +import org.eclipse.pde.core.plugin.*; +import org.eclipse.pde.internal.core.text.plugin.PluginNode; +import org.eclipse.pde.internal.ui.PDEUIMessages; +import org.eclipse.pde.internal.ui.editor.plugin.FormFilteredTree; +import org.eclipse.pde.internal.ui.search.ExtensionsPatternFilter; +import org.eclipse.swt.widgets.TreeItem; + +/** + * Reveals all extensions when the tree is in filter mode. + * The purpose is convenience. When the Filter Related action shows that + * a certain item that should have been found with the search is missing, + * it is convenient to bring up the required extension to add the + * missing element without loosing the focus on the search result. + * Once all extensions are revealed this action hides them again except + * for those extensions that received new elements. + * + * @author Sascha Becher + */ +public class ShowAllExtensionsAction extends Action { + + private static int SHOW_ALL = 0; + private static int HIDE_UNFILTERED = 1; + + private int mode; + private FormFilteredTree fFilteredTree; + private IPluginModelBase fModel; + private ExtensionsPatternFilter fPatternFilter; + + public ShowAllExtensionsAction(IBaseModel model, FormFilteredTree filteredTree, ExtensionsPatternFilter patternFilter) { + fModel = (IPluginModelBase) model; + fFilteredTree = filteredTree; + fPatternFilter = patternFilter; + mode = getRequiredChange(); + setText(mode == SHOW_ALL ? PDEUIMessages.ShowAllExtensionsAction_label : PDEUIMessages.HideUnfilteredExtensionsAction_label); + } + + public void run() { + if (mode == SHOW_ALL) { + IPluginExtension[] extensions = fModel.getExtensions().getExtensions(); + try { + ISelection selection = fFilteredTree.getViewer().getSelection(); + Object[] expanded = fFilteredTree.getViewer().getVisibleExpandedElements(); + fFilteredTree.setRedraw(false); + for (int i = 0; i < extensions.length; i++) { + fPatternFilter.addElement(extensions[i]); + } + fFilteredTree.update(); + fFilteredTree.redraw(); + fFilteredTree.getViewer().refresh(); + + TreeItem[] treeItems = fFilteredTree.getViewer().getTree().getItems(); + for (int i = 0; i < treeItems.length; i++) { + TreeItem treeItem = treeItems[i]; + if (treeItem != null && !treeItem.getExpanded()) { + treeItem.setExpanded(true); + } + } + fFilteredTree.getViewer().refresh(); + fFilteredTree.getViewer().setExpandedElements(expanded); + fFilteredTree.getViewer().setSelection(selection); + } finally { + fFilteredTree.setRedraw(true); + } + } else if (mode == HIDE_UNFILTERED) { + List unfiltered = getUnfilteredExtensions(fPatternFilter.getMatchingLeafs()); + for (Iterator iterator = unfiltered.iterator(); iterator.hasNext();) { + fPatternFilter.removeElement(iterator.next()); + } + } + fFilteredTree.getViewer().refresh(); + } + + public int getRequiredChange() { + boolean visible = true; + IPluginExtension[] extensions = fModel.getExtensions().getExtensions(); + for (int i = 0; i < extensions.length; i++) { + IPluginExtension iPluginExtension = extensions[i]; + visible &= fPatternFilter.containsElement(iPluginExtension); + } + if (visible) { + return HIDE_UNFILTERED; + } + return SHOW_ALL; + } + + private List getUnfilteredExtensions(Collection matchingLeafs) { + List unfilteredExtensions = new ArrayList(); + try { + fFilteredTree.getViewer().setExpandPreCheckFilters(true); + IPluginExtension[] extensions = fModel.getPluginBase().getExtensions(); + for (int i = 0; i < extensions.length; i++) { + IPluginExtension iPluginExtension = extensions[i]; + boolean found = false; + for (Iterator it = matchingLeafs.iterator(); it.hasNext();) { + IPluginObject element = ((IPluginObject) it.next()); + while (element.getParent() != null && !(element.getParent() instanceof PluginNode)) { + element = element.getParent(); + } + if (element.equals(iPluginExtension) || fFilteredTree.getViewer().isExpandable(iPluginExtension)) { + found = true; + break; + } + } + if (!found) { + unfilteredExtensions.add(iPluginExtension); + } + } + } finally { + fFilteredTree.getViewer().setExpandPreCheckFilters(false); + } + return unfilteredExtensions; + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ToggleExpandStateAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ToggleExpandStateAction.java new file mode 100644 index 0000000..aac421e --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/actions/ToggleExpandStateAction.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.actions; + +import java.util.Iterator; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.*; +import org.eclipse.pde.core.plugin.IPluginParent; +import org.eclipse.pde.internal.core.text.plugin.PluginExtensionNode; +import org.eclipse.pde.internal.core.text.plugin.PluginParentNode; +import org.eclipse.pde.internal.ui.PDEPluginImages; +import org.eclipse.pde.internal.ui.PDEUIMessages; +import org.eclipse.pde.internal.ui.editor.plugin.FormFilteredTree; +import org.eclipse.pde.internal.ui.search.ExtensionsPatternFilter; +import org.eclipse.swt.widgets.TreeItem; + +/** + * Expands and collapses selected tree nodes in the extension elements tree viewer upon their expand state. + * + * @author Sascha Becher + */ +public class ToggleExpandStateAction extends Action { + + public static int NEEDS_EXPAND = 1; + public static int NEEDS_COLLAPSE = 2; + + protected TreeViewer fExtensionTree; + protected FormFilteredTree fFilteredTree; + + public ToggleExpandStateAction(FormFilteredTree filteredTree, TreeViewer treeViewer) { + setImageDescriptor(PDEPluginImages.DESC_TOGGLE_EXPAND_STATE); + setDisabledImageDescriptor(PDEPluginImages.DESC_TOGGLE_EXPAND_STATE_DISABLED); + setText(PDEUIMessages.ExtensionsPage_toggleExpandState); + fExtensionTree = treeViewer; + fFilteredTree = filteredTree; + } + + public void run() { + StructuredSelection selection = (StructuredSelection) fExtensionTree.getSelection(); + if (fExtensionTree.getTree().getSelectionCount() > 0) { + TreeItem[] items = fExtensionTree.getTree().getSelection(); + try { + fFilteredTree.setRedraw(false); + int state = getStateChangeRequired(items); + toggleExpandState(state, selection); + } finally { + fFilteredTree.setRedraw(true); + fExtensionTree.refresh(); + } + } + } + + public void toggleExpandState(int state, StructuredSelection selection) { + TreeItem[] items = fExtensionTree.getTree().getSelection(); + if (state == NEEDS_EXPAND) { // expand sub tree + traverseChildrenAndSetExpanded(items); // load non-expanded children + fExtensionTree.refresh(); + expandChildrenElements(selection.toArray(), true); + fExtensionTree.setSelection(selection, false); + } else { // collapse sub tree + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + fExtensionTree.setExpandedState(iterator.next(), false); + } + } + } + + public int getStateChangeRequired(TreeItem[] selection) { + return (traverseStateChangeRequired(selection)) ? NEEDS_EXPAND : NEEDS_COLLAPSE; + } + + /** + * @param items items to be traversed + * @return true if at least one of the tree items could be expanded but is not. Otherwise false is returned. + */ + protected boolean traverseStateChangeRequired(TreeItem[] items) { + if (items != null) { + for (int i = 0; i < items.length; i++) { + TreeItem treeItem = items[i]; + TreeItem[] children = treeItem.getItems(); + if (children.length > 0) { + if (treeItem.getExpanded()) { + if (traverseStateChangeRequired(children)) { + return true; + } + } else { + return true; + } + } + } + } + return false; + } + + /** + * Expands subtrees of given items. Items of type PluginExtensionNode that have multiple children to expand + * will only be expanded to the that level. Further expanding is required to reveal the whole subtree. This is for reasons of + * convenience. + * + * @param items tree items to be expand with their children + */ + private void traverseChildrenAndSetExpanded(TreeItem[] items) { + for (int i = 0; i < items.length; i++) { + TreeItem treeItem = items[i]; + TreeItem[] children = treeItem.getItems(); + int extensionsChildCount = getExtensionsChildCount((IPluginParent) treeItem.getData()); + boolean furtherExpanding = !(extensionsChildCount > 1 && (!treeItem.getExpanded())); + treeItem.setExpanded(furtherExpanding); + if (furtherExpanding) { + traverseChildrenAndSetExpanded(children); + } + } + } + + private int getExtensionsChildCount(IPluginParent leafData) { + int extensionsChildCount = 0; + if (leafData != null && leafData instanceof PluginExtensionNode) { + if (!fFilteredTree.isFiltered()) { + return leafData.getChildCount(); + } + ExtensionsPatternFilter filter = (ExtensionsPatternFilter) fFilteredTree.getPatternFilter(); + for (int j = 0; j < leafData.getChildCount(); j++) { + if (filter.containsElement(leafData.getChildren()[j])) { + extensionsChildCount++; + } + } + } + return extensionsChildCount; + } + + /** + * @param children list of elements to be expand with their children + */ + private void expandChildrenElements(Object[] children, boolean fullExpand) { + for (int i = 0; i < children.length; i++) { + Object child = children[i]; + if (child instanceof PluginParentNode) { + PluginParentNode node = (PluginParentNode) child; + if (node.getChildCount() > 0 && fullExpand) { + boolean furtherExpanding = !(node instanceof PluginExtensionNode && !fExtensionTree.getExpandedState(node)); + expandChildrenElements(node.getChildren(), furtherExpanding); + } else { + fExtensionTree.expandToLevel(node, 0); + } + } + } + } + + /** + * Determines whether the selected leafs are expandable + * + * @param selection selection to test each item with + * @return whether the selection can be expanded + */ + public static boolean isExpandable(IStructuredSelection selection) { + boolean isExpandable = false; + if (selection != null) { + if (!selection.isEmpty()) { + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object element = iterator.next(); + if (element instanceof PluginParentNode) { + PluginParentNode node = (PluginParentNode) element; + if (node.getChildCount() > 0) { + isExpandable = true; + break; + } + } + } + } + } + return isExpandable; + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/AbstractPluginElementDetails.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/AbstractPluginElementDetails.java index aad91fc..bee2428 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/AbstractPluginElementDetails.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/AbstractPluginElementDetails.java @@ -13,6 +13,9 @@ import org.eclipse.pde.internal.ui.editor.PDEDetails; import org.eclipse.pde.internal.ui.editor.PDESection; +import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.actions.ActionFactory; public abstract class AbstractPluginElementDetails extends PDEDetails { @@ -26,4 +29,25 @@ return fMasterSection; } -} + public boolean doGlobalAction(String actionId) { + // TODO reveal the keybinding Ctrl+F to the user, ideally by showing the action in the context menu + if (actionId.equals(ActionFactory.FIND.getId())) { + if (fMasterSection != null && fMasterSection instanceof ExtensionsSection) { + final Control focusControl = Display.getCurrent().getFocusControl(); // getPage().getLastFocusControl(); + String filterText = (focusControl instanceof Text) ? ((Text) focusControl).getText() : (focusControl instanceof CCombo) ? ((CCombo) focusControl).getText() : null; + if (filterText != null) { + // add value of the currently focused attribute text to the filter + ((ExtensionsSection) fMasterSection).addAttributeToFilter(filterText, true); + Display.getCurrent().asyncExec(new Runnable() { + public void run() { + // bugfix: after tree refresh bring focus back to the element details form + getPage().updateFormSelection(); + } + }); + } + } + } + return super.doGlobalAction(actionId); + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionsSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionsSection.java index 2c9b627..46b9e7b 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionsSection.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionsSection.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2012 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -8,14 +8,19 @@ * Contributors: * IBM Corporation - initial API and implementation * Peter Friese - bug 194529, bug 196867 + * Sascha Becher - bug 360894 *******************************************************************************/ package org.eclipse.pde.internal.ui.editor.plugin; import java.util.*; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.action.*; import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.*; @@ -30,54 +35,68 @@ import org.eclipse.pde.internal.core.schema.SchemaRegistry; import org.eclipse.pde.internal.core.text.IDocumentElementNode; import org.eclipse.pde.internal.core.text.plugin.PluginBaseNode; +import org.eclipse.pde.internal.core.text.plugin.PluginExtensionNode; import org.eclipse.pde.internal.ui.*; import org.eclipse.pde.internal.ui.editor.*; -import org.eclipse.pde.internal.ui.editor.actions.CollapseAction; -import org.eclipse.pde.internal.ui.editor.actions.SortAction; +import org.eclipse.pde.internal.ui.editor.actions.*; import org.eclipse.pde.internal.ui.editor.contentassist.XMLElementProposalComputer; import org.eclipse.pde.internal.ui.elements.DefaultContentProvider; import org.eclipse.pde.internal.ui.parts.TreePart; +import org.eclipse.pde.internal.ui.search.ExtensionsPatternFilter; import org.eclipse.pde.internal.ui.search.PluginSearchActionGroup; -import org.eclipse.pde.internal.ui.util.SWTUtil; +import org.eclipse.pde.internal.ui.util.*; import org.eclipse.pde.internal.ui.wizards.extension.ExtensionEditorWizard; import org.eclipse.pde.internal.ui.wizards.extension.NewExtensionWizard; import org.eclipse.pde.ui.IExtensionEditorWizard; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.events.*; -import org.eclipse.swt.graphics.Cursor; -import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.BidiUtil; import org.eclipse.swt.widgets.*; import org.eclipse.ui.actions.ActionContext; import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.dialogs.PatternFilter; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.progress.WorkbenchJob; public class ExtensionsSection extends TreeSection implements IModelChangedListener, IPropertyChangeListener { + private static final int REFRESHJOB_DELAY_TIME = 1200; // milliseconds to wait + private static final int ACCELERATED_SCROLLING = 15; // lines to skip private static final int BUTTON_MOVE_DOWN = 4; private static final int BUTTON_MOVE_UP = 3; private static final int BUTTON_EDIT = 2; private static final int BUTTON_REMOVE = 1; + private static final int BUTTON_ADD = 0; private TreeViewer fExtensionTree; private Image fExtensionImage; private Image fGenericElementImage; private FormFilteredTree fFilteredTree; + private ExtensionsPatternFilter fPatternFilter; private SchemaRegistry fSchemaRegistry; private Hashtable fEditorWizards; private SortAction fSortAction; private CollapseAction fCollapseAction; + private ToggleExpandStateAction fExpandAction; + private FilterRelatedExtensionsAction fFilterRelatedAction; + private SearchExtensionsAction fSearchAction; + private boolean fBypassFilterDelay = false; - private static final int BUTTON_ADD = 0; - - private static final String[] COMMON_LABEL_PROPERTIES = {"label", //$NON-NLS-1$ - "name", //$NON-NLS-1$ - "id", //$NON-NLS-1$ - "commandId", //$NON-NLS-1$ - "activityId"}; //$NON-NLS-1$ + /** + * label, name, class, id, commandId, property, activityId, attribute, value + *
+ * While adding elements to the array at the end is possible without concern, changing + * previous elements requires to refactor occurences with indexed access to the array. + */ + // TODO common label properties might be configured through preferences + public static final String[] COMMON_LABEL_ATTRIBUTES = {"label", //$NON-NLS-1$ + "name", "locationURI", "class", "id", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + "commandId", "property", "activityId", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "attribute", "value"}; //$NON-NLS-1$ //$NON-NLS-2$ private static final String[] VALID_IMAGE_TYPES = {"png", "bmp", "ico", "gif", "jpg", "tiff"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ + private static final String MENU_NEW_ID = "NewMenu"; //$NON-NLS-1$ class ExtensionContentProvider extends DefaultContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parent) { @@ -112,13 +131,20 @@ } } - class ExtensionLabelProvider extends LabelProvider { + class ExtensionLabelProvider extends LabelProvider implements IFontProvider { public String getText(Object obj) { return resolveObjectName(obj); } public Image getImage(Object obj) { return resolveObjectImage(obj); + } + + public Font getFont(Object element) { + if (fFilteredTree.isFiltered() && fPatternFilter.getMatchingLeafs().contains(element)) { + return JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT); + } + return null; } } @@ -221,9 +247,11 @@ section.setText(PDEUIMessages.ManifestEditor_DetailExtension_title); initialize((IPluginModelBase) getPage().getModel()); createSectionToolbar(section, toolkit); + // accelerated tree scrolling enabled + fFilteredTree.addMouseWheelListener(new AcceleratedTreeScrolling(fExtensionTree.getTree(), ACCELERATED_SCROLLING)); // Create the adapted listener for the filter entry field fFilteredTree.createUIListenerEntryFilter(this); - Text filterText = fFilteredTree.getFilterControl(); + final Text filterText = fFilteredTree.getFilterControl(); if (filterText != null) { filterText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { @@ -252,9 +280,33 @@ } } }); + // Add action to filter tree with some of the selection's attributes + fFilterRelatedAction = new FilterRelatedExtensionsAction(fExtensionTree, fFilteredTree, this, false); + toolBarManager.add(fFilterRelatedAction); + // Add action to search all workspace plugins with current filtering applied to the tree viewer + fSearchAction = new SearchExtensionsAction(fFilteredTree, PDEUIMessages.ExtensionsPage_searchWithExtensionsFilter); + toolBarManager.add(fSearchAction); + // Add separator + Separator separator = new Separator(); + toolBarManager.add(separator); // Add sort action to the tool bar - fSortAction = new SortAction(fExtensionTree, PDEUIMessages.ExtensionsPage_sortAlpha, null, null, this); + fSortAction = new SortAction(fExtensionTree, PDEUIMessages.ExtensionsPage_sortAlpha, null, null, this) { + public void run() { + Object[] expanded = fFilteredTree.getViewer().getVisibleExpandedElements(); + try { + fFilteredTree.setRedraw(false); + super.run(); + // bugfix: retain tree expand state after sort action + fFilteredTree.getViewer().setExpandedElements(expanded); + } finally { + fFilteredTree.setRedraw(true); + } + } + }; toolBarManager.add(fSortAction); + // Add expand selected leafs action to the toolbar + fExpandAction = new ToggleExpandStateAction(fFilteredTree, fExtensionTree); + toolBarManager.add(fExpandAction); // Add collapse action to the tool bar fCollapseAction = new CollapseAction(fExtensionTree, PDEUIMessages.ExtensionsPage_collapseAll); toolBarManager.add(fCollapseAction); @@ -309,11 +361,13 @@ * @see org.eclipse.pde.internal.ui.editor.PDESection#doGlobalAction(java.lang.String) */ public boolean doGlobalAction(String actionId) { - + if (actionId.equals(ActionFactory.FIND.getId()) && fFilterRelatedAction != null) { + fFilterRelatedAction.run(); + return true; + } if (!isEditable()) { return false; } - if (actionId.equals(ActionFactory.DELETE.getId())) { handleDelete(); return true; @@ -348,13 +402,14 @@ protected void fillContextMenu(IMenuManager manager) { ISelection selection = fExtensionTree.getSelection(); - IStructuredSelection ssel = (IStructuredSelection) selection; + final IStructuredSelection ssel = (IStructuredSelection) selection; if (ssel.size() == 1) { Object object = ssel.getFirstElement(); if (object instanceof IPluginParent) { IPluginParent parent = (IPluginParent) object; if (parent.getModel().getUnderlyingResource() != null) { - fillContextMenu(getPage(), parent, manager); + boolean removeEnabled = !fFilteredTree.isFiltered() || isRemoveEnabled(ssel); + fillContextMenu(getPage(), parent, manager, false, removeEnabled); manager.add(new Separator()); } } @@ -366,6 +421,7 @@ manager.add(new Separator()); } } else if (ssel.size() > 1) { + boolean removeEnabled = !fFilteredTree.isFiltered() || isRemoveEnabled(ssel); // multiple Action delAction = new Action() { public void run() { @@ -375,14 +431,34 @@ delAction.setText(PDEUIMessages.Actions_delete_label); manager.add(delAction); manager.add(new Separator()); - delAction.setEnabled(isEditable()); + delAction.setEnabled(isEditable() && removeEnabled); + } + if (ssel.size() > 0) { + if (ExtensionsFilterUtil.isFilterRelatedEnabled(ssel)) { + FilterRelatedExtensionsAction filterRelatedAction = new FilterRelatedExtensionsAction(fExtensionTree, fFilteredTree, this, true); + manager.add(filterRelatedAction); + SearchExtensionsAction searchRelatedAction = new SearchExtensionsAction(ssel, PDEUIMessages.Actions_search_relatedPluginElements); + manager.add(searchRelatedAction); + manager.add(new Separator()); + } } manager.add(new Separator()); + + if (fFilteredTree.isFiltered()) { + // Add action to reveal all extensions when the tree is in filter mode + ShowAllExtensionsAction fShowAllAction = new ShowAllExtensionsAction(getPage().getModel(), fFilteredTree, fPatternFilter); + if (manager.find(MENU_NEW_ID) != null) { + manager.insertAfter(MENU_NEW_ID, fShowAllAction); + } else { + manager.add(fShowAllAction); + manager.add(new Separator()); + } + } if (ssel.size() < 2) { // only cut things when the selection is one getPage().getPDEEditor().getContributor().addClipboardActions(manager); } getPage().getPDEEditor().getContributor().contextMenuAboutToShow(manager, false); - + this.fFilteredTree.update(); } static IMenuManager fillContextMenu(PDEFormPage page, final IPluginParent parent, IMenuManager manager) { @@ -394,7 +470,7 @@ } static IMenuManager fillContextMenu(PDEFormPage page, final IPluginParent parent, IMenuManager manager, boolean addSiblingItems, boolean fullMenu) { - MenuManager menu = new MenuManager(PDEUIMessages.Menus_new_label); + MenuManager menu = new MenuManager(PDEUIMessages.Menus_new_label, MENU_NEW_ID); IPluginExtension extension = getExtension(parent); ISchema schema = getSchema(extension); if (schema == null) { @@ -493,6 +569,45 @@ } } + public FormFilteredTree getFormFilteredTree() { + return fFilteredTree; + } + + /** + * Adds another value to filter text and a preceding separator character if necessary. + * Empty values as well as true and false are omitted. + * + * @param attributeValue Value to be trimmed and added to the filter text + * @param clearFilterText When true the filter text is replaced with the attribute value, appended otherwise. + */ + public void addAttributeToFilter(String attributeValue, boolean clearFilterText) { + Text filterControl = fFilteredTree.getFilterControl(); + if (filterControl != null && attributeValue != null) { + String trimmedValue = attributeValue.trim(); + if (trimmedValue.length() > 0 && ExtensionsFilterUtil.isNotBoolean(trimmedValue)) { + if (trimmedValue.startsWith("%")) {//$NON-NLS-1$ + IPluginModelBase model = getPluginModelBase(); + trimmedValue = ((model != null) ? model.getResourceString(trimmedValue) : trimmedValue).replaceAll("\"", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + String filterPattern; + if (clearFilterText) { + filterPattern = trimmedValue; + } else { + filterPattern = filterControl.getText(); + if (filterPattern.length() > 0 && !filterPattern.endsWith("/")) { //$NON-NLS-1$ + filterPattern += "/"; //$NON-NLS-1$ + } + filterPattern += trimmedValue; + } + if (filterPattern.indexOf('/') != -1) { // quote when + filterPattern = "\"" + filterPattern + "\""; //$NON-NLS-1$ //$NON-NLS-2$ + } + setBypassFilterDelay(true); // force immediate job run + filterControl.setText(filterPattern); + } + } + } + private void handleNew() { final IProject project = getPage().getPDEEditor().getCommonProject(); BusyIndicator.showWhile(fExtensionTree.getTree().getDisplay(), new Runnable() { @@ -555,6 +670,7 @@ private void handleSelectAll() { fExtensionTree.getTree().selectAll(); + updateButtons(fFilteredTree.getViewer().getSelection()); } private ArrayList getEditorWizards(IStructuredSelection selection) { @@ -643,13 +759,62 @@ // The model changed but the editor is still open, we should try to retain expansion, selection will be retained on its own Object[] expanded = fExtensionTree.getExpandedElements(); IPluginModelBase model = (IPluginModelBase) getPage().getModel(); - fExtensionTree.getControl().setRedraw(false); - fExtensionTree.setInput(model.getPluginBase()); - fExtensionTree.setExpandedElements(expanded); - fExtensionTree.getControl().setRedraw(true); - reportMissingExtensionPointSchemas(model.getPluginBase()); - getManagedForm().fireSelectionChanged(ExtensionsSection.this, fExtensionTree.getSelection()); - super.refresh(); + int[] indexPath = getTreeIndexPath(fExtensionTree.getTree()); + try { + fExtensionTree.getControl().setRedraw(false); + fExtensionTree.setInput(model.getPluginBase()); + fExtensionTree.setExpandedElements(expanded); + + reportMissingExtensionPointSchemas(model.getPluginBase()); + getManagedForm().fireSelectionChanged(ExtensionsSection.this, fExtensionTree.getSelection()); + super.refresh(); + + if (indexPath != null) { + // fix for Bug 371066 + revealTopItem(fExtensionTree.getTree(), indexPath); + } + } finally { + fExtensionTree.getControl().setRedraw(true); + } + } + + private static int[] getTreeIndexPath(Tree tree) { + int[] indexPath = null; + if (tree != null) { + TreeItem item = tree.getTopItem(); + int count = 1; + while (item != null && (item = item.getParentItem()) != null) { + count++; + } + indexPath = new int[count]; + int index = 0; + item = tree.getTopItem(); + while (item != null && index < count) { + TreeItem parent = item.getParentItem(); + if (parent != null) { + indexPath[index++] = parent.indexOf(item); + } else { + indexPath[index++] = tree.indexOf(item); + } + item = parent; + } + } + return indexPath; + } + + private static void revealTopItem(Tree tree, int[] indexPath) { + TreeItem itemFound = null; + for (int i = indexPath.length - 1; i >= 0; i--) { + int index = indexPath[i]; + if (itemFound != null) { + itemFound = (itemFound.getItemCount() > index) ? itemFound.getItem(indexPath[i]) : null; + } else if (i == indexPath.length - 1) { + itemFound = (tree.getItemCount() > index) ? tree.getItem(indexPath[i]) : null; + } + } + if (itemFound != null) { + tree.setTopItem(itemFound); + } } public void modelChanged(IModelChangedEvent event) { @@ -669,6 +834,18 @@ IPluginObject pobj = (IPluginObject) changeObject; IPluginObject parent = changeObject instanceof IPluginExtension ? ((IPluginModelBase) getPage().getModel()).getPluginBase() : pobj.getParent(); if (event.getChangeType() == IModelChangedEvent.INSERT) { + // enables adding extensions while tree is filtered + if (fFilteredTree.isFiltered()) { + Object[] inserted = event.getChangedObjects(); + for (int i = 0; i < inserted.length; i++) { + fPatternFilter.addElement(inserted[i]); + } + if (inserted.length == 1) { + fFilteredTree.getViewer().setSelection(new StructuredSelection(inserted[0])); + } + } + + // fExtensionTree.refresh(parent); if (changeObject instanceof IPluginExtension) { IPluginExtension ext = (IPluginExtension) changeObject; @@ -809,9 +986,9 @@ if (labelAtt == null) { // try some hard-coded attributes that // are used frequently - for (int i = 0; i < COMMON_LABEL_PROPERTIES.length; i++) { - labelAtt = element.getAttribute(COMMON_LABEL_PROPERTIES[i]); - if (labelAtt != null) + for (int i = 0; i < COMMON_LABEL_ATTRIBUTES.length; i++) { + labelAtt = element.getAttribute(COMMON_LABEL_ATTRIBUTES[i]); + if (labelAtt != null && labelAtt.getValue().length() > 0) break; } if (labelAtt == null) { @@ -821,9 +998,14 @@ labelAtt = element.getAttributes()[0]; } } - if (labelAtt != null && labelAtt.getValue() != null) + if (labelAtt != null && labelAtt.getValue() != null) { fullName = stripShortcuts(labelAtt.getValue()); + if (labelAtt.getName().equals(COMMON_LABEL_ATTRIBUTES[3])) { // remove package from handler class + fullName = fullName.substring(fullName.lastIndexOf('.') + 1, fullName.length()); + } + } fullName = element.getResourceString(fullName); + if (fullNames) return fullName != null ? fullName : baseName; if (fullName == null) @@ -836,12 +1018,25 @@ return fullName + " \u200f(\u200e" + baseName + ")"; //$NON-NLS-1$ //$NON-NLS-2$ return fullName + " (" + baseName + ')'; //$NON-NLS-1$ } - return obj.toString(); + if (obj != null) { + return obj.toString(); + } + return new String(); } public void setFocus() { if (fExtensionTree != null) fExtensionTree.getTree().setFocus(); + } + + /** + * Temporarily bypasses default {@link FormFilteredTree#getRefreshJobDelay()} for several actions to immediatly start tree + * filtering. Only the next job to call getRefreshJobDelay() will be affected and reset this value. + * + * @param bypassFilterDelay true bypasses the refresh job delay by overriding it with 0 + */ + public void setBypassFilterDelay(boolean bypassFilterDelay) { + fBypassFilterDelay = bypassFilterDelay; } public static String stripShortcuts(String input) { @@ -855,6 +1050,24 @@ output.append(c); } return output.toString(); + } + + public boolean canCopy(ISelection selection) { + // Partial fix for Bug 360079, enables Ctrl+C in filter text if plugin model is editable + if (fFilteredTree.getFilterControl().isFocusControl() && !selection.isEmpty()) { + return true; + } + // TODO enable copy also when plugin model is not editable + return super.canCopy(selection); + } + + public boolean canPaste(Clipboard clipboard) { + // Partial fix for Bug 360079, enables Ctrl+V in filter text if plugin model is editable + if (fFilteredTree.getFilterControl().isFocusControl()) { + return true; + } + // TODO enable paste also when plugin model is not editable + return super.canPaste(clipboard); } /* (non-Javadoc) @@ -1081,6 +1294,18 @@ } private void updateButtons(Object item) { + if (fExpandAction != null) { + fExpandAction.setEnabled(ToggleExpandStateAction.isExpandable((IStructuredSelection) fExtensionTree.getSelection())); + } + if (fFilterRelatedAction != null) { + fFilterRelatedAction.setEnabled(ExtensionsFilterUtil.isFilterRelatedEnabled((IStructuredSelection) fExtensionTree.getSelection())); + } + if (fSearchAction != null) { + Text filterControl = fFilteredTree.getFilterControl(); + boolean searchEnabled = filterControl != null && filterControl.getText().length() > 0; + fSearchAction.setEnabled(searchEnabled); + } + if (getPage().getModel().isEditable() == false) return; boolean sorted = fSortAction != null && fSortAction.isChecked(); @@ -1089,44 +1314,42 @@ getTreePart().setButtonEnabled(BUTTON_MOVE_DOWN, false); return; } + IStructuredSelection selection = (item instanceof IStructuredSelection) ? (IStructuredSelection) item : null; boolean filtered = fFilteredTree.isFiltered(); boolean addEnabled = true; - boolean removeEnabled = false; + boolean removeEnabled = true; boolean upEnabled = false; boolean downEnabled = false; - if (item != null) { - removeEnabled = true; - } if (filtered) { // Fix for bug 194529 and bug 194828 - addEnabled = false; + // Update: adding during filtering enabled by additional filter capability + addEnabled = true; upEnabled = false; downEnabled = false; + removeEnabled = isRemoveEnabled(selection); } else { - if (item instanceof IStructuredSelection) { - if (((IStructuredSelection) item).size() == 1) { - Object selected = ((IStructuredSelection) item).getFirstElement(); - if (selected instanceof IPluginElement) { - IPluginElement element = (IPluginElement) selected; - IPluginParent parent = (IPluginParent) element.getParent(); - // check up - int index = parent.getIndexOf(element); - if (index > 0) - upEnabled = true; - if (index < parent.getChildCount() - 1) - downEnabled = true; - } else if (selected instanceof IPluginExtension) { - IPluginExtension extension = (IPluginExtension) selected; - IExtensions extensions = (IExtensions) extension.getParent(); - int index = extensions.getIndexOf(extension); - int size = extensions.getExtensions().length; - if (index > 0) - upEnabled = true; - if (index < size - 1) - downEnabled = true; - } + if (selection != null && selection.size() == 1) { + Object selected = selection.getFirstElement(); + if (selected instanceof IPluginElement) { + IPluginElement element = (IPluginElement) selected; + IPluginParent parent = (IPluginParent) element.getParent(); + // check up + int index = parent.getIndexOf(element); + if (index > 0) + upEnabled = true; + if (index < parent.getChildCount() - 1) + downEnabled = true; + } else if (selected instanceof IPluginExtension) { + IPluginExtension extension = (IPluginExtension) selected; + IExtensions extensions = (IExtensions) extension.getParent(); + int index = extensions.getIndexOf(extension); + int size = extensions.getExtensions().length; + if (index > 0) + upEnabled = true; + if (index < size - 1) + downEnabled = true; } } } @@ -1136,11 +1359,95 @@ getTreePart().setButtonEnabled(BUTTON_MOVE_DOWN, downEnabled); } + /** + * Since filtering potentially hides children of extensions, removing them when they still have children is intransparent. + * Needs to be called only when the tree is filtered. + * + * @param selection selection to be tested + * @return whether removing the selected elements is enabled + */ + boolean isRemoveEnabled(IStructuredSelection selection) { + if (selection != null) { + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object element = iterator.next(); + if (element instanceof PluginExtensionNode) { + return ((PluginExtensionNode) element).getChildCount() == 0; + } + } + } + return true; + } + /* (non-Javadoc) * @see org.eclipse.pde.internal.ui.editor.TreeSection#createTreeViewer(org.eclipse.swt.widgets.Composite, int) */ protected TreeViewer createTreeViewer(Composite parent, int style) { - fFilteredTree = new FormFilteredTree(parent, style, new PatternFilter()); + fPatternFilter = new ExtensionsPatternFilter(); + fFilteredTree = new FormFilteredTree(parent, style, fPatternFilter) { + protected WorkbenchJob doCreateRefreshJob() { + final WorkbenchJob job = super.doCreateRefreshJob(); + job.addJobChangeListener(new JobChangeAdapter() { + private ISelection selection; + private boolean aboutToRunPassed = false; + + public void scheduled(IJobChangeEvent event) { + ((ExtensionsPatternFilter) fFilteredTree.getPatternFilter()).clearMatchingLeafs(); + selection = fExtensionTree.getSelection(); + } + + public void aboutToRun(IJobChangeEvent event) { + aboutToRunPassed = true; + } + + /* + * Restores selection after tree refresh and expands tree up to matching leafs only + */ + public void done(IJobChangeEvent event) { + if (aboutToRunPassed) { // restoring is only required if the job actually ran + try { + fFilteredTree.setRedraw(false); + ExtensionsPatternFilter extensionsPatternFilter = ((ExtensionsPatternFilter) fFilteredTree.getPatternFilter()); + fExtensionTree.collapseAll(); + Object[] leafs = extensionsPatternFilter.getMatchingLeafsAsArray(); + for (int i = 0; i < leafs.length; i++) { + fExtensionTree.expandToLevel(leafs[i], 0); + } + if (selection != null && !(selection.isEmpty())) { + fExtensionTree.setSelection(selection, true); + } + } finally { + fFilteredTree.setRedraw(true); + } + } + } + }); + return job; + } + + protected long getRefreshJobDelay() { + // Prolonged job delay time is required because of the attribute search being more costly in nature. + // This can block input to the filter text severly. Thus it shouldn't happen when typing slowly. + // The delay of 1500ms is bypassed by some actions that use the filter text to initiate searches or clear the text. + long delay = (fBypassFilterDelay) ? 0 : REFRESHJOB_DELAY_TIME; + setBypassFilterDelay(false); // reset afterwards + return delay; + } + + protected void clearText() { + // bugfix: additional notification with textChanged() would cause a needless 2nd refresh job run + // which in turn would have a longer delay time than the 1st run. + setFilterText(""); //$NON-NLS-1$ + } + + protected void textChanged() { + String filterText = getFilterString(); + if (filterText != null && filterText.length() == 0) { + // clearing the filter text doesn't require a refresh job delay + setBypassFilterDelay(true); + } + super.textChanged(); + } + }; parent.setData("filtered", Boolean.TRUE); //$NON-NLS-1$ return fFilteredTree.getViewer(); } diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ExtensionAttributeRow.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ExtensionAttributeRow.java index 72e8557..e2fdbd0 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ExtensionAttributeRow.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ExtensionAttributeRow.java @@ -21,9 +21,12 @@ import org.eclipse.pde.internal.ui.editor.text.PDETextHover; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.*; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.keys.IBindingService; public abstract class ExtensionAttributeRow implements IControlHoverContentProvider { protected IContextPart part; @@ -100,17 +103,28 @@ } public String getHoverContent(Control c) { - if (c instanceof Label || c instanceof Hyperlink) - return getDescription(); + if (c instanceof Label || c instanceof Hyperlink) { + // reveal keybinding for shortcut to filtering + String filterBinding = ((IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class)).getBestActiveBindingFormattedFor(ActionFactory.FIND.getCommandId()); + String findKeybinding = (getValue().length() > 0) ? "

Press " + filterBinding + " within text to filter for this attribute." : ""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String description = getDescription().trim(); // prettify help text + if (description.length() > 0) { + String first = String.valueOf(description.charAt(0)); // always make first letter uppercase + return getDescription().replaceFirst(first, first.toUpperCase()) + findKeybinding; + } + return description; + } if (c instanceof Text) { String text = ((Text) c).getText(); ISchemaAttribute sAtt = getAttribute(); String translated = null; - if (input != null && sAtt != null && sAtt.isTranslatable() && text.startsWith("%")) //$NON-NLS-1$ + if (input != null && sAtt != null && sAtt.isTranslatable() && text.startsWith("%")) { //$NON-NLS-1$ translated = input.getResourceString(text); - if (!text.equals(translated)) + } + if (!text.equals(translated)) { return translated; } + } return null; } diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties index 36ab48f..2e77bce 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties @@ -1189,6 +1189,8 @@ EditorActions_revert = Re&vert Actions_open_label = &Open Actions_delete_label = &Delete +Actions_filter_relatedPluginElements=&Filter Related +Actions_search_relatedPluginElements=Search &Related Actions_synchronizeVersions_label = S&ynchronize Versions... Menus_new_label = &New @@ -1275,6 +1277,8 @@ SearchAction_Declaration = Find Declaratio&n ShowDescriptionAction_label = Sho&w Description ShowDescriptionAction_title=Extension Point Description +ShowAllExtensionsAction_label=Show all extensions +HideUnfilteredExtensionsAction_label=Hide unfiltered extensions DefaultJUnitWorkspaceBlock_name=JUnit workspace location DefinitionPage_0=Definition DefinitionPage_1=Target Definition @@ -1536,6 +1540,8 @@ ExtensionsPage_title=Extensions ExtensionsPage_tabName=Extensions ExtensionsPage_sortAlpha=Sort the Extensions alphabetically +ExtensionsPage_toggleExpandState=Toggle Expand State +ExtensionsPage_searchWithExtensionsFilter=Extension Element Search ExtensionDetails_title=Extension Details ExtensionDetails_desc=Set the properties of the selected extension. Required fields are denoted by "*". ExtensionDetails_id=ID diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/AttributesMatch.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/AttributesMatch.java new file mode 100644 index 0000000..abf9831 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/AttributesMatch.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.search; + +import org.eclipse.pde.internal.ui.editor.plugin.ManifestEditor; +import org.eclipse.search.ui.text.Match; + +/** + * An extension to {@link Match} in order to present matching plugins which resulted + * in a search queried from the extensions page of the {@link ManifestEditor} + * + * @author Sascha Becher + */ +public class AttributesMatch extends Match { + + /** + * A constant expressing that the {@link Match} resulted in a search queried from + * the extensions page of the {@link ManifestEditor} + */ + public static final int UNIT_ATTRIBUTE_SEARCH_PATTERN = 3; + + protected String searchPattern; + + public AttributesMatch(Object element, String searchPattern) { + super(element, UNIT_LINE, 0, 0); + this.searchPattern = searchPattern; + } + + public String getSearchPattern() { + return searchPattern; + } + + public int getBaseUnit() { + return UNIT_ATTRIBUTE_SEARCH_PATTERN; + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionElementSearchOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionElementSearchOperation.java new file mode 100644 index 0000000..6100824 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionElementSearchOperation.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.search; + +import java.util.ArrayList; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.pde.core.plugin.*; +import org.eclipse.pde.internal.core.plugin.PluginExtension; +import org.eclipse.pde.internal.core.plugin.PluginParent; +import org.eclipse.pde.internal.core.search.ISearchResultCollector; +import org.eclipse.pde.internal.core.search.PluginSearchInput; + +/** + * Search operation for finding extension elements within a plugin using the {@link ExtensionsPatternFilter}. + * + * @author Sascha Becher + */ +public class ExtensionElementSearchOperation { + + protected PluginSearchInput fInput; + private ISearchResultCollector fCollector; + + public ExtensionElementSearchOperation(PluginSearchInput input, ISearchResultCollector collector) { + this.fInput = input; + this.fCollector = collector; + } + + public void execute(IProgressMonitor monitor) { + IPluginModelBase[] entries = fInput.getSearchScope().getMatchingModels(); + monitor.beginTask("", entries.length); //$NON-NLS-1$ + + try { + for (int i = 0; i < entries.length; i++) { + IPluginModelBase candidate = entries[i]; + visit(candidate); + monitor.worked(1); + } + } finally { + monitor.done(); + } + } + + private void visit(IPluginModelBase model) { + ArrayList matches = findMatch(model); + for (int i = 0; i < matches.size(); i++) { + fCollector.accept(matches.get(i)); + } + } + + private ArrayList findMatch(IPluginModelBase model) { + ArrayList result = new ArrayList(); + int searchLimit = fInput.getSearchLimit(); + if (fInput.getSearchElement() == PluginSearchInput.ELEMENT_PLUGIN) { + if (searchLimit != PluginSearchInput.LIMIT_REFERENCES) + findPluginDeclaration(model, result); + if (searchLimit != PluginSearchInput.LIMIT_DECLARATIONS) + findPluginReferences(model, result); + } + return result; + } + + private void findPluginDeclaration(IPluginModelBase model, ArrayList result) { + IPluginBase pluginBase = model.getPluginBase(); + ExtensionsPatternFilter filter = new ExtensionsPatternFilter(); + filter.setPattern(fInput.getSearchString()); + IPluginExtension[] extensions = pluginBase.getExtensions(); + for (int i = 0; i < extensions.length; i++) { + PluginExtension pluginExtension = (PluginExtension) extensions[i]; + boolean foundAny = traversePluginElements(pluginExtension, filter); + if (foundAny && pluginBase instanceof IPlugin) { + result.add(pluginBase); + return; + } + } + } + + private boolean traversePluginElements(PluginParent pluginParent, ExtensionsPatternFilter filter) { + IPluginObject[] pluginObjects = pluginParent.getChildren(); + if (pluginObjects != null) { + for (int i = 0; i < pluginObjects.length; i++) { + boolean foundAny = traversePluginElements((PluginParent) pluginObjects[i], filter); + if (foundAny) { + return true; + } + } + } + return filter.isLeafMatch(null, pluginParent); + } + + private void findPluginReferences(IPluginModelBase model, ArrayList result) { + IPluginBase pluginBase = model.getPluginBase(); + IPluginImport[] imports = pluginBase.getImports(); + for (int i = 0; i < imports.length; i++) { + findPluginDeclaration(imports[i].getPluginModel(), result); + } + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionsPatternFilter.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionsPatternFilter.java new file mode 100644 index 0000000..dd46b14 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ExtensionsPatternFilter.java @@ -0,0 +1,309 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.search; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.pde.core.plugin.*; +import org.eclipse.pde.internal.core.bundle.BundlePlugin; +import org.eclipse.pde.internal.ui.util.ExtensionsFilterUtil; +import org.eclipse.ui.dialogs.PatternFilter; + +/** + * An extended filtering capability for the filtered tree of ExtensionsPage. The + * search criteria is splitted by / first. The resulting values are used to + * perform a search on all node's values. All elements fitting at least one of + * the split values will be displayed. This extensions does not compromise the + * default filtering behaviour of the tree while providing the ability to + * highlight related items such as commands along with their command images, + * handlers, menu entries and activities. + * + * @see org.eclipse.ui.dialogs.FilteredTree + * @since 3.8 + * + */ +public class ExtensionsPatternFilter extends PatternFilter { + + /** + * Limits the maximum number of attributes handled by the filter + */ + public static final int ATTRIBUTE_LIMIT = 30; + + protected String fSearchPattern; + + protected Set fAttributes = new HashSet(); + protected final Set fMatchingLeafs = new HashSet(); + protected final Set fFoundAnyElementsCache = new HashSet(); + + /** + * Check if the leaf element is a match with the filter text. The + * default behavior checks that the label of the element is a match. + * + * Subclasses should override this method. + * + * @param viewer + * the viewer that contains the element + * @param element + * the tree element to check + * @return true if the given element's label matches the filter text + */ + protected boolean isLeafMatch(Viewer viewer, Object element) { + // match label; default behaviour + if (viewer != null && super.isLeafMatch(viewer, element)) { + return true; + } + + // match all splitted attribute's values of IPluginElement against splitted filter patterns + if (element instanceof IPluginElement) { + return doIsLeafMatch((IPluginElement) element); + } + return false; + } + + protected boolean doIsLeafMatch(IPluginElement pluginElement) { + List syntheticAttributes = ExtensionsFilterUtil.handlePropertyTester(pluginElement); + if (fAttributes != null && fAttributes.size() > 0) { + int attributeNumber = 0; + for (Iterator iterator = fAttributes.iterator(); iterator.hasNext();) { + String valuePattern = (String) iterator.next(); + if (attributeNumber < fAttributes.size() && attributeNumber < ATTRIBUTE_LIMIT) { + boolean quoted = isQuoted(valuePattern); + if (valuePattern != null && valuePattern.length() > 0) { + int attributeCount = pluginElement.getAttributeCount(); + IPluginAttribute[] pluginAttributes = pluginElement.getAttributes(); + + for (int i = 0; i < attributeCount; i++) { + IPluginAttribute attributeElement = pluginAttributes[i]; + if (attributeElement != null && attributeElement.getValue() != null) { + String[] attributes = getAttributeSplit(attributeElement.getValue(), quoted); + if (attributes != null) { + List attributeList = new ArrayList(Arrays.asList(attributes)); + attributeList.addAll(syntheticAttributes); + if (matchWithAttributes(pluginElement, valuePattern, attributeList, quoted)) { + return true; + } + } + } + } + if (valuePattern.equalsIgnoreCase(pluginElement.getName())) { + return true; + } + } + } + attributeNumber++; + } + } + return false; + } + + private boolean matchWithAttributes(IPluginElement pluginElement, String valuePattern, List attributeList, boolean quoted) { + for (int k = 0; k < attributeList.size(); k++) { + String attribute = (String) attributeList.get(k); + if (attribute != null && attribute.length() > 0) { + if (!attribute.startsWith("%")) { //$NON-NLS-1$ + int delimiterPosition = attribute.indexOf('?'); // strip right of '?' + if (delimiterPosition != -1) { + attribute = attribute.substring(0, delimiterPosition); + } + } else { + String resourceValue = pluginElement.getResourceString(attribute); + attribute = (resourceValue != null && resourceValue.length() > 0) ? resourceValue : attribute; + } + String pattern = valuePattern.toLowerCase(); + if (quoted) { + pattern = pattern.substring(1, pattern.length() - 1); + } + if (attribute.toLowerCase().equals(pattern)) { + return true; + } + } + } + return false; + } + + private static boolean isQuoted(String value) { + return value.startsWith("\"") && value.endsWith("\""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private static String[] getAttributeSplit(String text, boolean quoted) { + if (text.length() < 2) { + return null; + } + if (!quoted) { + return text.replaceAll("/{1,}", "/").split("/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return new String[] {text}; + } + + public boolean isElementVisible(Viewer viewer, Object element) { + if (fFoundAnyElementsCache.contains(element)) { + return true; + } + return isLeafMatch(viewer, element); + } + + public Object[] filter(Viewer viewer, Object parent, Object[] elements) { + if (parent != null && parent instanceof BundlePlugin) { + if (fFoundAnyElementsCache.size() == 0 && fSearchPattern != null && fSearchPattern.length() > 0) { + BundlePlugin pluginPlugin = (BundlePlugin) parent; + doFilter(viewer, pluginPlugin, pluginPlugin.getExtensions(), false); + } + } + if (fFoundAnyElementsCache.size() > 0) { + List found = new ArrayList(); + for (int i = 0; i < elements.length; i++) { + if (fFoundAnyElementsCache.contains(elements[i])) { + found.add(elements[i]); + } + } + return found.toArray(); + } + return super.filter(viewer, parent, elements); + } + + protected boolean doFilter(Viewer viewer, Object parent, IPluginObject[] children, boolean addChildren) { + boolean isParentMatch = fFoundAnyElementsCache.contains(parent) ? true : false; + + // find leaf matches + boolean isAnyLeafMatch = false; + for (int j = 0; j < children.length; j++) { + IPluginObject iPluginObject = children[j]; + boolean isChildMatch = true; + if (!isParentMatch || children.length > 0) { + isChildMatch = this.isLeafMatch(viewer, iPluginObject); + isAnyLeafMatch |= isChildMatch; + if (isChildMatch) { + fMatchingLeafs.add(iPluginObject); + } + } + if (isChildMatch || addChildren) { + fFoundAnyElementsCache.add(iPluginObject); + } + } + + // traverse children when available + boolean isAnyChildMatch = false; + for (int i = 0; i < children.length; i++) { + IPluginObject iPluginObject = children[i]; + if (iPluginObject instanceof IPluginParent) { + IPluginParent pluginElement = (IPluginParent) iPluginObject; + if (pluginElement.getChildren().length > 0) { + boolean isChildrenMatch = doFilter(viewer, pluginElement, pluginElement.getChildren(), addChildren | fMatchingLeafs.contains(pluginElement)); + isAnyChildMatch |= isChildrenMatch; + if (isChildrenMatch) { + fFoundAnyElementsCache.add(pluginElement); + } + } + } + } + return isAnyChildMatch | isAnyLeafMatch; + } + + /** + * Splits a string at the occurrences of /. Any quoted parts of the filterText + * are not to be splitted but remain as a whole along with the quotation. + * + * @param filterText text to split + * @return split array + */ + protected String[] splitWithQuoting(String filterText) { + // remove multiple separators + String text = filterText.replaceAll("/{1,}", "/"); //$NON-NLS-1$//$NON-NLS-2$ + boolean containsQuoting = text.indexOf('\"') != -1; + if (containsQuoting) { + // remove multiple quotes + text = text.replaceAll("\"{1,}", "\""); //$NON-NLS-1$//$NON-NLS-2$ + // treat quoted text as a whole, thus enables searching for file paths + if (text.replaceAll("[^\"]", "").length() % 2 == 0) { //$NON-NLS-1$//$NON-NLS-2$ + List patterns = new ArrayList(); + List matchList = new ArrayList(); + Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'"); //$NON-NLS-1$ + Matcher regexMatcher = regex.matcher(text); + while (regexMatcher.find()) { + matchList.add(regexMatcher.group()); + } + for (int i = 0; i < matchList.size(); i++) { + String element = (String) matchList.get(i); + if (isQuoted(element)) { + patterns.add(element); + } else { + String[] elements = element.split("/"); //$NON-NLS-1$ + for (int k = 0; k < elements.length; k++) { + String splitted = elements[k]; + if (splitted.length() > 0) { + patterns.add(splitted); + } + } + } + } + return (String[]) patterns.toArray(new String[0]); + } // filter text must have erroneous quoting, replacing all + text = text.replaceAll("[\"]", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + return text.split("/"); //$NON-NLS-1$ + } + + /** + * Enables the filter to temporarily display arbitrary elements + * + * @param element + */ + public boolean addElement(Object element) { + return fFoundAnyElementsCache.add(element); + } + + /** + * Removes elements from the filter + * + * @param element + */ + public boolean removeElement(Object element) { + return fFoundAnyElementsCache.remove(element); + } + + /* + * The pattern string for which this filter should select + * elements in the viewer. + * + * @see org.eclipse.ui.dialogs.PatternFilter#setPattern(java.lang.String) + */ + public final void setPattern(String patternString) { + super.setPattern(patternString); + fSearchPattern = patternString; + String[] patterns = (patternString != null) ? splitWithQuoting(patternString) : new String[] {}; + fAttributes.clear(); + fAttributes.addAll(Arrays.asList(patterns)); + fFoundAnyElementsCache.clear(); + } + + public String getPattern() { + return fSearchPattern; + } + + public void clearMatchingLeafs() { + fMatchingLeafs.clear(); + } + + public Object[] getMatchingLeafsAsArray() { + return fMatchingLeafs.toArray(); + } + + public Set getMatchingLeafs() { + return fMatchingLeafs; + } + + public boolean containsElement(Object element) { + return fFoundAnyElementsCache.contains(element); + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/FindExtensionsByAttributeQuery.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/FindExtensionsByAttributeQuery.java new file mode 100644 index 0000000..610253f --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/FindExtensionsByAttributeQuery.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.search; + +import org.eclipse.core.runtime.*; +import org.eclipse.pde.core.plugin.IPlugin; +import org.eclipse.pde.internal.core.search.ISearchResultCollector; +import org.eclipse.pde.internal.core.search.PluginSearchInput; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.ISearchResult; +import org.eclipse.search.ui.text.AbstractTextSearchResult; + +/** + * @author Sascha Becher + */ +public class FindExtensionsByAttributeQuery implements ISearchQuery { + + private SearchResult fSearchResult; + + private PluginSearchInput fSearchInput; + + public FindExtensionsByAttributeQuery(PluginSearchInput input) { + fSearchInput = input; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#run(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus run(IProgressMonitor monitor) throws OperationCanceledException { + final AbstractTextSearchResult result = (AbstractTextSearchResult) getSearchResult(); + result.removeAll(); + ISearchResultCollector collector = new ISearchResultCollector() { + public void accept(Object match) { + if (match instanceof IPlugin) { + IPlugin plugin = (IPlugin) match; + result.addMatch(new AttributesMatch(plugin, fSearchInput.getSearchString())); + } + } + }; + ExtensionElementSearchOperation op = new ExtensionElementSearchOperation(fSearchInput, collector); + op.execute(monitor); + monitor.done(); + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#getLabel() + */ + public String getLabel() { + return fSearchInput.getSearchString(); + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#canRerun() + */ + public boolean canRerun() { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#canRunInBackground() + */ + public boolean canRunInBackground() { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#getSearchResult() + */ + public ISearchResult getSearchResult() { + if (fSearchResult == null) + fSearchResult = new SearchResult(this); + return fSearchResult; + } + + public PluginSearchInput getPluginSearchInput() { + return fSearchInput; + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ManifestEditorOpener.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ManifestEditorOpener.java index ded4863..7557067 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ManifestEditorOpener.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/ManifestEditorOpener.java @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Sascha Becher - bug 360894 *******************************************************************************/ package org.eclipse.pde.internal.ui.search; @@ -16,10 +17,14 @@ import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase; import org.eclipse.pde.internal.core.ibundle.IManifestHeader; import org.eclipse.pde.internal.core.text.plugin.PluginObjectNode; -import org.eclipse.pde.internal.ui.editor.plugin.ManifestEditor; +import org.eclipse.pde.internal.ui.editor.plugin.*; import org.eclipse.search.ui.text.Match; +import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PartInitException; +import org.eclipse.ui.forms.IFormPart; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.IFormPage; import org.osgi.framework.Constants; // TODO this needs a rewrite @@ -30,10 +35,25 @@ editorPart = ManifestEditor.open(match.getElement(), true); if (editorPart != null && editorPart instanceof ManifestEditor) { ManifestEditor editor = (ManifestEditor) editorPart; - IDocument doc = editor.getDocument(match); - if (doc != null) { - Match exact = findExactMatch(doc, match, editor); - editor.openToSourcePage(match.getElement(), exact.getOffset(), exact.getLength()); + if (match.getBaseUnit() != AttributesMatch.UNIT_ATTRIBUTE_SEARCH_PATTERN) { + IDocument doc = editor.getDocument(match); + if (doc != null) { + Match exact = findExactMatch(doc, match, editor); + editor.openToSourcePage(match.getElement(), exact.getOffset(), exact.getLength()); + } + } else { // open to extensions page and initialize filter with search result's pattern text + IFormPage page = editor.setActivePage(ExtensionsPage.PAGE_ID); + IManagedForm form = page.getManagedForm(); + IFormPart parts[] = form.getParts(); + if (parts != null && parts.length > 0) { + ExtensionsSection section = (ExtensionsSection) parts[0]; + String searchPattern = ((AttributesMatch) match).getSearchPattern(); + Text filterText = section.getFormFilteredTree().getFilterControl(); + if (!filterText.getText().equals(searchPattern)) { + section.setBypassFilterDelay(true); // force immediate job run + filterText.setText(searchPattern); + } + } } } return editorPart; diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java index f09c4ef..cd9f099 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Sascha Becher - bug 360894 *******************************************************************************/ package org.eclipse.pde.internal.ui.search; @@ -29,9 +30,15 @@ public class SearchResult extends AbstractTextSearchResult implements IEditorMatchAdapter { protected ISearchQuery fQuery; + private final ImageDescriptor fImage; public SearchResult(ISearchQuery query) { fQuery = query; + if (fQuery instanceof FindExtensionsByAttributeQuery) { + fImage = PDEPluginImages.DESC_ESEARCH_OBJ; + } else { + fImage = PDEPluginImages.DESC_PSEARCH_OBJ; + } } /* (non-Javadoc) @@ -60,7 +67,7 @@ * @see org.eclipse.search.ui.ISearchResult#getImageDescriptor() */ public ImageDescriptor getImageDescriptor() { - return PDEPluginImages.DESC_PSEARCH_OBJ; + return fImage; } /* (non-Javadoc) diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/AcceleratedTreeScrolling.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/AcceleratedTreeScrolling.java new file mode 100644 index 0000000..652079d --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/AcceleratedTreeScrolling.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sascha Becher - bug 360894 + *******************************************************************************/ +package org.eclipse.pde.internal.ui.util; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** + * Accelerated tree scrolling with the mouse wheel during which MOD1 (Ctrl) key modifier has been pressed + * + * @author Sascha Becher + */ +public class AcceleratedTreeScrolling implements MouseWheelListener { + + private Tree fTree; + private int fSkipLines; + + /** + * Accelerated mouse wheel scrolling during which the MOD1 (Ctrl) key modifier has been pressed + * + * @param tree tree to scroll + * @param skipLines lines to scroll + */ + public AcceleratedTreeScrolling(Tree tree, int skipLines) { + fTree = tree; + fSkipLines = (skipLines > 1) ? skipLines : 2; + } + + public void mouseScrolled(MouseEvent e) { + // scroll only when MOD1 is pressed + if ((e.stateMask & SWT.MOD1) == SWT.MOD1 && fTree != null) { + TreeItem item = fTree.getTopItem(); + if (item != null) { + TreeItem nextItem = item; + for (int i = 0; i < fSkipLines; i++) { + TreeItem foundItem = null; + if (e.count < 0) // determines scrolling direction + foundItem = NextItem(fTree, nextItem); + else + foundItem = PreviousItem(fTree, nextItem); + if (foundItem == null) { + break; + } + nextItem = foundItem; + } + fTree.setTopItem(nextItem); + } + } + } + + TreeItem PreviousItem(Tree tree, TreeItem item) { + if (item == null) + return null; + TreeItem childItem = item; + TreeItem parentItem = childItem.getParentItem(); + int index = parentItem == null ? tree.indexOf(childItem) : parentItem.indexOf(childItem); + if (index == 0) { + return parentItem; + } + TreeItem nextItem = parentItem == null ? tree.getItem(index - 1) : parentItem.getItem(index - 1); + int count = nextItem.getItemCount(); + while (count > 0 && nextItem.getExpanded()) { + nextItem = nextItem.getItem(count - 1); + count = nextItem.getItemCount(); + } + return nextItem; + } + + TreeItem NextItem(Tree tree, TreeItem item) { + if (item == null) + return null; + if (item.getExpanded()) { + return item.getItem(0); + } + TreeItem childItem = item; + TreeItem parentItem = childItem.getParentItem(); + int index = parentItem == null ? tree.indexOf(childItem) : parentItem.indexOf(childItem); + int count = parentItem == null ? tree.getItemCount() : parentItem.getItemCount(); + while (true) { + if (index + 1 < count) { + return parentItem == null ? tree.getItem(index + 1) : parentItem.getItem(index + 1); + } + if (parentItem == null) { + return null; + } + childItem = parentItem; + parentItem = childItem.getParentItem(); + index = parentItem == null ? tree.indexOf(childItem) : parentItem.indexOf(childItem); + count = parentItem == null ? tree.getItemCount() : parentItem.getItemCount(); + } + } + +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/ExtensionsFilterUtil.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/ExtensionsFilterUtil.java new file mode 100644 index 0000000..75a5a95 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/ExtensionsFilterUtil.java @@ -0,0 +1,294 @@ +package org.eclipse.pde.internal.ui.util; + +import java.util.*; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.pde.core.plugin.*; +import org.eclipse.pde.internal.core.text.plugin.PluginNode; +import org.eclipse.pde.internal.ui.search.ExtensionsPatternFilter; + +public class ExtensionsFilterUtil { + + /** + * id, class, commandId, pattern, locationURI, defaultHandler, variable + * property, contentTypeId, path, plugin, perspective, targetID + */ + // TODO related attributes might be configured through preferences + public static final String[] RELATED_ATTRIBUTES = {"id", //$NON-NLS-1$ + "class", "commandId", "pattern", "locationURI", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + "defaultHandler", "variable", "property", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "contentTypeId", "path", "plugin", "perspective", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + "targetID"}; //$NON-NLS-1$ + + public static final String ATTRIBUTE_ACTIVITYID = "activityId"; //$NON-NLS-1$ + public static final String ATTRIBUTE_CATEGORY = "category"; //$NON-NLS-1$ + public static final String ATTRIBUTE_CATEGORYID = "categoryId"; //$NON-NLS-1$ + public static final String ATTRIBUTE_COMMANDID = "commandId"; //$NON-NLS-1$ + public static final String ATTRIBUTE_DEFAULTHANDLER = "defaultHandler"; //$NON-NLS-1$ + public static final String ATTRIBUTE_ID = "id"; //$NON-NLS-1$ + public static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ + public static final String ATTRIBUTE_NAMESPACE = "namespace"; //$NON-NLS-1$ + public static final String ATTRIBUTE_PROPERTIES = "properties"; //$NON-NLS-1$ + public static final String ATTRIBUTE_PATTERN = "pattern"; //$NON-NLS-1$ + public static final String ATTRIBUTE_REQUIREDACTIVITYID = "requiredActivityId"; //$NON-NLS-1$ + public static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ + + public static final String ELEMENT_ACATEGORY = "org.eclipse.ui.activities.category"; //$NON-NLS-1$ + public static final String ELEMENT_ACTIVITY = "org.eclipse.ui.activities.activity"; //$NON-NLS-1$ + public static final String ELEMENT_AR_BINDING = "org.eclipse.ui.activities.activityRequirementBinding"; //$NON-NLS-1$ + public static final String ELEMENT_CA_BINDING = "org.eclipse.ui.activities.categoryActivityBinding"; //$NON-NLS-1$ + public static final String ELEMENT_COMMAND = "org.eclipse.ui.commands.command"; //$NON-NLS-1$ + public static final String ELEMENT_EQUALS = "org.eclipse.ui.handlers.equals"; //$NON-NLS-1$ + public static final String ELEMENT_HELP_TOC = "org.eclipse.help.toc.toc"; //$NON-NLS-1$ + public static final String ELEMENT_INSTANCEOF = "org.eclipse.ui.handlers.instanceof"; //$NON-NLS-1$ + public static final String ELEMENT_MENU_COMMAND = "org.eclipse.ui.menus.command"; //$NON-NLS-1$ + public static final String ELEMENT_PATTERNBINDING = "org.eclipse.ui.activities.activityPatternBinding"; //$NON-NLS-1$ + public static final String ELEMENT_PROPERTYTESTER = "org.eclipse.core.expressions.propertyTesters.propertyTester"; //$NON-NLS-1$ + + public static final String[] HIGH_PRIORITY_ELEMENTS = new String[] {ELEMENT_COMMAND, ELEMENT_MENU_COMMAND, ELEMENT_INSTANCEOF, ELEMENT_EQUALS}; + public static final String[] LOW_PRIORITY_ELEMENTS = new String[] {ELEMENT_CA_BINDING, ELEMENT_AR_BINDING, ELEMENT_HELP_TOC}; + + public static final Map CUSTOM_RELATIONS; + + static { + CUSTOM_RELATIONS = new HashMap(); + CUSTOM_RELATIONS.put(ELEMENT_COMMAND, new String[] {ATTRIBUTE_ID, ATTRIBUTE_DEFAULTHANDLER}); + CUSTOM_RELATIONS.put(ELEMENT_INSTANCEOF, new String[] {ATTRIBUTE_VALUE}); + CUSTOM_RELATIONS.put(ELEMENT_EQUALS, new String[] {ATTRIBUTE_VALUE}); + CUSTOM_RELATIONS.put(ELEMENT_MENU_COMMAND, new String[] {ATTRIBUTE_COMMANDID, ATTRIBUTE_ID}); + CUSTOM_RELATIONS.put(ELEMENT_CA_BINDING, new String[] {ATTRIBUTE_ACTIVITYID, ATTRIBUTE_CATEGORYID}); + CUSTOM_RELATIONS.put(ELEMENT_AR_BINDING, new String[] {ATTRIBUTE_REQUIREDACTIVITYID, ATTRIBUTE_ACTIVITYID}); + CUSTOM_RELATIONS.put(ELEMENT_HELP_TOC, new String[] {ATTRIBUTE_CATEGORY}); + } + + public static boolean add(Set pattern, IPluginElement pluginElement, String attributeName) { + IPluginAttribute attribute = pluginElement.getAttribute(attributeName); + if (attribute != null) { + return add(pattern, attribute.getValue()); + } + return false; + } + + public static boolean add(Set pattern, String value) { + if (value != null && value.length() > 0) { + String trimmed = value.trim(); + if (isNotBoolean(trimmed)) { + return pattern.add(trimmed); + } + } + return false; + } + + public static void addAll(Set pattern, IPluginElement pluginElement, String elementName) { + Object attributes = CUSTOM_RELATIONS.get(elementName); + if (attributes != null) { + String[] attributesArray = (String[]) attributes; + for (int i = 0; i < attributesArray.length; i++) { + add(pattern, pluginElement, attributesArray[i]); + } + } + } + + /** + * Get unique plugin element name. This will work only with + * editable plugin models, otherwise null is returned. + * + * @param pluginElement the element to get the unique name from + * @return extensionpoint name concatenated with the element name + */ + public static String getElementPath(IPluginElement pluginElement) { + IPluginObject element = pluginElement; + while (element.getParent() != null && !(element.getParent() instanceof PluginNode)) { + element = element.getParent(); + } + if (element instanceof IPluginExtension) { + IPluginExtension extension = (IPluginExtension) element; + return extension.getPoint() + '.' + pluginElement.getName(); + } + return null; + } + + /** + * Obtains common attributes from selected plugin element to filter tree for; + * attribute values are concatenated with a slash and set as filter text + * + * @param selection selected items to filter for related plugin elements + */ + public static String getFilterRelatedPattern(IStructuredSelection selection) { + Iterator it = selection.iterator(); + Set filterPatterns = new HashSet(); + while (it.hasNext()) { + Object treeElement = it.next(); + if (treeElement instanceof IPluginElement) { + IPluginElement pluginElement = (IPluginElement) treeElement; + Set customAttributes = getCustomRelations(pluginElement); + if (customAttributes.size() == 0) { + for (int i = 0; i < RELATED_ATTRIBUTES.length; i++) { + String property = RELATED_ATTRIBUTES[i]; + IPluginAttribute attribute = pluginElement.getAttribute(property); + if (attribute != null && attribute.getValue() != null && attribute.getValue().length() > 0) { + String value = attribute.getValue(); + if (!value.startsWith("%")) { //$NON-NLS-1$ + int delimiterPosition = value.indexOf('?'); // split before '?' and right after last '=' + if (delimiterPosition == -1) { + if (!value.equalsIgnoreCase("true") && !value.equalsIgnoreCase("false")) { //$NON-NLS-1$ //$NON-NLS-2$ + filterPatterns.add(value); + } + } else { + filterPatterns.add(value.substring(0, delimiterPosition)); + int position = value.lastIndexOf('='); + if (position != -1) { + filterPatterns.add(value.substring(position + 1, value.length())); + } + } + } else { + String resourceValue = pluginElement.getResourceString(value); + if ((resourceValue != null && resourceValue.length() > 0)) { + filterPatterns.add(resourceValue); + } + } + } + } + } else { + filterPatterns.addAll(customAttributes); + } + } + } + StringBuffer patternBuffer = new StringBuffer(); + int attributeCount = 0; + for (Iterator iterator = filterPatterns.iterator(); iterator.hasNext();) { + attributeCount++; + Object pattern = iterator.next(); + if (attributeCount < ExtensionsPatternFilter.ATTRIBUTE_LIMIT) { + if (pattern != null) { + patternBuffer.append(pattern); + patternBuffer.append('/'); + } + } + } + + String filterPattern = patternBuffer.toString(); + if (filterPattern.endsWith("/")) { //$NON-NLS-1$ + filterPattern = filterPattern.substring(0, filterPattern.length() - 1); + } + return filterPattern; + } + + public static Set getCustomRelations(IPluginElement pluginElement) { + Set customAttributes = new TreeSet(); + String elementName = (pluginElement != null) ? getElementPath(pluginElement) : null; + if (elementName == null) { + return customAttributes; + } else if (addMatchingElements(customAttributes, pluginElement, elementName, HIGH_PRIORITY_ELEMENTS)) { + } else if (ELEMENT_ACTIVITY.equalsIgnoreCase(elementName) || ELEMENT_ACATEGORY.equals(elementName)) { + if (!add(customAttributes, pluginElement, ATTRIBUTE_ID)) { + add(customAttributes, pluginElement, ATTRIBUTE_NAME); + } + } else if (ELEMENT_PROPERTYTESTER.equalsIgnoreCase(elementName)) { + customAttributes = handlePropertyTester(customAttributes, pluginElement); + add(customAttributes, pluginElement, ATTRIBUTE_ID); + } else if (ELEMENT_PATTERNBINDING.equals(elementName)) { + add(customAttributes, pluginElement, ATTRIBUTE_ACTIVITYID); + String attributeValue = pluginElement.getAttribute(ATTRIBUTE_PATTERN).getValue(); + if (attributeValue.length() > 0) { + int lastSeparator = attributeValue.lastIndexOf('/'); + if (lastSeparator > 0 && attributeValue.length() > lastSeparator + 1) { + customAttributes.add(attributeValue.substring(lastSeparator + 1, attributeValue.length())); + } + } + } else { + addMatchingElements(customAttributes, pluginElement, elementName, LOW_PRIORITY_ELEMENTS); + } + return customAttributes; + } + + private static boolean addMatchingElements(Set customAttributes, IPluginElement pluginElement, String elementName, final String[] elements) { + boolean elementMatch = false; + for (int i = 0; i < elements.length; i++) { + if (elements[i].equals(elementName)) { + addAll(customAttributes, pluginElement, elements[i]); + elementMatch = true; + } + } + return elementMatch; + } + + private static Set handlePropertyTester(Set customAttributes, IPluginElement pluginElement) { + String namespace = pluginElement.getAttribute(ATTRIBUTE_NAMESPACE).getValue(); + String properties = pluginElement.getAttribute(ATTRIBUTE_PROPERTIES).getValue(); + if (namespace.length() > 0) { + String[] propertiesArray = properties.split(","); //$NON-NLS-1$ + for (int i = 0; i < propertiesArray.length; i++) { + String property = propertiesArray[i].trim(); + if (property.length() > 0) { + customAttributes.add(namespace + '.' + property); + } + } + if (propertiesArray.length == 0) { + customAttributes.add(namespace); + } + } + return customAttributes; + } + + public static List handlePropertyTester(IPluginElement pluginElement) { + List propertyTestAttributes = new ArrayList(); + String elementName = getElementPath(pluginElement); + boolean elementMatch = false; + if (elementName == null) { + // workaround for non-editable plugins of the target platform + if (ELEMENT_PROPERTYTESTER.endsWith(pluginElement.getName())) { + elementMatch = true; + } + } else if (ELEMENT_PROPERTYTESTER.equalsIgnoreCase(elementName)) { + elementMatch = true; + } + if (elementMatch) { + Set attributes = handlePropertyTester(new HashSet(), pluginElement); + for (Iterator iterator = attributes.iterator(); iterator.hasNext();) { + propertyTestAttributes.add(iterator.next()); + } + } + return propertyTestAttributes; + } + + public static boolean isFilterRelatedEnabled(IPluginElement pluginElement) { + for (int i = 0; i < RELATED_ATTRIBUTES.length; i++) { + String property = RELATED_ATTRIBUTES[i]; + IPluginAttribute attribute = pluginElement.getAttribute(property); + if (attribute != null) { + return true; + } + } + // test for custom relations + Object attributes = CUSTOM_RELATIONS.get(getElementPath(pluginElement)); + if (attributes != null) { + String[] attributesArray = (String[]) attributes; + for (int i = 0; i < attributesArray.length; i++) { + IPluginAttribute attribute = pluginElement.getAttribute(attributesArray[i]); + if (attribute != null) { + return true; + } + } + } + return false; + } + + public static boolean isFilterRelatedEnabled(IStructuredSelection structuredSelection) { + boolean createFilterRelatedAction = false; + if (structuredSelection != null && !structuredSelection.isEmpty()) { + Iterator it = structuredSelection.iterator(); + while (it.hasNext()) { + Object treeElement = it.next(); + if (treeElement instanceof IPluginElement) { + createFilterRelatedAction |= isFilterRelatedEnabled((IPluginElement) treeElement); + } + } + } + return createFilterRelatedAction; + } + + public static boolean isNotBoolean(String bool) { + return !bool.equalsIgnoreCase(Boolean.TRUE.toString()) && !bool.equalsIgnoreCase(Boolean.FALSE.toString()); + } + +} \ No newline at end of file