/** * This file Copyright (c) 2005-2010 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * 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.ide.core.CoreStrings; import com.aptana.ide.core.FileUtils; import com.aptana.ide.core.StringUtils; import com.aptana.ide.core.io.IBaseRemoteConnectionPoint; import com.aptana.ide.core.io.IConnectionPoint; import com.aptana.ide.core.ui.CoreUIUtils; import com.aptana.ide.core.ui.SWTUtils; import com.aptana.ide.syncing.ui.SyncingUIPlugin; import com.aptana.ide.syncing.ui.internal.SyncUtils; import com.aptana.ide.ui.io.IOUIPlugin; 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; /** * @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); } 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 = StringUtils.format("{0} ({1})", new String[] { connection.getName(), //$NON-NLS-1$ path.toPortableString() }); } } fEndPointLink.setText(StringUtils.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; IFileInfo fileInfo = SyncUtils.getFileInfo((IAdaptable) object); if (fileInfo.isDirectory()) { // 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 = SyncUtils.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()); CoreUIUtils.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)); 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(125); 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)); 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; IFileInfo fileInfo = SyncUtils.getFileInfo((IAdaptable) object); if (fileInfo.isDirectory()) { // 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) { CoreUIUtils.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 = SyncUtils.getFileStore(data); if (fileStore != null) { IFileStore homeFileStore = SyncUtils.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 = FileUtils.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 = SyncUtils.getFileStore(rootElement); if (fileStore != null) { String path = fileStore.toString(); IFileStore homeFileStore = SyncUtils.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(); fOpenItem.setEnabled(hasSelection); fTransferItem.setEnabled(hasSelection); fDeleteItem.setEnabled(hasSelection); fRenameItem.setEnabled(hasSelection); fPropertiesItem.setEnabled(hasSelection); } private static IFileStore getFolderStore(IAdaptable destination) { IFileStore store = SyncUtils.getFileStore(destination); IFileInfo info = SyncUtils.getFileInfo(destination); 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()); } }