/******************************************************************************* * Copyright (c) 2011, 2017 The Eclipse Foundation 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: * The Eclipse Foundation - initial API and implementation * Ian Pun - reimplemented to work with Git Cloning DND using MarketplaceDropAdapter *******************************************************************************/ package org.eclipse.egit.ui.internal.clone; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.repository.tree.command.CloneCommand; import org.eclipse.jface.util.Util; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.dnd.URLTransfer; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IPageListener; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IPartService; import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IPerspectiveListener; import org.eclipse.ui.IStartup; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.UIJob; /** * Adapter to listen for any Drag and Drop operations that transfer a valid git * URL. If it goes through the URL parser correctly, a Clone Git Repo wizard * will appear and be populated. */ public class GitCloneDropAdapter implements IStartup { private static final int[] PREFERRED_DROP_OPERATIONS = { DND.DROP_DEFAULT, DND.DROP_COPY, DND.DROP_MOVE, DND.DROP_LINK }; private static final int DROP_OPERATIONS = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_DEFAULT; private final DropTargetAdapter dropListener = new GitDropTargetListener(); private final WorkbenchListener workbenchListener = new WorkbenchListener(); private Transfer[] transferAgents; @Override public void earlyStartup() { UIJob registerJob = new UIJob(Display.getDefault(), "Git Clone DND Initialization") { //$NON-NLS-1$ { setPriority(Job.SHORT); setSystem(true); } @Override public IStatus runInUIThread(IProgressMonitor monitor) { IWorkbench workbench = PlatformUI.getWorkbench(); workbench.addWindowListener(workbenchListener); IWorkbenchWindow[] workbenchWindows = workbench .getWorkbenchWindows(); for (IWorkbenchWindow window : workbenchWindows) { workbenchListener.hookWindow(window); } return Status.OK_STATUS; } }; registerJob.schedule(); } private void installDropTarget(final Shell shell) { hookUrlTransfer(shell, dropListener); } private DropTarget hookUrlTransfer(final Shell shell, DropTargetAdapter dropAdapter) { DropTarget target = findDropTarget(shell); if (target != null) { // target exists, get it and check proper registration registerWithExistingTarget(target); } else { target = new DropTarget(shell, DROP_OPERATIONS); if (transferAgents == null) { transferAgents = new Transfer[] { URLTransfer.getInstance() }; } target.setTransfer(transferAgents); } registerDropListener(target, dropAdapter); Control[] children = shell.getChildren(); for (Control child : children) { hookRecursive(child, dropAdapter); } return target; } private void registerDropListener(DropTarget target, DropTargetListener dropAdapter) { target.removeDropListener(dropAdapter); target.addDropListener(dropAdapter); } private void hookRecursive(Control child, DropTargetListener dropAdapter) { DropTarget childTarget = findDropTarget(child); if (childTarget != null) { registerWithExistingTarget(childTarget); registerDropListener(childTarget, dropAdapter); } if (child instanceof Composite) { Composite composite = (Composite) child; Control[] children = composite.getChildren(); for (Control control : children) { hookRecursive(control, dropAdapter); } } } private void registerWithExistingTarget(DropTarget target) { Transfer[] transfers = target.getTransfer(); if (transfers != null) { for (Transfer transfer : transfers) { if (transfer instanceof URLTransfer) { return; } } Transfer[] newTransfers = new Transfer[transfers.length + 1]; System.arraycopy(transfers, 0, newTransfers, 0, transfers.length); newTransfers[transfers.length] = URLTransfer.getInstance(); target.setTransfer(newTransfers); } } private DropTarget findDropTarget(Control control) { Object object = control.getData(DND.DROP_TARGET_KEY); if (object instanceof DropTarget) { return (DropTarget) object; } return null; } /** * @param url */ protected void proceedClone(String url) { CloneCommand command = new CloneCommand(url); try { command.execute(new ExecutionEvent()); } catch (ExecutionException e) { Activator.logError(e.getLocalizedMessage(), e); } } private class GitDropTargetListener extends DropTargetAdapter { @Override public void dragEnter(DropTargetEvent e) { updateDragDetails(e); } @Override public void dragOver(DropTargetEvent e) { updateDragDetails(e); } @Override public void dragLeave(DropTargetEvent e) { if (e.detail == DND.DROP_NONE) { setDropOperation(e); } } @Override public void dropAccept(DropTargetEvent e) { updateDragDetails(e); } @Override public void dragOperationChanged(DropTargetEvent e) { updateDragDetails(e); } private void setDropOperation(DropTargetEvent e) { int allowedOperations = e.operations; for (int op : PREFERRED_DROP_OPERATIONS) { if ((allowedOperations & op) != 0) { e.detail = op; return; } } e.detail = allowedOperations; } private void updateDragDetails(DropTargetEvent e) { if (dropTargetIsValid(e, false)) { setDropOperation(e); } } private boolean dropTargetIsValid(DropTargetEvent e, boolean isDrop) { if (URLTransfer.getInstance().isSupportedType(e.currentDataType)) { // on Windows, we get the URL already during drag operations... // FIXME find a way to check the URL early on other platforms, // too... if (isDrop || Util.isWindows()) { if (e.data == null && !extractEventData(e)) { // ... but if we don't, it's no problem, unless this is // already the final drop event return !isDrop; } final String url = getUrl(e.data); if (!GitUrlChecker.isValidGitUrl(url)) { return false; } } return true; } return false; } private boolean extractEventData(DropTargetEvent e) { TransferData transferData = e.currentDataType; if (transferData != null) { Object data = URLTransfer.getInstance() .nativeToJava(transferData); if (data != null && getUrl(data) != null) { e.data = data; return true; } } return false; } @Override public void drop(DropTargetEvent event) { if (!URLTransfer.getInstance() .isSupportedType(event.currentDataType)) { // ignore return; } if (event.data == null) { // reject event.detail = DND.DROP_NONE; return; } if (!dropTargetIsValid(event, true)) { // reject event.detail = DND.DROP_NONE; return; } final String url = getUrl(event.data); DropTarget source = (DropTarget) event.getSource(); Display display = source.getDisplay(); display.asyncExec(new Runnable() { @Override public void run() { proceedClone(url); } }); } private String getUrl(Object eventData) { if (!(eventData instanceof String)) { return null; } // Depending on the form the link and browser/os, // we get the url twice in the data separated by new lines String[] dataLines = ((String) eventData) .split(System.getProperty("line.separator")); //$NON-NLS-1$ String url = dataLines[0]; return url; } } private class WorkbenchListener implements IPartListener2, IPageListener, IPerspectiveListener, IWindowListener { @Override public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) { pageChanged(page); } @Override public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective, String changeId) { // Nothing to do } @Override public void pageActivated(IWorkbenchPage page) { pageChanged(page); } @Override public void pageClosed(IWorkbenchPage page) { // Nothing to do } @Override public void pageOpened(IWorkbenchPage page) { pageChanged(page); } private void pageChanged(IWorkbenchPage page) { if (page == null) { return; } IWorkbenchWindow workbenchWindow = page.getWorkbenchWindow(); windowChanged(workbenchWindow); } @Override public void windowActivated(IWorkbenchWindow window) { windowChanged(window); } private void windowChanged(IWorkbenchWindow window) { if (window == null) { return; } Shell shell = window.getShell(); runUpdate(shell); } @Override public void windowDeactivated(IWorkbenchWindow window) { // Nothing to do } @Override public void windowClosed(IWorkbenchWindow window) { // Nothing to do } @Override public void windowOpened(IWorkbenchWindow window) { hookWindow(window); } public void hookWindow(IWorkbenchWindow window) { if (window == null) { return; } window.addPageListener(this); window.addPerspectiveListener(this); IPartService partService = window.getService(IPartService.class); partService.addPartListener(this); windowChanged(window); } @Override public void partOpened(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partActivated(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partBroughtToTop(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partVisible(IWorkbenchPartReference partRef) { // Nothing to do } @Override public void partClosed(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partDeactivated(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partHidden(IWorkbenchPartReference partRef) { partUpdate(partRef); } @Override public void partInputChanged(IWorkbenchPartReference partRef) { // Nothing to do } private void partUpdate(IWorkbenchPartReference partRef) { if (partRef == null) { return; } IWorkbenchPage page = partRef.getPage(); pageChanged(page); } private void runUpdate(final Shell shell) { if (shell == null || shell.isDisposed()) { return; } Display display = shell.getDisplay(); if (display == null || display.isDisposed()) { return; } try { display.asyncExec(new Runnable() { @Override public void run() { if (!shell.isDisposed()) { installDropTarget(shell); } } }); } catch (RuntimeException ex) { // Swallow } } } }