Bug 428903 - Having a common 'debug' window for all spies

Add the css spy and the css scratch pad in the E4 spies window

Change-Id: I2ff59bd98fe23323f7c34b020231fa389a19f8ca
Signed-off-by: Olivier Prouvost <olivier.prouvost@opcoach.com>
diff --git a/bundles/org.eclipse.e4.tools.css.spy/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.tools.css.spy/META-INF/MANIFEST.MF
index 13b3a38..d7a24af 100644
--- a/bundles/org.eclipse.e4.tools.css.spy/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.tools.css.spy/META-INF/MANIFEST.MF
@@ -13,7 +13,10 @@
  org.eclipse.e4.ui.css.swt;bundle-version="0.10.0",
  org.eclipse.e4.ui.css.swt.theme;bundle-version="0.9.201",
  org.eclipse.e4.ui.widgets;bundle-version="0.11.0",
- org.eclipse.e4.ui.model.workbench;bundle-version="0.9.1"
+ org.eclipse.e4.ui.model.workbench;bundle-version="0.9.1",
+ org.eclipse.e4.tools.spy;bundle-version="0.1.0",
+ org.eclipse.e4.ui.di,
+ org.eclipse.e4.core.di.extensions
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Bundle-ActivationPolicy: lazy
 Import-Package: javax.inject,
diff --git a/bundles/org.eclipse.e4.tools.css.spy/icons/css_scratchpad.gif b/bundles/org.eclipse.e4.tools.css.spy/icons/css_scratchpad.gif
new file mode 100644
index 0000000..b226e41
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.css.spy/icons/css_scratchpad.gif
Binary files differ
diff --git a/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif b/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif
new file mode 100644
index 0000000..80c152a
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif
Binary files differ
diff --git a/bundles/org.eclipse.e4.tools.css.spy/plugin.xml b/bundles/org.eclipse.e4.tools.css.spy/plugin.xml
index c442ab4..82d1ffe 100644
--- a/bundles/org.eclipse.e4.tools.css.spy/plugin.xml
+++ b/bundles/org.eclipse.e4.tools.css.spy/plugin.xml
@@ -2,12 +2,20 @@
 <?eclipse version="3.4"?>
 <plugin>
    <extension
-         id="css.spy"
-         point="org.eclipse.e4.workbench.model">
-      <processor
-            beforefragment="false"
-            class="org.eclipse.e4.tools.css.spy.SpyInstaller">
-      </processor>
+         point="org.eclipse.e4.tools.spy.spyPart">
+      <spyPart
+            description="Open Css Spy"
+            icon="icons/cssspy.gif"
+            name="CSS Spy"
+            part="org.eclipse.e4.tools.css.spy.CssSpyPart"
+            shortcut="M2+M3+F5"></spyPart>
+      <spyPart
+            description="Open Css Scratch Pad"
+            icon="icons/css_scratchpad.gif"
+            name="CSS Scratch pad"
+            part="org.eclipse.e4.tools.css.spy.CSSScratchPadPart"
+            shortcut="M2+M3+F6">
+      </spyPart>
    </extension>
 
 </plugin>
diff --git a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CSSScratchPadPart.java b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CSSScratchPadPart.java
new file mode 100644
index 0000000..aa6c47c
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CSSScratchPadPart.java
@@ -0,0 +1,186 @@
+package org.eclipse.e4.tools.css.spy;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.e4.ui.css.core.dom.ExtendedDocumentCSS;
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.swt.internal.theme.ThemeEngine;
+import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.w3c.css.sac.CSSParseException;
+import org.w3c.dom.stylesheets.StyleSheet;
+import org.w3c.dom.stylesheets.StyleSheetList;
+
+@SuppressWarnings("restriction")
+public class CSSScratchPadPart
+{
+	@Inject
+	@Optional
+	private IThemeEngine themeEngine;
+
+	/*
+	 * public CSSScratchPadPart(Shell parentShell, IThemeEngine themeEngine) {
+	 * super(parentShell); this.themeEngine = themeEngine;
+	 * setShellStyle(SWT.DIALOG_TRIM | SWT.RESIZE/* | SWT.PRIMARY_MODAL
+	 */
+	// );
+		// setShellStyle(SWT.DIALOG_TRIM | SWT.MAX | SWT.RESIZE
+		// | getDefaultOrientation());
+	//}
+
+	/*
+	 * @Override protected void configureShell(Shell newShell) {
+	 * super.configureShell(newShell); newShell.setText("CSS Scratchpad"); }
+	 */
+
+	private static final int APPLY_ID = IDialogConstants.OK_ID + 100;
+	/**
+	 * Collection of buttons created by the <code>createButton</code> method.
+	 */
+	private HashMap<Integer, Button> buttons = new HashMap<Integer, Button>();
+
+	private Text cssText;
+	private Text exceptions;
+
+	@PostConstruct
+	protected Control createDialogArea(Composite parent)
+	{
+
+		Composite outer = parent;
+		outer.setLayout(new GridLayout());
+		outer.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+		SashForm sashForm = new SashForm(outer, SWT.VERTICAL);
+
+		cssText = new Text(sashForm, SWT.BORDER | SWT.MULTI | SWT.WRAP
+				| SWT.V_SCROLL);
+
+		exceptions = new Text(sashForm, SWT.BORDER | SWT.MULTI | SWT.READ_ONLY);
+
+		GridDataFactory.fillDefaults().grab(true, true).applyTo(sashForm);
+		sashForm.setWeights(new int[] { 80, 20 });
+
+		createButtonsForButtonBar(parent);
+		return outer;
+	}
+
+	private void createButtonsForButtonBar(Composite parent) {
+		createButton(parent, APPLY_ID, "Apply", true);
+		createButton(parent, IDialogConstants.OK_ID, "Close", false);
+		// createButton(parent, IDialogConstants.CANCEL_ID,
+		// IDialogConstants.CANCEL_LABEL, false);
+	}
+
+	protected Button createButton(Composite parent, int id, String label,
+			boolean defaultButton) {
+		// increment the number of columns in the button bar
+		((GridLayout) parent.getLayout()).numColumns++;
+		Button button = new Button(parent, SWT.PUSH);
+		button.setText(label);
+		button.setFont(JFaceResources.getDialogFont());
+		button.setData(new Integer(id));
+		button.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent event) {
+				buttonPressed(((Integer) event.widget.getData()).intValue());
+			}
+		});
+		if (defaultButton) {
+			Shell shell = parent.getShell();
+			if (shell != null) {
+				shell.setDefaultButton(button);
+			}
+		}
+		buttons.put(new Integer(id), button);
+		//setButtonLayoutData(button);
+		return button;
+	}
+
+
+
+
+
+	protected void buttonPressed(int buttonId) {
+		switch (buttonId) {
+		case APPLY_ID:
+			applyCSS();
+			break;
+		default:
+			break;
+		}
+	}
+
+	private void applyCSS() {
+		if (themeEngine == null) {
+			exceptions.setText("No theme engine available!");
+			return;
+		}
+		long start = System.nanoTime();
+		exceptions.setText("");
+
+		StringBuilder sb = new StringBuilder();
+
+		// FIXME: expose these new protocols: resetCurrentTheme() and
+		// getCSSEngines()
+		((ThemeEngine) themeEngine).resetCurrentTheme();
+
+		int count = 0;
+		for (CSSEngine engine : ((ThemeEngine) themeEngine).getCSSEngines()) {
+			if (count++ > 0) {
+				sb.append("\n\n");
+			}
+			sb.append("Engine[").append(engine.getClass().getSimpleName())
+					.append("]");
+			ExtendedDocumentCSS doc = (ExtendedDocumentCSS) engine
+					.getDocumentCSS();
+			List<StyleSheet> sheets = new ArrayList<StyleSheet>();
+			StyleSheetList list = doc.getStyleSheets();
+			for (int i = 0; i < list.getLength(); i++) {
+				sheets.add(list.item(i));
+			}
+
+			try {
+				Reader reader = new StringReader(cssText.getText());
+				sheets.add(0, engine.parseStyleSheet(reader));
+				doc.removeAllStyleSheets();
+				for (StyleSheet sheet : sheets) {
+					doc.addStyleSheet(sheet);
+				}
+				engine.reapply();
+
+				long nanoDiff = System.nanoTime() - start;
+				sb.append("\nTime: ").append(nanoDiff / 1000000).append("ms");
+			} catch (CSSParseException e) {
+				sb.append("\nError: line ").append(e.getLineNumber())
+						.append(" col ").append(e.getColumnNumber())
+						.append(": ").append(e.getLocalizedMessage());
+			} catch (IOException e) {
+				sb.append("\nError: ").append(e.getLocalizedMessage());
+			}
+		}
+		exceptions.setText(sb.toString());
+	}
+
+}
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 982596a..f5eae29 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
@@ -91,6 +91,12 @@
 import org.w3c.dom.css.CSSStyleDeclaration;
 import org.w3c.dom.css.CSSValue;
 
+/**
+ *
+ * @deprecated use now cssSpyPart
+ *
+ */
+@Deprecated
 public class CssSpyDialog extends Dialog {
 	/** @return the CSS element corresponding to the argument, or null if none */
 	public static CSSStylableElement getCSSElement(Object o) {
@@ -666,11 +672,13 @@
 			private Runnable updater;
 			private IProgressMonitor monitor;
 
+			@Override
 			public void modifyText(ModifyEvent e) {
 				if (monitor != null) {
 					monitor.setCanceled(false);
 				}
 				display.timerExec(200, updater = new Runnable() {
+					@Override
 					public void run() {
 						if (updater == this) {
 							performCSSSearch(monitor = new NullProgressMonitor());
@@ -691,6 +699,7 @@
 
 		widgetTreeViewer
 				.addSelectionChangedListener(new ISelectionChangedListener() {
+					@Override
 					public void selectionChanged(SelectionChangedEvent event) {
 						updateForWidgetSelection(event.getSelection());
 						showCssFragment.setEnabled(!event.getSelection()
@@ -699,6 +708,7 @@
 				});
 		if (isLive()) {
 			container.addMouseMoveListener(new MouseMoveListener() {
+				@Override
 				public void mouseMove(MouseEvent e) {
 					update();
 				}
@@ -732,6 +742,7 @@
 		});
 
 		outer.addDisposeListener(new DisposeListener() {
+			@Override
 			public void widgetDisposed(DisposeEvent e) {
 				dispose();
 			}
diff --git a/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyPart.java b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyPart.java
new file mode 100644
index 0000000..826a328
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.css.spy/src/org/eclipse/e4/tools/css/spy/CssSpyPart.java
@@ -0,0 +1,1102 @@
+/*******************************************************************************
+ * 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Brian de Alwis (MT) - initial API and implementation
+ *     Olivier Prouvost <olivier.prouvost@opcoach.com>
+ *       - Fix bug 428903 : transform the 'old' dialog into a part to be defined with spyPart extension
+ *******************************************************************************/
+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.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+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;
+import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+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;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnViewerEditor;
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+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.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TableViewerEditor;
+import org.eclipse.jface.viewers.TableViewerFocusCellManager;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.Widget;
+import org.w3c.css.sac.CSSParseException;
+import org.w3c.css.sac.SelectorList;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.css.CSSStyleDeclaration;
+import org.w3c.dom.css.CSSValue;
+
+@SuppressWarnings("restriction")
+public class CssSpyPart
+{
+
+	@Inject
+	@Named(IServiceConstants.ACTIVE_SHELL)
+	private Shell activeShell;
+
+
+
+	/** @return the CSS element corresponding to the argument, or null if none */
+	public static CSSStylableElement getCSSElement(Object o)
+	{
+		if (o instanceof CSSStylableElement)
+		{
+			return (CSSStylableElement) o;
+		} else
+		{
+			CSSEngine engine = getCSSEngine(o);
+			if (engine != null)
+			{
+				return (CSSStylableElement) engine.getElement(o);
+			}
+		}
+		return null;
+	}
+
+	/** @return the CSS engine governing the argument, or null if none */
+	public static CSSEngine getCSSEngine(Object o)
+	{
+		CSSEngine engine = null;
+		if (o instanceof CSSStylableElement)
+		{
+			CSSStylableElement element = (CSSStylableElement) o;
+			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)
+		{
+			engine = new CSSSWTEngineImpl(Display.getCurrent());
+		}
+		return engine;
+	}
+
+	@Inject
+	private Display display;
+
+	private Widget specimen; // specimen (can be reused if reopened)
+
+	@Inject
+	IEclipseContext mpartContext; // Used to remember of last specimen.
+
+	private Widget shown;
+
+	private TreeViewer widgetTreeViewer;
+	private WidgetTreeProvider widgetTreeProvider;
+	private Button showAllShells;
+	private TableViewer cssPropertiesViewer;
+	private Text cssRules;
+
+	private List<Shell> highlights = new LinkedList<Shell>();
+	private List<Region> highlightRegions = new LinkedList<Region>();
+	private Text cssSearchBox;
+	private Button showUnsetProperties;
+	private Button showCssFragment;
+
+	protected ViewerFilter unsetPropertyFilter = new ViewerFilter()
+		{
+
+			@Override
+			public boolean select(Viewer viewer, Object parentElement, Object element)
+			{
+				if (element instanceof CSSPropertyProvider)
+				{
+					try
+					{
+						return ((CSSPropertyProvider) element).getValue() != null;
+					} catch (Exception e)
+					{
+						return false;
+					}
+				}
+				return false;
+			}
+		};
+
+	private Composite outer;
+
+	public Widget getSpecimen()
+	{
+		return specimen;
+	}
+
+	private boolean isLive()
+	{
+		return specimen == null;
+	}
+
+	public void setSpecimen(Widget specimen)
+	{
+		this.specimen = specimen;
+		update();
+	}
+
+	private Widget getActiveSpecimen()
+	{
+		if (specimen != null)
+		{
+			return specimen;
+		}
+		return display.getCursorControl();
+	}
+
+	protected boolean shouldDismissOnLostFocus()
+	{
+		return false;
+	}
+
+	protected void update()
+	{
+		if (activeShell == null)
+		{
+			return;
+		}
+		Widget current = getActiveSpecimen();
+		if (shown == current)
+		{
+			return;
+		}
+		shown = current;
+
+		CSSEngine engine = getCSSEngine(shown);
+		CSSStylableElement element = (CSSStylableElement) engine.getElement(shown);
+		if (element == null)
+		{
+			return;
+		}
+
+		updateWidgetTreeInput();
+		revealAndSelect(Collections.singletonList(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;
+		}
+
+		cssPropertiesViewer.setInput(selected);
+
+		StringBuilder sb = new StringBuilder();
+		CSSEngine engine = getCSSEngine(element);
+		CSSStyleDeclaration decl = engine.getViewCSS().getComputedStyle(element, null);
+
+		if (element.getCSSStyle() != null)
+		{
+			sb.append("\nCSS Inline Style(s):\n  ");
+			Activator.join(sb, element.getCSSStyle().split(";"), ";\n  ");
+		}
+
+		if (decl != null)
+		{
+			sb.append("\n\nCSS Properties:\n");
+			try
+			{
+				if (decl != null)
+				{
+					sb.append(decl.getCssText());
+				}
+			} catch (Exception e)
+			{
+				sb.append(e);
+			}
+		}
+		if (element.getStaticPseudoInstances().length > 0)
+		{
+			sb.append("\n\nStatic Pseudoinstances:\n  ");
+			Activator.join(sb, element.getStaticPseudoInstances(), "\n  ");
+		}
+
+		if (element.getCSSClass() != null)
+		{
+			sb.append("\n\nCSS Classes:\n  ");
+			Activator.join(sb, element.getCSSClass().split(" +"), "\n  ");
+		}
+
+		if (element.getAttribute("style") != null)
+		{
+			sb.append("\n\nSWT Style Bits:\n  ");
+			Activator.join(sb, element.getAttribute("style").split(" +"), "\n  ");
+		}
+
+		sb.append("\n\nCSS Class Element:\n  ").append(element.getClass().getName());
+
+		// this is useful for diagnosing issues
+		if (element.getNativeWidget() instanceof Shell && ((Shell) element.getNativeWidget()).getParent() != null)
+		{
+			Shell nw = (Shell) element.getNativeWidget();
+			sb.append("\n\nShell parent: ").append(nw.getParent());
+		}
+		if (element.getNativeWidget() instanceof Composite)
+		{
+			Composite nw = (Composite) element.getNativeWidget();
+			sb.append("\n\nSWT Layout: ").append(nw.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());
+
+		disposeHighlights();
+		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)
+	{
+		if (selected == null || selected.isDisposed())
+		{
+			return;
+		}
+
+		Rectangle bounds = getBounds(selected); // relative to absolute display,
+												// not the widget
+		if (bounds == null /* || bounds.height == 0 || bounds.width == 0 */)
+		{
+			return;
+		}
+		// emulate a transparent background as per SWT Snippet180
+		Shell selectedShell = getShell(selected);
+		// create the highlight; want it to appear on top
+		Shell highlight = new Shell(selectedShell, SWT.NO_TRIM | SWT.MODELESS | SWT.NO_FOCUS | SWT.ON_TOP);
+		highlight.setBackground(display.getSystemColor(SWT.COLOR_RED));
+		Region highlightRegion = new Region();
+		highlightRegion.add(0, 0, 1, bounds.height + 2);
+		highlightRegion.add(0, 0, bounds.width + 2, 1);
+		highlightRegion.add(bounds.width + 1, 0, 1, bounds.height + 2);
+		highlightRegion.add(0, bounds.height + 1, bounds.width + 2, 1);
+		highlight.setRegion(highlightRegion);
+		highlight.setBounds(bounds.x - 1, bounds.y - 1, bounds.width + 2, bounds.height + 2);
+		highlight.setEnabled(false);
+		highlight.setVisible(true); // not open(): setVisible() prevents taking
+									// focus
+
+		highlights.add(highlight);
+		highlightRegions.add(highlightRegion);
+	}
+
+	private void disposeHighlights()
+	{
+		for (Shell highlight : highlights)
+		{
+			highlight.dispose();
+		}
+		highlights.clear();
+		for (Region region : highlightRegions)
+		{
+			region.dispose();
+		}
+		highlightRegions.clear();
+	}
+
+	private Rectangle getBounds(Widget widget)
+	{
+		if (widget instanceof Shell)
+		{
+			// Shell bounds are already in display coordinates
+			return ((Shell) widget).getBounds();
+		} else if (widget instanceof Control)
+		{
+			Control control = (Control) widget;
+			Rectangle bounds = control.getBounds();
+			return control.getDisplay().map(control.getParent(), null, bounds);
+		} else if (widget instanceof ToolItem)
+		{
+			ToolItem item = (ToolItem) widget;
+			Rectangle bounds = item.getBounds();
+			return item.getDisplay().map(item.getParent(), null, bounds);
+		} else if (widget instanceof CTabItem)
+		{
+			CTabItem item = (CTabItem) widget;
+			Rectangle bounds = item.getBounds();
+			return item.getDisplay().map(item.getParent(), null, bounds);
+		}
+		// FIXME: figure out how to map items to a position
+		return null;
+	}
+
+	/**
+	 * Create contents of the spy.
+	 *
+	 * @param parent
+	 */
+	@PostConstruct
+	protected Control createDialogArea(Composite parent, IEclipseContext ctx)
+	{
+		outer = parent;
+		outer.setLayout(new GridLayout());
+		outer.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+		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");
+		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, 1));
+
+		// / THE WIDGET TREE
+		Composite widgetsComposite = new Composite(sashForm, SWT.NONE);
+
+		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);
+		ColumnViewerToolTipSupport.enableFor(widgetTreeViewer);
+
+		TreeViewerColumn widgetTypeColumn = new TreeViewerColumn(widgetTreeViewer, SWT.NONE);
+		widgetTypeColumn.getColumn().setWidth(100);
+		widgetTypeColumn.getColumn().setText("Widget");
+		widgetTypeColumn.setLabelProvider(new ColumnLabelProvider()
+			{
+				@Override
+				public String getText(Object item)
+				{
+					CSSStylableElement element = CssSpyPart.getCSSElement(item);
+					return element.getLocalName() + " (" + element.getNamespaceURI() + ")";
+				}
+			});
+
+		TreeViewerColumn widgetClassColumn = new TreeViewerColumn(widgetTreeViewer, SWT.NONE);
+		widgetClassColumn.getColumn().setText("CSS Class");
+		widgetClassColumn.getColumn().setWidth(100);
+		widgetClassColumn.setLabelProvider(new ColumnLabelProvider()
+			{
+				@Override
+				public String getText(Object item)
+				{
+					CSSStylableElement element = CssSpyPart.getCSSElement(item);
+					if (element.getCSSClass() == null)
+					{
+						return null;
+					}
+					String classes[] = element.getCSSClass().split(" +");
+					return classes.length <= 1 ? classes[0] : classes[0] + " (+" + (classes.length - 1) + " others)";
+				}
+
+				@Override
+				public String getToolTipText(Object item)
+				{
+					CSSStylableElement element = CssSpyPart.getCSSElement(item);
+					if (element == null)
+					{
+						return null;
+					}
+					StringBuilder sb = new StringBuilder();
+					sb.append(element.getLocalName()).append(" (").append(element.getNamespaceURI()).append(")");
+					if (element.getCSSClass() != null)
+					{
+						sb.append("\nClasses:\n  ");
+						Activator.join(sb, element.getCSSClass().split(" +"), "\n  ");
+					}
+					return sb.toString();
+				}
+			});
+
+		TreeViewerColumn widgetIdColumn = new TreeViewerColumn(widgetTreeViewer, SWT.NONE);
+		widgetIdColumn.getColumn().setWidth(100);
+		widgetIdColumn.getColumn().setText("CSS Id");
+		widgetIdColumn.setLabelProvider(new ColumnLabelProvider()
+			{
+				@Override
+				public String getText(Object item)
+				{
+					CSSStylableElement element = CssSpyPart.getCSSElement(item);
+					return element.getCSSId();
+				}
+			});
+
+		TreeColumnLayout widgetsTableLayout = new TreeColumnLayout();
+		widgetsTableLayout.setColumnData(widgetTypeColumn.getColumn(), new ColumnWeightData(50));
+		widgetsTableLayout.setColumnData(widgetIdColumn.getColumn(), new ColumnWeightData(40));
+		widgetsTableLayout.setColumnData(widgetClassColumn.getColumn(), new ColumnWeightData(40));
+		widgetsComposite.setLayout(widgetsTableLayout);
+
+		// / HEADERS
+		Composite container = new Composite(sashForm, SWT.NONE);
+		container.setLayout(new GridLayout(2, true));
+
+		Label lblCssProperties = new Label(container, SWT.NONE);
+		lblCssProperties.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
+		lblCssProperties.setText("CSS Properties");
+
+		Label lblCssRules = new Label(container, SWT.NONE);
+		lblCssRules.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
+		lblCssRules.setText("CSS Rules");
+
+		// // THE CSS PROPERTIES TABLE
+		Composite propsComposite = new Composite(container, SWT.BORDER);
+		GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
+		gridData.minimumHeight = 50;
+		propsComposite.setLayoutData(gridData);
+
+		cssPropertiesViewer = new TableViewer(propsComposite, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
+		cssPropertiesViewer.setContentProvider(new CSSPropertiesContentProvider());
+		cssPropertiesViewer.getTable().setLinesVisible(true);
+		cssPropertiesViewer.getTable().setHeaderVisible(true);
+		cssPropertiesViewer.setComparator(new ViewerComparator());
+
+		final TextCellEditor textCellEditor = new TextCellEditor(cssPropertiesViewer.getTable());
+		TableViewerEditor.create(cssPropertiesViewer, new TableViewerFocusCellManager(cssPropertiesViewer,
+				new FocusCellOwnerDrawHighlighter(cssPropertiesViewer)), new ColumnViewerEditorActivationStrategy(
+				cssPropertiesViewer), ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
+				| ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION);
+
+		TableViewerColumn propName = new TableViewerColumn(cssPropertiesViewer, SWT.NONE);
+		propName.getColumn().setWidth(100);
+		propName.getColumn().setText("Property");
+		propName.setLabelProvider(new ColumnLabelProvider()
+			{
+				@Override
+				public String getText(Object element)
+				{
+					return ((CSSPropertyProvider) element).getPropertyName();
+				}
+			});
+
+		TableViewerColumn propValue = new TableViewerColumn(cssPropertiesViewer, SWT.NONE);
+		propValue.getColumn().setWidth(100);
+		propValue.getColumn().setText("Value");
+		propValue.setLabelProvider(new ColumnLabelProvider()
+			{
+				@Override
+				public String getText(Object element)
+				{
+					try
+					{
+						return ((CSSPropertyProvider) element).getValue();
+					} catch (Exception e)
+					{
+						System.err.println("Error fetching property: " + element + ": " + e);
+						return null;
+					}
+				}
+			});
+		propValue.setEditingSupport(new EditingSupport(cssPropertiesViewer)
+			{
+				@Override
+				protected CellEditor getCellEditor(Object element)
+				{
+					// do the fancy footwork here to return an appropriate
+					// editor to
+					// the value-type
+					return textCellEditor;
+				}
+
+				@Override
+				protected boolean canEdit(Object element)
+				{
+					return true;
+				}
+
+				@Override
+				protected Object getValue(Object element)
+				{
+					try
+					{
+						String value = ((CSSPropertyProvider) element).getValue();
+						return value == null ? "" : value;
+					} catch (Exception e)
+					{
+						return "";
+					}
+				}
+
+				@Override
+				protected void setValue(Object element, Object value)
+				{
+					try
+					{
+						if (value == null || ((String) value).trim().length() == 0)
+						{
+							return;
+						}
+						CSSPropertyProvider provider = (CSSPropertyProvider) element;
+						provider.setValue((String) value);
+					} catch (Exception e)
+					{
+						MessageDialog.openError(activeShell, "Error", "Unable to set property:\n\n" + e.getMessage());
+					}
+					cssPropertiesViewer.update(element, null);
+				}
+			});
+
+		TableColumnLayout propsTableLayout = new TableColumnLayout();
+		propsTableLayout.setColumnData(propName.getColumn(), new ColumnWeightData(50));
+		propsTableLayout.setColumnData(propValue.getColumn(), new ColumnWeightData(50));
+		propsComposite.setLayout(propsTableLayout);
+
+		// / THE CSS RULES
+		cssRules = new Text(container, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI);
+		cssRules.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+		// / THE CSS PROPERTIES TABLE (again)
+		showUnsetProperties = new Button(container, SWT.CHECK);
+		showUnsetProperties.setText("Show unset properties");
+		showCssFragment = new Button(container, SWT.PUSH);
+		showCssFragment.setText("Show CSS fragment");
+		showCssFragment.setToolTipText("Generates CSS rule block for the selected widget");
+
+		// and for balance
+		new Label(container, SWT.NONE);
+
+		// / The listeners
+
+		cssSearchBox.addModifyListener(new ModifyListener()
+			{
+				private Runnable updater;
+				private IProgressMonitor monitor;
+
+				@Override
+				public void modifyText(ModifyEvent e)
+				{
+					if (monitor != null)
+					{
+						monitor.setCanceled(false);
+					}
+					display.timerExec(200, updater = new Runnable()
+						{
+							@Override
+							public void run()
+							{
+								if (updater == this)
+								{
+									performCSSSearch(monitor = new NullProgressMonitor());
+								}
+							}
+						});
+				}
+			});
+		cssSearchBox.addKeyListener(new KeyAdapter()
+			{
+				@Override
+				public void keyPressed(KeyEvent e)
+				{
+					if (e.keyCode == SWT.ARROW_DOWN && (e.stateMask & SWT.MODIFIER_MASK) == 0)
+					{
+						widgetTreeViewer.getControl().setFocus();
+					}
+				}
+			});
+
+		widgetTreeViewer.addSelectionChangedListener(new ISelectionChangedListener()
+			{
+				@Override
+				public void selectionChanged(SelectionChangedEvent event)
+				{
+					updateForWidgetSelection(event.getSelection());
+					showCssFragment.setEnabled(!event.getSelection().isEmpty());
+				}
+			});
+		if (isLive())
+		{
+			container.addMouseMoveListener(new MouseMoveListener()
+				{
+					@Override
+					public void mouseMove(MouseEvent e)
+					{
+						update();
+					}
+				});
+		}
+
+		if (shouldDismissOnLostFocus())
+		{
+			container.addFocusListener(new FocusAdapter()
+				{
+					@Override
+					public void focusLost(FocusEvent e)
+					{
+						// setReturnCode(Window.OK);
+						// close();
+					}
+				});
+		}
+		/*
+		 * container.addKeyListener(new KeyAdapter() {
+		 *
+		 * @Override public void keyPressed(KeyEvent e) { if (e.character ==
+		 * SWT.ESC) { cancelPressed(); } else if (e.character == SWT.CR |
+		 * e.character == SWT.LF) { okPressed(); } } });
+		 */
+		showAllShells.addSelectionListener(new SelectionAdapter()
+			{
+				@Override
+				public void widgetSelected(SelectionEvent e)
+				{
+					updateWidgetTreeInput();
+				}
+			});
+
+		outer.addDisposeListener(new DisposeListener()
+			{
+				@Override
+				public void widgetDisposed(DisposeEvent e)
+				{
+					dispose();
+				}
+			});
+
+		showUnsetProperties.setSelection(true);
+		showUnsetProperties.addSelectionListener(new SelectionAdapter()
+			{
+				@Override
+				public void widgetSelected(SelectionEvent e)
+				{
+					if (showUnsetProperties.getSelection())
+					{
+						cssPropertiesViewer.removeFilter(unsetPropertyFilter);
+					} else
+					{
+						cssPropertiesViewer.addFilter(unsetPropertyFilter);
+					}
+				}
+			});
+
+		showCssFragment.addSelectionListener(new SelectionAdapter()
+			{
+				@Override
+				public void widgetSelected(SelectionEvent e)
+				{
+					showCssFragment();
+				}
+
+				@Override
+				public void widgetDefaultSelected(SelectionEvent e)
+				{
+					widgetSelected(e);
+				}
+			});
+
+		/*
+		 * if ((specimen != null) && !specimen.isDisposed()) { // Reopen this
+		 * part from a toolbar and widget always displayed. update(); } else {
+		 * if ((specimen != null) && !specimen.isDisposed()) { // Reopen this
+		 * part from a toolbar and widget always displayed. update(); } else {
+		 * // Must set a specimen from application. Control control =
+		 * display.getCursorControl(); // it may be that only the shell was
+		 * selected if (control == null) { control = display.getActiveShell();
+		 * if (control.getParent() != null) { // Take the main shell of this
+		 * window (spy window) control = control.getParent(); } }
+		 * setSpecimen(control); } }
+		 */
+
+		// update(); (called twice)
+		sashForm.setWeights(new int[] { 50, 50 });
+		widgetTreeViewer.getControl().setFocus();
+
+		return outer;
+	}
+
+	/** This method listen to current part and adapt the contents of spy part. */
+	@Inject
+	protected void reactOnActivate(@Named(IServiceConstants.ACTIVE_PART) MPart p, MPart cssPart,
+			@Named(IServiceConstants.ACTIVE_SHELL) Shell s)
+	{
+		if (outer == null)
+		{
+			// Do nothing if no UI created.
+			return;
+		}
+		activeShell = s;
+
+		// Check if control is in the css spy part shell.
+		Control control = display.getCursorControl();
+
+		Shell controlShell = (control == null) ? display.getActiveShell() : control.getShell();
+		Shell spyPartShell = outer.getShell();
+
+		if (spyPartShell != controlShell)
+		{
+			// A widget has been selected in another shell.. We can display the
+			// corresponding control as a specimen
+			shown = null;
+			setSpecimen(control);
+		}
+		else if (p != cssPart)
+		{
+			// Must remove the highlights if selected
+			disposeHighlights();
+		}
+
+
+	}
+
+	protected void showCssFragment()
+	{
+		if (!(widgetTreeViewer.getSelection() instanceof IStructuredSelection) || widgetTreeViewer.getSelection().isEmpty())
+		{
+			return;
+		}
+
+		StringBuilder sb = new StringBuilder();
+		for (Object o : ((IStructuredSelection) widgetTreeViewer.getSelection()).toArray())
+		{
+			if (o instanceof Widget)
+			{
+				if (sb.length() > 0)
+				{
+					sb.append('\n');
+				}
+				addCssFragment((Widget) o, sb);
+			}
+		}
+		TextPopupDialog tpd = new TextPopupDialog(widgetTreeViewer.getControl().getShell(), "CSS", sb.toString(), true,
+				"Escape to dismiss");
+		tpd.open();
+	}
+
+	private void addCssFragment(Widget w, StringBuilder sb)
+	{
+		CSSStylableElement element = getCSSElement(w);
+		if (element == null)
+		{
+			return;
+		}
+
+		sb.append(element.getLocalName());
+		if (element.getCSSId() != null)
+		{
+			sb.append("#").append(element.getCSSId());
+		}
+		sb.append(" {");
+
+		CSSEngine engine = getCSSEngine(element);
+		// we first check the viewCSS and then the property values
+		CSSStyleDeclaration decl = engine.getViewCSS().getComputedStyle(element, null);
+
+		List<String> propertyNames = new ArrayList<String>(engine.getCSSProperties(element));
+		Collections.sort(propertyNames);
+
+		int count = 0;
+
+		// First list the generated properties
+		for (Iterator<String> iter = propertyNames.iterator(); iter.hasNext();)
+		{
+			String propertyName = iter.next();
+			String genValue = trim(engine.retrieveCSSProperty(element, propertyName, ""));
+			String declValue = null;
+
+			if (genValue == null)
+			{
+				continue;
+			}
+
+			if (decl != null)
+			{
+				CSSValue cssValue = decl.getPropertyCSSValue(propertyName);
+				if (cssValue != null)
+				{
+					declValue = trim(cssValue.getCssText());
+				}
+			}
+			if (count == 0)
+			{
+				sb.append("\n  /* actual values */");
+			}
+			sb.append("\n  ").append(propertyName).append(": ").append(genValue).append(";");
+			if (declValue != null)
+			{
+				sb.append("\t/* declared in CSS: ").append(declValue).append(" */");
+			}
+			count++;
+			iter.remove(); // remove so we don't re-report below
+		}
+
+		// then list any declared properties; generated properties already
+		// removed
+		if (decl != null)
+		{
+			int declCount = 0;
+			for (String propertyName : propertyNames)
+			{
+				String declValue = null;
+				CSSValue cssValue = decl.getPropertyCSSValue(propertyName);
+				if (cssValue != null)
+				{
+					declValue = trim(cssValue.getCssText());
+				}
+				if (declValue == null)
+				{
+					continue;
+				}
+				if (declCount == 0)
+				{
+					sb.append("\n\n  /* declared in CSS rules */");
+				}
+				sb.append("\n  ").append(propertyName).append(": ").append(declValue).append(";");
+				count++;
+				declCount++;
+			}
+		}
+		sb.append(count > 0 ? "\n}" : "}");
+	}
+
+	/** Trim the string; return null if empty */
+	private String trim(String s)
+	{
+		if (s == null)
+		{
+			return null;
+		}
+		s = s.trim();
+		return s.length() > 0 ? s : null;
+	}
+
+	protected void performCSSSearch(IProgressMonitor progress)
+	{
+		List<Widget> widgets = new ArrayList<Widget>();
+		performCSSSearch(progress, cssSearchBox.getText(), widgets);
+		if (!progress.isCanceled())
+		{
+			revealAndSelect(widgets);
+		}
+	}
+
+	private void performCSSSearch(IProgressMonitor monitor, String text, Collection<Widget> results)
+	{
+		if (text.trim().length() == 0)
+		{
+			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;
+			}
+
+			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(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))
+			{
+				break;
+			}
+		}
+		if (matched)
+		{
+			results.add((Widget) element.getNativeWidget());
+		}
+		monitor.worked(5);
+		for (int i = 0; i < children.getLength(); i++)
+		{
+			if (monitor.isCanceled())
+			{
+				return;
+			}
+			processCSSSearch(new SubProgressMonitor(monitor, 5), engine, selectors, (CSSStylableElement) children.item(i), pseudo,
+					results);
+		}
+		monitor.done();
+	}
+
+	protected void dispose()
+	{
+		disposeHighlights();
+	}
+
+}