Bug 530835: Preference page for URI Scheme handler

Link Handlers Preference page is used to choose handler instance of
eclipse for URI Schemes which are created in URI Scheme extensions


Change-Id: Iab91cd10451a6bc52aab8bcdf1a35fefba27b418
Signed-off-by: Arunkumar <arunkumar.s@sap.com>
Signed-off-by: Marcus Hoepfner <marcus.hoepfner@sap.com>
diff --git a/bundles/org.eclipse.ui.ide.application/plugin.properties b/bundles/org.eclipse.ui.ide.application/plugin.properties
index 0333c3b..2ae6ca9 100644
--- a/bundles/org.eclipse.ui.ide.application/plugin.properties
+++ b/bundles/org.eclipse.ui.ide.application/plugin.properties
@@ -17,6 +17,7 @@
 
 PreferencePages.Startup = Startup and Shutdown
 PreferencePages.Startup.Workspaces = Workspaces
+PreferencePages.General.LinkHandlers = Link Handlers
 
 Perspective.resourcePerspective = Resource
 Perspective.resourceDescription = This perspective is designed to provide general resource viewing and navigation. 
@@ -40,4 +41,7 @@
 command.zoomIn.mnemonic=I
 command.zoomOut.mnemonic=O
 
-keyword.problems = problems
\ No newline at end of file
+keyword.problems = problems
+keyword.linkhanders = link handlers schemes
+
+page.name = Link Handlers
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.ide.application/plugin.xml b/bundles/org.eclipse.ui.ide.application/plugin.xml
index 945d1e9..1fdda9b 100644
--- a/bundles/org.eclipse.ui.ide.application/plugin.xml
+++ b/bundles/org.eclipse.ui.ide.application/plugin.xml
@@ -27,6 +27,15 @@
             id="org.eclipse.ui.preferencePages.Startup.Workspaces">
          <keywordReference id="org.eclipse.ui.ide.recentWorkspaces"/>
       </page>
+     <page
+           category="org.eclipse.ui.preferencePages.Workbench"
+           class="org.eclipse.ui.internal.ide.application.dialogs.UriSchemeHandlerPreferencePage"
+           id="org.eclipse.ui.preferencePages.General.LinkHandlers"
+           name="%PreferencePages.General.LinkHandlers">
+        <keywordReference
+              id="org.eclipse.ui.ide.application.linkhandler">
+        </keywordReference>
+     </page>
     </extension>
      <extension
          point="org.eclipse.ui.perspectives">
@@ -168,5 +177,9 @@
             id="org.eclipse.ui.ide.application.problems"
             label="%keyword.problems">
       </keyword>
+      <keyword
+            id="org.eclipse.ui.ide.application.linkhandler"
+            label="%keyword.linkhanders">
+      </keyword>
    </extension>
 </plugin>
diff --git a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/dialogs/UriSchemeHandlerPreferencePage.java b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/dialogs/UriSchemeHandlerPreferencePage.java
new file mode 100644
index 0000000..8c8c59a
--- /dev/null
+++ b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/dialogs/UriSchemeHandlerPreferencePage.java
@@ -0,0 +1,309 @@
+/*******************************************************************************
+* Copyright (c) 2018 SAP SE 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:
+*     SAP SE - initial API and implementation
+*******************************************************************************/
+package org.eclipse.ui.internal.ide.application.dialogs;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
+import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
+import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.urischeme.IOperatingSystemRegistration;
+import org.eclipse.urischeme.ISchemeInformation;
+import org.eclipse.urischeme.IUriSchemeExtensionReader;
+import org.eclipse.urischeme.IUriSchemeExtensionReader.Scheme;
+
+/**
+ * This page contributes to URL handler for URISchemes in preference page of
+ * General section
+ *
+ */
+public class UriSchemeHandlerPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
+
+	private Label handlerLocation;
+	private TableViewer tableViewer;
+	private IOperatingSystemRegistration operatingSystemRegistration;
+	private Collection<UiSchemeInformation> schemeInformationList = null;
+	private String currentLocation = null;
+
+	@SuppressWarnings("javadoc")
+	public UriSchemeHandlerPreferencePage() {
+		super.setDescription(IDEWorkbenchMessages.UrlHandlerPreferencePage_Page_Description);
+	}
+
+	@Override
+	protected Control createContents(Composite parent) {
+		noDefaultAndApplyButton();
+		addFiller(parent, 2);
+		createTableViewerForSchemes(parent);
+		GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
+		handlerLocation = new Label(parent, SWT.FILL);
+		handlerLocation.setLayoutData(gridData);
+		return parent;
+	}
+
+	private void createTableViewerForSchemes(Composite parent) {
+		Composite editorComposite = new Composite(parent, SWT.NONE);
+		GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
+		gridData.horizontalSpan = 2;
+		gridData.horizontalIndent = 0;
+		editorComposite.setLayoutData(gridData);
+		Table schemeTable = new Table(editorComposite,
+				SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION | SWT.CHECK);
+		schemeTable.setHeaderVisible(true);
+		schemeTable.setLinesVisible(true);
+		schemeTable.setFont(parent.getFont());
+		// Selection listener for both check and selection
+		schemeTable.addListener(SWT.Selection, new TableSchemeSelectionListener());
+
+		// Table columns, Scheme name and Scheme Descriptions
+		TableColumnLayout tableColumnlayout = new TableColumnLayout();
+		editorComposite.setLayout(tableColumnlayout);
+
+		TableColumn nameColumn = new TableColumn(schemeTable, SWT.NONE, 0);
+		nameColumn.setText(IDEWorkbenchMessages.UrlHandlerPreferencePage_ColumnName_SchemeName);
+
+		TableColumn descriptionColumn = new TableColumn(schemeTable, SWT.NONE, 1);
+		descriptionColumn.setText(IDEWorkbenchMessages.UrlHandlerPreferencePage_ColumnName_SchemeDescription);
+
+		tableColumnlayout.setColumnData(nameColumn, new ColumnWeightData(20));
+		tableColumnlayout.setColumnData(descriptionColumn, new ColumnWeightData(80));
+
+		tableViewer = new TableViewer(schemeTable);
+		tableViewer.setContentProvider(ArrayContentProvider.getInstance());
+		tableViewer.setLabelProvider(new ItemLabelProvider());
+
+		// Gets the schemes from extension points for URI schemes
+		try {
+			schemeInformationList = retrieveSchemeInformationList();
+		} catch (Exception e) {
+			IStatus status = new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
+					IDEWorkbenchMessages.UrlHandlerPreferencePage_Error_Reading_Scheme, e);
+			StatusManager.getManager().handle(status, StatusManager.BLOCK | StatusManager.LOG);
+		}
+		tableViewer.setInput(schemeInformationList);
+		TableItem[] tableSchemes = tableViewer.getTable().getItems();
+		// Initialize the check state for schemes which are already registered to this
+		// scheme instance
+		if (tableSchemes == null) {
+			return;
+		}
+		for (TableItem tableScheme : tableSchemes) {
+			UiSchemeInformation schemeInformation = (UiSchemeInformation) (tableScheme.getData());
+			if (schemeInformation != null) {
+				tableScheme.setChecked(schemeInformation.checked);
+			}
+		}
+	}
+
+	private void addFiller(Composite composite, int horizontalSpan) {
+		PixelConverter pixelConverter = new PixelConverter(composite);
+		Label filler = new Label(composite, SWT.LEFT);
+		GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
+		gd.horizontalSpan = horizontalSpan;
+		gd.heightHint = pixelConverter.convertHeightInCharsToPixels(1) / 2;
+		filler.setLayoutData(gd);
+	}
+
+	/**
+	 * Schemes which are part of extension points for URI Schemes and are registered
+	 * to operating system are consolidated here
+	 *
+	 * @return the supported and registered URI schemes of this instance of eclipse
+	 * @throws Exception
+	 */
+	private Collection<UiSchemeInformation> retrieveSchemeInformationList() throws Exception {
+		Collection<UiSchemeInformation> returnList = new ArrayList<>();
+		Collection<Scheme> schemes = IUriSchemeExtensionReader.INSTANCE.getSchemes();
+		if (operatingSystemRegistration != null) {
+			for (ISchemeInformation info : operatingSystemRegistration.getSchemesInformation(schemes)) {
+				returnList.add(new UiSchemeInformation(info.isHandled(), info));
+			}
+		}
+		return returnList;
+	}
+
+	@Override
+	public void init(IWorkbench workbench) {
+		operatingSystemRegistration = IOperatingSystemRegistration.getInstance();
+		if (operatingSystemRegistration != null) {
+			currentLocation = operatingSystemRegistration.getEclipseLauncher();
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.eclipse.jface.preference.PreferencePage#performOk()
+	 */
+	@Override
+	public boolean performOk() {
+		if (operatingSystemRegistration == null) {
+			return true;
+		}
+
+		List<ISchemeInformation> toAdd = new ArrayList<>();
+		List<ISchemeInformation> toRemove = new ArrayList<>();
+		for (UiSchemeInformation info : schemeInformationList) {
+			if (info.checked && !info.information.isHandled()) {
+				toAdd.add(info.information);
+			}
+			if (!info.checked && info.information.isHandled()) {
+				toRemove.add(info.information);
+			}
+		}
+
+		try {
+			operatingSystemRegistration.handleSchemes(toAdd, toRemove);
+		} catch (Exception e) {
+			IStatus status = new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
+					IDEWorkbenchMessages.UrlHandlerPreferencePage_Error_Writing_Scheme, e);
+			StatusManager.getManager().handle(status, StatusManager.BLOCK | StatusManager.LOG);
+		}
+		return true;
+	}
+
+	private class TableSchemeSelectionListener implements Listener {
+		@Override
+		public void handleEvent(Event event) {
+			if (event.detail == SWT.CHECK) {
+				handleCheckbox(event);
+			} else {
+				handleSelection();
+			}
+		}
+
+		private void handleSelection() {
+			IStructuredSelection selection = tableViewer.getStructuredSelection();
+			Object firstElement = selection != null ? selection.getFirstElement() : null;
+			if (firstElement != null && firstElement instanceof UiSchemeInformation) {
+				setSchemeDetails((UiSchemeInformation) firstElement);
+			}
+		}
+
+		private void setSchemeDetails(UiSchemeInformation schemeInfo) {
+			if (schemeInfo.checked) {
+				handlerLocation.setText(
+						NLS.bind(IDEWorkbenchMessages.UrlHandlerPreferencePage_Label_Message_Current_Application,
+								currentLocation));
+
+			} else if (schemeIsHandledByOther(schemeInfo.information)) {
+				handlerLocation
+						.setText(NLS.bind(IDEWorkbenchMessages.UrlHandlerPreferencePage_Label_Message_Other_Application,
+								schemeInfo.information.getHandlerInstanceLocation()));
+			} else {
+				// checkbox not checked and:
+				// - no other handler handles it
+				// - or this eclipse handled it before (checkbox was unchecked but not yet
+				// applied)
+				handlerLocation.setText(IDEWorkbenchMessages.UrlHandlerPreferencePage_Label_Message_No_Application);
+			}
+		}
+
+		private void handleCheckbox(Event event) {
+			TableItem tableItem = (TableItem) event.item;
+			if (tableItem != null && tableItem.getData() instanceof UiSchemeInformation) {
+				UiSchemeInformation schemeInformation = (UiSchemeInformation) tableItem.getData();
+
+				if (tableItem.getChecked() && schemeIsHandledByOther(schemeInformation.information)) {
+					// macOS: As lsregister always registers all plist files we cannot set a handler
+					// for a scheme that is already handled by another application.
+					// For Windows and Linux this might work - needs to be tested.
+					// As we think this is a edge case we start with a restrictive implementation.
+					event.doit = false;
+					tableItem.setChecked(false);
+					MessageDialog.openWarning(getShell(),
+							IDEWorkbenchMessages.UriHandlerPreferencePage_Warning_OtherApp,
+							NLS.bind(IDEWorkbenchMessages.UriHandlerPreferencePage_Warning_OtherApp_Description,
+									schemeInformation.information.getHandlerInstanceLocation(),
+									schemeInformation.information.getScheme()));
+					return;
+				}
+				schemeInformation.checked = tableItem.getChecked();
+				setSchemeDetails(schemeInformation);
+			}
+		}
+	}
+
+	private boolean schemeIsHandledByOther(ISchemeInformation info) {
+		boolean schemeIsNotHandled = !info.isHandled();
+		boolean handlerLocationIsSet = info.getHandlerInstanceLocation() != null
+				&& !info.getHandlerInstanceLocation().isEmpty();
+		return schemeIsNotHandled && handlerLocationIsSet;
+	}
+
+	private final class ItemLabelProvider extends LabelProvider implements ITableLabelProvider {
+
+		@Override
+		public Image getColumnImage(Object element, int columnIndex) {
+			return null;
+		}
+
+		@Override
+		public String getColumnText(Object element, int columnIndex) {
+			if (element instanceof UiSchemeInformation) {
+				UiSchemeInformation schemeInfo = (UiSchemeInformation) element;
+				switch (columnIndex) {
+				case 0:
+					return schemeInfo.information.getScheme();
+				case 1:
+					return schemeInfo.information.getDescription();
+				default:
+					throw new IllegalArgumentException("Unknown column"); //$NON-NLS-1$
+				}
+			}
+			return null; // cannot happen
+		}
+
+		@Override
+		public boolean isLabelProperty(Object element, String property) {
+			return false;
+		}
+	}
+
+	private final static class UiSchemeInformation {
+		public boolean checked;
+		public ISchemeInformation information;
+
+		public UiSchemeInformation(boolean checked, ISchemeInformation information) {
+			this.checked = checked;
+			this.information = information;
+		}
+
+	}
+}
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java
index 3626843..80b6d89 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java
@@ -525,6 +525,20 @@
 	public static String WorkbenchPreference_unsupportedEncoding;
 	public static String WorkbenchPreference_encoding_encodingMessage;
 
+	public static String UriHandlerPreferencePage_Warning_OtherApp;
+
+	public static String UriHandlerPreferencePage_Warning_OtherApp_Description;
+
+	// --- Link Handler Preference Page ---
+	public static String UrlHandlerPreferencePage_Label_Message_Current_Application;
+	public static String UrlHandlerPreferencePage_Label_Message_Other_Application;
+	public static String UrlHandlerPreferencePage_Label_Message_No_Application;
+	public static String UrlHandlerPreferencePage_Page_Description;
+	public static String UrlHandlerPreferencePage_ColumnName_SchemeName;
+	public static String UrlHandlerPreferencePage_ColumnName_SchemeDescription;
+	public static String UrlHandlerPreferencePage_Error_Reading_Scheme;
+	public static String UrlHandlerPreferencePage_Error_Writing_Scheme;
+
 	// ---workspace ---
 	public static String IDEWorkspacePreference_autobuild;
 	public static String IDEWorkspacePreference_autobuildToolTip;
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties
index e1dc5ce..e036d70 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties
@@ -504,6 +504,18 @@
 
 WorkbenchPreference_encoding_encodingMessage = Byte Order Mark is {0}
 
+# --- Link Handler Preference Page ---
+UriHandlerPreferencePage_Warning_OtherApp=Action not possible
+UriHandlerPreferencePage_Warning_OtherApp_Description=Another application ({0}) handles the "{1}" scheme.\n\nRemove the "{1}" scheme in the other application first.
+UrlHandlerPreferencePage_Label_Message_Current_Application=Handler: This application ({0})
+UrlHandlerPreferencePage_Label_Message_Other_Application=Handler: Other application ({0})
+UrlHandlerPreferencePage_Label_Message_No_Application=Handler: No application
+UrlHandlerPreferencePage_Page_Description=Link Handlers can handle links with a given scheme.\nThis page allows you to enable and disable link handlers in this application. 
+UrlHandlerPreferencePage_ColumnName_SchemeName=Scheme 
+UrlHandlerPreferencePage_ColumnName_SchemeDescription=Description 
+UrlHandlerPreferencePage_Error_Reading_Scheme=Error while reading Scheme information from Operating System 
+UrlHandlerPreferencePage_Error_Writing_Scheme=Error while writing Scheme information to Operating System 
+
 # ---Workspace
 IDEWorkspacePreference_autobuild = &Build automatically
 IDEWorkspacePreference_autobuildToolTip = Build automatically on resource modification
diff --git a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/IOperatingSystemRegistration.java b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/IOperatingSystemRegistration.java
index ac3f036..84dc609 100644
--- a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/IOperatingSystemRegistration.java
+++ b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/IOperatingSystemRegistration.java
@@ -18,6 +18,7 @@
 import org.eclipse.urischeme.internal.registration.RegistrationLinux;
 import org.eclipse.urischeme.internal.registration.RegistrationMacOsX;
 import org.eclipse.urischeme.internal.registration.RegistrationWindows;
+
 /**
  * Interface for registration or uri schemes in the different operating systems
  * (MacOSX, Linux and Windows)<br />
@@ -50,8 +51,7 @@
 	 * @param toRemove the uri schemes which this Eclipse should not handle anymore
 	 * @throws Exception something went wrong
 	 */
-	void handleSchemes(Collection<ISchemeInformation> toAdd, Collection<ISchemeInformation> toRemove)
-			throws Exception;
+	void handleSchemes(Collection<ISchemeInformation> toAdd, Collection<ISchemeInformation> toRemove) throws Exception;
 
 	/**
 	 * Takes the given schemes and fills information like whether they are
diff --git a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/ISchemeInformation.java b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/ISchemeInformation.java
index b4ac84c..e366541 100644
--- a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/ISchemeInformation.java
+++ b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/ISchemeInformation.java
@@ -35,6 +35,13 @@
 	boolean isHandled();
 
 	/**
+	 * Sets the handled value to true if scheme is handled by current Eclipse
+	 * installation and false otherwise
+	 * @param value
+	 */
+	void setHandled(boolean value);
+
+	/**
 	 * @return the path of the application
 	 */
 	String getHandlerInstanceLocation();
diff --git a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/internal/registration/SchemeInformation.java b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/internal/registration/SchemeInformation.java
index 37aae39..344909d 100644
--- a/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/internal/registration/SchemeInformation.java
+++ b/bundles/org.eclipse.urischeme/src/org/eclipse/urischeme/internal/registration/SchemeInformation.java
@@ -46,7 +46,7 @@
 		return handlerInstanceLocation;
 	}
 
-	@SuppressWarnings("javadoc")
+	@Override
 	public void setHandled(boolean handled) {
 		this.handled = handled;
 	}
@@ -60,5 +60,4 @@
 	public String getDescription() {
 		return schemeDescription;
 	}
-
-}
\ No newline at end of file
+}
diff --git a/examples/org.eclipse.ui.examples.uriSchemeHandler/plugin.xml b/examples/org.eclipse.ui.examples.uriSchemeHandler/plugin.xml
index e31a92f..37b3d90 100644
--- a/examples/org.eclipse.ui.examples.uriSchemeHandler/plugin.xml
+++ b/examples/org.eclipse.ui.examples.uriSchemeHandler/plugin.xml
@@ -65,6 +65,16 @@
             uriSchemeDescription="The Hello World demo protocol"
             uriScheme="hello">
       </uriSchemeHandler>
+      <uriSchemeHandler
+            class="org.eclipse.ui.examples.urischemehandler.uriHandlers.HelloSchemeHandler"
+            uriSchemeDescription="The Hello World demo protocol"
+            uriScheme="hello2">
+      </uriSchemeHandler>
+      <uriSchemeHandler
+            class="org.eclipse.ui.examples.urischemehandler.uriHandlers.HelloSchemeHandler"
+            uriSchemeDescription="The Hello World demo protocol"
+            uriScheme="hello3">
+      </uriSchemeHandler>
    </extension>
 
 </plugin>