Bug 375253 - [CSS Spy] Add support for spying across multiple windows
diff --git a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyDialog.java b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyDialog.java
index 9c312f2..177af62 100644
--- a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyDialog.java
+++ b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyDialog.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011 Manumitting Technologies, Inc.
+ * Copyright (c) 2011, 2012 Manumitting Technologies, Inc.
  * 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
@@ -11,9 +11,15 @@
 package org.eclipse.e4.tools.css.spy;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.e4.ui.css.core.dom.CSSStylableElement;
 import org.eclipse.e4.ui.css.core.engine.CSSEngine;
 import org.eclipse.e4.ui.css.swt.dom.WidgetElement;
@@ -21,6 +27,8 @@
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
 import org.eclipse.jface.layout.TableColumnLayout;
 import org.eclipse.jface.layout.TreeColumnLayout;
 import org.eclipse.jface.viewers.CellEditor;
@@ -31,6 +39,7 @@
 import org.eclipse.jface.viewers.ColumnWeightData;
 import org.eclipse.jface.viewers.EditingSupport;
 import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter;
+import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
@@ -100,6 +109,9 @@
             engine = WidgetElement.getEngine((Widget) element.getNativeWidget());
         }
         if (engine == null && o instanceof Widget) {
+			if (((Widget) o).isDisposed()) {
+				return null;
+			}
             engine = WidgetElement.getEngine((Widget) o);
         }
         if (engine == null && Display.getCurrent() != null) {
@@ -112,8 +124,10 @@
 	private Widget specimen;
 	private Widget shown;
 
-	private TableViewer cssPropertiesViewer;
 	private TreeViewer widgetTreeViewer;
+	private WidgetTreeProvider widgetTreeProvider;
+	private Button showAllShells;
+	private TableViewer cssPropertiesViewer;
 	private Text cssRules;
 
 	private List<Shell> highlights = new LinkedList<Shell>();
@@ -189,18 +203,57 @@
 			return;
 		}
 
-		widgetTreeViewer
-				.setInput(new Object[] { shown instanceof Control ? ((Control) shown)
-						.getShell() : shown });
-		widgetTreeViewer.reveal(shown);
-		widgetTreeViewer.setSelection(new StructuredSelection(shown));
+		updateWidgetTreeInput();
+		revealAndSelect(Collections.singletonList(shown));
+	}
 
-		populate(shown);
+	private <T> void revealAndSelect(List<T> elements) {
+		widgetTreeViewer.setSelection(new StructuredSelection(elements), true);
+	}
+
+	private void updateForWidgetSelection(ISelection sel) {
+		disposeHighlights();
+		if (sel.isEmpty()) {
+			return;
+		}
+		StructuredSelection selection = (StructuredSelection) sel;
+		for (Object s : selection.toList()) {
+			if (s instanceof Widget) {
+				highlightWidget((Widget) s);
+			}
+		}
+		populate(selection.size() == 1
+				&& selection.getFirstElement() instanceof Widget ? (Widget) selection
+				.getFirstElement() : null);
+	}
+
+	private void updateWidgetTreeInput() {
+		if (showAllShells.getSelection()) {
+			widgetTreeViewer.setInput(display);
+		} else {
+			widgetTreeViewer
+					.setInput(new Object[] { shown instanceof Control ? ((Control) shown)
+							.getShell() : shown });
+		}
+		performCSSSearch(new NullProgressMonitor());
 	}
 
 	protected void populate(Widget selected) {
+		if (selected == null) {
+			cssPropertiesViewer.setInput(null);
+			cssRules.setText("");
+			return;
+		}
+		if (selected.isDisposed()) {
+			cssPropertiesViewer.setInput(null);
+			cssRules.setText("*DISPOSED*");
+			return;
+		}
+
 		CSSStylableElement element = getCSSElement(selected);
 		if (element == null) {
+			cssPropertiesViewer.setInput(null);
+			cssRules.setText("Not a stylable element");
 			return;
 		}
 
@@ -236,7 +289,6 @@
 			Activator.join(sb, element.getCSSClass().split(" +"), "\n  ");
 		}
 
-		// FIXME: shouldn't this be getCSSStyle?
 		if (element.getAttribute("style") != null) {
 			sb.append("\n\nSWT Style Bits:\n  ");
 			Activator.join(sb, element.getAttribute("style").split(" +"),
@@ -248,9 +300,31 @@
 
 		// this is useful for diagnosing issues
 		if (element.getNativeWidget() instanceof Composite) {
-			sb.append("\n\nSWT Layout:\n  ").append(
+			sb.append("\n\nSWT Layout: ").append(
 					((Composite) element.getNativeWidget()).getLayout());
 		}
+		Rectangle bounds = getBounds(selected);
+		if (bounds != null) {
+			sb.append("\nBounds: x=").append(bounds.x).append(" y=")
+					.append(bounds.y);
+			sb.append(" h=").append(bounds.height).append(" w=")
+					.append(bounds.width);
+		}
+
+		if (element.getNativeWidget() instanceof Widget) {
+			Widget w = (Widget) element.getNativeWidget();
+			if (w.getData() != null) {
+				sb.append("\nWidget data: ").append(w.getData());
+			}
+			if (w.getData(SWT.SKIN_ID) != null) {
+				sb.append("\nWidget Skin ID (").append(SWT.SKIN_ID)
+						.append("): ").append(w.getData(SWT.SKIN_ID));
+			}
+			if (w.getData(SWT.SKIN_CLASS) != null) {
+				sb.append("\nWidget Skin Class (").append(SWT.SKIN_CLASS)
+						.append("): ").append(w.getData(SWT.SKIN_CLASS));
+			}
+		}
 
 		cssRules.setText(sb.toString().trim());
 
@@ -258,8 +332,18 @@
 		highlightWidget(selected);
 	}
 
+	private Shell getShell(Widget widget) {
+		if (widget instanceof Control) {
+			return ((Control) widget).getShell();
+		}
+		return null;
+	}
+
+	/** Add a highlight-rectangle for the selected widget */
 	private void highlightWidget(Widget selected) {
-		widgetTreeViewer.reveal(selected);
+		if (selected == null || selected.isDisposed()) {
+			return;
+		}
 
 		Rectangle bounds = getBounds(selected); // relative to absolute display,
 												// not the widget
@@ -292,13 +376,6 @@
 		highlightRegions.add(highlightRegion);
 	}
 
-	private Shell getShell(Widget widget) {
-		if (widget instanceof Control) {
-			return ((Control) widget).getShell();
-		}
-		return null;
-	}
-
 	private void disposeHighlights() {
 		for (Shell highlight : highlights) {
 			highlight.dispose();
@@ -344,12 +421,18 @@
 	protected Control createDialogArea(Composite parent) {
 		Composite outer = (Composite) super.createDialogArea(parent);
 
-		cssSearchBox = new Text(outer, SWT.BORDER | SWT.SEARCH
+		Composite top = new Composite(outer, SWT.NONE);
+		GridLayoutFactory.swtDefaults().numColumns(2).applyTo(top);
+		cssSearchBox = new Text(top, SWT.BORDER | SWT.SEARCH
 				| SWT.ICON_SEARCH | SWT.ICON_CANCEL);
 		cssSearchBox.setMessage("CSS Selector");
 		cssSearchBox.setToolTipText("Highlight matching widgets");
-		cssSearchBox.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
-				false, 1, 1));
+		GridDataFactory.fillDefaults().grab(true, false).applyTo(cssSearchBox);
+
+		showAllShells = new Button(top, SWT.CHECK);
+		showAllShells.setText("All shells");
+		GridDataFactory.swtDefaults().applyTo(showAllShells);
+		GridDataFactory.fillDefaults().applyTo(top);
 
 		SashForm sashForm = new SashForm(outer, SWT.VERTICAL);
 		sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1,
@@ -358,8 +441,10 @@
 		// / THE WIDGET TREE
 		Composite widgetsComposite = new Composite(sashForm, SWT.NONE);
 
-		widgetTreeViewer = new TreeViewer(widgetsComposite, SWT.BORDER);
-		widgetTreeViewer.setContentProvider(new WidgetTreeProvider());
+		widgetTreeViewer = new TreeViewer(widgetsComposite, SWT.BORDER
+				| SWT.MULTI);
+		widgetTreeProvider = new WidgetTreeProvider();
+		widgetTreeViewer.setContentProvider(widgetTreeProvider);
 		widgetTreeViewer.setAutoExpandLevel(0);
 		widgetTreeViewer.getTree().setLinesVisible(true);
 		widgetTreeViewer.getTree().setHeaderVisible(true);
@@ -568,12 +653,16 @@
 
 		cssSearchBox.addModifyListener(new ModifyListener() {
 			private Runnable updater;
+			private IProgressMonitor monitor;
 
 			public void modifyText(ModifyEvent e) {
+				if (monitor != null) {
+					monitor.setCanceled(false);
+				}
 				display.timerExec(200, updater = new Runnable() {
 					public void run() {
 						if (updater == this) {
-							performCSSSearch(cssSearchBox.getText());
+							performCSSSearch(monitor = new NullProgressMonitor());
 						}
 					}
 				});
@@ -592,10 +681,7 @@
 		widgetTreeViewer
 				.addSelectionChangedListener(new ISelectionChangedListener() {
 					public void selectionChanged(SelectionChangedEvent event) {
-						if (!event.getSelection().isEmpty()) {
-							populate((Widget) ((StructuredSelection) event
-									.getSelection()).getFirstElement());
-						}
+						updateForWidgetSelection(event.getSelection());
 					}
 				});
 		if (isLive()) {
@@ -624,6 +710,11 @@
 				}
 			}
 		});
+		showAllShells.addSelectionListener(new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				updateWidgetTreeInput();
+			}
+		});
 
 		outer.addDisposeListener(new DisposeListener() {
 			public void widgetDisposed(DisposeEvent e) {
@@ -649,37 +740,56 @@
 		return outer;
 	}
 
-	private String searchInProgress;
+	protected void performCSSSearch(IProgressMonitor progress) {
+		List<Widget> widgets = new ArrayList<Widget>();
+		performCSSSearch(progress, cssSearchBox.getText(), widgets);
+		if (!progress.isCanceled()) {
+			revealAndSelect(widgets);
+		}
+	}
 
-	private void performCSSSearch(String text) {
-		disposeHighlights();
-		widgetTreeViewer.collapseAll();
+	private void performCSSSearch(IProgressMonitor monitor, String text,
+			Collection<Widget> results) {
 		if (text.trim().length() == 0) {
 			return;
 		}
-		searchInProgress = text;
-		CSSStylableElement element = getCSSElement(getShell(shown));
-		if (element == null) {
-			return;
-		}
+		widgetTreeViewer.collapseAll();
+		Object[] roots = widgetTreeProvider.getElements(widgetTreeViewer
+				.getInput());
+		monitor.beginTask("Searching for \"" + text + "\"", roots.length * 10);
+		for (Object root : roots) {
+			if (monitor.isCanceled()) {
+				return;
+			}
 
-        CSSEngine engine = getCSSEngine(shown);
-		try {
-			SelectorList selectors = engine.parseSelectors(text);
-			processCSSSearch(text, null, engine, selectors, element);
-		} catch (CSSParseException e) {
-			// ignore: e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
+			CSSStylableElement element = getCSSElement(root);
+			if (element == null) {
+				continue;
+			}
 
+			CSSEngine engine = getCSSEngine(root);
+			try {
+				SelectorList selectors = engine.parseSelectors(text);
+				monitor.worked(2);
+				processCSSSearch(new SubProgressMonitor(monitor, 8), engine,
+						selectors, element, null, results);
+			} catch (CSSParseException e) {
+				System.out.println(e.toString());
+			} catch (IOException e) {
+				System.out.println(e.toString());
+			}
+		}
+		monitor.done();
 	}
 
-	private void processCSSSearch(String text, String pseudo, CSSEngine engine,
-			SelectorList selectors, CSSStylableElement element) {
-		if (text != searchInProgress) {
+	private void processCSSSearch(IProgressMonitor monitor, CSSEngine engine,
+			SelectorList selectors, CSSStylableElement element, String pseudo,
+			Collection<Widget> results) {
+		if (monitor.isCanceled()) {
 			return;
 		}
+		NodeList children = element.getChildNodes();
+		monitor.beginTask("Searching", 5 + 5 * children.getLength());
 		boolean matched = false;
 		for (int i = 0; i < selectors.getLength(); i++) {
 			if (matched = engine.matches(selectors.item(i), element, pseudo)) {
@@ -687,16 +797,18 @@
 			}
 		}
 		if (matched) {
-			highlightWidget((Widget) element.getNativeWidget());
+			results.add((Widget) element.getNativeWidget());
 		}
-		NodeList children = element.getChildNodes();
+		monitor.worked(5);
 		for (int i = 0; i < children.getLength(); i++) {
-			if (text != searchInProgress) {
+			if (monitor.isCanceled()) {
 				return;
 			}
-			processCSSSearch(text, pseudo, engine, selectors,
-					(CSSStylableElement) children.item(i));
+			processCSSSearch(new SubProgressMonitor(monitor, 5), engine,
+					selectors, (CSSStylableElement) children.item(i), pseudo,
+					results);
 		}
+		monitor.done();
 	}
 
 	protected void dispose() {
diff --git a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/WidgetTreeProvider.java b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/WidgetTreeProvider.java
index 9eb62d2..fe8aaf6 100644
--- a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/WidgetTreeProvider.java
+++ b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/WidgetTreeProvider.java
@@ -12,11 +12,14 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
 import org.eclipse.e4.ui.css.core.dom.CSSStylableElement;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.ToolItem;
 import org.w3c.dom.NodeList;
 
@@ -37,6 +40,15 @@
 
 
     public Object[] getChildren(Object parentElement) {
+		if (parentElement instanceof Display) {
+			List<Shell> shells = new ArrayList<Shell>();
+			for (Shell s : ((Display) parentElement).getShells()) {
+				if (!s.isDisposed()) {
+					shells.add(s);
+				}
+			}
+			return shells.toArray();
+		}
         CSSStylableElement element = CssSpyDialog.getCSSElement(parentElement);
         if (element == null) {
 			return EMPTY_ARRAY;
@@ -51,7 +63,8 @@
 
     public Object getParent(Object element) {
 		if (element instanceof Control) {
-			return ((Control) element).getParent();
+			Control control = (Control) element;
+			return control.isDisposed() ? null : control.getParent();
 		} else if (element instanceof org.eclipse.swt.custom.CTabItem) {
 			return ((org.eclipse.swt.custom.CTabItem) element).getParent();
 		} else if (element instanceof org.eclipse.e4.ui.widgets.CTabItem) {