| /******************************************************************************* |
| * Copyright (c) 2009, 2018 QNX Software Systems 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: |
| * QNX Software Systems - initial API and implementation |
| * Freescale Semiconductor |
| * SSI Schaefer |
| * IBM Corporation - Bug 517809 |
| *******************************************************************************/ |
| package org.eclipse.debug.internal.ui.groups; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; |
| import org.eclipse.debug.internal.core.DebugCoreMessages; |
| import org.eclipse.debug.internal.core.groups.GroupLaunchConfigurationDelegate; |
| import org.eclipse.debug.internal.core.groups.GroupLaunchElement; |
| import org.eclipse.debug.internal.core.groups.GroupLaunchElement.GroupElementPostLaunchAction; |
| import org.eclipse.debug.internal.ui.DebugPluginImages; |
| import org.eclipse.debug.internal.ui.DebugUIMessages; |
| import org.eclipse.debug.internal.ui.DebugUIPlugin; |
| import org.eclipse.debug.internal.ui.IInternalDebugUIConstants; |
| import org.eclipse.debug.internal.ui.SWTFactory; |
| import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager; |
| import org.eclipse.debug.internal.ui.launchConfigurations.LaunchHistory; |
| import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; |
| import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; |
| import org.eclipse.debug.ui.DebugUITools; |
| import org.eclipse.debug.ui.ILaunchConfigurationDialog; |
| import org.eclipse.debug.ui.ILaunchConfigurationTab; |
| import org.eclipse.debug.ui.ILaunchGroup; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.viewers.BaseLabelProvider; |
| import org.eclipse.jface.viewers.CheckStateChangedEvent; |
| import org.eclipse.jface.viewers.CheckboxTreeViewer; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.ICheckStateProvider; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredContentProvider; |
| import org.eclipse.jface.viewers.ITableLabelProvider; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeColumn; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.activities.WorkbenchActivityHelper; |
| |
| /** |
| * Tab group for Launch Group. |
| * |
| * @since 3.12 |
| */ |
| public class GroupLaunchConfigurationTabGroup extends AbstractLaunchConfigurationTabGroup { |
| static class ContentProvider implements IStructuredContentProvider, ITreeContentProvider { |
| protected List<GroupLaunchElement> input; |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| return getChildren(inputElement); |
| } |
| |
| @Override |
| public void dispose() { |
| input = null; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") // nothing we can do about this |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| if (newInput instanceof List<?>) { |
| input = (List<GroupLaunchElement>) newInput; |
| } |
| } |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| return (parentElement == input) ? input.toArray() : null; |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| return (element == input) ? null : input; |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| return (element == input) ? (input.size() > 0) : false; |
| } |
| } |
| static class LabelProvider extends BaseLabelProvider implements ITableLabelProvider { |
| @Override |
| public Image getColumnImage(Object element, int columnIndex) { |
| if (!(element instanceof GroupLaunchElement)) { |
| return null; |
| } |
| if (columnIndex == 0) { |
| GroupLaunchElement el = (GroupLaunchElement) element; |
| if (el.data == null || !isValidLaunchReference(el.data)) { |
| Image errorImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK); |
| return errorImage; |
| } |
| |
| try { |
| String key = el.data.getType().getIdentifier(); |
| return DebugPluginImages.getImage(key); |
| } catch (CoreException e) { |
| Image errorImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK); |
| return errorImage; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String getColumnText(Object element, int columnIndex) { |
| if (!(element instanceof GroupLaunchElement)) { |
| return null; |
| } |
| GroupLaunchElement el = (GroupLaunchElement) element; |
| |
| // launch name |
| if (columnIndex == 0) { |
| try { |
| return (el.data != null) ? el.data.getType().getName() + "::" + el.name : el.name; //$NON-NLS-1$ |
| } catch (CoreException e) { |
| return el.name; |
| } |
| } |
| |
| // launch mode |
| if (columnIndex == 1) { |
| return getLabel(el.mode) + (el.adoptIfRunning ? DebugUIMessages.GroupLaunchConfigurationTabGroup_lblAdopt : ""); //$NON-NLS-1$ |
| } |
| |
| // launch post action |
| if (columnIndex == 2) { |
| GroupElementPostLaunchAction action = el.action; |
| switch (action) { |
| case NONE: |
| return ""; //$NON-NLS-1$ |
| case WAIT_FOR_TERMINATION: |
| return action.getDescription(); |
| case DELAY: |
| final Object actionParam = el.actionParam; |
| return NLS.bind(DebugUIMessages.GroupLaunchConfigurationTabGroup_13, actionParam instanceof Integer ? Integer.toString((Integer) actionParam) : "?"); //$NON-NLS-1$ |
| case OUTPUT_REGEXP: |
| return NLS.bind(DebugUIMessages.GroupLaunchConfigurationTabGroup_0, el.actionParam); |
| default: |
| assert false : "new post launch action missing logic here"; //$NON-NLS-1$ |
| return ""; //$NON-NLS-1$ |
| } |
| } |
| return null; |
| } |
| } |
| |
| static class CheckStateProvider implements ICheckStateProvider { |
| |
| @Override |
| public boolean isChecked(Object element) { |
| if (element instanceof GroupLaunchElement) { |
| return ((GroupLaunchElement)element).enabled; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean isGrayed(Object element) { |
| return false; |
| } |
| } |
| static abstract class ButtonComposite extends Composite implements SelectionListener { |
| Button upButton; |
| Button downButton; |
| Button addButton; |
| Button deleteButton; |
| Button editButton; |
| |
| public ButtonComposite(Composite parent, int style) { |
| super(parent, style); |
| setLayout(new GridLayout()); |
| upButton = createPushButton(this, DebugUIMessages.GroupLaunchConfigurationTabGroup_1); |
| downButton = createPushButton(this, DebugUIMessages.GroupLaunchConfigurationTabGroup_2); |
| editButton = createPushButton(this, DebugUIMessages.GroupLaunchConfigurationTabGroup_3); |
| addButton = createPushButton(this, DebugUIMessages.GroupLaunchConfigurationTabGroup_4); |
| deleteButton = createPushButton(this, DebugUIMessages.GroupLaunchConfigurationTabGroup_5); |
| } |
| |
| protected abstract void updateWidgetEnablement(); |
| |
| /** |
| * Helper method to create a push button. |
| * |
| * @param parent |
| * the parent control |
| * @param key |
| * the resource name used to supply the button's label text |
| * @return Button |
| */ |
| protected Button createPushButton(Composite parent, String key) { |
| Button button = SWTFactory.createPushButton(parent, key, null); |
| button.addSelectionListener(this); |
| return button; |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| // nothing |
| } |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| Widget widget = e.widget; |
| if (widget == upButton) { |
| upPressed(); |
| } else if (widget == downButton) { |
| downPressed(); |
| } else if (widget == addButton) { |
| addPressed(); |
| } else if (widget == deleteButton) { |
| deletePressed(); |
| } else if (widget == editButton) { |
| editPressed(); |
| } |
| } |
| |
| protected abstract void addPressed(); |
| |
| protected abstract void editPressed(); |
| |
| protected abstract void deletePressed(); |
| |
| protected abstract void downPressed(); |
| |
| protected abstract void upPressed(); |
| } |
| static class GroupLaunchTab extends AbstractLaunchConfigurationTab { |
| protected CheckboxTreeViewer treeViewer; |
| protected List<GroupLaunchElement> input = new ArrayList<>(); |
| |
| /** |
| * copy of the initial state of the configuration used for cycle |
| * checking. This is not updated when the user changes settings! |
| */ |
| private ILaunchConfiguration self; |
| |
| @Override |
| public void createControl(Composite parent) { |
| Composite comp = new Composite(parent, SWT.NONE); |
| setControl(comp); |
| //comp.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN)); |
| comp.setLayout(new GridLayout(2, false)); |
| treeViewer = new CheckboxTreeViewer(comp, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION); |
| Tree table = treeViewer.getTree(); |
| table.setFont(parent.getFont()); |
| treeViewer.setContentProvider(new ContentProvider()); |
| treeViewer.setLabelProvider(new LabelProvider()); |
| treeViewer.setCheckStateProvider(new CheckStateProvider()); |
| table.setHeaderVisible(true); |
| table.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| TreeColumn col1 = new TreeColumn(table, SWT.NONE); |
| col1.setText(DebugUIMessages.GroupLaunchConfigurationTabGroup_6); |
| col1.setWidth(300); |
| TreeColumn col2 = new TreeColumn(table, SWT.NONE); |
| col2.setText(DebugUIMessages.GroupLaunchConfigurationTabGroup_7); |
| col2.setWidth(100); |
| TreeColumn col3 = new TreeColumn(table, SWT.NONE); |
| col3.setText(DebugUIMessages.GroupLaunchConfigurationTabGroup_12); |
| col3.setWidth(100); |
| |
| treeViewer.setInput(input); |
| final ButtonComposite buts = new ButtonComposite(comp, SWT.NONE) { |
| @Override |
| protected void addPressed() { |
| GroupLaunchConfigurationSelectionDialog dialog = |
| GroupLaunchConfigurationSelectionDialog.createDialog( |
| treeViewer.getControl().getShell(), GroupLaunchElement.MODE_INHERIT, false, self); |
| if (dialog.open() == Window.OK) { |
| ILaunchConfiguration[] configs = dialog.getSelectedLaunchConfigurations(); |
| if (configs.length < 1) { |
| return; |
| } |
| for (ILaunchConfiguration config : configs) { |
| GroupLaunchElement el = new GroupLaunchElement(); |
| input.add(el); |
| el.index = input.size() - 1; |
| el.enabled = true; |
| applyFromDialog(el, dialog, config); |
| treeViewer.refresh(true); |
| treeViewer.setChecked(el, el.enabled); |
| } |
| updateWidgetEnablement(); |
| updateLaunchConfigurationDialog(); |
| } |
| } |
| @Override |
| protected void updateWidgetEnablement(){ |
| downButton.setEnabled(isDownEnabled()); |
| upButton.setEnabled(isUpEnabled()); |
| |
| int selectionCount = getSelectionCount(); |
| editButton.setEnabled(selectionCount == 1); |
| deleteButton.setEnabled(selectionCount > 0); |
| } |
| |
| |
| @Override |
| protected void editPressed() { |
| int index = getSingleSelectionIndex(); |
| if (index < 0) { |
| return; |
| } |
| GroupLaunchElement el = input.get(index); |
| GroupLaunchConfigurationSelectionDialog dialog = |
| GroupLaunchConfigurationSelectionDialog.createDialog( |
| treeViewer.getControl().getShell(), el.mode, true, self); |
| if (isValidLaunchReference(el.data)) { |
| dialog.setInitialSelection(el); |
| } |
| if (dialog.open() == Window.OK) { |
| ILaunchConfiguration[] confs = dialog.getSelectedLaunchConfigurations(); |
| if (confs.length < 0) { |
| return; |
| } |
| assert confs.length == 1 : "invocation of the dialog for editing an entry sholdn't allow OK to be hit if the user chooses multiple launch configs in the dialog"; //$NON-NLS-1$ |
| applyFromDialog(el, dialog, confs[0]); |
| treeViewer.refresh(true); |
| updateWidgetEnablement(); |
| updateLaunchConfigurationDialog(); |
| } |
| } |
| |
| private void applyFromDialog(GroupLaunchElement el, GroupLaunchConfigurationSelectionDialog dialog, ILaunchConfiguration config) { |
| el.name = config.getName(); |
| el.data = config; |
| el.mode = dialog.getMode(); |
| el.action = dialog.getAction(); |
| el.adoptIfRunning = dialog.getAdoptIfRunning(); |
| el.actionParam = dialog.getActionParam(); |
| } |
| |
| @Override |
| protected void deletePressed() { |
| int[] indices = getMultiSelectionIndices(); |
| if (indices.length < 1) { |
| return; |
| } |
| // need to delete from high to low |
| for (int i = indices.length - 1; i >= 0; i--) { |
| input.remove(indices[i]); |
| } |
| treeViewer.refresh(true); |
| updateWidgetEnablement(); |
| updateLaunchConfigurationDialog(); |
| } |
| |
| /** |
| * @return the index of the selection if a single item is |
| * selected. If zero or multiple are selected, -1 is |
| * returned |
| */ |
| private int getSingleSelectionIndex() { |
| StructuredSelection sel = (StructuredSelection) treeViewer.getSelection(); |
| if (sel.size() != 1) { |
| return -1; |
| } |
| GroupLaunchElement el = ((GroupLaunchElement) sel |
| .getFirstElement()); |
| return input.indexOf(el); |
| } |
| |
| /** |
| * @return the indices of one or more selected items. Indices |
| * are always returned in ascending order |
| */ |
| private int[] getMultiSelectionIndices() { |
| StructuredSelection sel = (StructuredSelection) treeViewer.getSelection(); |
| List<Integer> indices = new ArrayList<>(); |
| |
| for (Iterator<?> iter = sel.iterator(); iter.hasNext(); ) { |
| GroupLaunchElement el = (GroupLaunchElement) iter.next(); |
| indices.add(input.indexOf(el)); |
| |
| } |
| int[] result = new int[indices.size()]; |
| for (int i = 0; i < result.length; i++) { |
| result[i] = indices.get(i); |
| } |
| return result; |
| } |
| |
| private int getSelectionCount() { |
| return ((StructuredSelection)treeViewer.getSelection()).size(); |
| } |
| |
| |
| @Override |
| protected void downPressed() { |
| if (!isDownEnabled()) { |
| return; |
| } |
| int index = getSingleSelectionIndex(); |
| |
| GroupLaunchElement x = input.get(index); |
| input.set(index, input.get(index + 1)); |
| input.set(index + 1, x); |
| treeViewer.refresh(true); |
| updateWidgetEnablement(); |
| updateLaunchConfigurationDialog(); |
| } |
| |
| protected boolean isDownEnabled() { |
| final int index = getSingleSelectionIndex(); |
| return (index >= 0) && (index != input.size() - 1); |
| } |
| |
| protected boolean isUpEnabled(){ |
| return getSingleSelectionIndex() > 0; |
| } |
| |
| @Override |
| protected void upPressed() { |
| if (!isUpEnabled()) { |
| return; |
| } |
| int index = getSingleSelectionIndex(); |
| GroupLaunchElement x = input.get(index); |
| input.set(index, input.get(index - 1)); |
| input.set(index - 1, x); |
| treeViewer.refresh(true); |
| updateWidgetEnablement(); |
| updateLaunchConfigurationDialog(); |
| } |
| }; |
| treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| buts.updateWidgetEnablement(); |
| } |
| }); |
| |
| treeViewer.getTree().addSelectionListener(new SelectionAdapter(){ |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| buts.editPressed(); |
| } |
| }); |
| |
| treeViewer.addCheckStateListener(new ICheckStateListener(){ |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| ((GroupLaunchElement)event.getElement()).enabled = event.getChecked(); |
| updateLaunchConfigurationDialog(); |
| } |
| }); |
| buts.updateWidgetEnablement(); |
| GridData layoutData = new GridData(GridData.GRAB_VERTICAL); |
| layoutData.verticalAlignment = SWT.BEGINNING; |
| buts.setLayoutData(layoutData); |
| } |
| |
| @Override |
| public String getName() { |
| return DebugUIMessages.GroupLaunchConfigurationTabGroup_10; |
| } |
| |
| @Override |
| public Image getImage() { |
| return DebugUITools.getImage(IInternalDebugUIConstants.IMG_OBJS_LAUNCH_GROUP); |
| } |
| |
| @Override |
| public void initializeFrom(ILaunchConfiguration configuration) { |
| try { |
| self = configuration.copy(configuration.getName()); |
| } catch (CoreException e) { |
| DebugPlugin.log(e); |
| } |
| |
| // replace the input from previously shown launch configurations |
| input = GroupLaunchConfigurationDelegate.createLaunchElements(configuration); |
| if (treeViewer != null) { |
| treeViewer.setInput(input); |
| } |
| } |
| |
| @Override |
| public void performApply(ILaunchConfigurationWorkingCopy configuration) { |
| GroupLaunchConfigurationDelegate.storeLaunchElements(configuration, input); |
| } |
| |
| @Override |
| public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { |
| // defaults is empty list |
| } |
| |
| @Override |
| public boolean isValid(ILaunchConfiguration launchConfig) { |
| setMessage(null); |
| setErrorMessage(null); |
| int validLaunches = 0; |
| // test if each launch is valid |
| for (GroupLaunchElement element : input) { |
| if (element.enabled) { |
| if ( element.data == null) { |
| // error referencing invalid launch |
| setErrorMessage(MessageFormat.format(DebugUIMessages.GroupLaunchConfigurationTabGroup_14, |
| element.name)); |
| return false; |
| } else if (!isValidLaunchReference(element.data)) { |
| // error referencing invalid launch |
| setErrorMessage(MessageFormat.format(DebugUIMessages.GroupLaunchConfigurationTabGroup_15, |
| element.name)); |
| return false; |
| } |
| validLaunches++; |
| } |
| } |
| if (validLaunches < 1) { |
| // must have at least one valid and enabled launch |
| setErrorMessage(DebugUIMessages.GroupLaunchConfigurationTabGroup_16); |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| public GroupLaunchConfigurationTabGroup() { |
| // nothing |
| } |
| |
| /** |
| * Test if a launch configuration is a valid reference. |
| * |
| * @param config configuration reference |
| * @return <code>true</code> if it is a valid reference, <code>false</code> |
| * if launch configuration should be filtered |
| */ |
| public static boolean isValidLaunchReference(ILaunchConfiguration config) { |
| if (config == null) { |
| return false; |
| } |
| return DebugUIPlugin.doLaunchConfigurationFiltering(config) && !WorkbenchActivityHelper.filterItem(config); |
| } |
| |
| @Override |
| public void createTabs(ILaunchConfigurationDialog dialog, String mode) { |
| ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {// |
| new GroupLaunchTab(), // |
| new CommonTabLite() // |
| }; |
| setTabs(tabs); |
| } |
| |
| public static Map<String, ILaunchGroup> getModes() { |
| if (modes == null) { |
| initializeModes(); |
| } |
| return modes; |
| } |
| |
| private static Map<String, ILaunchGroup> modes; |
| |
| /** |
| * Required to satisfy the tree in mode inherit. |
| */ |
| private static final class InheritModeGroup implements ILaunchGroup { |
| |
| @Override |
| public ImageDescriptor getImageDescriptor() { |
| return null; |
| } |
| |
| @Override |
| public ImageDescriptor getBannerImageDescriptor() { |
| return null; |
| } |
| |
| @Override |
| public String getLabel() { |
| return DebugCoreMessages.GroupLaunchElement_inherit_launch_mode_label; |
| } |
| |
| @Override |
| public String getIdentifier() { |
| return null; |
| } |
| |
| @Override |
| public String getCategory() { |
| return null; |
| } |
| |
| @Override |
| public String getMode() { |
| return null; |
| } |
| |
| @Override |
| public boolean isPublic() { |
| return false; |
| } |
| |
| } |
| |
| private static synchronized void initializeModes() { |
| if (modes != null) { |
| return; |
| } |
| modes = new LinkedHashMap<>(); |
| modes.put(GroupLaunchElement.MODE_INHERIT, new InheritModeGroup()); |
| Set<ILaunchGroup> sortedGroups = new TreeSet<>((a, b) -> { |
| return a.getLabel().compareTo(b.getLabel()); |
| }); |
| LaunchConfigurationManager mgr = DebugUIPlugin.getDefault().getLaunchConfigurationManager(); |
| sortedGroups.addAll(Arrays.asList(mgr.getLaunchGroups())); |
| for (ILaunchGroup launchGroup : sortedGroups) { |
| LaunchHistory history = mgr.getLaunchHistory(launchGroup.getIdentifier()); |
| if (history == null) { |
| // mode currently not supported. |
| continue; |
| } |
| |
| String modeName = launchGroup.getMode(); |
| if (!modes.containsKey(modeName)) { |
| modes.put(modeName, launchGroup); |
| } |
| } |
| } |
| |
| public static String getLabel(String mode) { |
| if (mode == null || mode.isEmpty()) |
| { |
| return ""; //$NON-NLS-1$ |
| } |
| if (modes == null) { |
| initializeModes(); |
| } |
| ILaunchGroup launchGrp = modes.get(mode); |
| return DebugUIPlugin.removeAccelerators(launchGrp.getLabel()); |
| } |
| } |