/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2006 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** 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.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.ui.internal.views.explorer;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.TransferData;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IFolderChild;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IFolderDAO;
import org.rssowl.core.util.LoggingSafeRunnable;
import org.rssowl.core.util.RegExUtils;
import org.rssowl.core.util.ReparentInfo;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.actions.NewBookMarkAction;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.ModelUtils;
import java.util.ArrayList;
import java.util.List;
/**
* The <code>BookMarkDropImpl</code> is handling all drop-operations resulting
* from a DND.
*
* @author bpasero
*/
public class BookMarkDNDImpl extends ViewerDropAdapter implements DragSourceListener {
private final BookMarkExplorer fExplorer;
private final IFolderDAO fFolderDAO;
/**
* @param explorer
* @param viewer
*/
protected BookMarkDNDImpl(BookMarkExplorer explorer, Viewer viewer) {
super(viewer);
fExplorer = explorer;
fFolderDAO = DynamicDAO.getDAO(IFolderDAO.class);
}
/*
* @see org.eclipse.swt.dnd.DragSourceListener#dragStart(org.eclipse.swt.dnd.DragSourceEvent)
*/
public void dragStart(final DragSourceEvent event) {
/* Set normalized selection into Transfer if not in grouping mode */
if (!fExplorer.isGroupingEnabled()) {
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
LocalSelectionTransfer.getTransfer().setSelection(getNormalizedSelection());
LocalSelectionTransfer.getTransfer().setSelectionSetTime(event.time & 0xFFFFFFFFL);
event.doit = true;
}
});
}
}
private ISelection getNormalizedSelection() {
IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection();
List<?> selectedObjects = selection.toList();
/* Retrieve dragged Marks / Folders and separate */
List<IEntity> draggedEntities = new ArrayList<IEntity>(selectedObjects.size());
List<IFolder> draggedFolders = new ArrayList<IFolder>(selectedObjects.size());
for (Object object : selectedObjects) {
/* Dragged Mark */
if (object instanceof IMark)
draggedEntities.add((IMark) object);
/* Dragged Folder */
else if (object instanceof IFolder) {
draggedEntities.add((IFolder) object);
draggedFolders.add((IFolder) object);
}
}
/* Normalize the dragged entities */
for (IFolder folder : draggedFolders)
ModelUtils.normalize(folder, draggedEntities);
return new StructuredSelection(draggedEntities);
}
/*
* @see org.eclipse.swt.dnd.DragSourceListener#dragSetData(org.eclipse.swt.dnd.DragSourceEvent)
*/
public void dragSetData(final DragSourceEvent event) {
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
/* Set Selection using LocalSelectionTransfer */
if (LocalSelectionTransfer.getTransfer().isSupportedType(event.dataType))
event.data = LocalSelectionTransfer.getTransfer().getSelection();
/* Set Text using TextTransfer */
else if (TextTransfer.getInstance().isSupportedType(event.dataType))
setTextData(event);
}
});
}
private void setTextData(DragSourceEvent event) {
StringBuilder str = new StringBuilder("");
IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection();
List<?> selectedObjects = selection.toList();
for (Object selectedObject : selectedObjects) {
/* IFolder */
if (selectedObject instanceof IFolder) {
IFolder folder = (IFolder) selectedObject;
str.append(folder.getName()).append("\n");
}
/* IBookMark */
else if (selectedObject instanceof IBookMark) {
IBookMark bookmark = (IBookMark) selectedObject;
str.append(bookmark.getFeedLinkReference().getLink()).append("\n");
str.append(bookmark.getName()).append("\n\n");
}
/* ISearchMark */
else if (selectedObject instanceof ISearchMark) {
ISearchMark searchmark = (ISearchMark) selectedObject;
str.append(searchmark.getName()).append("\n");
}
/* Entity Group */
else if (selectedObject instanceof EntityGroup) {
EntityGroup entitygroup = (EntityGroup) selectedObject;
str.append(entitygroup.getName()).append("\n");
}
}
event.data = str.toString();
}
/*
* @see org.eclipse.swt.dnd.DragSourceListener#dragFinished(org.eclipse.swt.dnd.DragSourceEvent)
*/
public void dragFinished(DragSourceEvent event) {
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
LocalSelectionTransfer.getTransfer().setSelection(null);
LocalSelectionTransfer.getTransfer().setSelectionSetTime(0);
}
});
}
/*
* @see org.eclipse.jface.viewers.ViewerDropAdapter#dragOver(org.eclipse.swt.dnd.DropTargetEvent)
*/
@Override
public void dragOver(DropTargetEvent event) {
super.dragOver(event);
/* Un-Set some feedback if sorting by name */
if (fExplorer.isSortByNameEnabled()) {
event.feedback &= ~DND.FEEDBACK_INSERT_AFTER;
event.feedback &= ~DND.FEEDBACK_INSERT_BEFORE;
}
/* Un-Set some feedback if grouping */
if (fExplorer.isGroupingEnabled()) {
event.feedback &= ~DND.FEEDBACK_INSERT_AFTER;
event.feedback &= ~DND.FEEDBACK_INSERT_BEFORE;
event.feedback &= ~DND.FEEDBACK_SELECT;
}
/* Never give Select as Feedback from a Mark */
if (getCurrentTarget() instanceof IMark)
event.feedback &= ~DND.FEEDBACK_SELECT;
/* Unset some feedback when Text-Transfer is used */
if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
event.feedback &= ~DND.FEEDBACK_INSERT_AFTER;
event.feedback &= ~DND.FEEDBACK_INSERT_BEFORE;
}
}
/*
* @see org.eclipse.jface.viewers.ViewerDropAdapter#validateDrop(java.lang.Object,
* int, org.eclipse.swt.dnd.TransferData)
*/
@Override
public boolean validateDrop(final Object target, int operation, TransferData transferType) {
/* Grouping does not allow Drag and Drop */
if (fExplorer.isGroupingEnabled())
return false;
/* Require Entity as Target */
if (!(target instanceof IEntity))
return false;
/* Selection Transfer */
if (LocalSelectionTransfer.getTransfer().isSupportedType(transferType)) {
final boolean[] result = new boolean[] { false };
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
ISelection selection = LocalSelectionTransfer.getTransfer().getSelection();
if (selection instanceof IStructuredSelection) {
List<?> draggedObjects = ((IStructuredSelection) selection).toList();
result[0] = isValidDrop((IEntity) target, draggedObjects);
}
}
});
return result[0];
}
/* Text-Transfer (supported for all) */
else if (TextTransfer.getInstance().isSupportedType(transferType)) {
return true;
}
return false;
}
private boolean isValidDrop(IEntity dropTarget, List<?> draggedObjects) {
/* Check validity for each dragged Object */
for (Object draggedObject : draggedObjects) {
/* Shared rule: Do not allow to drop on same Entity */
if (draggedObject.equals(dropTarget))
return false;
/* Dragged Folder */
if (draggedObject instanceof IFolder) {
IFolder draggedFolder = (IFolder) draggedObject;
if (!isValidDrop(draggedFolder, dropTarget))
return false;
}
/* Dragged Mark */
else if (draggedObject instanceof IMark) {
IMark draggedMark = (IMark) draggedObject;
if (!isValidDrop(draggedMark, dropTarget))
return false;
}
}
return true;
}
private boolean isValidDrop(IFolder dragSource, IEntity dropTarget) {
int loc = getCurrentLocation();
/* Do not allow dropping on same Parent */
if (loc == LOCATION_ON && dragSource.getParent().equals(dropTarget))
return false;
/* Do not allow Re-Ordering of Entities if sort by name */
if (fExplorer.isSortByNameEnabled() && (loc == LOCATION_AFTER || loc == LOCATION_BEFORE)) {
if (dropTarget instanceof IFolder) {
IFolder target = (IFolder) dropTarget;
if (target.getParent().getChildren().contains(dragSource))
return false;
}
}
/* Do not allow Re-Ordering over IMarks (when sorting or grouping) */
if ((fExplorer.isSortByNameEnabled() || fExplorer.isGroupingEnabled()) && dropTarget instanceof IMark) {
IMark target = (IMark) dropTarget;
if (target.getParent().getChildren().contains(dragSource))
return false;
}
/* Do not allow dropping in Child of Drag-Folder */
if (ModelUtils.hasChildRelation(dragSource, dropTarget))
return false;
return true;
}
private boolean isValidDrop(IMark dragSource, IEntity dropTarget) {
int loc = getCurrentLocation();
/* Do not allow dropping on same Parent */
if (loc == LOCATION_ON && dragSource.getParent().equals(dropTarget))
return false;
/* Do not allow Re-Ordering of Entities if sort by name */
if (fExplorer.isSortByNameEnabled()) {
if (dropTarget instanceof IMark) {
IMark target = (IMark) dropTarget;
if (target.getParent().getChildren().contains(dragSource))
return false;
}
}
/* Do not allow Re-Ordering over IFolder (when sorting or grouping) */
if ((fExplorer.isSortByNameEnabled() || fExplorer.isGroupingEnabled()) && dropTarget instanceof IFolder && (loc == LOCATION_AFTER || loc == LOCATION_BEFORE)) {
IFolder target = (IFolder) dropTarget;
if (target.getParent().getChildren().contains(dragSource))
return false;
}
return true;
}
/*
* @see org.eclipse.jface.viewers.ViewerDropAdapter#performDrop(java.lang.Object)
*/
@Override
public boolean performDrop(final Object data) {
/* Selection-Transfer */
if (data instanceof IStructuredSelection) {
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
IStructuredSelection selection = (IStructuredSelection) data;
List<?> draggedObjects = selection.toList();
perfromDrop(draggedObjects);
}
});
return true;
}
/* Text-Transfer (check for URLs) */
else if (data instanceof String) {
SafeRunner.run(new LoggingSafeRunnable() {
public void run() throws Exception {
final List<String> urls = RegExUtils.extractLinksFromText((String) data, false);
if (urls.size() > 0) {
/* Determine parent folder */
final IFolder parent;
Object dropTarget = getCurrentTarget();
if (dropTarget instanceof IFolder)
parent = (IFolder) dropTarget;
else if (dropTarget instanceof IMark)
parent = ((IMark) dropTarget).getParent();
else
parent = null;
/* Determine Position */
final IMark position = (IMark) ((dropTarget instanceof IMark) ? dropTarget : null);
/* Open Dialog to add new BookMark (asyncly!) */
JobRunner.runInUIThread(getViewer().getControl(), new Runnable() {
public void run() {
new NewBookMarkAction(getViewer().getControl().getShell(), parent, position, urls.get(0)).run(null);
}
});
}
}
});
return true;
}
return false;
}
private void perfromDrop(List<?> draggedObjects) {
Object dropTarget = getCurrentTarget();
int location = getCurrentLocation();
IFolder parentFolder = null;
boolean requireSave = false;
boolean on = (location == ViewerDropAdapter.LOCATION_ON);
IFolderChild position = dropTarget != null ? (IFolderChild) dropTarget : null;
/* Fix invalid Position */
if (on && dropTarget instanceof IFolder)
position = null;
Boolean after = (location == ViewerDropAdapter.LOCATION_AFTER);
if (position == null)
after = null;
/* Target is a Folder */
if (dropTarget instanceof IFolder) {
IFolder dropFolder = (IFolder) dropTarget;
/* Target is the exact Folder */
if (on)
parentFolder = (IFolder) dropTarget;
/* Target is below or above of the Folder */
else
parentFolder = dropFolder.getParent();
}
/* Target is a Mark */
else if (dropTarget instanceof IMark) {
IMark dropMark = (IMark) dropTarget;
parentFolder = dropMark.getParent();
}
/* Require a Parent-Folder */
if (parentFolder == null)
return;
/* Separate into Reparented FolderChildren and Re-Orders */
List<IFolderChild> childReordering = null;
List<ReparentInfo<IFolderChild, IFolder>> reparenting = null;
/* For each dragged Object */
for (Object object : draggedObjects) {
/* Dragged Folder or Mark */
if (object instanceof IFolder || object instanceof IMark) {
IFolderChild draggedFolderChild = (IFolderChild) object;
/* Reparenting to new Parent */
if (!draggedFolderChild.getParent().equals(parentFolder)) {
if (reparenting == null)
reparenting = new ArrayList<ReparentInfo<IFolderChild, IFolder>>(draggedObjects.size());
ReparentInfo<IFolderChild, IFolder> reparentInfo = ReparentInfo.create(draggedFolderChild, parentFolder, position, after);
reparenting.add(reparentInfo);
}
/* Re-Ordering in same Parent */
else {
if (childReordering == null)
childReordering = new ArrayList<IFolderChild>(draggedObjects.size());
childReordering.add(draggedFolderChild);
}
}
}
/* Perform reparenting */
if (reparenting != null) {
final List<ReparentInfo<IFolderChild, IFolder>> finalReparenting = reparenting;
BusyIndicator.showWhile(getViewer().getControl().getDisplay(), new Runnable() {
public void run() {
fFolderDAO.reparent(finalReparenting);
}
});
}
/* Perform Re-Ordering on Children */
if (childReordering != null) {
parentFolder.reorderChildren(childReordering, (IFolderChild)dropTarget, after);
requireSave = true;
}
/* Save the Folder if required */
if (requireSave)
fFolderDAO.save(parentFolder);
}
}