/**
* Aptana Studio
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.syncing.ui.views;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.util.TransferDragSourceListener;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import com.aptana.core.CoreStrings;
import com.aptana.core.io.vfs.IExtendedFileStore;
import com.aptana.core.util.FileUtil;
import com.aptana.ide.core.io.IBaseRemoteConnectionPoint;
import com.aptana.ide.core.io.IConnectionPoint;
import com.aptana.ide.syncing.ui.SyncingUIPlugin;
import com.aptana.ide.ui.io.IOUIPlugin;
import com.aptana.ide.ui.io.Utils;
import com.aptana.ide.ui.io.actions.CopyFilesOperation;
import com.aptana.ide.ui.io.navigator.FileTreeContentProvider;
import com.aptana.ide.ui.io.navigator.FileTreeNameSorter;
import com.aptana.ide.ui.io.navigator.actions.FileSystemDeleteAction;
import com.aptana.ide.ui.io.navigator.actions.FileSystemRenameAction;
import com.aptana.ide.ui.io.navigator.actions.OpenFileAction;
import com.aptana.theme.IControlThemerFactory;
import com.aptana.theme.ThemePlugin;
import com.aptana.ui.util.SWTUtils;
import com.aptana.ui.util.UIUtils;
/**
* @author Michael Xia (mxia@aptana.com)
*/
public class ConnectionPointComposite implements SelectionListener, ISelectionChangedListener, IDoubleClickListener,
TransferDragSourceListener, DropTargetListener
{
public static interface Client
{
public void transfer(ConnectionPointComposite source);
}
private static final String[] COLUMN_NAMES = { Messages.ConnectionPointComposite_Column_Filename,
Messages.ConnectionPointComposite_Column_Size, Messages.ConnectionPointComposite_Column_LastModified };
private Composite fMain;
private Link fEndPointLink;
private ToolItem fRefreshItem;
private ToolItem fHomeItem;
private Link fPathLink;
private TreeViewer fTreeViewer;
private MenuItem fOpenItem;
private MenuItem fTransferItem;
private MenuItem fDeleteItem;
private MenuItem fRenameItem;
private MenuItem fRefreshMenuItem;
private MenuItem fPropertiesItem;
private String fName;
private IConnectionPoint fConnectionPoint;
private List<IAdaptable> fEndPointData;
private Client fClient;
public ConnectionPointComposite(Composite parent, String name, Client client)
{
fName = name;
fClient = client;
fEndPointData = new ArrayList<IAdaptable>();
fMain = createControl(parent);
getControlThemerFactory().apply(fTreeViewer);
}
protected IControlThemerFactory getControlThemerFactory()
{
return ThemePlugin.getDefault().getControlThemerFactory();
}
public Control getControl()
{
return fMain;
}
public IAdaptable getCurrentInput()
{
// the root input is always IAdaptable
return (IAdaptable) fTreeViewer.getInput();
}
public IAdaptable[] getSelectedElements()
{
ISelection selection = fTreeViewer.getSelection();
if (selection.isEmpty() || !(selection instanceof IStructuredSelection))
{
return new IAdaptable[0];
}
Object[] elements = ((IStructuredSelection) selection).toArray();
// the selection should be all IAdaptable objects, but just to make sure
List<IAdaptable> list = new ArrayList<IAdaptable>();
for (Object element : elements)
{
if (element instanceof IAdaptable)
{
list.add((IAdaptable) element);
}
}
return list.toArray(new IAdaptable[list.size()]);
}
public void setFocus()
{
fMain.setFocus();
}
public void setConnectionPoint(IConnectionPoint connection)
{
fConnectionPoint = connection;
fEndPointData.clear();
if (fConnectionPoint == null)
{
fEndPointLink.setText(""); //$NON-NLS-1$
}
else
{
String label = connection.getName();
String tooltip = label;
if (connection instanceof IBaseRemoteConnectionPoint)
{
IPath path = ((IBaseRemoteConnectionPoint) connection).getPath();
if (path.segmentCount() > 0)
{
tooltip = MessageFormat.format("{0} ({1})", new Object[] { connection.getName(), //$NON-NLS-1$
path.toPortableString() });
}
}
fEndPointLink.setText(MessageFormat.format("<a>{0}</a>", label)); //$NON-NLS-1$
fEndPointLink.setToolTipText(tooltip);
fEndPointData.add(fConnectionPoint);
}
setPath(""); //$NON-NLS-1$
fMain.layout(true, true);
fTreeViewer.setInput(connection);
}
/**
* Adds a focus listener to the tree control to allow users the ability to listen for focus events.
*
* @param listener
*/
public void addTreeFocusListener(FocusListener listener)
{
fTreeViewer.getControl().addFocusListener(listener);
}
public void refresh()
{
Object input = fTreeViewer.getInput();
IResource resource = null;
if (input instanceof IAdaptable)
{
resource = (IResource) ((IAdaptable) input).getAdapter(IResource.class);
}
if (resource != null)
{
try
{
resource.refreshLocal(IResource.DEPTH_INFINITE, null);
}
catch (CoreException e)
{
}
}
updateContent(fEndPointData.get(fEndPointData.size() - 1));
}
public void widgetDefaultSelected(SelectionEvent e)
{
}
public void widgetSelected(SelectionEvent e)
{
Object source = e.getSource();
if (source == fRefreshItem)
{
refresh();
}
else if (source == fHomeItem)
{
gotoHome();
}
else if (source == fOpenItem)
{
open(fTreeViewer.getSelection());
}
else if (source == fTransferItem)
{
if (fClient != null)
{
fClient.transfer(this);
}
}
else if (source == fDeleteItem)
{
delete(fTreeViewer.getSelection());
}
else if (source == fRenameItem)
{
rename();
}
else if (source == fRefreshMenuItem)
{
refresh(fTreeViewer.getSelection());
}
else if (source == fPropertiesItem)
{
openPropertyPage(fTreeViewer.getSelection());
}
else if (source == fPathLink)
{
// e.text has the index; needs to increment by 1 since 0 for
// fEndPointData is the root
updateContent(fEndPointData.get(Integer.parseInt(e.text) + 1));
}
else if (source == fEndPointLink)
{
gotoHome();
}
}
public void selectionChanged(SelectionChangedEvent event)
{
updateMenuStates();
}
public void doubleClick(DoubleClickEvent event)
{
if (fClient == null)
{
open(event.getSelection());
}
else
{
Object object = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (object instanceof IAdaptable)
{
IAdaptable adaptable = (IAdaptable) object;
if (Utils.isDirectory((IAdaptable) object))
{
// goes into the folder
updateContent(adaptable);
}
else
{
fClient.transfer(this);
}
}
}
}
public Transfer getTransfer()
{
return LocalSelectionTransfer.getTransfer();
}
public void dragFinished(DragSourceEvent event)
{
LocalSelectionTransfer.getTransfer().setSelection(null);
LocalSelectionTransfer.getTransfer().setSelectionSetTime(0);
}
public void dragSetData(DragSourceEvent event)
{
event.data = fTreeViewer.getSelection();
}
public void dragStart(DragSourceEvent event)
{
LocalSelectionTransfer.getTransfer().setSelection(fTreeViewer.getSelection());
LocalSelectionTransfer.getTransfer().setSelectionSetTime(event.time & 0xFFFFFFFFL);
}
public void dragEnter(DropTargetEvent event)
{
if (event.detail == DND.DROP_DEFAULT)
{
if ((event.operations & DND.DROP_COPY) == 0)
{
event.detail = DND.DROP_NONE;
}
else
{
event.detail = DND.DROP_COPY;
}
}
}
public void dragLeave(DropTargetEvent event)
{
}
public void dragOperationChanged(DropTargetEvent event)
{
}
public void dragOver(DropTargetEvent event)
{
}
public void drop(DropTargetEvent event)
{
IFileStore targetStore = null;
if (event.item == null)
{
targetStore = Utils.getFileStore((IAdaptable) fTreeViewer.getInput());
}
else
{
TreeItem target = (TreeItem) event.item;
targetStore = getFolderStore((IAdaptable) target.getData());
}
if (targetStore == null)
{
return;
}
if (event.data instanceof ITreeSelection)
{
ITreeSelection selection = (ITreeSelection) event.data;
TreePath[] paths = selection.getPaths();
if (paths.length > 0)
{
List<IAdaptable> elements = new ArrayList<IAdaptable>();
for (TreePath path : paths)
{
boolean alreadyIn = false;
for (TreePath path2 : paths)
{
if (!path.equals(path2) && path.startsWith(path2, null))
{
alreadyIn = true;
break;
}
}
if (!alreadyIn)
{
elements.add((IAdaptable) path.getLastSegment());
}
}
CopyFilesOperation operation = new CopyFilesOperation(getControl().getShell());
operation.copyFiles(elements.toArray(new IAdaptable[elements.size()]), targetStore,
new JobChangeAdapter()
{
@Override
public void done(IJobChangeEvent event)
{
IOUIPlugin.refreshNavigatorView(fTreeViewer.getInput());
UIUtils.getDisplay().asyncExec(new Runnable()
{
public void run()
{
refresh();
}
});
}
});
}
}
}
public void dropAccept(DropTargetEvent event)
{
}
protected Composite createControl(Composite parent)
{
Composite main = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 0;
main.setLayout(layout);
Composite top = createTopComposite(main);
top.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
Composite path = createPathComposite(main);
path.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
TreeViewer treeViewer = createTreeViewer(main);
treeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
treeViewer.getTree().addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
getControlThemerFactory().dispose(fTreeViewer);
}
});
return main;
}
private Composite createTopComposite(Composite parent)
{
Composite main = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(3, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
main.setLayout(layout);
Label label = new Label(main, SWT.NONE);
label.setText(fName + ":"); //$NON-NLS-1$
fEndPointLink = new Link(main, SWT.NONE);
fEndPointLink.addSelectionListener(this);
ToolBar toolbar = new ToolBar(main, SWT.FLAT);
fHomeItem = new ToolItem(toolbar, SWT.PUSH);
fHomeItem.setImage(SyncingUIPlugin.getImage("icons/full/obj16/home.png")); //$NON-NLS-1$
fHomeItem.setToolTipText(Messages.ConnectionPointComposite_TTP_Home);
fHomeItem.addSelectionListener(this);
return main;
}
private Composite createPathComposite(Composite parent)
{
Composite main = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
main.setLayout(layout);
fPathLink = new Link(main, SWT.NONE);
fPathLink.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// uses a bold font for path
final Font font = new Font(fPathLink.getDisplay(), SWTUtils.boldFont(fPathLink.getFont()));
fPathLink.setFont(font);
fPathLink.addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
font.dispose();
}
});
fPathLink.addSelectionListener(this);
ToolBar toolbar = new ToolBar(main, SWT.FLAT);
fRefreshItem = new ToolItem(toolbar, SWT.PUSH);
fRefreshItem.setImage(SyncingUIPlugin.getImage("icons/full/obj16/refresh.gif")); //$NON-NLS-1$
fRefreshItem.setToolTipText(Messages.ConnectionPointComposite_TTP_Refresh);
fRefreshItem.addSelectionListener(this);
return main;
}
private TreeViewer createTreeViewer(Composite parent)
{
fTreeViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
Tree tree = fTreeViewer.getTree();
tree.setHeaderVisible(true);
TreeColumn column = new TreeColumn(tree, SWT.LEFT);
column.setWidth(300);
column.setText(COLUMN_NAMES[0]);
column = new TreeColumn(tree, SWT.LEFT);
column.setWidth(50);
column.setText(COLUMN_NAMES[1]);
column = new TreeColumn(tree, SWT.LEFT);
column.setWidth(150);
column.setText(COLUMN_NAMES[2]);
fTreeViewer.setContentProvider(new FileTreeContentProvider());
fTreeViewer.setLabelProvider(new ConnectionPointLabelProvider());
fTreeViewer.setComparator(new FileTreeNameSorter());
fTreeViewer.addSelectionChangedListener(this);
fTreeViewer.addDoubleClickListener(this);
fTreeViewer.addDragSupport(DND.DROP_COPY | DND.DROP_DEFAULT,
new Transfer[] { LocalSelectionTransfer.getTransfer() }, this);
fTreeViewer.addDropSupport(DND.DROP_COPY | DND.DROP_DEFAULT,
new Transfer[] { LocalSelectionTransfer.getTransfer() }, this);
// builds the context menu
tree.setMenu(createMenu(tree));
updateMenuStates();
return fTreeViewer;
}
private Menu createMenu(Control parent)
{
Menu menu = new Menu(parent);
fOpenItem = new MenuItem(menu, SWT.PUSH);
fOpenItem.setText(CoreStrings.OPEN);
fOpenItem.setAccelerator(SWT.F3);
fOpenItem.addSelectionListener(this);
fTransferItem = new MenuItem(menu, SWT.PUSH);
fTransferItem.setText(Messages.ConnectionPointComposite_LBL_Transfer);
fTransferItem.addSelectionListener(this);
new MenuItem(menu, SWT.SEPARATOR);
fDeleteItem = new MenuItem(menu, SWT.PUSH);
fDeleteItem.setText(CoreStrings.DELETE);
fDeleteItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ETOOL_DELETE));
fDeleteItem.setAccelerator(SWT.DEL);
fDeleteItem.addSelectionListener(this);
fRenameItem = new MenuItem(menu, SWT.PUSH);
fRenameItem.setText(CoreStrings.RENAME);
fRenameItem.setAccelerator(SWT.F2);
fRenameItem.addSelectionListener(this);
new MenuItem(menu, SWT.SEPARATOR);
fRefreshMenuItem = new MenuItem(menu, SWT.PUSH);
fRefreshMenuItem.setText(CoreStrings.REFRESH);
fRefreshMenuItem.setImage(SyncingUIPlugin.getImage("/icons/full/obj16/refresh.gif")); //$NON-NLS-1$
fRefreshMenuItem.setAccelerator(SWT.F5);
fRefreshMenuItem.addSelectionListener(this);
new MenuItem(menu, SWT.SEPARATOR);
fPropertiesItem = new MenuItem(menu, SWT.PUSH);
fPropertiesItem.setText(CoreStrings.PROPERTIES);
fPropertiesItem.setAccelerator(SWT.ALT | '\r');
fPropertiesItem.addSelectionListener(this);
return menu;
}
private void gotoHome()
{
updateContent(fConnectionPoint);
}
private void open(ISelection selection)
{
Object object = ((IStructuredSelection) selection).getFirstElement();
if (object instanceof IAdaptable)
{
IAdaptable adaptable = (IAdaptable) object;
if (Utils.isDirectory((IAdaptable) object))
{
// goes into the folder
updateContent(adaptable);
}
else
{
// opens the file in the editor
OpenFileAction action = new OpenFileAction();
action.updateSelection((IStructuredSelection) selection);
action.run();
}
}
}
private void delete(ISelection selection)
{
final FileSystemDeleteAction action = new FileSystemDeleteAction(getControl().getShell(), fTreeViewer.getTree());
action.updateSelection((IStructuredSelection) selection);
action.addJobListener(new JobChangeAdapter()
{
@Override
public void done(IJobChangeEvent event)
{
UIUtils.getDisplay().asyncExec(new Runnable()
{
public void run()
{
refresh();
}
});
action.removeJobListener(this);
}
});
action.run();
}
private void rename()
{
FileSystemRenameAction action = new FileSystemRenameAction(getControl().getShell(), fTreeViewer.getTree());
action.run();
refresh();
}
private void refresh(ISelection selection)
{
if (selection.isEmpty())
{
// refreshes the root
refresh();
}
else
{
Object[] elements = ((IStructuredSelection) selection).toArray();
IResource resource;
for (Object element : elements)
{
resource = null;
if (element instanceof IAdaptable)
{
resource = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
}
if (resource != null)
{
try
{
resource.refreshLocal(IResource.DEPTH_INFINITE, null);
}
catch (CoreException e)
{
}
}
fTreeViewer.refresh(element);
}
}
}
private void openPropertyPage(ISelection selection)
{
IAdaptable element = (IAdaptable) ((IStructuredSelection) selection).getFirstElement();
PreferenceDialog dialog = PreferencesUtil.createPropertyDialogOn(getControl().getShell(), element, null, null,
null);
dialog.open();
}
private void setComboData(IAdaptable data)
{
fEndPointData.clear();
if (data instanceof IContainer)
{
// a workspace project/folder
IContainer container = (IContainer) data;
IContainer root = (IContainer) fConnectionPoint.getAdapter(IResource.class);
String path = getRelativePath(root, container);
if (path != null)
{
String[] segments = (new Path(path)).segments();
IContainer segmentPath = root;
for (String segment : segments)
{
segmentPath = (IContainer) segmentPath.findMember(segment);
fEndPointData.add(segmentPath);
}
}
}
else
{
// a filesystem or remote path
IFileStore fileStore = Utils.getFileStore(data);
if (fileStore != null)
{
IFileStore homeFileStore = Utils.getFileStore(fConnectionPoint);
while (fileStore.getParent() != null && !fileStore.equals(homeFileStore))
{
fEndPointData.add(0, fileStore);
fileStore = fileStore.getParent();
}
}
}
fEndPointData.add(0, fConnectionPoint);
}
private void setPath(String path)
{
StringBuilder linkPath = new StringBuilder();
path = path.replace('\\', '/');
String separator = "/"; //$NON-NLS-1$
if (path.startsWith(separator))
{
// removes the leading separator
path = path.substring(1);
}
String displayedPath = FileUtil.compressLeadingPath(path, 60);
if (displayedPath.equals(path))
{
String[] folders = path.split(separator);
int i;
for (i = 0; i < folders.length - 1; ++i)
{
linkPath.append(MessageFormat.format("<a href=\"{0}\">{1}</a>", i, folders[i])); //$NON-NLS-1$
linkPath.append(separator);
}
if (folders.length > 0)
{
// no need for a link on the last directory since we are in it
linkPath.append(folders[i]);
}
}
else
{
// deals with the compression
linkPath.append("...").append(separator); //$NON-NLS-1$
// strips out the leading '.../'
String endPath = displayedPath.substring(4);
String[] endFolders = endPath.split(separator);
int startIndex = path.split(separator).length - endFolders.length;
int i;
for (i = 0; i < endFolders.length - 1; ++i)
{
linkPath.append(MessageFormat.format("<a href=\"{0}\">{1}</a>", startIndex + i, endFolders[i])); //$NON-NLS-1$
linkPath.append(separator);
}
if (endFolders.length > 0)
{
// no need for a link on the last directory since we are in it
linkPath.append(endFolders[i]);
}
}
fPathLink.setText(Messages.ConnectionPointComposite_LBL_Path + linkPath.toString());
}
private void updateContent(IAdaptable rootElement)
{
setComboData(rootElement);
if (rootElement instanceof IContainer)
{
setPath(getRelativePath((IContainer) fConnectionPoint.getAdapter(IResource.class), (IContainer) rootElement));
}
else
{
IFileStore fileStore = Utils.getFileStore(rootElement);
if (fileStore != null)
{
String path = fileStore.toString();
IFileStore homeFileStore = Utils.getFileStore(fConnectionPoint);
if (homeFileStore != null)
{
String homePath = homeFileStore.toString();
int index = path.indexOf(homePath);
if (index > -1)
{
path = path.substring(index + homePath.length());
}
}
setPath(path);
}
}
fTreeViewer.setInput(rootElement);
}
private void updateMenuStates()
{
ISelection selection = fTreeViewer.getSelection();
boolean hasSelection = !selection.isEmpty() && (selection instanceof IStructuredSelection);
boolean singleSelection = hasSelection && ((IStructuredSelection) selection).size() == 1;
fOpenItem.setEnabled(hasSelection);
fTransferItem.setEnabled(hasSelection);
fDeleteItem.setEnabled(hasSelection);
fRenameItem.setEnabled(hasSelection && singleSelection);
fPropertiesItem.setEnabled(hasSelection && singleSelection);
}
private static IFileStore getFolderStore(IAdaptable destination)
{
IFileStore store = Utils.getFileStore(destination);
IFileInfo info = Utils.getFileInfo(destination, IExtendedFileStore.EXISTENCE);
if (store != null && info != null && !info.isDirectory())
{
store = store.getParent();
}
return store;
}
/**
* @param root
* the root container
* @param element
* a container under the root
* @return the relative path string of the element from the root
*/
private static String getRelativePath(IContainer root, IContainer element)
{
String rootPath = root.getFullPath().toString();
String elementPath = element.getFullPath().toString();
int index = elementPath.indexOf(rootPath);
if (index == -1)
{
return null;
}
return elementPath.substring(index + rootPath.length());
}
}