| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Anton Leherbauer (Wind River Systems) |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.ui.workingsets; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| |
| import org.eclipse.cdt.core.model.CModelException; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.model.ICContainer; |
| import org.eclipse.cdt.core.model.ICElement; |
| import org.eclipse.cdt.core.model.ICModel; |
| import org.eclipse.cdt.core.model.ICProject; |
| import org.eclipse.cdt.internal.ui.CPluginImages; |
| import org.eclipse.cdt.internal.ui.actions.SelectionConverter; |
| import org.eclipse.cdt.internal.ui.viewsupport.AppearanceAwareLabelProvider; |
| import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider; |
| import org.eclipse.cdt.internal.ui.viewsupport.DecoratingCLabelProvider; |
| import org.eclipse.cdt.ui.CElementGrouping; |
| import org.eclipse.cdt.ui.CElementSorter; |
| import org.eclipse.cdt.ui.CUIPlugin; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.viewers.CheckStateChangedEvent; |
| import org.eclipse.jface.viewers.CheckboxTreeViewer; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.ITreeViewerListener; |
| import org.eclipse.jface.viewers.TreeExpansionEvent; |
| import org.eclipse.jface.wizard.WizardPage; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkingSet; |
| import org.eclipse.ui.IWorkingSetManager; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.dialogs.IWorkingSetPage; |
| |
| /** |
| * The C element working set page allows the user to create |
| * and edit a C element working set. |
| * <p> |
| * Working set elements are presented as a C element tree. |
| * </p> |
| * |
| */ |
| public class CElementWorkingSetPage extends WizardPage implements IWorkingSetPage { |
| |
| final private static String PAGE_TITLE = WorkingSetMessages.CElementWorkingSetPage_title; |
| final private static String PAGE_ID = "CElementWorkingSetPage"; //$NON-NLS-1$ |
| |
| private final static int SIZING_SELECTION_WIDGET_WIDTH = 50; |
| private final static int SIZING_SELECTION_WIDGET_HEIGHT = 200; |
| |
| private Text fWorkingSetName; |
| private CheckboxTreeViewer fTree; |
| private IWorkingSet fWorkingSet; |
| private boolean fFirstCheck; // set to true if selection is set in setSelection |
| private ITreeContentProvider fTreeContentProvider; |
| |
| /** |
| * Creates a new instance of the receiver. |
| */ |
| public CElementWorkingSetPage() { |
| super(PAGE_ID, PAGE_TITLE, CPluginImages.DESC_WIZABAN_C_APP); |
| setDescription(WorkingSetMessages.CElementWorkingSetPage_description); |
| fFirstCheck = true; |
| } |
| |
| /* |
| * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) |
| */ |
| @Override |
| public void createControl(Composite parent) { |
| initializeDialogUnits(parent); |
| |
| Composite composite = new Composite(parent, SWT.NULL); |
| composite.setLayout(new GridLayout()); |
| composite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL)); |
| setControl(composite); |
| |
| Label label = new Label(composite, SWT.WRAP); |
| label.setText(WorkingSetMessages.CElementWorkingSetPage_name); |
| GridData gd = new GridData( |
| GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); |
| label.setLayoutData(gd); |
| |
| fWorkingSetName = new Text(composite, SWT.SINGLE | SWT.BORDER); |
| fWorkingSetName.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL)); |
| fWorkingSetName.addModifyListener(e -> validateInput()); |
| fWorkingSetName.setFocus(); |
| |
| label = new Label(composite, SWT.WRAP); |
| label.setText(WorkingSetMessages.CElementWorkingSetPage_content); |
| gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); |
| label.setLayoutData(gd); |
| |
| fTree = new CheckboxTreeViewer(composite); |
| gd = new GridData(GridData.FILL_BOTH | GridData.GRAB_VERTICAL); |
| gd.heightHint = SIZING_SELECTION_WIDGET_HEIGHT; |
| gd.widthHint = SIZING_SELECTION_WIDGET_WIDTH; |
| fTree.getControl().setLayoutData(gd); |
| |
| fTreeContentProvider = new CElementWorkingSetPageContentProvider(); |
| fTree.setContentProvider(fTreeContentProvider); |
| |
| AppearanceAwareLabelProvider cElementLabelProvider = new AppearanceAwareLabelProvider( |
| AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS, |
| AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS | CElementImageProvider.SMALL_ICONS); |
| |
| fTree.setLabelProvider(new DecoratingCLabelProvider(cElementLabelProvider)); |
| fTree.setComparator(new CElementSorter()); |
| fTree.setUseHashlookup(true); |
| |
| fTree.setInput(CoreModel.create(CUIPlugin.getWorkspace().getRoot())); |
| |
| fTree.addCheckStateListener(event -> handleCheckStateChange(event)); |
| |
| fTree.addTreeListener(new ITreeViewerListener() { |
| @Override |
| public void treeCollapsed(TreeExpansionEvent event) { |
| } |
| |
| @Override |
| public void treeExpanded(TreeExpansionEvent event) { |
| final Object element = event.getElement(); |
| if (fTree.getGrayed(element) == false) |
| BusyIndicator.showWhile(getShell().getDisplay(), |
| () -> setSubtreeChecked(element, fTree.getChecked(element), false)); |
| } |
| }); |
| |
| if (fWorkingSet != null) { |
| fWorkingSetName.setText(fWorkingSet.getName()); |
| } |
| initializeCheckedState(); |
| validateInput(); |
| |
| Dialog.applyDialogFont(composite); |
| // TODO Set help for the page |
| // CUIHelp.setHelp(fTree, ICHelpContextIds.C_WORKING_SET_PAGE); |
| } |
| |
| /* |
| * @see org.eclipse.ui.dialogs.IWorkingSetPage#finish() |
| */ |
| @Override |
| public void finish() { |
| String workingSetName = fWorkingSetName.getText(); |
| ArrayList<Object> elements = new ArrayList<>(10); |
| findCheckedElements(elements, fTree.getInput()); |
| if (fWorkingSet == null) { |
| IWorkingSetManager workingSetManager = PlatformUI.getWorkbench().getWorkingSetManager(); |
| fWorkingSet = workingSetManager.createWorkingSet(workingSetName, |
| elements.toArray(new IAdaptable[elements.size()])); |
| } else { |
| // Add inaccessible resources |
| IAdaptable[] oldItems = fWorkingSet.getElements(); |
| HashSet<IProject> closedWithChildren = new HashSet<>(elements.size()); |
| for (IAdaptable oldItem : oldItems) { |
| IResource oldResource = null; |
| if (oldItem instanceof IResource) { |
| oldResource = (IResource) oldItem; |
| } else { |
| oldResource = oldItem.getAdapter(IResource.class); |
| } |
| if (oldResource != null && oldResource.isAccessible() == false) { |
| IProject project = oldResource.getProject(); |
| if (closedWithChildren.contains(project) || elements.contains(project)) { |
| elements.add(oldItem); |
| elements.remove(project); |
| closedWithChildren.add(project); |
| } |
| } |
| } |
| fWorkingSet.setName(workingSetName); |
| fWorkingSet.setElements(elements.toArray(new IAdaptable[elements.size()])); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.ui.dialogs.IWorkingSetPage#getSelection() |
| */ |
| @Override |
| public IWorkingSet getSelection() { |
| return fWorkingSet; |
| } |
| |
| /** |
| * Called when the checked state of a tree item changes. |
| * |
| * @param event the checked state change event. |
| */ |
| void handleCheckStateChange(final CheckStateChangedEvent event) { |
| BusyIndicator.showWhile(getShell().getDisplay(), () -> { |
| IAdaptable element = (IAdaptable) event.getElement(); |
| boolean state = event.getChecked(); |
| |
| fTree.setGrayed(element, false); |
| if (isExpandable(element)) { |
| setSubtreeChecked(element, state, true); |
| } |
| updateParentState(element, state); |
| validateInput(); |
| }); |
| } |
| |
| private boolean isExpandable(Object element) { |
| return (element instanceof ICProject || element instanceof ICContainer || element instanceof CElementGrouping |
| || element instanceof ICModel || element instanceof IContainer); |
| } |
| |
| private void updateParentState(Object child, boolean baseChildState) { |
| if (child == null) |
| return; |
| if (child instanceof IAdaptable) { |
| IResource resource = ((IAdaptable) child).getAdapter(IResource.class); |
| if (resource != null && !resource.isAccessible()) |
| return; |
| } |
| Object parent = fTreeContentProvider.getParent(child); |
| if (parent == null) |
| return; |
| |
| updateObjectState(parent, baseChildState); |
| } |
| |
| private void updateObjectState(Object element, boolean baseChildState) { |
| |
| boolean allSameState = true; |
| Object[] children = fTreeContentProvider.getChildren(element); |
| |
| for (int i = children.length - 1; i >= 0; i--) { |
| if (fTree.getChecked(children[i]) != baseChildState || fTree.getGrayed(children[i])) { |
| allSameState = false; |
| break; |
| } |
| } |
| |
| fTree.setGrayed(element, !allSameState); |
| fTree.setChecked(element, !allSameState || baseChildState); |
| |
| updateParentState(element, baseChildState); |
| } |
| |
| /** |
| * Sets the checked state of tree items based on the initial |
| * working set, if any. |
| */ |
| private void initializeCheckedState() { |
| BusyIndicator.showWhile(getShell().getDisplay(), () -> { |
| Object[] elements; |
| if (fWorkingSet == null) { |
| // Use current part's selection for initialization |
| IWorkbenchPage page = CUIPlugin.getActivePage(); |
| if (page == null) |
| return; |
| |
| IWorkbenchPart part = CUIPlugin.getActivePage().getActivePart(); |
| if (part == null) |
| return; |
| |
| try { |
| elements = SelectionConverter.getStructuredSelection(part).toArray(); |
| for (int i1 = 0; i1 < elements.length; i1++) { |
| if (elements[i1] instanceof IResource) { |
| ICElement ce = ((IResource) elements[i1]).getAdapter(ICElement.class); |
| if (ce != null && ce.exists() && ce.getCProject().isOnSourceRoot((IResource) elements[i1])) |
| elements[i1] = ce; |
| } |
| } |
| } catch (CModelException e) { |
| return; |
| } |
| } else |
| elements = fWorkingSet.getElements(); |
| |
| for (int i2 = 0; i2 < elements.length; i2++) { |
| Object element1 = elements[i2]; |
| if (element1 instanceof IResource) { |
| IProject project = ((IResource) element1).getProject(); |
| if (!project.isAccessible()) { |
| elements[i2] = project; |
| } else { |
| // for backwards compatibility: adapt to ICElement if possible |
| if (CoreModel.hasCNature(project)) { |
| ICElement cElement = CoreModel.getDefault().create((IResource) element1); |
| if (cElement != null) { |
| elements[i2] = cElement; |
| } |
| } |
| } |
| } else if (element1 instanceof ICElement) { |
| ICProject cProject = ((ICElement) element1).getCProject(); |
| if (cProject != null && !cProject.getProject().isAccessible()) |
| elements[i2] = cProject.getProject(); |
| } |
| } |
| fTree.setCheckedElements(elements); |
| HashSet<Object> parents = new HashSet<>(); |
| for (Object element2 : elements) { |
| if (isExpandable(element2)) |
| setSubtreeChecked(element2, true, true); |
| |
| if (element2 instanceof IAdaptable) { |
| IResource resource = ((IAdaptable) element2).getAdapter(IResource.class); |
| if (resource != null && !resource.isAccessible()) |
| continue; |
| } |
| Object parent = fTreeContentProvider.getParent(element2); |
| if (parent != null) |
| parents.add(parent); |
| } |
| |
| for (Object object : parents) |
| updateObjectState(object, true); |
| }); |
| } |
| |
| /* |
| * @see org.eclipse.ui.dialogs.IWorkingSetPage#setSelection(org.eclipse.ui.IWorkingSet) |
| */ |
| @Override |
| public void setSelection(IWorkingSet workingSet) { |
| if (workingSet == null) { |
| throw new IllegalArgumentException("Working set must not be null"); //$NON-NLS-1$ |
| } |
| fWorkingSet = workingSet; |
| if (getContainer() != null && fWorkingSetName != null) { |
| fFirstCheck = false; |
| fWorkingSetName.setText(workingSet.getName()); |
| initializeCheckedState(); |
| validateInput(); |
| } |
| } |
| |
| /** |
| * Sets the checked state of the container's members. |
| * |
| * @param parent the parent whose children should be checked/unchecked |
| * @param state true=check all members in the container. false=uncheck all |
| * members in the container. |
| * @param checkExpandedState true=recurse into sub-containers and set the |
| * checked state. false=only set checked state of members of this container |
| */ |
| private void setSubtreeChecked(Object parent, boolean state, boolean checkExpandedState) { |
| if (!(parent instanceof IAdaptable)) |
| return; |
| IContainer container = ((IAdaptable) parent).getAdapter(IContainer.class); |
| if ((!fTree.getExpandedState(parent) && checkExpandedState) || (container != null && !container.isAccessible())) |
| return; |
| |
| Object[] children = fTreeContentProvider.getChildren(parent); |
| for (int i = children.length - 1; i >= 0; i--) { |
| Object element = children[i]; |
| if (state) { |
| fTree.setChecked(element, true); |
| fTree.setGrayed(element, false); |
| } else |
| fTree.setGrayChecked(element, false); |
| if (isExpandable(element)) |
| setSubtreeChecked(element, state, true); |
| } |
| } |
| |
| /** |
| * Validates the working set name and the checked state of the |
| * resource tree. |
| */ |
| void validateInput() { |
| String errorMessage = null; |
| String newText = fWorkingSetName.getText(); |
| |
| if (newText.equals(newText.trim()) == false) { |
| errorMessage = WorkingSetMessages.CElementWorkingSetPage_warning_nameMustNotBeEmpty; |
| } |
| if (newText.isEmpty()) { |
| if (fFirstCheck) { |
| setPageComplete(false); |
| fFirstCheck = false; |
| return; |
| } |
| errorMessage = WorkingSetMessages.CElementWorkingSetPage_warning_nameMustNotBeEmpty; |
| } |
| |
| fFirstCheck = false; |
| |
| if (errorMessage == null && (fWorkingSet == null || newText.equals(fWorkingSet.getName()) == false)) { |
| IWorkingSet[] workingSets = PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets(); |
| for (IWorkingSet workingSet : workingSets) { |
| if (newText.equals(workingSet.getName())) { |
| errorMessage = WorkingSetMessages.CElementWorkingSetPage_warning_workingSetExists; |
| } |
| } |
| } |
| |
| if (errorMessage == null && fTree.getCheckedElements().length == 0) { |
| String infoMessage = WorkingSetMessages.CElementWorkingSetPage_warning_resourceMustBeChecked; |
| setMessage(infoMessage, INFORMATION); |
| } |
| setErrorMessage(errorMessage); |
| setPageComplete(errorMessage == null); |
| } |
| |
| /** |
| * Collects all checked elements of the given parent. |
| * |
| * @param checkedElements the output, list of checked elements |
| * @param parent the parent to collect checked elements in |
| */ |
| private void findCheckedElements(List<Object> checkedElements, Object parent) { |
| Object[] children = fTreeContentProvider.getChildren(parent); |
| for (Object element : children) { |
| if (fTree.getGrayed(element)) |
| findCheckedElements(checkedElements, element); |
| else if (fTree.getChecked(element)) |
| checkedElements.add(element); |
| } |
| } |
| } |