Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Richard2017-03-10 10:30:18 -0500
committerSarika Sinha2017-12-11 00:47:08 -0500
commitca4c3de585c743486c2df55b551ec3f2dfd9b6a2 (patch)
treee408d2fd23d7014b1db5116fce46d5ce6e4f0bfb /org.eclipse.debug.ui/ui/org/eclipse/debug/ui
parent8803c89e9471914c5a66f0f869cd4fea67357576 (diff)
downloadeclipse.platform.debug-ca4c3de585c743486c2df55b551ec3f2dfd9b6a2.tar.gz
eclipse.platform.debug-ca4c3de585c743486c2df55b551ec3f2dfd9b6a2.tar.xz
eclipse.platform.debug-ca4c3de585c743486c2df55b551ec3f2dfd9b6a2.zip
Bug 41353 - Launch configuration prototypesI20171211-2000
First implementation of the launch configuration prototypes mechanism. Change-Id: I21a2a97b83aa9cb1be79153bbc4b72171cc6be20 Signed-off-by: Axel Richard <axel.richard@obeo.fr>
Diffstat (limited to 'org.eclipse.debug.ui/ui/org/eclipse/debug/ui')
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/AbstractLaunchConfigurationTab.java61
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/CommonTab.java22
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/EnvironmentTab.java16
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java10
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/PrototypeDecorator.java52
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/ProtoypeTab.java760
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/ui/sourcelookup/SourceLookupTab.java16
7 files changed, 929 insertions, 8 deletions
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/AbstractLaunchConfigurationTab.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/AbstractLaunchConfigurationTab.java
index 5503ed937..cbe4e76f3 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/AbstractLaunchConfigurationTab.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/AbstractLaunchConfigurationTab.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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,10 +8,14 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Mohamed Hussein (Mentor Graphics) - Added s/getWarningMessage (Bug 386673)
+ * Axel Richard (Obeo) - Bug 41353 - Launch configurations prototypes
*******************************************************************************/
package org.eclipse.debug.ui;
+import java.util.HashMap;
+import java.util.Map;
+
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -21,6 +25,7 @@ import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.core.IPrototypeAttributesLabelProvider;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
@@ -40,7 +45,7 @@ import org.eclipse.ui.progress.WorkbenchJob;
* @see ILaunchConfigurationTab
* @since 2.0
*/
-public abstract class AbstractLaunchConfigurationTab implements ILaunchConfigurationTab2 {
+public abstract class AbstractLaunchConfigurationTab implements ILaunchConfigurationTab2, IPrototypeAttributesLabelProvider {
/**
* The control for this page, or <code>null</code>
@@ -92,8 +97,46 @@ public abstract class AbstractLaunchConfigurationTab implements ILaunchConfigura
private String fHelpContextId = null;
/**
- * Returns the dialog this tab is contained in, or
- * <code>null</code> if not yet set.
+ * Attributes labels for prototype tab.
+ *
+ * @since 3.13
+ */
+ private Map<String, String> fAttributesLabelsForPrototype;
+
+ /**
+ * Default constructor.
+ *
+ * @since 3.13
+ */
+ public AbstractLaunchConfigurationTab() {
+ fAttributesLabelsForPrototype = new HashMap<>();
+ initializeAttributes();
+ }
+
+ /**
+ * Initialize attributes labels.
+ *
+ * @since 3.13
+ */
+ protected void initializeAttributes() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.core.IPrototypeAttributesLabelProvider#getLabel(java.lang.String)
+ */
+ @Override
+ public String getAttributeLabel(String attribute) {
+ String label = fAttributesLabelsForPrototype.get(attribute);
+ if (label != null) {
+ return label;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the dialog this tab is contained in, or <code>null</code> if not
+ * yet set.
*
* @return launch configuration dialog, or <code>null</code>
*/
@@ -513,5 +556,15 @@ public abstract class AbstractLaunchConfigurationTab implements ILaunchConfigura
public String getHelpContextId() {
return fHelpContextId;
}
+
+ /**
+ * Get the attributes labels for prototype tab.
+ *
+ * @return the attributes labels for prototype tab.
+ * @since 3.13
+ */
+ protected Map<String, String> getAttributesLabelsForPrototype() {
+ return fAttributesLabelsForPrototype;
+ }
}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/CommonTab.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/CommonTab.java
index 6ed04e176..1c482fd7e 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/CommonTab.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/CommonTab.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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,6 +8,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Wahlbrink <sw@wahlbrink.eu> - Bug 471829
+ * Axel Richard (Obeo) - Bug 41353 - Launch configurations prototypes
*******************************************************************************/
package org.eclipse.debug.ui;
@@ -160,6 +161,7 @@ public class CommonTab extends AbstractLaunchConfigurationTab {
* Constructs a new tab with default context help.
*/
public CommonTab() {
+ super();
setHelpContextId(IDebugHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_COMMON_TAB);
}
@@ -1101,6 +1103,24 @@ public class CommonTab extends AbstractLaunchConfigurationTab {
@Override
public void deactivated(ILaunchConfigurationWorkingCopy workingCopy) {}
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#initializeAttributes()
+ * @since 3.13
+ */
+ @Override
+ protected void initializeAttributes() {
+ super.initializeAttributes();
+ getAttributesLabelsForPrototype().put(DebugPlugin.ATTR_CONSOLE_ENCODING, LaunchConfigurationsMessages.CommonTab_AttributeLabel_ConsoleEncoding);
+ getAttributesLabelsForPrototype().put(DebugPlugin.ATTR_CAPTURE_OUTPUT, LaunchConfigurationsMessages.CommonTab_AttributeLabel_CaptureOutput);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, LaunchConfigurationsMessages.CommonTab_AttributeLabel_CaptureInConsole);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_CAPTURE_STDIN_FILE, LaunchConfigurationsMessages.CommonTab_AttributeLabel_CaptureStdInFile);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, LaunchConfigurationsMessages.CommonTab_AttributeLabel_CaptureInFile);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_APPEND_TO_FILE, LaunchConfigurationsMessages.CommonTab_AttributeLabel_AppendToFile);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, LaunchConfigurationsMessages.CommonTab_AttributeLabel_LaunchInBackground);
+ getAttributesLabelsForPrototype().put(IDebugUIConstants.ATTR_FAVORITE_GROUPS, LaunchConfigurationsMessages.CommonTab_AttributeLabel_FavoriteGroups);
+ }
+
/**
* Content provider for the favorites table
*/
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/EnvironmentTab.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/EnvironmentTab.java
index b307bcf90..b19a779ac 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/EnvironmentTab.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/EnvironmentTab.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2013 Keith Seitz and others.
+ * Copyright (c) 2000, 2017 Keith Seitz 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
@@ -9,6 +9,7 @@
* Keith Seitz (keiths@redhat.com) - initial implementation
* IBM Corporation - integration and code cleanup
* Jan Opacki (jan.opacki@gmail.com) bug 307139
+ * Axel Richard (Obeo) - Bug 41353 - Launch configurations prototypes
*******************************************************************************/
package org.eclipse.debug.ui;
@@ -191,6 +192,7 @@ public class EnvironmentTab extends AbstractLaunchConfigurationTab {
* Constructs a new tab with default context help.
*/
public EnvironmentTab() {
+ super();
setHelpContextId(IDebugHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_ENVIRONMENT_TAB);
}
@@ -603,6 +605,18 @@ public class EnvironmentTab extends AbstractLaunchConfigurationTab {
// do nothing when deactivated
}
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#initializeAttributes()
+ * @since 3.13
+ */
+ @Override
+ protected void initializeAttributes() {
+ super.initializeAttributes();
+ getAttributesLabelsForPrototype().put(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, LaunchConfigurationsMessages.EnvironmentTab_AttributeLabel_AppendEnvironmentVariables);
+ getAttributesLabelsForPrototype().put(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, LaunchConfigurationsMessages.EnvironmentTab_AttributeLabel_EnvironmentVariables);
+ }
+
/**
* This dialog allows users to select one or more known native environment variables from a list.
*/
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
index 04659d962..467afa298 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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
@@ -9,6 +9,7 @@
* IBM Corporation - initial API and implementation
* QNX Software Systems - Mikhail Khodjaiants - Registers View (Bug 53640)
* Wind River Systems - Pawel Piech - Added Modules view (bug 211158)
+ * Axel Richard (Obeo) - Bug 41353 - Launch configurations prototypes
*******************************************************************************/
package org.eclipse.debug.ui;
@@ -619,6 +620,13 @@ public interface IDebugUIConstants {
public static final String IMG_OVR_SKIP_BREAKPOINT = "IMG_OVR_SKIP_BREAKPOINT"; //$NON-NLS-1$
/**
+ * Prototype image overlay identifier.
+ *
+ * @since 3.13
+ */
+ public static final String IMG_OVR_PROTOTYPE = "IMG_OVR_PROTOTYPE"; //$NON-NLS-1$
+
+ /**
* Debug action set identifier (value <code>"org.eclipse.debug.ui.debugActionSet"</code>).
*/
public static final String DEBUG_ACTION_SET= PLUGIN_ID + ".debugActionSet"; //$NON-NLS-1$
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/PrototypeDecorator.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/PrototypeDecorator.java
new file mode 100644
index 000000000..e39b2dc2f
--- /dev/null
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/PrototypeDecorator.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.ui;
+
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.internal.core.LaunchConfiguration;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+
+/**
+ * Decorator for {@link ILaunchConfiguration} prototypes.
+ *
+ * @since 3.13
+ *
+ */
+public class PrototypeDecorator implements ILightweightLabelDecorator {
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ @Override
+ public void decorate(Object element, IDecoration decoration) {
+ if (element instanceof LaunchConfiguration) {
+ if (((LaunchConfiguration) element).isPrototype()) {
+ decoration.addOverlay(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OVR_PROTOTYPE));
+ }
+ }
+ }
+}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/ProtoypeTab.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/ProtoypeTab.java
new file mode 100644
index 000000000..84d243876
--- /dev/null
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/ProtoypeTab.java
@@ -0,0 +1,760 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.ui;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationType;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.internal.core.LaunchConfiguration;
+import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.internal.ui.IDebugHelpContextIds;
+import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
+import org.eclipse.debug.internal.ui.SWTFactory;
+import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationsMessages;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.DecoratingLabelProvider;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ICheckStateProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+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.Menu;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+
+/**
+ * Launch configuration tab used to specify the prototype associated with a
+ * launch configuration, and also listed attributes from prototype shared with
+ * the launch configuration.
+ * <p>
+ * Clients may call {@link #setHelpContextId(String)} on this tab prior to
+ * control creation to alter the default context help associated with this tab.
+ * </p>
+ * <p>
+ * Clients may instantiate this class only if the associated launch
+ * configuration type allows prototypes.
+ *
+ * @see ILaunchConfigurationType#supportsPrototypes()
+ * </p>
+ *
+ * @since 3.13
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class ProtoypeTab extends AbstractLaunchConfigurationTab {
+
+ private static final String PROTOTYPE_TAB_ID = "org.eclipse.debug.ui.prototypeTab"; //$NON-NLS-1$
+
+ private static final String ATTRIBUTE = "Attribute"; //$NON-NLS-1$
+
+ private static final String MODIFIED = "Modified"; //$NON-NLS-1$
+
+ private static final String PROTOTYPE_VALUE = "Prototype Value"; //$NON-NLS-1$
+
+ private ILaunchConfiguration fSelectedLaunchConfiguration;
+
+ private ILaunchConfiguration fAppliedPrototype;
+
+ private Composite fPrototypeComposite;
+
+ private Text fPrototypeText;
+
+ private ControlDecoration fPrototypeTextDecoration;
+
+ private Button fLinkPrototypeButton;
+
+ private Button fUnlinkPrototypeButton;
+
+ private Button fResetPrototypeButton;
+
+ private CheckboxTreeViewer fAttributesTreeViewer;
+
+ /**
+ * Constructs a new tab with default context help.
+ */
+ public ProtoypeTab() {
+ setHelpContextId(IDebugHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_PROTOTYPE_TAB);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.
+ * swt.widgets.Composite)
+ */
+ @Override
+ public void createControl(Composite parent) {
+ Composite comp = SWTFactory.createComposite(parent, 1, 1, GridData.FILL_BOTH);
+ setControl(comp);
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), getHelpContextId());
+
+ createConfigPrototypeComponent(comp);
+ createPrototypeExplanationsComponent(comp);
+ createPrototypeAttributesTreeComponent(comp);
+ }
+
+ /**
+ * Creates the config prototype location component
+ *
+ * @param parent the parent composite to add this component to
+ */
+ private void createConfigPrototypeComponent(Composite parent) {
+ fPrototypeComposite = new Composite(parent, SWT.NONE);
+ fPrototypeComposite.setLayout(new GridLayout(5, false));
+ fPrototypeComposite.setFont(parent.getFont());
+ GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ fPrototypeComposite.setLayoutData(gd);
+ SWTFactory.createLabel(fPrototypeComposite, LaunchConfigurationsMessages.PrototypeTab_Label_2, 1);
+ fPrototypeText = SWTFactory.createSingleText(fPrototypeComposite, 1);
+ fPrototypeText.setEditable(false);
+ fPrototypeTextDecoration = new ControlDecoration(fPrototypeText, SWT.TOP | SWT.LEFT);
+ FieldDecoration errorDecoration = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
+ fPrototypeTextDecoration.setDescriptionText(LaunchConfigurationsMessages.PrototypeTab_Select_Prototype_Error_7);
+ fPrototypeTextDecoration.setImage(errorDecoration.getImage());
+ fLinkPrototypeButton = SWTFactory.createPushButton(fPrototypeComposite, LaunchConfigurationsMessages.PrototypeTab_Link_Button_Label_3, null);
+ fLinkPrototypeButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleLinkPrototypeButtonSelected();
+ }
+ });
+ fUnlinkPrototypeButton = SWTFactory.createPushButton(fPrototypeComposite, LaunchConfigurationsMessages.PrototypeTab_Unlink_Button_Label_4, null);
+ fUnlinkPrototypeButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleUnlinkPrototypeButtonSelected();
+ }
+ });
+ fResetPrototypeButton = SWTFactory.createPushButton(fPrototypeComposite, LaunchConfigurationsMessages.PrototypeTab_Reset_Button_Label_8, null);
+ fResetPrototypeButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent evt) {
+ handleResetPrototypeButtonSelected();
+ }
+ });
+ }
+
+ /**
+ * Creates the prototype explanations component
+ *
+ * @param parent the parent composite to add this component to
+ */
+ private void createPrototypeExplanationsComponent(Composite parent) {
+ SWTFactory.createLabel(parent, LaunchConfigurationsMessages.PrototypeTab_Explanation_Label_10, 1);
+ SWTFactory.createLabel(parent, LaunchConfigurationsMessages.PrototypeTab_Explanation_Label_11, 1);
+ }
+
+ /**
+ * Creates the prototype attributes tree component
+ *
+ * @param parent the parent composite to add this one to
+ */
+ private void createPrototypeAttributesTreeComponent(Composite parent) {
+ Composite attributesPrototypeTableComposite = new Composite(parent, SWT.NONE);
+ attributesPrototypeTableComposite.setLayout(new GridLayout(1, false));
+ attributesPrototypeTableComposite.setFont(parent.getFont());
+ GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ attributesPrototypeTableComposite.setLayoutData(gd);
+
+ fAttributesTreeViewer = createCheckboxTreeViewer(attributesPrototypeTableComposite);
+ }
+
+ /**
+ * Update prototype text widget decorator.
+ */
+ private void updateProductDecorator() {
+ if (fAppliedPrototype != null && !fAppliedPrototype.exists()) {
+ fPrototypeTextDecoration.show();
+ } else {
+ fPrototypeTextDecoration.hide();
+ }
+ }
+
+ /**
+ * Handles the config prototype apply button being selected
+ */
+ private void handleLinkPrototypeButtonSelected() {
+ String currentSelectedPrototypeString = fPrototypeText.getText();
+ DecoratingLabelProvider labelProvider = new DecoratingLabelProvider(DebugUITools.newDebugModelPresentation(), PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator());
+ ElementListSelectionDialog selectPrototypeDialog = new ElementListSelectionDialog(getShell(), labelProvider);
+ try {
+ ILaunchConfigurationType type = fSelectedLaunchConfiguration.getType();
+ ILaunchConfiguration[] prototypes = type.getPrototypes();
+ selectPrototypeDialog.setElements(prototypes);
+ selectPrototypeDialog.setMultipleSelection(false);
+ selectPrototypeDialog.setTitle(LaunchConfigurationsMessages.PrototypeTab_Select_Message_5);
+ selectPrototypeDialog.setEmptySelectionMessage(LaunchConfigurationsMessages.PrototypeTab_Select_Empty_Message_6);
+ selectPrototypeDialog.setInitialSelections(getPrototype(prototypes, currentSelectedPrototypeString));
+ int open = selectPrototypeDialog.open();
+ if (open == Window.OK) {
+ Object selectedPrototype = selectPrototypeDialog.getFirstResult();
+ if (selectedPrototype instanceof ILaunchConfiguration) {
+ fAppliedPrototype = (ILaunchConfiguration) selectedPrototype;
+ ILaunchConfigurationWorkingCopy workingCopy = getWorkingCopy();
+ workingCopy.setPrototype(fAppliedPrototype, true);
+ fPrototypeText.setText(((ILaunchConfiguration) selectedPrototype).getName());
+ fAttributesTreeViewer.setInput(fAppliedPrototype);
+ fUnlinkPrototypeButton.setEnabled(true);
+ fResetPrototypeButton.setEnabled(true);
+ updateColumnsWidth();
+ updateProductDecorator();
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ reinitTabs(workingCopy);
+ }
+ }
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+
+ /**
+ * Handles the config prototype unapply button being selected
+ */
+ private void handleUnlinkPrototypeButtonSelected() {
+ try {
+ fAppliedPrototype = null;
+ ILaunchConfigurationWorkingCopy workingCopy = getWorkingCopy();
+ workingCopy.setPrototype(null, false);
+ fPrototypeText.setText(""); //$NON-NLS-1$
+ fAttributesTreeViewer.setInput(null);
+ fUnlinkPrototypeButton.setEnabled(false);
+ fResetPrototypeButton.setEnabled(false);
+ updateProductDecorator();
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ reinitTabs(workingCopy);
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+
+ /**
+ * Handles the config prototype reset button being selected
+ */
+ private void handleResetPrototypeButtonSelected() {
+ try {
+ ILaunchConfigurationWorkingCopy workingCopy = getWorkingCopy();
+ workingCopy.setPrototype(fAppliedPrototype, true);
+ fAttributesTreeViewer.refresh();
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ reinitTabs(workingCopy);
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+
+ /**
+ * Get the working copy of the launch configuration associated with this
+ * tab.
+ *
+ * @return an {@link ILaunchConfigurationWorkingCopy}.
+ * @throws CoreException
+ */
+ private ILaunchConfigurationWorkingCopy getWorkingCopy() throws CoreException {
+ ILaunchConfigurationWorkingCopy workingCopy;
+ if (fSelectedLaunchConfiguration instanceof ILaunchConfigurationWorkingCopy) {
+ workingCopy = (ILaunchConfigurationWorkingCopy) fSelectedLaunchConfiguration;
+ } else {
+ workingCopy = fSelectedLaunchConfiguration.getWorkingCopy();
+ }
+ return workingCopy;
+ }
+
+ /**
+ * Reinit the tabs of the launch configuration dialog.
+ *
+ * @param launchConfiguration the {@link LaunchConfiguration}.
+ */
+ private void reinitTabs(ILaunchConfiguration launchConfiguration) {
+ ILaunchConfigurationTab[] tabs = getLaunchConfigurationDialog().getTabs();
+ for (ILaunchConfigurationTab configTab : tabs) {
+ configTab.initializeFrom(launchConfiguration);
+ }
+ }
+
+ /**
+ * Get the prototype with the given name in the given prototypes list.
+ *
+ * @param prototypes the prototypes list
+ * @param name the prototype to get
+ * @return the prototype with the given name in the given prototypes list.
+ */
+ private Object[] getPrototype(ILaunchConfiguration[] prototypes, String name) {
+ for (ILaunchConfiguration prototype : prototypes) {
+ if (name.equals(prototype.getName())) {
+ return new Object[] { prototype };
+ }
+ }
+ return new Object[0];
+ }
+
+ /**
+ * Creates the checkbox tree viewer that contains attributes.
+ *
+ * @param parent the parent composite to add this one to
+ * @return a {@link CheckboxTreeViewer}
+ */
+ private CheckboxTreeViewer createCheckboxTreeViewer(Composite parent) {
+ Tree tree = new Tree(parent, SWT.MULTI | SWT.CHECK | SWT.H_SCROLL | SWT.V_SCROLL);
+ GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ tree.setLayoutData(gd);
+ tree.setHeaderVisible(true);
+ tree.setLinesVisible(true);
+
+ CheckboxTreeViewer treeViewer = new CheckboxTreeViewer(tree);
+
+ treeViewer.setContentProvider(new PrototypeAttributesContentProvider());
+ treeViewer.setCheckStateProvider(new AttributesTreeCheckStateProvider());
+ treeViewer.addCheckStateListener(new AttributesTreeCheckStateListener());
+
+ MenuManager menuMgr = new MenuManager();
+ Menu contextMenu = menuMgr.createContextMenu(tree);
+ menuMgr.addMenuListener(new ResetMenuListener());
+ menuMgr.setRemoveAllWhenShown(true);
+ tree.setMenu(contextMenu);
+
+ return treeViewer;
+ }
+
+ /**
+ * Add columns to the given {@link CheckboxTreeViewer}.
+ *
+ * @param treeViewer the given {@link CheckboxTreeViewer}.
+ */
+ private void addColumnsToTreeViewer(CheckboxTreeViewer treeViewer) {
+ for (TreeColumn treeColumn : treeViewer.getTree().getColumns()) {
+ treeColumn.dispose();
+ }
+ TreeViewerColumn columnLabel = new TreeViewerColumn(treeViewer, SWT.LEFT, 0);
+ columnLabel.setLabelProvider(new ColumnAttributeLabelProvider());
+ columnLabel.getColumn().setText(ATTRIBUTE);
+ if (fSelectedLaunchConfiguration != null && !fSelectedLaunchConfiguration.isPrototype()) {
+ TreeViewerColumn columnModified = new TreeViewerColumn(treeViewer, SWT.CENTER, 1);
+ columnModified.setLabelProvider(new ColumnModifiedLabelProvider());
+ columnModified.getColumn().setText(MODIFIED);
+ }
+ TreeViewerColumn columnValue = new TreeViewerColumn(treeViewer, SWT.LEFT, fSelectedLaunchConfiguration != null && !fSelectedLaunchConfiguration.isPrototype() ? 2 : 1);
+ columnValue.setLabelProvider(new ColumnValueLabelProvider());
+ columnValue.getColumn().setText(PROTOTYPE_VALUE);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.
+ * debug.core.ILaunchConfiguration)
+ */
+ @Override
+ public void initializeFrom(ILaunchConfiguration configuration) {
+ initialize(configuration);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#dispose()
+ */
+ @Override
+ public void dispose() {
+ super.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#activated(org.eclipse.debug.
+ * core.ILaunchConfigurationWorkingCopy)
+ */
+ @Override
+ public void activated(ILaunchConfigurationWorkingCopy workingCopy) {
+ initialize(workingCopy);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#deactivated(org.eclipse.
+ * debug.core.ILaunchConfigurationWorkingCopy)
+ */
+ @Override
+ public void deactivated(ILaunchConfigurationWorkingCopy workingCopy) {
+ }
+
+ /**
+ * Initialize this tab with the given configuration.
+ *
+ * @param configuration the given configuration
+ * @see ProtoypeTab#activated(ILaunchConfigurationWorkingCopy)
+ * @see ProtoypeTab#initializeFrom(ILaunchConfiguration)
+ */
+ private void initialize(ILaunchConfiguration configuration) {
+ try {
+ if (configuration.isPrototype()) {
+ fPrototypeComposite.setVisible(false);
+ GridData gridData = (GridData) fPrototypeComposite.getLayoutData();
+ gridData.exclude = true;
+ fAppliedPrototype = configuration;
+ fSelectedLaunchConfiguration = configuration;
+ addColumnsToTreeViewer(fAttributesTreeViewer);
+ fAttributesTreeViewer.setInput(fAppliedPrototype);
+ fAttributesTreeViewer.getTree().setEnabled(true);
+ } else {
+ fPrototypeComposite.setVisible(true);
+ GridData gridData = (GridData) fPrototypeComposite.getLayoutData();
+ gridData.exclude = false;
+ fSelectedLaunchConfiguration = configuration;
+ fAppliedPrototype = configuration.getPrototype();
+ addColumnsToTreeViewer(fAttributesTreeViewer);
+ fAttributesTreeViewer.getTree().setEnabled(false);
+ if (fAppliedPrototype == null) {
+ fPrototypeText.setText(""); //$NON-NLS-1$
+ fUnlinkPrototypeButton.setEnabled(false);
+ fResetPrototypeButton.setEnabled(false);
+ fAttributesTreeViewer.setInput(null);
+ } else if (!fAppliedPrototype.exists()) {
+ fPrototypeText.setText(fAppliedPrototype.getName());
+ fUnlinkPrototypeButton.setEnabled(true);
+ fResetPrototypeButton.setEnabled(true);
+ fAttributesTreeViewer.setInput(null);
+ } else {
+ fPrototypeText.setText(fAppliedPrototype.getName());
+ fUnlinkPrototypeButton.setEnabled(true);
+ fResetPrototypeButton.setEnabled(true);
+ fAttributesTreeViewer.setInput(fAppliedPrototype);
+ }
+ }
+ updateProductDecorator();
+ updateColumnsWidth();
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+
+ /**
+ * Set columns tree widths dynamically
+ */
+ private void updateColumnsWidth() {
+ getShell().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ TreeColumn[] columns = fAttributesTreeViewer.getTree().getColumns();
+ for (TreeColumn treeColumn : columns) {
+ treeColumn.pack();
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#isValid(org.eclipse.debug.
+ * core.ILaunchConfiguration)
+ */
+ @Override
+ public boolean isValid(ILaunchConfiguration config) {
+ setMessage(null);
+ setErrorMessage(null);
+
+ if (fAppliedPrototype != null && !fAppliedPrototype.exists()) {
+ setErrorMessage(LaunchConfigurationsMessages.PrototypeTab_Select_Prototype_Error_7);
+ return false;
+ }
+ return !config.isPrototype();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.
+ * debug.core.ILaunchConfigurationWorkingCopy)
+ */
+ @Override
+ public void setDefaults(ILaunchConfigurationWorkingCopy config) {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.
+ * debug.core.ILaunchConfigurationWorkingCopy)
+ */
+ @Override
+ public void performApply(ILaunchConfigurationWorkingCopy configuration) {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
+ */
+ @Override
+ public String getName() {
+ return LaunchConfigurationsMessages.PrototypeTab_Prototype_1;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getImage()
+ */
+ @Override
+ public Image getImage() {
+ return DebugUITools.getImage(IInternalDebugUIConstants.IMG_OBJS_PROTO_TAB);
+ }
+
+ /**
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getId()
+ *
+ */
+ @Override
+ public String getId() {
+ return PROTOTYPE_TAB_ID;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.ILaunchConfigurationTab#canSave()
+ */
+ @Override
+ public boolean canSave() {
+ return true;
+ }
+
+ /**
+ * Check if an attribute from the selected launch configuration is different
+ * from its prototype.
+ *
+ * @param element the given attribute.
+ */
+ private boolean isAttributeModified(Entry<String, Object> element) {
+ try {
+ if (fSelectedLaunchConfiguration != null && !fSelectedLaunchConfiguration.isPrototype()) {
+ return fSelectedLaunchConfiguration.isAttributeModified(element.getKey());
+ }
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ return false;
+ }
+
+ /**
+ * Content provider for the prototype attributes table
+ */
+ private class PrototypeAttributesContentProvider implements ITreeContentProvider {
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof ILaunchConfiguration) {
+ try {
+ Map<String, Object> attributes = ((ILaunchConfiguration) inputElement).getAttributes();
+ return attributes.entrySet().toArray();
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+ return new Object[0];
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ return null;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ return false;
+ }
+ }
+
+ /**
+ * Label provider for the prototype attributes tree column "Edited"
+ */
+ private class ColumnModifiedLabelProvider extends ColumnLabelProvider {
+ @SuppressWarnings("unchecked")
+ @Override
+ public String getText(Object element) {
+ if (fSelectedLaunchConfiguration != null && !fSelectedLaunchConfiguration.isPrototype()) {
+ if (element instanceof Entry && isAttributeModified((Entry<String, Object>) element)) {
+ return "true"; //$NON-NLS-1$
+ }
+ }
+ return "false"; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Label provider for the prototype attributes tree column "Attribute"
+ */
+ @SuppressWarnings({ "unchecked" })
+ private class ColumnAttributeLabelProvider extends ColumnLabelProvider {
+ @Override
+ public String getText(Object element) {
+ String key = ((Entry<String, Object>) element).getKey();
+ ILaunchConfigurationTab[] tabs = getLaunchConfigurationDialog().getTabs();
+ for (ILaunchConfigurationTab tab : tabs) {
+ if (tab instanceof AbstractLaunchConfigurationTab) {
+ String attributeLabel = ((AbstractLaunchConfigurationTab) tab).getAttributeLabel(key);
+ if (attributeLabel != null) {
+ return attributeLabel;
+ }
+ }
+ }
+ return key;
+ }
+ }
+
+ /**
+ * Label provider for the prototype attributes tree column "Value"
+ */
+ @SuppressWarnings({ "unchecked" })
+ private class ColumnValueLabelProvider extends ColumnLabelProvider {
+ @Override
+ public String getText(Object element) {
+ Object value = ((Entry<String, Object>) element).getValue();
+ if (value instanceof Boolean) {
+ return ((Boolean) value).toString();
+ } else if (value instanceof String) {
+ return (String) value;
+ } else if (value instanceof Integer) {
+ return value.toString();
+ } else if (value instanceof List) {
+ return "[" + ((List<String>) value).stream().collect(Collectors.joining(", ")) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ } else if (value instanceof Set) {
+ return "[" + ((Set<String>) value).stream().collect(Collectors.joining(", ")) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ } else if (value instanceof Map) {
+ return "[" + ((Map<String, String>) value).entrySet().stream().map(i -> "[" + i.getKey() + ", " + i.getValue() + "]").collect(Collectors.joining(", ")) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
+ }
+ return super.getText(element);
+ }
+ }
+
+ /**
+ * Check state provider for attributes tree.
+ */
+ @SuppressWarnings("unchecked")
+ private class AttributesTreeCheckStateProvider implements ICheckStateProvider {
+ @Override
+ public boolean isGrayed(Object element) {
+ return false;
+ }
+
+ @Override
+ public boolean isChecked(Object element) {
+ try {
+ if (element instanceof Entry) {
+ return fAppliedPrototype.getPrototypeVisibleAttributes().contains(((Entry<String, Object>) element).getKey());
+ }
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Check state listener for attributes tree.
+ */
+ @SuppressWarnings("unchecked")
+ private class AttributesTreeCheckStateListener implements ICheckStateListener {
+ @Override
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ try {
+ Object data = event.getElement();
+ if (data instanceof Entry) {
+ fAppliedPrototype.setPrototypeAttributeVisibility(((Entry<String, Object>) data).getKey(), event.getChecked());
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ }
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+ }
+
+ /**
+ * Menu Listener for attributes table.
+ */
+ private class ResetMenuListener implements IMenuListener {
+
+ @Override
+ public void menuAboutToShow(IMenuManager manager) {
+ if (fSelectedLaunchConfiguration != null && !fSelectedLaunchConfiguration.isPrototype()) {
+ manager.add(new ResetAction());
+ }
+ }
+ }
+
+ /**
+ * Reset attribute menu action.
+ */
+ @SuppressWarnings("unchecked")
+ private class ResetAction extends Action {
+
+ @Override
+ public String getText() {
+ return LaunchConfigurationsMessages.PrototypeTab_Reset_Menu_Action_9;
+ }
+
+ @Override
+ public void run() {
+ IStructuredSelection selection = (IStructuredSelection) fAttributesTreeViewer.getSelection();
+ try {
+ ILaunchConfigurationWorkingCopy workingCopy = getWorkingCopy();
+ for (Object element : selection.toList()) {
+ if (element instanceof Entry) {
+ String key = ((Entry<String, Object>) element).getKey();
+ if (fAppliedPrototype.getPrototypeVisibleAttributes().contains(key)) {
+ Object prototypeValue = fAppliedPrototype.getAttributes().get(key);
+ workingCopy.setAttribute(key, prototypeValue);
+ }
+ }
+ }
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ reinitTabs(workingCopy);
+ } catch (CoreException e) {
+ DebugUIPlugin.log(e.getStatus());
+ }
+ }
+ }
+}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/sourcelookup/SourceLookupTab.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/sourcelookup/SourceLookupTab.java
index e908e70d0..173a13668 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/sourcelookup/SourceLookupTab.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/sourcelookup/SourceLookupTab.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * Copyright (c) 2003, 2017 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Axel Richard (Obeo) - Bug 41353 - Launch configurations prototypes
*******************************************************************************/
package org.eclipse.debug.ui.sourcelookup;
@@ -50,6 +51,7 @@ public class SourceLookupTab extends AbstractLaunchConfigurationTab {
* Constructs a new tab with default context help.
*/
public SourceLookupTab() {
+ super();
setHelpContextId(IDebugHelpContextIds.SOURCELOOKUP_TAB);
}
@@ -169,4 +171,16 @@ public class SourceLookupTab extends AbstractLaunchConfigurationTab {
}
return super.getMessage();
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#initializeAttributes()
+ * @since 3.13
+ */
+ @Override
+ protected void initializeAttributes() {
+ super.initializeAttributes();
+ getAttributesLabelsForPrototype().put(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, SourceLookupUIMessages.sourceTab_AttributeLabel_SourceLocatorMemento);
+ getAttributesLabelsForPrototype().put(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, SourceLookupUIMessages.sourceTab_AttributeLabel_SourceLocatorID);
+ }
}

Back to the top