/******************************************************************************* * Copyright (c) 2011 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 v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tm.internal.tcf.cdt.ui.launch; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.IDebugUIConstants; 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.Control; 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.tm.tcf.protocol.IChannel; import org.eclipse.tm.tcf.protocol.IChannel.IChannelListener; import org.eclipse.tm.tcf.protocol.IPeer; import org.eclipse.tm.tcf.protocol.IToken; import org.eclipse.tm.tcf.protocol.Protocol; import org.eclipse.tm.tcf.services.IProcesses; import org.eclipse.tm.tcf.services.IProcesses.ProcessContext; public class ProcessListControl { private final class ProcessListener implements IProcesses.ProcessesListener { public void exited(final String process_id, int exit_code) { if (display != null) { display.asyncExec(new Runnable() { public void run() { ProcessInfo info = findProcessInfo(root_info, process_id); if (info != null && info.parent != null && info.parent.children != null) { info.parent.children = null; loadChildren(info.parent); } } }); } } } static class ProcessInfo { String name; String id; boolean isContainer; ProcessInfo[] children; Throwable children_error; int index; boolean children_pending; ProcessInfo parent; protected boolean isAttached; } private Tree fProcessTree; private Display display; private IPeer fPeer; private final ProcessInfo root_info = new ProcessInfo(); private IChannel fChannel; private IProcesses fProcesses; protected final ProcessListener fProcessListener = new ProcessListener(); private String fContextToSelect; private LinkedList<String> fPathToSelect; private Composite fComposite; public ProcessListControl(Composite parent) { display = parent.getDisplay(); parent.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { handleDispose(); } }); createProcessListArea(parent); } public void setInput(IPeer peer) { if (peer == fPeer) { return; } if (fPeer != null) { Protocol.invokeAndWait(new Runnable() { public void run() { disconnectPeer(); } }); } fProcessTree.setItemCount(0); root_info.children = null; fPeer = peer; if (fPeer != null) { Protocol.invokeAndWait(new Runnable() { public void run() { connectPeer(); } }); } } public Control getControl() { return fComposite; } public Tree getTree() { return fProcessTree; } public ProcessInfo getSelection() { if (fProcessTree != null) { TreeItem[] items = fProcessTree.getSelection(); if (items.length > 0) { ProcessInfo info = findProcessInfo(items[0]); return info; } } return null; } private void createProcessListArea(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)); fComposite = composite; fProcessTree = new Tree(composite, SWT.VIRTUAL | SWT.BORDER | SWT.SINGLE); GridData gd = new GridData(GridData.FILL_BOTH); gd.minimumHeight = 150; gd.minimumWidth = 470; fProcessTree.setLayoutData(gd); fProcessTree.setFont(font); fProcessTree.addListener(SWT.SetData, new Listener() { public void handleEvent(Event event) { TreeItem item = (TreeItem)event.item; ProcessInfo info = findProcessInfo(item); if (info == null) { updateItems(item.getParentItem(), false); } else { fillItem(item, info); } } }); } private void handleDispose() { Protocol.invokeAndWait(new Runnable() { public void run() { disconnectPeer(); if (fProcesses != null) { fProcesses.removeListener(fProcessListener); fProcesses = null; } display = null; } }); } protected void disconnectPeer() { if (fChannel != null && fChannel.getState() != IChannel.STATE_CLOSED) { fChannel.close(); } } protected void connectPeer() { final IChannel channel = fPeer.openChannel(); fChannel = channel; fProcesses = null; channel.addChannelListener(new IChannelListener() { public void congestionLevel(int level) { } public void onChannelClosed(final Throwable error) { if (fChannel != channel) return; fChannel = null; if (display != null) { display.asyncExec(new Runnable() { public void run() { if (root_info.children_pending) return; root_info.children = null; root_info.children_error = error; updateItems(root_info); } }); } } public void onChannelOpened() { if (fChannel != channel) return; fProcesses = fChannel.getRemoteService(IProcesses.class); if (fProcesses != null) { fProcesses.addListener(fProcessListener); if (fContextToSelect != null) { final LinkedList<String> contextPath = new LinkedList<String>(); contextPath.addFirst(fContextToSelect); fProcesses.getContext(fContextToSelect, new IProcesses.DoneGetContext() { public void doneGetContext(IToken token, Exception error, ProcessContext context) { if (error == null) { String parentId = context.getParentID(); if (parentId != null) { contextPath.addFirst(parentId); fProcesses.getContext(parentId, this); return; } if (display != null) { display.asyncExec(new Runnable() { public void run() { fPathToSelect = contextPath; expandSelect(); } }); } } } }); } } if (display != null) { display.asyncExec(new Runnable() { public void run() { if (root_info.children_pending) return; root_info.children = null; root_info.children_error = null; updateItems(root_info); } }); } } }); } private void updateItems(TreeItem parent_item, boolean reload) { final ProcessInfo parent_info = findProcessInfo(parent_item); if (parent_info == null) { parent_item.setText("Invalid"); } else { if (reload && parent_info.children_error != null) { loadChildren(parent_info); } display.asyncExec(new Runnable() { public void run() { updateItems(parent_info); } }); } } private void updateItems(final ProcessInfo parent) { if (display == null) return; assert Thread.currentThread() == display.getThread(); TreeItem[] items = null; boolean expanded = true; if (parent.children == null || parent.children_error != null) { if (parent == root_info) { fProcessTree.setItemCount(1); items = fProcessTree.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(display.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(display.getSystemColor(SWT.COLOR_RED)); items[0].setText(msg); items[0].setImage((Image) null); } else if (expanded) { loadChildren(parent); items[0].setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); items[0].setText("Pending..."); } else { items[0].setText(""); } } else { ProcessInfo[] arr = parent.children; if (parent == root_info) { fProcessTree.setItemCount(arr.length); items = fProcessTree.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]); // auto-expand single children if (items.length == 1 && !items[0].getExpanded()) { items[0].setExpanded(true); } expandSelect(); } else { items[0].setText(""); } } } private void expandSelect() { if (fPathToSelect == null) return; if (fPathToSelect.isEmpty()) { fPathToSelect = null; fContextToSelect = null; return; } do { String id = fPathToSelect.get(0); ProcessInfo info = findProcessInfo(root_info, id); if (info == null) break; TreeItem item = findItem(info); if (item == null) break; fPathToSelect.removeFirst(); if (fPathToSelect.isEmpty()) { fProcessTree.setSelection(item); } else { item.setExpanded(true); } } while (!fPathToSelect.isEmpty()); } private void loadChildren(final ProcessInfo parent) { assert Thread.currentThread() == display.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 IProcesses proc = fProcesses; if (proc == null || !canHaveChildren(parent)) { doneLoadChildren(parent, null, new ProcessInfo[0]); } else { proc.getChildren(parent.id, false, new IProcesses.DoneGetChildren() { public void doneGetChildren(IToken token, Exception error, String[] context_ids) { if (error != null) { doneLoadChildren(parent, error, null); } else if (context_ids.length > 0){ final List<ProcessInfo> contextInfos = new ArrayList<ProcessInfo>(context_ids.length); final Set<IToken> pending = new HashSet<IToken>(); for (String id : context_ids) { pending.add(proc.getContext(id, new IProcesses.DoneGetContext() { public void doneGetContext(IToken token, Exception error, ProcessContext context) { if (context != null) { ProcessInfo info = new ProcessInfo(); info.parent = parent; info.id = context.getID(); info.name = context.getName(); if (info.name == null || info.name.length() == 0) { info.name = info.id; } else { info.name += " [" + info.id + ']'; } info.isContainer = false; info.isAttached = context.isAttached(); info.index = contextInfos.size(); contextInfos.add(info); } pending.remove(token); if (pending.isEmpty()) { doneLoadChildren(parent, null, contextInfos.toArray(new ProcessInfo[contextInfos.size()])); } } })); } } else { doneLoadChildren(parent, null, new ProcessInfo[0]); } } }); } } }); } private void doneLoadChildren(final ProcessInfo parent, final Throwable error, final ProcessInfo[] children) { assert Protocol.isDispatchThread(); assert error == null || children == null; if (display == null) return; display.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 ProcessInfo findProcessInfo(TreeItem item) { assert Thread.currentThread() == display.getThread(); if (item == null) return root_info ; TreeItem parent = item.getParentItem(); ProcessInfo info = findProcessInfo(parent); if (info == null) return null; if (info.children == null) return null; if (info.children_error != null) return null; int i = parent == null ? fProcessTree.indexOf(item) : parent.indexOf(item); if (i < 0 || i >= info.children.length) return null; assert info.children[i].index == i; return info.children[i]; } public ProcessInfo findProcessInfo(ProcessInfo parent, String id) { assert Thread.currentThread() == display.getThread(); if (id == null) return root_info; if (id.equals(parent.id)) return parent; ProcessInfo[] childInfos = parent.children; if (childInfos != null) { for (ProcessInfo contextInfo : childInfos) { ProcessInfo found = findProcessInfo(contextInfo, id); if (found != null) { return found; } } } return null; } private TreeItem findItem(ProcessInfo info) { if (info == null) return null; assert info.parent != null; if (info.parent == root_info) { int n = fProcessTree.getItemCount(); if (info.index >= n) return null; return fProcessTree.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, ProcessInfo info) { assert Thread.currentThread() == display.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.id; item.setText(text); item.setForeground(display.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(ProcessInfo info) { return info.isContainer || info == root_info; } private Image getImage(ProcessInfo info) { return DebugUITools.getImage(IDebugUIConstants.IMG_OBJS_OS_PROCESS); } public void selectContext(final String contextId) { fPathToSelect = null; Protocol.invokeLater(new Runnable() { public void run() { fContextToSelect = contextId; } }); } }