| /******************************************************************************* |
| * Copyright (c) 2011, 2013 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.tcf.internal.debug.ui.launch; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeItem; |
| import org.eclipse.tcf.protocol.IChannel; |
| import org.eclipse.tcf.protocol.IChannel.IChannelListener; |
| import org.eclipse.tcf.protocol.IPeer; |
| import org.eclipse.tcf.protocol.IToken; |
| import org.eclipse.tcf.protocol.Protocol; |
| import org.eclipse.tcf.services.IFileSystem; |
| import org.eclipse.tcf.services.IFileSystem.DirEntry; |
| import org.eclipse.tcf.services.IFileSystem.FileSystemException; |
| import org.eclipse.tcf.services.IFileSystem.IFileHandle; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.PlatformUI; |
| |
| public class FileSystemBrowserControl { |
| |
| static class FileInfo { |
| String name; |
| String fullname; |
| boolean isDir; |
| FileInfo[] children; |
| Throwable children_error; |
| int index; |
| boolean children_pending; |
| FileInfo parent; |
| } |
| |
| private Tree fileTree; |
| private Display fDisplay; |
| private IPeer fPeer; |
| private final FileInfo fRootInfo = new FileInfo(); |
| private IChannel fChannel; |
| private IFileSystem fFileSystem; |
| private String fFileToSelect; |
| private LinkedList<String> fPathToSelect; |
| private FileInfo fLastSelectedFileInfo; |
| private final boolean fDirectoriesOnly; |
| |
| public FileSystemBrowserControl(Composite parent, boolean directoriesOnly) { |
| fDirectoriesOnly = directoriesOnly; |
| fDisplay = parent.getDisplay(); |
| parent.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| handleDispose(); |
| } |
| }); |
| createFileListArea(parent); |
| } |
| |
| public void setInput(IPeer peer) { |
| if (peer == fPeer) { |
| return; |
| } |
| if (fPeer != null) { |
| Protocol.invokeAndWait(new Runnable() { |
| public void run() { |
| disconnectPeer(); |
| } |
| }); |
| } |
| fileTree.setItemCount(0); |
| fRootInfo.children = null; |
| fPeer = peer; |
| if (fPeer != null) { |
| Protocol.invokeAndWait(new Runnable() { |
| public void run() { |
| connectPeer(); |
| } |
| }); |
| } |
| } |
| |
| public Tree getTree() { |
| return fileTree; |
| } |
| |
| private void createFileListArea(Composite parent) { |
| Font font = parent.getFont(); |
| Composite composite = new Composite(parent, SWT.NONE); |
| composite.setFont(font); |
| GridLayout layout = new GridLayout(1, false); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| composite.setLayout(layout); |
| composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); |
| |
| fileTree = new Tree(composite, SWT.VIRTUAL | SWT.BORDER | SWT.SINGLE); |
| GridData gd = new GridData(GridData.FILL_BOTH); |
| gd.minimumHeight = 300; |
| gd.minimumWidth = 350; |
| fileTree.setLayoutData(gd); |
| fileTree.setFont(font); |
| fileTree.addListener(SWT.SetData, new Listener() { |
| public void handleEvent(Event event) { |
| TreeItem item = (TreeItem)event.item; |
| FileInfo info = findFileInfo(item); |
| if (info == null) { |
| updateItems(item.getParentItem(), false); |
| } |
| else { |
| fillItem(item, info); |
| } |
| } |
| }); |
| } |
| |
| private void handleDispose() { |
| Protocol.invokeAndWait(new Runnable() { |
| public void run() { |
| disconnectPeer(); |
| fFileSystem = null; |
| fDisplay = null; |
| } |
| }); |
| } |
| |
| protected void disconnectPeer() { |
| if (fChannel != null && fChannel.getState() != IChannel.STATE_CLOSED) { |
| fChannel.close(); |
| } |
| } |
| |
| protected void connectPeer() { |
| final IChannel channel = fPeer.openChannel(); |
| fChannel = channel; |
| fFileSystem = null; |
| channel.addChannelListener(new IChannelListener() { |
| public void congestionLevel(int level) { |
| } |
| public void onChannelClosed(final Throwable error) { |
| if (fChannel != channel) return; |
| fChannel = null; |
| if (fDisplay != null) { |
| fDisplay.asyncExec(new Runnable() { |
| public void run() { |
| if (fRootInfo.children_pending) return; |
| fRootInfo.children = null; |
| fRootInfo.children_error = error; |
| updateItems(fRootInfo); |
| } |
| }); |
| } |
| } |
| public void onChannelOpened() { |
| if (fChannel != channel) return; |
| fFileSystem = fChannel.getRemoteService(IFileSystem.class); |
| if (fFileSystem != null) { |
| if (fFileToSelect != null && fFileToSelect.length() > 0) { |
| final LinkedList<String> filePath = new LinkedList<String>(); |
| filePath.addAll(Arrays.asList(fFileToSelect.split("[/\\\\]", -1))); |
| if (fFileToSelect.charAt(0) == '/') { |
| filePath.set(0, "/"); |
| } |
| fPathToSelect = filePath; |
| fLastSelectedFileInfo = fRootInfo; |
| } |
| } |
| if (fDisplay != null) { |
| fDisplay.asyncExec(new Runnable() { |
| public void run() { |
| if (fRootInfo.children_pending) return; |
| fRootInfo.children = null; |
| fRootInfo.children_error = null; |
| updateItems(fRootInfo); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| |
| private void updateItems(TreeItem parent_item, boolean reload) { |
| final FileInfo parent_info = findFileInfo(parent_item); |
| if (parent_info == null) { |
| parent_item.setText("Invalid"); |
| } |
| else { |
| if (reload && parent_info.children_error != null) { |
| loadChildren(parent_info); |
| } |
| fDisplay.asyncExec(new Runnable() { |
| public void run() { |
| updateItems(parent_info); |
| } |
| }); |
| } |
| } |
| |
| private void updateItems(final FileInfo parent) { |
| if (fDisplay == null) return; |
| assert Thread.currentThread() == fDisplay.getThread(); |
| TreeItem[] items = null; |
| boolean expanded = true; |
| if (parent.children == null || parent.children_error != null) { |
| if (parent == fRootInfo) { |
| fileTree.setItemCount(1); |
| items = fileTree.getItems(); |
| } |
| else { |
| TreeItem item = findItem(parent); |
| if (item == null) return; |
| expanded = item.getExpanded(); |
| item.setItemCount(1); |
| items = item.getItems(); |
| } |
| assert items.length == 1; |
| items[0].removeAll(); |
| if (parent.children_pending) { |
| items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); |
| items[0].setText("Pending..."); |
| } |
| else if (parent.children_error != null) { |
| String msg = parent.children_error.getMessage(); |
| if (msg == null) msg = parent.children_error.getClass().getName(); |
| else msg = msg.replace('\n', ' '); |
| items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_RED)); |
| items[0].setText(msg); |
| items[0].setImage((Image) null); |
| } |
| else if (expanded) { |
| loadChildren(parent); |
| items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); |
| items[0].setText("Pending..."); |
| } |
| else { |
| items[0].setText(""); |
| } |
| } |
| else { |
| FileInfo[] arr = parent.children; |
| if (parent == fRootInfo) { |
| fileTree.setItemCount(arr.length); |
| items = fileTree.getItems(); |
| } |
| else { |
| TreeItem item = findItem(parent); |
| if (item == null) return; |
| expanded = item.getExpanded(); |
| item.setItemCount(expanded ? arr.length : 1); |
| items = item.getItems(); |
| } |
| if (expanded) { |
| assert items.length == arr.length; |
| for (int i = 0; i < items.length; i++) fillItem(items[i], arr[i]); |
| expandSelect(); |
| } |
| else { |
| items[0].setText(""); |
| } |
| } |
| } |
| |
| private void expandSelect() { |
| if (fPathToSelect == null) return; |
| if (fPathToSelect.isEmpty()) { |
| fPathToSelect = null; |
| fFileToSelect = null; |
| return; |
| } |
| do { |
| String name = fPathToSelect.getFirst(); |
| if (name.length() == 0) { |
| fPathToSelect.removeFirst(); |
| continue; |
| } |
| FileInfo info = findFileInfo(fLastSelectedFileInfo, name); |
| if (info == null) break; |
| TreeItem item = findItem(info); |
| if (item == null) break; |
| fPathToSelect.removeFirst(); |
| if (fPathToSelect.isEmpty()) { |
| fileTree.setSelection(item); |
| fileTree.showItem(item); |
| } else { |
| item.setExpanded(true); |
| fileTree.showItem(item); |
| } |
| fLastSelectedFileInfo = info; |
| } while (!fPathToSelect.isEmpty()); |
| } |
| |
| private void loadChildren(final FileInfo parent) { |
| assert Thread.currentThread() == fDisplay.getThread(); |
| if (parent.children_pending) return; |
| assert parent.children == null; |
| parent.children_pending = true; |
| parent.children_error = null; |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| final IFileSystem fs = fFileSystem; |
| if (fs == null || !canHaveChildren(parent)) { |
| doneLoadChildren(parent, null, new FileInfo[0]); |
| return; |
| } |
| if (parent.fullname == null) { |
| fs.roots(new IFileSystem.DoneRoots() { |
| public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) { |
| if (error != null) { |
| doneLoadChildren(parent, error, null); |
| } else { |
| final List<FileInfo> fileInfos = new ArrayList<FileInfo>(entries.length); |
| for (DirEntry entry : entries) { |
| FileInfo info = new FileInfo(); |
| info.parent = parent; |
| String name = entry.filename; |
| int length = name.length(); |
| if (length > 1 && (name.endsWith("\\") || name.endsWith("/"))) { |
| name = name.substring(0, length - 1); |
| } |
| info.name = name; |
| info.fullname = entry.longname != null ? entry.longname : entry.filename; |
| info.isDir = entry.attrs != null ? entry.attrs.isDirectory() : false; |
| if (!fDirectoriesOnly || info.isDir) { |
| fileInfos.add(info); |
| } |
| } |
| doneLoadChildren(parent, null, fileInfos.toArray(new FileInfo[fileInfos.size()])); |
| } |
| } |
| }); |
| return; |
| } |
| fs.opendir(parent.fullname, new IFileSystem.DoneOpen() { |
| final List<FileInfo> fileInfos = new ArrayList<FileInfo>(); |
| public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) { |
| if (error != null) { |
| doneLoadChildren(parent, error, null); |
| return; |
| } |
| fs.readdir(handle, new IFileSystem.DoneReadDir() { |
| public void doneReadDir(IToken token, FileSystemException error, DirEntry[] entries, boolean eof) { |
| if (entries != null) { |
| for (DirEntry entry : entries) { |
| FileInfo info = new FileInfo(); |
| info.parent = parent; |
| info.name = entry.filename; |
| info.fullname = entry.longname != null ? entry.longname : (new Path(parent.fullname).append(info.name).toString()); |
| info.isDir = entry.attrs != null ? entry.attrs.isDirectory() : false; |
| if (!fDirectoriesOnly || info.isDir) { |
| fileInfos.add(info); |
| } |
| } |
| } |
| if (error != null || eof) { |
| fs.close(handle, new IFileSystem.DoneClose() { |
| public void doneClose(IToken token, FileSystemException error) { |
| // ignore error |
| } |
| }); |
| int size = fileInfos.size(); |
| if (size == 0 && error != null) { |
| doneLoadChildren(parent, error, null); |
| } else { |
| doneLoadChildren(parent, null, fileInfos.toArray(new FileInfo[size])); |
| } |
| } else { |
| fs.readdir(handle, this); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private void doneLoadChildren(final FileInfo parent, final Throwable error, final FileInfo[] children) { |
| assert Protocol.isDispatchThread(); |
| assert error == null || children == null; |
| if (fDisplay == null) return; |
| Arrays.sort(children, new Comparator<FileInfo>() { |
| public int compare(FileInfo o1, FileInfo o2) { |
| if (o1.isDir == o2.isDir) |
| return o1.name.compareTo(o2.name); |
| if (o1.isDir) return 1; |
| return -1; |
| }}); |
| int i = 0; |
| for (FileInfo fileInfo : children) { |
| fileInfo.index = i++; |
| } |
| fDisplay.asyncExec(new Runnable() { |
| public void run() { |
| assert parent.children_pending; |
| assert parent.children == null; |
| parent.children_pending = false; |
| parent.children = children; |
| parent.children_error = error; |
| updateItems(parent); |
| } |
| }); |
| } |
| |
| public FileInfo findFileInfo(TreeItem item) { |
| assert Thread.currentThread() == fDisplay.getThread(); |
| if (item == null) return fRootInfo ; |
| TreeItem parent = item.getParentItem(); |
| FileInfo info = findFileInfo(parent); |
| if (info == null) return null; |
| if (info.children == null) return null; |
| if (info.children_error != null) return null; |
| int i = parent == null ? fileTree.indexOf(item) : parent.indexOf(item); |
| if (i < 0 || i >= info.children.length) return null; |
| assert info.children[i].index == i; |
| return info.children[i]; |
| } |
| |
| private FileInfo findFileInfo(FileInfo parent, String name) { |
| assert Thread.currentThread() == fDisplay.getThread(); |
| if (name == null) return fRootInfo; |
| if (name.equals(parent.name)) return parent; |
| FileInfo[] childInfos = parent.children; |
| if (childInfos != null) { |
| for (FileInfo fileInfo : childInfos) { |
| FileInfo found = findFileInfo(fileInfo, name); |
| if (found != null) { |
| return found; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private TreeItem findItem(FileInfo info) { |
| if (info == null) return null; |
| assert info.parent != null; |
| if (info.parent == fRootInfo) { |
| int n = fileTree.getItemCount(); |
| if (info.index >= n) return null; |
| return fileTree.getItem(info.index); |
| } |
| TreeItem i = findItem(info.parent); |
| if (i == null) return null; |
| int n = i.getItemCount(); |
| if (info.index >= n) return null; |
| return i.getItem(info.index); |
| } |
| |
| private void fillItem(TreeItem item, FileInfo info) { |
| assert Thread.currentThread() == fDisplay.getThread(); |
| Object data = item.getData("TCFContextInfo"); |
| if (data != null && data != info) item.removeAll(); |
| item.setData("TCFContextInfo", info); |
| String text = info.name != null ? info.name : info.fullname; |
| item.setText(text); |
| item.setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); |
| item.setImage(getImage(info)); |
| if (!canHaveChildren(info)) item.setItemCount(0); |
| else if (info.children == null || info.children_error != null) item.setItemCount(1); |
| else item.setItemCount(info.children.length); |
| } |
| |
| private boolean canHaveChildren(FileInfo info) { |
| return info.isDir || info == fRootInfo; |
| } |
| |
| private Image getImage(FileInfo info) { |
| if (info.isDir) { |
| return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); |
| } else { |
| return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); |
| } |
| } |
| |
| public void setInitialSelection(final String filename) { |
| fPathToSelect = null; |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| fFileToSelect = filename; |
| } |
| }); |
| } |
| |
| public FileInfo getSelection() { |
| if (fileTree != null) { |
| TreeItem[] items = fileTree.getSelection(); |
| if (items.length > 0) { |
| FileInfo info = findFileInfo(items[0]); |
| return info; |
| } |
| } |
| return null; |
| } |
| |
| } |