diff options
author | Olivier Prouvost | 2014-06-30 17:09:34 +0000 |
---|---|---|
committer | Olivier Prouvost | 2014-06-30 22:37:32 +0000 |
commit | 22abeb62d51c79021f2224463ff132f8544a642e (patch) | |
tree | fc5465704fd987cdcd124e594d5d239864a8c8bc | |
parent | dc053372d3ccb9d48d7e233a886a356f4c48c395 (diff) | |
download | org.eclipse.e4.tools-22abeb62d51c79021f2224463ff132f8544a642e.tar.gz org.eclipse.e4.tools-22abeb62d51c79021f2224463ff132f8544a642e.tar.xz org.eclipse.e4.tools-22abeb62d51c79021f2224463ff132f8544a642e.zip |
Bug 428903 - Having a common 'debug' window for all spiesI20140702-2200I20140702-0900I20140701-2200I20140630-2200
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>
7 files changed, 1317 insertions, 7 deletions
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 13b3a386..d7a24afc 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 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.6.0", 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 Binary files differnew file mode 100644 index 00000000..b226e41c --- /dev/null +++ b/bundles/org.eclipse.e4.tools.css.spy/icons/css_scratchpad.gif diff --git a/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif b/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif Binary files differnew file mode 100644 index 00000000..80c152ab --- /dev/null +++ b/bundles/org.eclipse.e4.tools.css.spy/icons/cssspy.gif diff --git a/bundles/org.eclipse.e4.tools.css.spy/plugin.xml b/bundles/org.eclipse.e4.tools.css.spy/plugin.xml index c442ab42..82d1ffe0 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 00000000..aa6c47c2 --- /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 982596ad..f5eae299 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.NodeList; 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 @@ public class CssSpyDialog extends Dialog { 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 @@ public class CssSpyDialog extends Dialog { widgetTreeViewer .addSelectionChangedListener(new ISelectionChangedListener() { + @Override public void selectionChanged(SelectionChangedEvent event) { updateForWidgetSelection(event.getSelection()); showCssFragment.setEnabled(!event.getSelection() @@ -699,6 +708,7 @@ public class CssSpyDialog extends Dialog { }); if (isLive()) { container.addMouseMoveListener(new MouseMoveListener() { + @Override public void mouseMove(MouseEvent e) { update(); } @@ -732,6 +742,7 @@ public class CssSpyDialog extends Dialog { }); 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 00000000..826a328e --- /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(); + } + +} |