Bug 515922 - Contribute Glace http://ystrot.github.io/glance/ to the e4
project
Change-Id: I55b2fd953d3064aa169c77cc5501decb3501b4ed
Signed-off-by: Lars Vogel <Lars.Vogel@vogella.com>
diff --git a/bundles/org.eclipse.ui.glance/.classpath b/bundles/org.eclipse.ui.glance/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.ui.glance/.project b/bundles/org.eclipse.ui.glance/.project
new file mode 100644
index 0000000..d48c9ce
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.ui.glance</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.ui.glance/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.glance/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2e672bb
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/META-INF/MANIFEST.MF
@@ -0,0 +1,25 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Eclipse Glance
+Bundle-SymbolicName: org.eclipse.ui.glance;singleton:=true
+Bundle-Version: 0.0.1.qualifier
+Bundle-Activator: org.eclipse.ui.glance.internal.GlancePlugin
+Bundle-Vendor: Exyte
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.ui.editors,
+ org.eclipse.jface.text,
+ org.eclipse.ui.ide
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
+Export-Package: org.eclipse.ui.glance.panels,
+ org.eclipse.ui.glance.sources,
+ org.eclipse.ui.glance.utils,
+ org.eclipse.ui.glance.controls.decor,
+ org.eclipse.ui.glance.controls.descriptors,
+ org.eclipse.ui.glance.controls.items,
+ org.eclipse.ui.glance.controls.table,
+ org.eclipse.ui.glance.controls.text.styled,
+ org.eclipse.ui.glance.controls.tree,
+ org.eclipse.ui.glance.viewers.descriptors
+
diff --git a/bundles/org.eclipse.ui.glance/build.properties b/bundles/org.eclipse.ui.glance/build.properties
new file mode 100644
index 0000000..0bf3861
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/build.properties
@@ -0,0 +1,17 @@
+###############################################################################
+# Copyright (c) 2017 Exyte
+# 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:
+# Yuri Strot - initial API and implementation
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ icons/,\
+ schema/
diff --git a/bundles/org.eclipse.ui.glance/icons/marker.gif b/bundles/org.eclipse.ui.glance/icons/marker.gif
new file mode 100644
index 0000000..10c71c0
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/marker.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/next.gif b/bundles/org.eclipse.ui.glance/icons/next.gif
new file mode 100644
index 0000000..8667900
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/next.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/prev.gif b/bundles/org.eclipse.ui.glance/icons/prev.gif
new file mode 100644
index 0000000..cd9a705
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/prev.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/search.png b/bundles/org.eclipse.ui.glance/icons/search.png
new file mode 100644
index 0000000..6c05b9b
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/search.png
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/update_1.gif b/bundles/org.eclipse.ui.glance/icons/update_1.gif
new file mode 100644
index 0000000..3ca04d0
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/update_1.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/update_2.gif b/bundles/org.eclipse.ui.glance/icons/update_2.gif
new file mode 100644
index 0000000..3241d7c
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/update_2.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/update_done.gif b/bundles/org.eclipse.ui.glance/icons/update_done.gif
new file mode 100644
index 0000000..e4a1d38
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/update_done.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/icons/wait.gif b/bundles/org.eclipse.ui.glance/icons/wait.gif
new file mode 100644
index 0000000..6bc0eeb
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/icons/wait.gif
Binary files differ
diff --git a/bundles/org.eclipse.ui.glance/plugin.xml b/bundles/org.eclipse.ui.glance/plugin.xml
new file mode 100644
index 0000000..bc87ba7
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/plugin.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<!--
+ Copyright (c) 2017 Exyte
+ 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:
+ Yuri Strot - initial API and implementation
+ -->
+
+<plugin>
+ <extension-point id="sources" name="sources" schema="schema/sources.exsd"/>
+ <extension-point id="excludedSources" name="excludedSources" schema="schema/excludedSources.exsd"/>
+
+ <extension
+ point="org.eclipse.ui.handlers">
+ <handler
+ commandId="org.eclipse.ui.glance.commands.open"
+ class="org.eclipse.ui.glance.internal.OpenSearchPanelHandler">
+ </handler>
+ </extension>
+
+ <extension point="org.eclipse.ui.startup">
+ <startup class="org.eclipse.ui.glance.internal.GlanceStartup"/>
+ </extension>
+
+ <extension point="org.eclipse.ui.commands">
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Open Glance search panel"
+ id="org.eclipse.ui.glance.commands.open"
+ name="Open Glance">
+ </command>
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Close Glance search panel"
+ id="org.eclipse.ui.glance.commands.close"
+ name="Close Glance">
+ </command>
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Show next Glance match"
+ id="org.eclipse.ui.glance.nextResult"
+ name="Next Match">
+ </command>
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Show previous Glance match"
+ id="org.eclipse.ui.glance.prevResult"
+ name="Previous Match">
+ </command>
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Return focus back from Glance search panel to control"
+ id="org.eclipse.ui.glance.commands.focus"
+ name="Return Focus Back">
+ </command>
+ <command
+ categoryId="org.eclipse.ui.glance.commands"
+ description="Clear Glance search history"
+ id="org.eclipse.ui.glance.commands.clearHistory"
+ name="Clear History">
+ </command>
+ <category
+ description="Glance search commands"
+ id="org.eclipse.ui.glance.commands"
+ name="Glance">
+ </category>
+ </extension>
+
+ <extension point="org.eclipse.ui.bindings">
+ <key
+ sequence="M1+M3+F"
+ contextId="org.eclipse.ui.contexts.dialogAndWindow"
+ commandId="org.eclipse.ui.glance.commands.open"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
+ <key
+ commandId="org.eclipse.ui.glance.commands.focus"
+ contextId="org.eclipse.ui.glance.context"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M1+M3+F">
+ </key>
+ <key
+ commandId="org.eclipse.ui.glance.commands.close"
+ contextId="org.eclipse.ui.glance.context"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="ESC">
+ </key>
+ <key
+ commandId="org.eclipse.ui.glance.nextResult"
+ contextId="org.eclipse.ui.glance.context"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="ENTER">
+ </key>
+ <key
+ commandId="org.eclipse.ui.glance.prevResult"
+ contextId="org.eclipse.ui.glance.context"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M2+ENTER">
+ </key>
+ <key
+ commandId="org.eclipse.ui.glance.commands.clearHistory"
+ contextId="org.eclipse.ui.glance.context"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M1+M2+BS">
+ </key>
+ <sequenceModifier find="M1+M3" replace="M1+M4" platforms="cocoa,carbon"/>
+ </extension>
+
+ <extension point="org.eclipse.ui.preferencePages">
+ <page
+ class="org.eclipse.ui.glance.internal.preferences.GlancePreferencePage"
+ id="org.eclipse.ui.glance.preference"
+ category="org.eclipse.ui.preferencePages.Workbench"
+ name="Glance Search"/>
+ </extension>
+
+ <extension point="org.eclipse.core.runtime.preferences">
+ <initializer class="org.eclipse.ui.glance.internal.preferences.GlancePreferenceInitializer"/>
+ </extension>
+
+ <extension point="org.eclipse.core.resources.markers" id="marker" name="Glance Results">
+ <super type="org.eclipse.core.resources.textmarker"/>
+ <attribute name="line"/>
+ </extension>
+
+ <extension point="org.eclipse.ui.ide.markerImageProviders">
+ <imageprovider
+ id="org.eclipse.ui.glance.markerProvider"
+ markertype="org.eclipse.ui.glance.marker"
+ icon="icons/marker.gif">
+ </imageprovider>
+ </extension>
+
+ <extension point="org.eclipse.ui.editors.annotationTypes">
+ <type
+ name="org.eclipse.ui.glance.highlight"
+ markerType="org.eclipse.ui.glance.marker">
+ </type>
+ <type
+ markerType="org.eclipse.ui.glance.marker"
+ name="org.eclipse.ui.glance.select">
+ </type>
+ </extension>
+
+ <extension point="org.eclipse.ui.editors.markerAnnotationSpecification">
+ <specification
+ annotationType="org.eclipse.ui.glance.highlight"
+ label="Glance Results"
+ icon="icons/marker.gif"
+ textPreferenceKey="glanceText"
+ textPreferenceValue="false"
+ highlightPreferenceKey="glanceHighlight"
+ highlightPreferenceValue="true"
+ overviewRulerPreferenceKey="glanceOverviewRuler"
+ overviewRulerPreferenceValue="true"
+ verticalRulerPreferenceKey="glanceVerticalRuler"
+ verticalRulerPreferenceValue="true"
+ colorPreferenceKey="glanceColorBackground"
+ colorPreferenceValue="255,255,128"
+ presentationLayer="5"
+ showInNextPrevDropdownToolbarActionKey="glanceShowNextPrev"
+ showInNextPrevDropdownToolbarAction="true"
+ isGoToNextNavigationTargetKey="glanceGoNext"
+ isGoToNextNavigationTarget="false"
+ isGoToPreviousNavigationTargetKey="glanceGoPrev"
+ isGoToPreviousNavigationTarget="false">
+ </specification>
+ <specification
+ annotationType="org.eclipse.ui.glance.select"
+ colorPreferenceKey="glanceSelectedColorBackground"
+ colorPreferenceValue="255,128,0"
+ highlightPreferenceKey="glanceHighlight"
+ highlightPreferenceValue="true"
+ icon="icons/marker.gif"
+ isGoToNextNavigationTarget="false"
+ isGoToNextNavigationTargetKey="glanceGoNext"
+ isGoToPreviousNavigationTarget="false"
+ isGoToPreviousNavigationTargetKey="glanceGoPrev"
+ label="Glance Selected Result"
+ overviewRulerPreferenceKey="glanceOverviewRuler"
+ overviewRulerPreferenceValue="true"
+ presentationLayer="6"
+ showInNextPrevDropdownToolbarAction="true"
+ showInNextPrevDropdownToolbarActionKey="glanceShowNextPrev"
+ textPreferenceKey="glanceText"
+ textPreferenceValue="false"
+ verticalRulerPreferenceKey="glanceVerticalRuler"
+ verticalRulerPreferenceValue="true">
+ </specification>
+ </extension>
+ <extension
+ point="org.eclipse.ui.contexts">
+ <context
+ id="org.eclipse.ui.glance.context"
+ name="Glance Search Context">
+ </context>
+ </extension>
+ <extension point="org.eclipse.ui.glance.sources">
+ <source
+ class="org.eclipse.ui.glance.controls.descriptors.StyledTextDescriptor"
+ priority="1"/>
+ <source
+ class="org.eclipse.ui.glance.controls.descriptors.ListeningStyledTextDescriptor"
+ priority="5"/>
+ <source
+ class="org.eclipse.ui.glance.controls.descriptors.TreeDescriptor"
+ priority="1"/>
+ <source
+ class="org.eclipse.ui.glance.controls.descriptors.TableDescriptor"
+ priority="1"/>
+ <source
+ class="org.eclipse.ui.glance.viewers.descriptors.SourceViewerDescriptor"
+ priority="3"/>
+ <source
+ class="org.eclipse.ui.glance.viewers.descriptors.TextViewerDescriptor"
+ priority="2"/>
+ </extension>
+</plugin>
diff --git a/bundles/org.eclipse.ui.glance/pom.xml b/bundles/org.eclipse.ui.glance/pom.xml
new file mode 100644
index 0000000..79a7588
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2017 Exyte
+ 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:
+ Yuri Strot - initial API and implementation
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>plugins</artifactId>
+ <groupId>org.eclipse.ui.glance</groupId>
+ <version>0.0.1-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.eclipse.ui.glance</groupId>
+ <artifactId>org.eclipse.ui.glance</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>eclipse-plugin</packaging>
+
+</project>
diff --git a/bundles/org.eclipse.ui.glance/schema/excludedSources.exsd b/bundles/org.eclipse.ui.glance/schema/excludedSources.exsd
new file mode 100644
index 0000000..7adf813
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/schema/excludedSources.exsd
@@ -0,0 +1,102 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.ui.glance" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appInfo>
+ <meta.schema plugin="org.eclipse.ui.glance" id="excludedTextProvider" name="excludedTextProvider"/>
+ </appInfo>
+ <documentation>
+ [Enter description of this extension point.]
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appInfo>
+ <meta.element />
+ </appInfo>
+ </annotation>
+ <complexType>
+ <sequence>
+ <element ref="source" minOccurs="1" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="source">
+ <complexType>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="java" basedOn=":org.eclipse.ui.glance.sources.ITextSourceDescriptor"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="since"/>
+ </appInfo>
+ <documentation>
+ [Enter the first release in which this extension point appears.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="examples"/>
+ </appInfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="apiinfo"/>
+ </appInfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="implementation"/>
+ </appInfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+
+</schema>
diff --git a/bundles/org.eclipse.ui.glance/schema/sources.exsd b/bundles/org.eclipse.ui.glance/schema/sources.exsd
new file mode 100644
index 0000000..081c813
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/schema/sources.exsd
@@ -0,0 +1,109 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.ui.glance" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appinfo>
+ <meta.schema plugin="org.eclipse.ui.glance" id="textProvider" name="textProvider"/>
+ </appinfo>
+ <documentation>
+ [Enter description of this extension point.]
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appinfo>
+ <meta.element />
+ </appinfo>
+ </annotation>
+ <complexType>
+ <sequence>
+ <element ref="source" minOccurs="1" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute translatable="true"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="source">
+ <complexType>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="java" basedOn=":org.eclipse.ui.glance.sources.ITextSourceDescriptor"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ <attribute name="priority" type="string" use="default" value="1">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="since"/>
+ </appinfo>
+ <documentation>
+ [Enter the first release in which this extension point appears.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="examples"/>
+ </appinfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="apiinfo"/>
+ </appinfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="implementation"/>
+ </appinfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+
+</schema>
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/Cell.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/Cell.java
new file mode 100644
index 0000000..cddaae8
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/Cell.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+public abstract class Cell {
+
+ private int column;
+
+ public Cell(int column) {
+ this.column = column;
+ }
+
+ public int getColumn() {
+ return column;
+ }
+
+ @Override
+ public int hashCode() {
+ return getElement().hashCode() ^ column;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ Cell cell = (Cell) obj;
+ return cell.getElement().equals(this.getElement())
+ && cell.column == column;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("(");
+ buffer.append(getElement());
+ buffer.append(", ");
+ buffer.append(column);
+ buffer.append(")");
+ return buffer.toString();
+ }
+
+ protected abstract Object getElement();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IPath.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IPath.java
new file mode 100644
index 0000000..2e20960
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IPath.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import org.eclipse.swt.widgets.Composite;
+
+public interface IPath {
+
+ public void select(Composite composite);
+
+ public void discardSelection();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructContent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructContent.java
new file mode 100644
index 0000000..1fba4ee
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructContent.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+
+public interface IStructContent {
+
+ public void index(IProgressMonitor monitor);
+
+ public ITextBlock[] getBlocks();
+
+ public ITextBlock getContent(StructCell cell);
+
+ public IPath getPath(ITextBlock block);
+
+ public void dispose();
+
+ public void addListener(ITextSourceListener listener);
+
+ public void removeListener(ITextSourceListener listener);
+
+ public ITextSourceListener[] getListeners();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructProvider.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructProvider.java
new file mode 100644
index 0000000..9e218c8
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/IStructProvider.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import org.eclipse.swt.widgets.Item;
+
+public interface IStructProvider {
+
+ StructCell getCell(Item item, int column);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructCell.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructCell.java
new file mode 100644
index 0000000..05052eb
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructCell.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import org.eclipse.jface.util.Policy;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Item;
+
+import org.eclipse.ui.glance.utils.TextUtils;
+
+public abstract class StructCell extends Cell {
+
+ static final StyleRange[] NO_STYLES = new StyleRange[0];
+
+ static final String KEY_TEXT_LAYOUT = Policy.JFACE + "styled_label_key_"; //$NON-NLS-1$
+
+ public StyleRange[] styles = NO_STYLES;
+
+ public StructCell(int column) {
+ super(column);
+ }
+
+ public abstract Rectangle getBounds();
+
+ public abstract Color getForeground();
+
+ public abstract Color getBackground();
+
+ public abstract Image getImage();
+
+ public abstract Rectangle getImageBounds();
+
+ public abstract Rectangle getTextBounds();
+
+ public abstract String getText();
+
+ public abstract Font getFont();
+
+ public abstract boolean isSelected();
+
+ @Override
+ protected Object getElement() {
+ return getItem();
+ }
+
+ protected abstract Item getItem();
+
+ protected StyleRange[] nativeStyles() {
+ String key = KEY_TEXT_LAYOUT + getColumn();
+ Object data = getItem().getData(key);
+ if (data instanceof StyleRange[]) {
+ return TextUtils.copy((StyleRange[]) data);
+ }
+ return new StyleRange[0];
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructDecorator.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructDecorator.java
new file mode 100644
index 0000000..f78d23f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructDecorator.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import java.lang.reflect.Field;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.TextLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Tree;
+
+import org.eclipse.ui.glance.sources.ColorManager;
+
+public class StructDecorator implements Listener {
+
+ private Composite composite;
+ private IStructProvider provider;
+
+ private TextLayout textLayout;
+
+ public StructDecorator(Composite composite, IStructProvider provider) {
+ this.composite = composite;
+ this.provider = provider;
+ init();
+ }
+
+ public void redraw() {
+ Rectangle rect = composite.getClientArea();
+ composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
+ }
+
+ public void redraw(StructCell cell) {
+ Rectangle rect = cell.getBounds();
+ composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
+ }
+
+ protected TextLayout getTextLayout() {
+ if (textLayout == null) {
+ int orientation = composite.getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT);
+ textLayout = new TextLayout(composite.getDisplay());
+ textLayout.setOrientation(orientation);
+ } else {
+ textLayout.setText("");
+ }
+ return textLayout;
+ }
+
+ public void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.PaintItem:
+ paint(event);
+ break;
+ case SWT.EraseItem:
+ erase(event);
+ break;
+ }
+ }
+
+ protected void paint(Event event) {
+ Item item = (Item) event.item;
+ GC gc = event.gc;
+ // remember colors to restore the GC later
+ Color oldForeground = gc.getForeground();
+ Color oldBackground = gc.getBackground();
+
+ StructCell cell = provider.getCell(item, event.index);
+
+ Color foreground = cell.getForeground();
+ if (foreground != null) {
+ gc.setForeground(foreground);
+ }
+
+ Color background = cell.getBackground();
+ if (background != null) {
+ gc.setBackground(background);
+ }
+
+ if (!ColorManager.getInstance().isUseNative() && cell.isSelected()) {
+ gc.setBackground(ColorManager.getInstance().getTreeSelectionBg());
+ gc.setForeground(ColorManager.getInstance().getTreeSelectionFg());
+ gc.fillRectangle(cell.getBounds());
+ }
+
+ Image image = cell.getImage();
+ if (image != null) {
+ Rectangle imageBounds = cell.getImageBounds();
+ if (imageBounds != null) {
+ Rectangle bounds = image.getBounds();
+
+ // center the image in the given space
+ int x = imageBounds.x + Math.max(0, (imageBounds.width - bounds.width) / 2);
+ int y = imageBounds.y + Math.max(0, (imageBounds.height - bounds.height) / 2);
+ gc.drawImage(image, x, y);
+ }
+ }
+
+ Rectangle textBounds = cell.getTextBounds();
+ if (textBounds != null) {
+ TextLayout layout = getTextLayout();
+ layout.setText(cell.getText());
+ layout.setFont(cell.getFont());
+
+ StyleRange[] styles = cell.styles;
+ for (StyleRange range : styles) {
+ layout.setStyle(range, range.start, range.start + range.length - 1);
+ }
+
+ Rectangle layoutBounds = layout.getBounds();
+
+ int x = textBounds.x;
+ int avg = (textBounds.height - layoutBounds.height) / 2;
+ int y = textBounds.y + Math.max(0, avg);
+
+ layout.draw(gc, x, y);
+ }
+
+ gc.setForeground(oldForeground);
+ gc.setBackground(oldBackground);
+ }
+
+ public void erase(Event event) {
+ int style = SWT.BACKGROUND | SWT.FOREGROUND;
+ if (!ColorManager.getInstance().isUseNative()) {
+ style |= SWT.SELECTED | SWT.HOT;
+ }
+
+ event.detail &= ~style;
+ }
+
+ protected void init() {
+ eraseListeners = addListener(SWT.EraseItem);
+ paintListeners = addListener(SWT.PaintItem);
+ redraw();
+ }
+
+ private Listener[] addListener(int event) {
+ Listener[] listeners = composite.getListeners(event);
+ // should never happen, but just in case
+ if (listeners == null) {
+ listeners = new Listener[0];
+ }
+ for (Listener listener : listeners) {
+ composite.removeListener(event, listener);
+ }
+ composite.addListener(event, this);
+ return listeners;
+ }
+
+ public void dispose() {
+ if (!disposed) {
+ disposed = true;
+ if (!composite.isDisposed()) {
+ composite.removeListener(SWT.EraseItem, StructDecorator.this);
+ for (Listener listener : eraseListeners) {
+ composite.addListener(SWT.EraseItem, listener);
+ }
+
+ composite.removeListener(SWT.PaintItem, StructDecorator.this);
+ for (Listener listener : paintListeners) {
+ composite.addListener(SWT.PaintItem, listener);
+ }
+
+ if ("gtk".equalsIgnoreCase(Platform.getWS()) && composite instanceof Tree) {
+ try {
+ Field field = composite.getClass().getDeclaredField("drawForeground");
+ field.setAccessible(true);
+ if (field.get(composite) != null) {
+ // System.out.println("Fixed tree drawForeground bug");
+ field.set(composite, null);
+ }
+ } catch (Exception e) {
+ // ignore if no such field
+ }
+ }
+
+ redraw();
+ }
+ }
+ }
+
+ private Listener[] eraseListeners;
+ private Listener[] paintListeners;
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ private boolean disposed;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructSource.java
new file mode 100644
index 0000000..9304fba
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/decor/StructSource.java
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.decor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Item;
+
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+import org.eclipse.ui.glance.utils.TextUtils;
+
+public abstract class StructSource implements ITextSource, IStructProvider,
+ SelectionListener {
+
+ private final StructDecorator decorator;
+ private final Composite composite;
+ protected IStructContent content;
+
+ public StructSource(final Composite composite) {
+ this.composite = composite;
+ content = createContent();
+ decorator = new StructDecorator(composite, this);
+ }
+
+ public StructCell getCell(final Item item, final int column) {
+ final StructCell cell = createCell(item, column);
+ StyleRange[] styles = cellToStyles.get(cell);
+ if (styles == null) {
+ styles = calcStyles(cell);
+ cellToStyles.put(cell, styles);
+ }
+ cell.styles = styles;
+ return cell;
+ }
+
+ public boolean isIndexRequired() {
+ return true;
+ }
+
+ private StyleRange[] calcStyles(final StructCell cell) {
+ final ITextBlock block = content.getContent(cell);
+ blockToCell.put(block, cell);
+
+ final StyleRange[] cellStyles = cell.nativeStyles();
+ final StyleRange[] blockStyles = createStyles(block);
+
+ if (blockStyles == null || blockStyles.length == 0)
+ return cellStyles;
+ if (cellStyles.length == 0)
+ return blockStyles;
+ final Region region = new Region(0, cell.getText().length());
+ final int size = cellStyles.length + blockStyles.length;
+ final TextPresentation presentation = new TextPresentation(region, size);
+ presentation.replaceStyleRanges(cellStyles);
+ presentation.mergeStyleRanges(blockStyles);
+ return TextUtils.getStyles(presentation);
+ }
+
+ protected abstract StructCell createCell(Item item, int column);
+
+ protected abstract IStructContent createContent();
+
+ protected abstract SourceSelection getSourceSelection();
+
+ /**
+ * @return the composite
+ */
+ public Composite getControl() {
+ return composite;
+ }
+
+ public ITextBlock[] getBlocks() {
+ return content.getBlocks();
+ }
+
+ public void dispose() {
+ content.dispose();
+ decorator.dispose();
+ }
+
+ public boolean isDisposed() {
+ return decorator.isDisposed();
+ }
+
+ public void index(final IProgressMonitor monitor) {
+ content.index(monitor);
+ }
+
+ public void widgetDefaultSelected(final SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ public void widgetSelected(final SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ protected void fireSelectionChanged() {
+ discardSelection();
+ final SourceSelection selection = getSourceSelection();
+ final ITextSourceListener[] listeners = content.getListeners();
+ for (final ITextSourceListener listener : listeners) {
+ listener.selectionChanged(selection);
+ }
+ }
+
+ public void select(final Match match) {
+ if (match != null) {
+ discardSelection();
+ path = content.getPath(match.getBlock());
+ path.select(getControl());
+ }
+ setMatch(match);
+ }
+
+ public void show(final Match[] matches) {
+ setMatches(matches);
+ }
+
+ public void addTextSourceListener(final ITextSourceListener listener) {
+ content.addListener(listener);
+ }
+
+ public void removeTextSourceListener(final ITextSourceListener listener) {
+ content.removeListener(listener);
+ }
+
+ private void discardSelection() {
+ if (path != null) {
+ path.discardSelection();
+ path = null;
+ }
+ }
+
+ private void setMatch(final Match match) {
+ final StructCell oldSel = updateSelection(false);
+ selection = match;
+ final StructCell newSel = updateSelection(true);
+ if (oldSel != null) {
+ decorator.redraw(oldSel);
+ }
+ if (newSel != null && newSel != oldSel) {
+ decorator.redraw(newSel);
+ }
+ }
+
+ private StructCell updateSelection(final boolean add) {
+ if (selection != null) {
+ final ITextBlock block = selection.getBlock();
+ final StructCell cell = blockToCell.get(block);
+ if (cell != null) {
+ cellToStyles.remove(cell);
+ return cell;
+ }
+ }
+ return null;
+ }
+
+ private void setMatches(final Match[] matches) {
+ init();
+ for (final Match match : matches) {
+ final ITextBlock block = match.getBlock();
+ List<Match> list = blockToMatches.get(block);
+ if (list == null) {
+ list = new ArrayList<Match>();
+ blockToMatches.put(block, list);
+ }
+ list.add(match);
+ }
+ decorator.redraw();
+ }
+
+ private StyleRange[] createStyles(final ITextBlock block) {
+ final List<StyleRange> list = new ArrayList<StyleRange>();
+ boolean selectionFound = false;
+ final List<Match> matches = blockToMatches.get(block);
+ if (matches != null) {
+ for (final Match match : matches) {
+ final boolean sel = match.equals(selection);
+ selectionFound = selectionFound | sel;
+ final StyleRange range = createStyle(match, sel);
+ list.add(range);
+ }
+ }
+
+ if (!selectionFound && selection != null) {
+ final ITextBlock sBlock = selection.getBlock();
+ if (sBlock.equals(block)) {
+ list.add(createStyle(selection, true));
+ }
+ }
+ return list.toArray(new StyleRange[list.size()]);
+ }
+
+ private StyleRange createStyle(final Match match, final boolean selection) {
+ final Display display = composite.getDisplay();
+ Color fgColor, bgColor;
+ if (selection) {
+ // Lighten to avoid search selection on system selection
+ fgColor = ColorManager.lighten(display
+ .getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT), 50);
+ bgColor = ColorManager.getInstance().getSelectedBackgroundColor();
+ } else {
+ // To avoid white text on light-green background
+ fgColor = display.getSystemColor(SWT.COLOR_BLACK);
+ bgColor = ColorManager.getInstance().getBackgroundColor();
+ }
+ return new StyleRange(match.getOffset(), match.getLength(), fgColor,
+ bgColor);
+ }
+
+ public SourceSelection getSelection() {
+ return null;
+ }
+
+ public void init() {
+ blockToMatches = new HashMap<ITextBlock, List<Match>>();
+ blockToCell = new HashMap<ITextBlock, StructCell>();
+ cellToStyles = new HashMap<StructCell, StyleRange[]>();
+ }
+
+ private Match selection;
+ private IPath path;
+
+ private Map<ITextBlock, List<Match>> blockToMatches;
+ private Map<ITextBlock, StructCell> blockToCell;
+ private Map<StructCell, StyleRange[]> cellToStyles;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/ListeningStyledTextDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/ListeningStyledTextDescriptor.java
new file mode 100644
index 0000000..1a00916
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/ListeningStyledTextDescriptor.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.descriptors;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.controls.text.styled.ListeningStyledTextSource;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ListeningStyledTextDescriptor implements ITextSourceDescriptor {
+
+ public ITextSource createSource(Control control) {
+ return new ListeningStyledTextSource((StyledText) control);
+ }
+
+ public boolean isValid(Control control) {
+ if (control instanceof StyledText) {
+ StyledText text = (StyledText) control;
+ return text.isListening(LineGetStyle);
+ }
+ return false;
+ }
+
+ static final int LineGetStyle = 3002;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/StyledTextDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/StyledTextDescriptor.java
new file mode 100644
index 0000000..4d69641
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/StyledTextDescriptor.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.descriptors;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.controls.text.styled.StyledTextSource;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class StyledTextDescriptor implements ITextSourceDescriptor {
+
+ public ITextSource createSource(Control control) {
+ return new StyledTextSource((StyledText) control);
+ }
+
+ public boolean isValid(Control control) {
+ return control instanceof StyledText;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TableDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TableDescriptor.java
new file mode 100644
index 0000000..bbf79fd
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TableDescriptor.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.descriptors;
+
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Table;
+
+import org.eclipse.ui.glance.controls.table.TableSource;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TableDescriptor implements ITextSourceDescriptor {
+
+ public ITextSource createSource(Control control) {
+ return new TableSource((Table) control);
+ }
+
+ public boolean isValid(Control control) {
+ return control instanceof Table;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TreeDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TreeDescriptor.java
new file mode 100644
index 0000000..0b5117b
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/descriptors/TreeDescriptor.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.descriptors;
+
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Tree;
+
+import org.eclipse.ui.glance.controls.tree.TreeControlSource;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TreeDescriptor implements ITextSourceDescriptor {
+
+ public ITextSource createSource(Control control) {
+ return new TreeControlSource((Tree) control);
+ }
+
+ public boolean isValid(Control control) {
+ return control instanceof Tree;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemCell.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemCell.java
new file mode 100644
index 0000000..bb7af7d
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemCell.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.items;
+
+import org.eclipse.jface.util.Policy;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Item;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.utils.TextUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ItemCell implements ITextBlock {
+
+ private Item item;
+ private int index;
+ private ItemProvider provider;
+
+ public static final String KEY_TEXT_LAYOUT = Policy.JFACE
+ + "styled_label_key_"; //$NON-NLS-1$
+
+ public ItemCell(Item item, int index, ItemProvider provider) {
+ this.item = item;
+ this.index = index;
+ this.provider = provider;
+ }
+
+ public Image getImage() {
+ return provider.getImage(item, index);
+ }
+
+ public Object getKey() {
+ Object data = item.getData();
+ if (data != null)
+ return data;
+ return item;
+ }
+
+ /**
+ * @return the item
+ */
+ public Item getItem() {
+ return item;
+ }
+
+ public String getText() {
+ return provider.getColumnCount(item) == 0 ? item.getText() : provider
+ .getText(item, index);
+ }
+
+ public int getLength() {
+ return getText().length();
+ }
+
+ public StyleRange[] getStyles() {
+ String key = KEY_TEXT_LAYOUT + index;
+ Object data = item.getData(key);
+ if (data instanceof StyleRange[]) {
+ return TextUtils.copy((StyleRange[]) data);
+ }
+ return new StyleRange[0];
+ }
+
+ /**
+ * @return the index
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return item.hashCode() ^ index;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ ItemCell item = (ItemCell) obj;
+ return item.item.equals(this.item) && item.index == index;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(ITextBlock block) {
+ ItemCell cell = (ItemCell) block;
+ return provider.compare(item, cell.item);
+ }
+
+ public void addTextBlockListener(ITextBlockListener listener) {
+ }
+
+ public void removeTextBlockListener(ITextBlockListener listener) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("{");
+ buffer.append(item);
+ buffer.append(", ");
+ buffer.append(index);
+ buffer.append("}");
+ return buffer.toString();
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemDecorator.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemDecorator.java
new file mode 100644
index 0000000..e6aaf5d
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemDecorator.java
@@ -0,0 +1,283 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.items;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.TextLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Listener;
+
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.utils.TextUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ItemDecorator implements Listener {
+
+ public static final int DEFAULT_STYLE = SWT.BACKGROUND | SWT.FOREGROUND | SWT.SELECTED | SWT.HOT;
+
+ protected Composite composite;
+ protected ItemProvider provider;
+ protected int style;
+
+ protected TextLayout textLayout;
+ protected Map<ItemCell, StyleRange[]> itemToMatches;
+ protected Map<ItemCell, StyleRange[]> cacheStyles;
+ protected List<ItemCell> cells;
+ protected HashSet<ItemCell> cellSet;
+
+ private ListenerList listeners = new ListenerList();
+
+ public ItemDecorator(Composite composite, ItemProvider provider) {
+ this(composite, provider, DEFAULT_STYLE);
+ }
+
+ public ItemDecorator(Composite composite, ItemProvider provider, int style) {
+ this.composite = composite;
+ this.provider = provider;
+ this.style = style;
+ clearStyles();
+ init();
+ }
+
+ public void addTextSourceListener(ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTextSourceListener(ITextSourceListener listener) {
+ listeners.remove(listener);
+ }
+
+ public ITextSourceListener[] getListeners() {
+ Object[] objects = listeners.getListeners();
+ ITextSourceListener[] listeners = new ITextSourceListener[objects.length];
+ System.arraycopy(objects, 0, listeners, 0, objects.length);
+ return listeners;
+ }
+
+ public void blocksChanged(ItemCell[] removed, ItemCell[] added) {
+ for (ItemCell cell : removed) {
+ cells.remove(cell);
+ cellSet.remove(cell);
+ }
+ for (ItemCell cell : added) {
+ cells.add(cell);
+ cellSet.add(cell);
+ cell.getItem().addListener(SWT.Dispose, this);
+ }
+ for (ITextSourceListener listener : getListeners()) {
+ listener.blocksChanged(removed, added);
+ }
+ }
+
+ public void clearStyles() {
+ itemToMatches = new HashMap<ItemCell, StyleRange[]>();
+ cacheStyles = new HashMap<ItemCell, StyleRange[]>();
+ }
+
+ public List<ItemCell> getCells() {
+ return cells;
+ }
+
+ public void setCells(List<ItemCell> cells) {
+ this.cells = cells;
+ for (ItemCell cell : cells) {
+ cell.getItem().addListener(SWT.Dispose, this);
+ }
+ cellSet = new HashSet<ItemCell>(cells);
+ }
+
+ public void setStyles(ItemCell cell, StyleRange[] styles) {
+ if (styles == null)
+ itemToMatches.remove(cell);
+ else
+ itemToMatches.put(cell, styles);
+ cacheStyles.put(cell, calculateStyles(cell));
+ }
+
+ public void redraw() {
+ Rectangle rect = composite.getClientArea();
+ composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
+ }
+
+ public void redraw(ItemCell cell) {
+ Rectangle rect = provider.getBounds(cell.getItem(), cell.getIndex());
+ composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
+ }
+
+ protected TextLayout getTextLayout() {
+ if (textLayout == null) {
+ int orientation = composite.getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT);
+ textLayout = new TextLayout(composite.getDisplay());
+ textLayout.setOrientation(orientation);
+ } else {
+ textLayout.setText("");
+ }
+ return textLayout;
+ }
+
+ protected StyleRange[] getRanges(ItemCell cell) {
+ StyleRange[] ranges = cacheStyles.get(cell);
+ if (ranges == null) {
+ ranges = calculateStyles(cell);
+ cacheStyles.put(cell, ranges);
+ }
+ return ranges;
+ }
+
+ protected StyleRange[] calculateStyles(ItemCell cell) {
+ StyleRange[] cellStyles = cell.getStyles();
+ StyleRange[] matchStyles = itemToMatches.get(cell);
+ if (matchStyles == null || matchStyles.length == 0)
+ return cellStyles;
+ if (cellStyles.length == 0)
+ return matchStyles;
+ Region region = new Region(0, cell.getLength());
+ int size = cellStyles.length + matchStyles.length;
+ TextPresentation presentation = new TextPresentation(region, size);
+ presentation.replaceStyleRanges(cellStyles);
+ presentation.mergeStyleRanges(matchStyles);
+ return TextUtils.getStyles(presentation);
+ }
+
+ public void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.PaintItem:
+ paint(event);
+ break;
+ case SWT.EraseItem:
+ erase(event);
+ break;
+ case SWT.Dispose:
+ if (event.widget instanceof Item) {
+ ItemCell cell = new ItemCell((Item) event.widget, event.index, provider);
+ blocksChanged(new ItemCell[] { cell }, new ItemCell[0]);
+ }
+ break;
+ }
+ }
+
+ protected void paint(Event event) {
+ Item item = (Item) event.item;
+ GC gc = event.gc;
+ // remember colors to restore the GC later
+ Color oldForeground = gc.getForeground();
+ Color oldBackground = gc.getBackground();
+
+ Color foreground = provider.getForeground(item, event.index);
+ if (foreground != null) {
+ gc.setForeground(foreground);
+ }
+
+ Color background = provider.getBackground(item, event.index);
+ if (background != null) {
+ gc.setBackground(background);
+ }
+
+ if (!ColorManager.getInstance().isUseNative() && (event.detail & SWT.SELECTED) != 0) {
+ gc.setBackground(ColorManager.getInstance().getTreeSelectionBg());
+ gc.setForeground(ColorManager.getInstance().getTreeSelectionFg());
+ gc.fillRectangle(provider.getBounds(item, event.index));
+ }
+
+ Image image = provider.getImage(item, event.index);
+ if (image != null) {
+ Rectangle imageBounds = provider.getImageBounds(item, event.index);
+ if (imageBounds != null) {
+ Rectangle bounds = image.getBounds();
+
+ // center the image in the given space
+ int x = imageBounds.x + Math.max(0, (imageBounds.width - bounds.width) / 2);
+ int y = imageBounds.y + Math.max(0, (imageBounds.height - bounds.height) / 2);
+ gc.drawImage(image, x, y);
+ }
+ }
+
+ Rectangle textBounds = provider.getTextBounds(item, event.index);
+ if (textBounds != null) {
+ TextLayout layout = getTextLayout();
+ layout.setText(provider.getText(item, event.index));
+ layout.setFont(provider.getFont(item, event.index));
+
+ ItemCell cell = new ItemCell(item, event.index, provider);
+ if (!cellSet.contains(cell)) {
+ blocksChanged(new ItemCell[0], new ItemCell[] { cell });
+ }
+ StyleRange[] ranges = getRanges(cell);
+ for (StyleRange range : ranges) {
+ layout.setStyle(range, range.start, range.start + range.length - 1);
+ }
+
+ Rectangle layoutBounds = layout.getBounds();
+
+ int x = textBounds.x;
+ int avg = (textBounds.height - layoutBounds.height) / 2;
+ int y = textBounds.y + Math.max(0, avg);
+
+ layout.draw(gc, x, y);
+ }
+
+ gc.setForeground(oldForeground);
+ gc.setBackground(oldBackground);
+ }
+
+ public void erase(Event event) {
+ int style = SWT.BACKGROUND | SWT.FOREGROUND;
+ if (!ColorManager.getInstance().isUseNative()) {
+ style |= SWT.SELECTED | SWT.HOT;
+ }
+
+ event.detail &= ~style;
+ }
+
+ protected void init() {
+ // FIXME
+ composite.addListener(SWT.EraseItem, this);
+ composite.addListener(SWT.PaintItem, this);
+ redraw();
+ }
+
+ public void dispose() {
+ if (!disposed) {
+ clearStyles();
+ composite.removeListener(SWT.PaintItem, this);
+ composite.removeListener(SWT.EraseItem, this);
+ disposed = true;
+ redraw();
+ }
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ private boolean disposed;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemProvider.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemProvider.java
new file mode 100644
index 0000000..4231b1c
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemProvider.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.items;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Item;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ItemProvider {
+
+ public String getText(Item item, int index);
+
+ public Image getImage(Item item, int index);
+
+ public Rectangle getTextBounds(Item item, int index);
+
+ public Rectangle getImageBounds(Item item, int index);
+
+ public Rectangle getBounds(Item item, int index);
+
+ public Color getBackground(Item item, int index);
+
+ public Color getForeground(Item item, int index);
+
+ public Font getFont(Item item, int index);
+
+ public int getColumnCount(Item item);
+
+ public void show(Item item);
+
+ public int compare(Item item1, Item item2);
+
+ public void select(Item item);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemSource.java
new file mode 100644
index 0000000..fede502
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/items/ItemSource.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.items;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Item;
+
+import org.eclipse.ui.glance.sources.BaseTextSource;
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class ItemSource extends BaseTextSource implements SelectionListener {
+
+ protected ItemDecorator decorator;
+ protected Composite composite;
+
+ public ItemSource(Composite composite) {
+ decorator = new ItemDecorator(composite, getItemProvider());
+ this.composite = composite;
+ }
+
+ /**
+ * @return the composite
+ */
+ public Composite getControl() {
+ return composite;
+ }
+
+ protected abstract ItemProvider getItemProvider();
+
+ protected abstract void collectCells(List<ItemCell> cells);
+
+ public List<ItemCell> getCells() {
+ List<ItemCell> cells = decorator.getCells();
+ if (cells == null) {
+ cells = new ArrayList<ItemCell>();
+ collectCells(cells);
+ decorator.setCells(cells);
+ }
+ return cells;
+ }
+
+ public ITextBlock[] getBlocks() {
+ return getCells().toArray(new ITextBlock[getCells().size()]);
+ }
+
+ public Font getFont() {
+ return composite.getFont();
+ }
+
+ public void select(Match match) {
+ if (match != null) {
+ Item item = getCell(match).getItem();
+ getItemProvider().show(item);
+ getItemProvider().select(item);
+ }
+
+ setMatch(match);
+ }
+
+ public void dispose() {
+ if (selection != null) {
+ getItemProvider().select(getCell(selection).getItem());
+ }
+ decorator.dispose();
+ }
+
+ public boolean isDisposed() {
+ return decorator.isDisposed();
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ protected void fireSelectionChanged() {
+ SourceSelection selection = getSelection();
+ ITextSourceListener[] listeners = decorator.getListeners();
+ for (ITextSourceListener listener : listeners) {
+ listener.selectionChanged(selection);
+ }
+ }
+
+ public void show(Match[] matches) {
+ setMatches(matches);
+ }
+
+ public void removeTextSourceListener(ITextSourceListener listener) {
+ decorator.removeTextSourceListener(listener);
+ }
+
+ public void addTextSourceListener(ITextSourceListener listener) {
+ decorator.addTextSourceListener(listener);
+ }
+
+ public void setMatch(Match match) {
+ ItemCell cell1 = null, cell2 = null;
+ if (selection != null) {
+ cell1 = getCell(selection);
+ selection = null;
+ updateCell(cell1);
+ }
+ selection = match;
+ if (selection != null) {
+ cell2 = getCell(selection);
+ updateCell(cell2);
+ }
+ if (cell1 != null)
+ decorator.redraw(cell1);
+ if (cell2 != null && cell2 != cell1)
+ decorator.redraw(cell2);
+ }
+
+ public void setMatches(Match[] matches) {
+ cellToMatches = new HashMap<ItemCell, List<Match>>();
+ for (Match match : matches) {
+ ItemCell cell = getCell(match);
+ List<Match> list = cellToMatches.get(cell);
+ if (list == null) {
+ list = new ArrayList<Match>();
+ cellToMatches.put(cell, list);
+ }
+ list.add(match);
+ }
+ decorator.clearStyles();
+ for (ItemCell cell : cellToMatches.keySet()) {
+ updateCell(cell);
+ }
+ decorator.redraw();
+ }
+
+ protected void updateCell(ItemCell cell) {
+ List<StyleRange> list = new ArrayList<StyleRange>();
+ boolean selectionFound = false;
+ if (cellToMatches != null) {
+ List<Match> matches = cellToMatches.get(cell);
+ if (matches != null) {
+ for (Match match : matches) {
+ boolean sel = match.equals(selection);
+ selectionFound = selectionFound | sel;
+ StyleRange range = createRange(match, sel);
+ list.add(range);
+ }
+ }
+ }
+ if (!selectionFound && selection != null) {
+ ItemCell selCell = (ItemCell) selection.getBlock();
+ if (selCell.equals(cell)) {
+ list.add(createRange(selection, true));
+ }
+ }
+ StyleRange[] ranges = list.toArray(new StyleRange[list.size()]);
+ decorator.setStyles(cell, ranges);
+ }
+
+ private StyleRange createRange(Match match, boolean selection) {
+ Display display = composite.getDisplay();
+ Color fgColor, bgColor;
+ if (selection) {
+ // Lighten to avoid search selection on system selection
+ fgColor = ColorManager.lighten(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT), 50);
+ bgColor = ColorManager.getInstance().getSelectedBackgroundColor();
+ } else {
+ // To avoid white text on light-green background
+ fgColor = display.getSystemColor(SWT.COLOR_BLACK);
+ bgColor = ColorManager.getInstance().getBackgroundColor();
+ }
+ return new StyleRange(match.getOffset(), match.getLength(), fgColor, bgColor);
+ }
+
+ protected ItemCell getCell(Match match) {
+ return (ItemCell) match.getBlock();
+ }
+
+ private Match selection;
+ private Map<ItemCell, List<Match>> cellToMatches;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableCell.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableCell.java
new file mode 100644
index 0000000..2f681ca
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableCell.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.table;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.ui.glance.controls.decor.StructCell;
+
+public class TableCell extends StructCell {
+
+ private TableItem item;
+
+ public TableCell(TableItem item, int column) {
+ super(column);
+ this.item = item;
+ }
+
+ @Override
+ public boolean isSelected() {
+ TableItem[] items = item.getParent().getSelection();
+ for (TableItem treeItem : items) {
+ if (treeItem == item)
+ return true;
+ }
+ return false;
+ }
+
+ public TableItem getTableItem() {
+ return item;
+ }
+
+ @Override
+ public Color getBackground() {
+ return item.getBackground(getColumn());
+ }
+
+ @Override
+ public Rectangle getBounds() {
+ return item.getBounds(getColumn());
+ }
+
+ @Override
+ public Font getFont() {
+ return item.getFont(getColumn());
+ }
+
+ @Override
+ public Color getForeground() {
+ return item.getForeground(getColumn());
+ }
+
+ @Override
+ public Image getImage() {
+ return item.getImage(getColumn());
+ }
+
+ @Override
+ public Rectangle getImageBounds() {
+ return item.getImageBounds(getColumn());
+ }
+
+ @Override
+ protected Item getItem() {
+ return item;
+ }
+
+ @Override
+ public String getText() {
+ return item.getText(getColumn());
+ }
+
+ @Override
+ public Rectangle getTextBounds() {
+ return item.getTextBounds(getColumn());
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableContent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableContent.java
new file mode 100644
index 0000000..8660d73
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableContent.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.table;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.swt.widgets.Table;
+
+import org.eclipse.ui.glance.controls.decor.IPath;
+import org.eclipse.ui.glance.controls.decor.IStructContent;
+import org.eclipse.ui.glance.controls.decor.StructCell;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+
+public class TableContent implements IStructContent {
+
+ private final ListenerList listeners = new ListenerList();
+
+ public TableContent(Table table) {
+ }
+
+ public void addListener(ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(ITextSourceListener listener) {
+ listeners.remove(listener);
+ }
+
+ public ITextSourceListener[] getListeners() {
+ Object[] objects = listeners.getListeners();
+ ITextSourceListener[] listeners = new ITextSourceListener[objects.length];
+ System.arraycopy(objects, 0, listeners, 0, objects.length);
+ return listeners;
+ }
+
+ public void dispose() {
+ }
+
+ public ITextBlock[] getBlocks() {
+ return null;
+ }
+
+ public ITextBlock getContent(StructCell cell) {
+ return null;
+ }
+
+ public IPath getPath(ITextBlock block) {
+ return null;
+ }
+
+ public void index(IProgressMonitor monitor) {
+ monitor.done();
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableItemProvider.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableItemProvider.java
new file mode 100644
index 0000000..ac7638b
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableItemProvider.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.table;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.ui.glance.controls.items.ItemProvider;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TableItemProvider implements ItemProvider {
+
+ private TableItemProvider() {
+ }
+
+ private static TableItemProvider INSTANCE;
+
+ public static TableItemProvider getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new TableItemProvider();
+ }
+ return INSTANCE;
+ }
+
+ public static TableItem getItem(Item item) {
+ return (TableItem) item;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getBackground(org.eclipse
+ * .swt.widgets.Item, int)
+ */
+ public Color getBackground(Item item, int index) {
+ return getItem(item).getBackground(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getColumnCount(org.eclipse
+ * .swt.widgets.Item)
+ */
+ public int getColumnCount(Item item) {
+ return getItem(item).getParent().getColumnCount();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getForeground(org.eclipse
+ * .swt.widgets.Item, int)
+ */
+ public Color getForeground(Item item, int index) {
+ return getItem(item).getForeground(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getText(org.eclipse.swt.
+ * widgets.Item, int)
+ */
+ public String getText(Item item, int index) {
+ return getItem(item).getText(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getImage(org.eclipse.swt
+ * .widgets.Item, int)
+ */
+ public Image getImage(Item item, int index) {
+ return getItem(item).getImage(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getImageBounds(org.eclipse
+ * .swt.widgets.Item, int)
+ */
+ public Rectangle getImageBounds(Item item, int index) {
+ return getItem(item).getImageBounds(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getTextBounds(org.eclipse
+ * .swt.widgets.Item, int)
+ */
+ public Rectangle getTextBounds(Item item, int index) {
+ return getItem(item).getTextBounds(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getBounds(org.eclipse.swt
+ * .widgets.Item, int)
+ */
+ public Rectangle getBounds(Item item, int index) {
+ return getItem(item).getBounds(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#getFont(org.eclipse.swt.
+ * widgets.Item, int)
+ */
+ public Font getFont(Item item, int index) {
+ return getItem(item).getFont(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#showItem(org.eclipse.swt
+ * .widgets.Item)
+ */
+ public void show(Item item) {
+ TableItem tItem = getItem(item);
+ tItem.getParent().showItem(tItem);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.glance.controls.items.ItemProvider#compare(org.eclipse.swt.widgets.Item, org.eclipse.swt.widgets.Item)
+ */
+ public int compare(Item item1, Item item2) {
+ if (item1.equals(item2))
+ return 0;
+ Table table = getItem(item1).getParent();
+ TableItem[] items = table.getItems();
+ for (TableItem item : items) {
+ if (item1.equals(item))
+ return -1;
+ if (item2.equals(item))
+ return 1;
+ }
+ return 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.internal.items.ItemProvider#select(org.eclipse.swt.widgets
+ * .Item)
+ */
+ public void select(Item item) {
+ TableItem tItem = getItem(item);
+ tItem.getParent().setSelection(tItem);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableSource.java
new file mode 100644
index 0000000..9aa725a
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableSource.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.table;
+
+import java.util.List;
+
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.ui.glance.controls.items.ItemCell;
+import org.eclipse.ui.glance.controls.items.ItemProvider;
+import org.eclipse.ui.glance.controls.items.ItemSource;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TableSource extends ItemSource {
+
+ public TableSource(Table table) {
+ super(table);
+ table.addSelectionListener(this);
+ }
+
+ @Override
+ public Table getControl() {
+ return (Table) super.getControl();
+ }
+
+ @Override
+ public void dispose() {
+ getControl().removeSelectionListener(this);
+ super.dispose();
+ }
+
+ public SourceSelection getSelection() {
+ TableItem[] items = getControl().getSelection();
+ if (items.length > 0) {
+ List<ItemCell> cells = getCells();
+ for (ItemCell cell : cells) {
+ if (cell.getItem().equals(items[0])) {
+ return new SourceSelection(cell, 0, cell.getText().length());
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void collectCells(List<ItemCell> cells) {
+ Table table = getControl();
+ TableItem[] items = table.getItems();
+ int columns = table.getColumnCount();
+ if (columns == 0)
+ columns = 1;
+ for (int i = 0; i < items.length; i++) {
+ for (int j = 0; j < columns; j++) {
+ cells.add(new ItemCell(items[i], j, getItemProvider()));
+ }
+ }
+ }
+
+ @Override
+ protected ItemProvider getItemProvider() {
+ return TableItemProvider.getInstance();
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableStructSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableStructSource.java
new file mode 100644
index 0000000..f677e26
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/table/TableStructSource.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.table;
+
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.ui.glance.controls.decor.StructCell;
+import org.eclipse.ui.glance.controls.decor.StructSource;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+public class TableStructSource extends StructSource {
+
+ public TableStructSource(Table table) {
+ super(table);
+ table.addSelectionListener(this);
+ }
+
+ @Override
+ public Table getControl() {
+ return (Table) super.getControl();
+ }
+
+ @Override
+ protected StructCell createCell(Item item, int column) {
+ return new TableCell((TableItem) item, column);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ getControl().removeSelectionListener(this);
+ }
+
+ @Override
+ protected TableContent createContent() {
+ return new TableContent(getControl());
+ }
+
+ protected SourceSelection getSourceSelection() {
+ TableItem[] items = getControl().getSelection();
+ if (items.length > 0) {
+ ITextBlock block = content.getContent(createCell(items[0], 0));
+ if (block != null) {
+ return new SourceSelection(block, 0, block.getText().length());
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/AbstractStyledTextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/AbstractStyledTextSource.java
new file mode 100644
index 0000000..6324037
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/AbstractStyledTextSource.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.ui.glance.sources.BaseTextSource;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class AbstractStyledTextSource extends BaseTextSource implements SelectionListener {
+
+ public AbstractStyledTextSource(final StyledText text) {
+ this.text = text;
+ blocks = new StyledTextBlock[] { createTextBlock() };
+ list = new ListenerList();
+ }
+
+ protected StyledTextBlock createTextBlock() {
+ return new StyledTextBlock(text);
+ }
+
+ public final void dispose() {
+ if (text != null && !text.isDisposed() && !disposed) {
+ doDispose();
+ }
+ disposed = true;
+ }
+
+ protected void doDispose() {
+ focusKeeper.dispose();
+ text.removeSelectionListener(this);
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public ITextBlock[] getBlocks() {
+ return blocks;
+ }
+
+ public void addTextSourceListener(final ITextSourceListener listener) {
+ list.add(listener);
+ }
+
+ public void removeTextSourceListener(final ITextSourceListener listener) {
+ list.remove(listener);
+ }
+
+ public void widgetDefaultSelected(final SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ public void widgetSelected(final SelectionEvent e) {
+ fireSelectionChanged();
+ }
+
+ private void fireSelectionChanged() {
+ final SourceSelection selection = getSelection();
+ final Object[] objects = list.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.selectionChanged(selection);
+ }
+ }
+
+ public SourceSelection getSelection() {
+ final Point point = text.getSelection();
+ final SourceSelection selection = new SourceSelection(blocks[0], point.x, point.y - point.x);
+ return selection;
+ }
+
+ public void select(final Match match) {
+ this.selected = match;
+ focusKeeper.setMatch(match);
+ }
+
+ protected StyledText getText() {
+ return text;
+ }
+
+ @Override
+ public void init() {
+ focusKeeper = new StyledTextSelector(text);
+ text.addSelectionListener(this);
+ }
+
+ private StyledTextSelector focusKeeper;
+ private final StyledText text;
+
+ private boolean disposed;
+ private final ListenerList list;
+ private final StyledTextBlock[] blocks;
+ protected Match selected;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/ListeningStyledTextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/ListeningStyledTextSource.java
new file mode 100644
index 0000000..94ffdd8
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/ListeningStyledTextSource.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.swt.custom.ExtendedModifyEvent;
+import org.eclipse.swt.custom.LineStyleEvent;
+import org.eclipse.swt.custom.LineStyleListener;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.utils.TextUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ListeningStyledTextSource extends AbstractStyledTextSource
+ implements LineStyleListener {
+
+ /**
+ * @param text
+ */
+ public ListeningStyledTextSource(final StyledText text) {
+ super(text);
+ text.addLineStyleListener(this);
+ }
+
+ @Override
+ protected void doDispose() {
+ final StyledText text = getText();
+ try {
+ super.doDispose();
+ try {
+ text.removeLineStyleListener(this);
+ } finally {
+ refresh();
+ }
+ } catch (final Exception e) {
+ GlancePlugin.log("Problems with '" + text.getClass() + "'", e);
+ }
+ }
+
+ @Override
+ protected StyledTextBlock createTextBlock() {
+ return new StyledTextBlock(getText()) {
+ @Override
+ public void modifyText(final ExtendedModifyEvent event) {
+ matches = Match.EMPTY;
+ super.modifyText(event);
+ }
+ };
+ }
+
+ public void show(final Match[] matches) {
+ this.matches = matches;
+ refresh();
+ }
+
+ @Override
+ public void select(final Match match){
+ super.select(match);
+ refresh();
+ }
+
+ public void lineGetStyle(final LineStyleEvent event) {
+ if (matches.length > 0) {
+ final int offset = event.lineOffset;
+ final int length = event.lineText.length();
+
+ int size = event.styles == null ? 0 : event.styles.length;
+ if (size == 0)
+ size = 1;
+ final TextPresentation presentation = new TextPresentation(new Region(
+ offset, length), size);
+ if (event.styles != null && event.styles.length > 0)
+ presentation.replaceStyleRanges(event.styles);
+ applyTextPresentation(presentation);
+ event.styles = TextUtils.getStyles(presentation);
+ }
+ }
+
+ private void refresh() {
+ final String text = getText().getText();
+ getText().redrawRange(0, text.length(), false);
+ }
+
+ private void applyTextPresentation(final TextPresentation presentation) {
+ TextUtils.applyStyles(presentation, matches, selected);
+ }
+
+ private Match[] matches = Match.EMPTY;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/RangeGroup.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/RangeGroup.java
new file mode 100644
index 0000000..cf5d504
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/RangeGroup.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.swt.custom.StyleRange;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class RangeGroup {
+
+ private int start;
+ private int end;
+ private StyleRange[] ranges;
+
+ public RangeGroup(int start, int end, StyleRange[] ranges) {
+ this.start = start;
+ this.end = end;
+ this.ranges = ranges;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ public StyleRange[] getRanges() {
+ return ranges;
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextBlock.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextBlock.java
new file mode 100644
index 0000000..559bc08
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextBlock.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.swt.custom.ExtendedModifyEvent;
+import org.eclipse.swt.custom.ExtendedModifyListener;
+import org.eclipse.swt.custom.StyledText;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class StyledTextBlock implements ITextBlock, ExtendedModifyListener {
+
+ public StyledTextBlock(StyledText text) {
+ this.text = text;
+ listeners = new ListenerList();
+ text.addExtendedModifyListener(this);
+ }
+
+ public String getText() {
+ return text.getText();
+ }
+
+ public void addTextBlockListener(ITextBlockListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTextBlockListener(ITextBlockListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void modifyText(ExtendedModifyEvent event) {
+ Object[] objects = listeners.getListeners();
+ TextChangedEvent textEvent = new TextChangedEvent(event.start,
+ event.length, event.replacedText);
+ for (Object object : objects) {
+ ITextBlockListener listener = (ITextBlockListener) object;
+ listener.textChanged(textEvent);
+ }
+ }
+
+ public int compareTo(ITextBlock o) {
+ //style text support only one text block
+ return 0;
+ }
+
+ private StyledText text;
+ private ListenerList listeners;
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSelector.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSelector.java
new file mode 100644
index 0000000..3a5eb62
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSelector.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.jface.text.Region;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+
+public class StyledTextSelector extends TextSelector {
+
+ private final StyledText text;
+
+ public StyledTextSelector(StyledText text) {
+ this.text = text;
+ init();
+ }
+
+ @Override
+ protected Control getControl() {
+ return text;
+ }
+
+ @Override
+ protected Region getSelection() {
+ final Point point = text.getSelection();
+ return new Region(point.x, point.y - point.x);
+ }
+
+ @Override
+ protected void setSelection(int offset, int length) {
+ text.setSelectionRange(offset, length);
+ }
+
+ @Override
+ protected void reveal(int offset, int length) {
+ Region region = getSelection();
+ if (region.getOffset() == offset && region.getLength() == length) {
+ text.showSelection();
+ } else {
+ setSelection(offset, length);
+ try {
+ text.showSelection();
+ } finally {
+ text.setSelectionRange(region.getOffset(), region.getLength());
+ }
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSource.java
new file mode 100644
index 0000000..c4cf30e
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/StyledTextSource.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Color;
+
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class StyledTextSource extends AbstractStyledTextSource {
+
+ public StyledTextSource(final StyledText text) {
+ super(text);
+ }
+
+ @Override
+ protected void doDispose() {
+ super.doDispose();
+ clearAll();
+ }
+
+ protected void clearAll() {
+ clearHighlight();
+ clearSelected();
+ }
+ protected void clearHighlight() {
+ if (previous != null && previous.length > 0) {
+ for (int i = 0; i < previous.length; i++) {
+ clearRangeGroup(previous[i]);
+ }
+ }
+ previous = null;
+ }
+
+ protected void clearSelected() {
+ if (selectedRange != null) {
+ clearRangeGroup(selectedRange);
+ selectedRange = null;
+ }
+ }
+
+ private void clearRangeGroup(final RangeGroup group){
+ getText().replaceStyleRanges(group.getStart(),
+ group.getEnd() - group.getStart(), new StyleRange[0]);
+ final StyleRange[] ranges = group.getRanges();
+ for (final StyleRange range : ranges) {
+ getText().replaceStyleRanges(range.start, range.length,
+ new StyleRange[] { range });
+ }
+ }
+
+ @Override
+ public void select(final Match match) {
+ super.select(match);
+ clearSelected();
+ if (match != null) {
+ final List<StyleRange> ranges = createRanges(match, ColorManager.getInstance()
+ .getSelectedBackgroundColor());
+ selectedRange = new RangeGroup(match.getOffset(), match.getOffset() + match.getLength(),
+ ranges.toArray(new StyleRange[ranges.size()]));
+ }
+ }
+
+ public void show(final Match[] matches) {
+ clearAll();
+ previous = new RangeGroup[matches.length];
+ for (int i = 0; i < matches.length; i++) {
+ final Match match = matches[i];
+ final List<StyleRange> ranges = createRanges(match, ColorManager.getInstance()
+ .getBackgroundColor());
+ previous[i] = new RangeGroup(match.getOffset(), match.getOffset() + match.getLength(),
+ ranges
+ .toArray(new StyleRange[ranges.size()]));
+ }
+
+ if (selected != null) {
+ select(selected);
+ }
+ }
+
+ private List<StyleRange> createRanges(final Match match, final Color bg) {
+ int index = match.getOffset();
+ final int lastIndex = index + match.getLength();
+ final StyleRange[] matchRanges = getText().getStyleRanges(index, match.getLength());
+ final List<StyleRange> ranges = new ArrayList<StyleRange>();
+ for (final StyleRange styleRange : matchRanges) {
+ ranges.add(styleRange);
+ if (styleRange.length < 0) {
+ continue;
+ }
+ final StyleRange newStyleRange = new StyleRange(styleRange.start, styleRange.length, null, bg,
+ styleRange.fontStyle);
+ if (styleRange.start - index > 0)
+ getText().replaceStyleRanges(index, styleRange.start - index,
+ new StyleRange[] { newStyleRange });
+ getText().replaceStyleRanges(styleRange.start, styleRange.length,
+ new StyleRange[] { newStyleRange });
+ index = styleRange.start + styleRange.length;
+ }
+ if (lastIndex - index > 0) {
+ final StyleRange newStyleRange = new StyleRange(index, lastIndex - index, null, bg);
+ getText().replaceStyleRanges(index, lastIndex - index, new StyleRange[] { newStyleRange });
+ }
+ return ranges;
+ }
+
+ private RangeGroup[] previous;
+ private RangeGroup selectedRange;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/TextSelector.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/TextSelector.java
new file mode 100644
index 0000000..fae6ef8
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/text/styled/TextSelector.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.text.styled;
+
+import org.eclipse.jface.text.Region;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * For all rich text components we remove native selection (make it empty) to
+ * draw our own colorful current match. However if focus was moved back to text
+ * component, selection should be drawn as usually.
+ *
+ * TextSelector manages this behavior.
+ *
+ * @author Yuri Strot
+ */
+public abstract class TextSelector implements FocusListener {
+
+ public void setMatch(Match match) {
+ this.match = match;
+ if (match != null) {
+ reveal(match.getOffset(), match.getLength());
+ }
+ }
+
+ public void dispose() {
+ getControl().removeFocusListener(this);
+ showSelection();
+ }
+
+ public void focusLost(FocusEvent e) {
+ hideSelection();
+ }
+
+ public void focusGained(FocusEvent e) {
+ showSelection();
+ }
+
+ protected void init() {
+ getControl().addFocusListener(this);
+ hideSelection();
+ }
+
+ private void hideSelection() {
+ Region region = getSelection();
+ setSelection(region.getOffset() + region.getLength(), 0);
+ }
+
+ private void showSelection() {
+ if (match != null) {
+ setSelection(match.getOffset(), match.getLength());
+ }
+ }
+
+ protected abstract Control getControl();
+
+ protected abstract Region getSelection();
+
+ protected abstract void setSelection(int offset, int length);
+
+ protected abstract void reveal(int offset, int length);
+
+ private Match match;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/BusyIndicatorUtils.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/BusyIndicatorUtils.java
new file mode 100644
index 0000000..e30ec07
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/BusyIndicatorUtils.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class BusyIndicatorUtils {
+
+ static final String BUSYID_NAME = "SWT BusyIndicator"; //$NON-NLS-1$
+ static final Integer NO_BUSY = new Integer(0);
+
+ public static void withoutIndicator(Display display, Runnable runnable) {
+ if (runnable == null)
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (display == null) {
+ display = Display.getCurrent();
+ if (display == null) {
+ runnable.run();
+ return;
+ }
+ }
+
+ Shell[] shells = display.getShells();
+ for (int i = 0; i < shells.length; i++) {
+ Integer id = (Integer) shells[i].getData(BUSYID_NAME);
+ if (id == null) {
+ shells[i].setData(BUSYID_NAME, NO_BUSY);
+ }
+ }
+
+ try {
+ runnable.run();
+ } finally {
+ shells = display.getShells();
+ for (int i = 0; i < shells.length; i++) {
+ Integer id = (Integer) shells[i].getData(BUSYID_NAME);
+ if (id == NO_BUSY) {
+ shells[i].setData(BUSYID_NAME, null);
+ }
+ }
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeCell.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeCell.java
new file mode 100644
index 0000000..334d9ee
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeCell.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.TreeItem;
+
+import org.eclipse.ui.glance.controls.decor.StructCell;
+
+public class TreeCell extends StructCell {
+
+ private TreeItem item;
+
+ public TreeCell(TreeItem item, int column) {
+ super(column);
+ this.item = item;
+ }
+
+ @Override
+ public boolean isSelected() {
+ TreeItem[] items = item.getParent().getSelection();
+ for (TreeItem treeItem : items) {
+ if (treeItem == item)
+ return true;
+ }
+ return false;
+ }
+
+ public TreeItem getTreeItem() {
+ return item;
+ }
+
+ @Override
+ public Color getBackground() {
+ return item.getBackground(getColumn());
+ }
+
+ @Override
+ public Rectangle getBounds() {
+ return item.getBounds(getColumn());
+ }
+
+ @Override
+ public Font getFont() {
+ return item.getFont(getColumn());
+ }
+
+ @Override
+ public Color getForeground() {
+ return item.getForeground(getColumn());
+ }
+
+ @Override
+ public Image getImage() {
+ return item.getImage(getColumn());
+ }
+
+ @Override
+ public Rectangle getImageBounds() {
+ return item.getImageBounds(getColumn());
+ }
+
+ @Override
+ protected Item getItem() {
+ return item;
+ }
+
+ @Override
+ public String getText() {
+ return item.getText(getColumn());
+ }
+
+ @Override
+ public Rectangle getTextBounds() {
+ return item.getTextBounds(getColumn());
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeContent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeContent.java
new file mode 100644
index 0000000..fd8dc92
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeContent.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ListenerList;
+
+import org.eclipse.ui.glance.controls.decor.IPath;
+import org.eclipse.ui.glance.controls.decor.IStructContent;
+import org.eclipse.ui.glance.controls.decor.StructCell;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+
+public abstract class TreeContent extends TreeNode implements IStructContent {
+
+ private ListenerList listeners = new ListenerList();
+
+ public TreeContent() {
+ super(null);
+ index = new int[0];
+ }
+
+ public abstract TreeItemContent getContent(TreeCell cell);
+
+ public abstract void index(IProgressMonitor monitor);
+
+ public void dispose() {
+ }
+
+ public final ITextBlock getContent(StructCell cell) {
+ return getContent((TreeCell) cell);
+ }
+
+ public IPath getPath(ITextBlock block) {
+ TreeItemContent content = (TreeItemContent) block;
+ return new TreePath(content.getNode());
+ }
+
+ public void addListener(ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(ITextSourceListener listener) {
+ listeners.remove(listener);
+ }
+
+ public ITextSourceListener[] getListeners() {
+ Object[] objects = listeners.getListeners();
+ ITextSourceListener[] listeners = new ITextSourceListener[objects.length];
+ System.arraycopy(objects, 0, listeners, 0, objects.length);
+ return listeners;
+ }
+
+ public ITextBlock[] getBlocks() {
+ return blocks.toArray(new TreeItemContent[0]);
+ }
+
+ void changed(TreeItemContent[] removed, TreeItemContent[] added) {
+ for (TreeItemContent item : removed) {
+ blocks.remove(item);
+ }
+ for (TreeItemContent item : added) {
+ blocks.add(item);
+ }
+ for (ITextSourceListener listener : getListeners()) {
+ listener.blocksChanged(removed, added);
+ }
+ }
+
+ private Set<TreeItemContent> blocks = new HashSet<TreeItemContent>();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlContent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlContent.java
new file mode 100644
index 0000000..3350a06
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlContent.java
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+import org.eclipse.ui.glance.sources.ConfigurationManager;
+
+public class TreeControlContent extends TreeContent {
+
+ private Tree tree;
+ private final Map<TreeCell, TreeItemContent> cellToContent = new HashMap<TreeCell, TreeItemContent>();
+
+ public TreeControlContent(Tree tree) {
+ this.tree = tree;
+ collectCells(this, tree.getItems());
+ }
+
+ @Override
+ public void dispose() {
+ tree = null;
+ }
+
+ @Override
+ public TreeItemContent getContent(TreeCell cell) {
+ TreeItemContent content = cellToContent.get(cell);
+ if (content == null) {
+ TreeItem parent = null;
+ do {
+ parent = cell.getTreeItem().getParentItem();
+ if (parent == null) {
+ break;
+ }
+ TreeCell parentCell = new TreeCell(parent, 0);
+ cell = parentCell;
+ content = cellToContent.get(parentCell);
+ } while (content == null);
+ if (content != null && parent != null) {
+ collectCells(content.getNode(), parent.getItems());
+ content = cellToContent.get(cell);
+ }
+ }
+ return content;
+ }
+
+ @Override
+ public void index(final IProgressMonitor monitor) {
+ if (tree == null || tree.isDisposed()) {
+ monitor.done();
+ return;
+ }
+ tree.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ try {
+ if (tree == null || tree.isDisposed()) {
+ return;
+ }
+ final LinkedList<TreeItem> items = new LinkedList<TreeItem>();
+ for (TreeItem item : tree.getItems()) {
+ items.add(item);
+ }
+ if (items.size() > 0) {
+ expand(items, monitor);
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+ }
+
+ private void expand(final LinkedList<TreeItem> items,
+ final IProgressMonitor monitor) {
+ if (tree == null || tree.isDisposed())
+ return;
+ final Display display = tree.getDisplay();
+ BusyIndicatorUtils.withoutIndicator(display, new Runnable() {
+ public void run() {
+ int maxIndexingDepth = ConfigurationManager.getInstance()
+ .getMaxIndexingDepth();
+ int level = 1;
+ TreeItem lastInLevel = items.getLast();
+ monitor.beginTask("1/" + maxIndexingDepth, items.size());
+ while (true) {
+ if (tree == null
+ || tree.isDisposed()
+ || (maxIndexingDepth >= 0 && level >= maxIndexingDepth))
+ return;
+ if (monitor.isCanceled())
+ return;
+ TreeItem item = items.poll();
+ if (item == null)
+ return;
+ try {
+ if (item.isDisposed())
+ continue;
+ if (!item.getExpanded()) {
+ Event event = new Event();
+ event.item = item;
+ event.type = SWT.Expand;
+ event.widget = item.getParent();
+ event.display = display;
+ event.widget.notifyListeners(SWT.Expand, event);
+ }
+ TreeItem[] kids = item.getItems();
+ TreeItemContent content = getContent(item);
+ if (content != null) {
+ collectCells(content.getNode(), item.getItems());
+ }
+ for (TreeItem child : kids) {
+ items.addLast(child);
+ }
+ while (display.readAndDispatch())
+ ;
+ } finally {
+ monitor.worked(1);
+ if (item == lastInLevel && items.size() > 0) {
+ lastInLevel = items.getLast();
+ level++;
+ int total = items.size();
+ monitor.beginTask(level + "/?", total);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private void collectCells(TreeNode node, TreeItem[] items) {
+ if (items.length > 0) {
+ int columns = items[0].getParent().getColumnCount();
+ if (columns == 0) {
+ columns = 1;
+ }
+ List<TreeNode> nodes = new ArrayList<TreeNode>(items.length);
+ for (int i = 0; i < items.length; i++) {
+ TreeItem item = items[i];
+ TreeItemContent c = getContent(item);
+ if (c != null) {
+ continue;
+ }
+ TreeNode child = new TreeNode(item);
+ for (int j = 0; j < columns; j++) {
+ TreeCell cell = new TreeCell(item, j);
+ TreeItemContent itemContent = new TreeItemContent(child,
+ item.getText(j), j);
+ cellToContent.put(cell, itemContent);
+ }
+ if (item.getExpanded()) {
+ collectCells(child, item.getItems());
+ }
+ nodes.add(child);
+ }
+ if (nodes.size() > 0) {
+ node.add(nodes.toArray(new TreeNode[nodes.size()]));
+ }
+ }
+ }
+
+ private TreeItemContent getContent(TreeItem item) {
+ return cellToContent.get(new TreeCell(item, 0));
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlSource.java
new file mode 100644
index 0000000..0fc5ac0
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeControlSource.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import org.eclipse.swt.widgets.Tree;
+
+
+public class TreeControlSource extends TreeStructSource {
+
+ public TreeControlSource(Tree tree) {
+ super(tree);
+ }
+
+ @Override
+ protected TreeContent createContent() {
+ return new TreeControlContent(getControl());
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeItemContent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeItemContent.java
new file mode 100644
index 0000000..4a3ebe0
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeItemContent.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import org.eclipse.core.runtime.ListenerList;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+
+public class TreeItemContent implements ITextBlock {
+
+ private TreeNode node;
+ private String text;
+ private ListenerList listeners = new ListenerList();
+ private int column;
+
+ public TreeItemContent(TreeNode node, String text, int column) {
+ this.node = node;
+ node.items.add(this);
+ this.text = text;
+ this.column = column;
+ }
+
+ public TreeNode getNode() {
+ return node;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ if (text.equals(this.text))
+ return;
+ int length = text.length();
+ TextChangedEvent event = new TextChangedEvent(0, length, this.text);
+ this.text = text;
+ for (Object object : listeners.getListeners()) {
+ ITextBlockListener listener = (ITextBlockListener) object;
+ listener.textChanged(event);
+ }
+ }
+
+ public void addTextBlockListener(ITextBlockListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTextBlockListener(ITextBlockListener listener) {
+ listeners.remove(listener);
+ }
+
+ public int compareTo(ITextBlock that) {
+ TreeItemContent item = (TreeItemContent) that;
+ int diff = this.node.compareTo(item.node);
+ if (diff != 0) {
+ return diff;
+ }
+ return column - item.column;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("(");
+ buffer.append(text);
+ buffer.append(", ");
+ buffer.append(column);
+ buffer.append(")");
+ return buffer.toString();
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeNode.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeNode.java
new file mode 100644
index 0000000..d76c4b9
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeNode.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class TreeNode implements DisposeListener {
+
+ public static final TreeNode[] EMPTY = new TreeNode[0];
+
+ List<TreeNode> kids = new ArrayList<TreeNode>();
+ List<TreeItemContent> items = new ArrayList<TreeItemContent>();
+ TreeNode parent;
+ int[] index;
+
+ TreeItem item;
+
+ public TreeNode(TreeItem item) {
+ this.item = item;
+ if (item != null) {
+ item.addDisposeListener(this);
+ }
+ }
+
+ public void widgetDisposed(DisposeEvent e) {
+ close();
+ }
+
+ public void add(TreeNode[] nodes) {
+ for (TreeNode node : nodes) {
+ doAdd(node);
+ }
+ notifyRoot(TreeNode.EMPTY, nodes);
+ }
+
+ public void remove(TreeNode[] nodes) {
+ doRemove(nodes);
+ notifyRoot(nodes, TreeNode.EMPTY);
+ }
+
+ public TreeNode[] getChildren() {
+ return kids.toArray(new TreeNode[0]);
+ }
+
+ void notifyRoot(TreeNode[] removed, TreeNode[] added) {
+ TreeContent root = getRoot();
+ if (root != null) {
+ root.changed(getContent(removed), getContent(added));
+ }
+ }
+
+ protected void close() {
+ if (item != null) {
+ if (!item.isDisposed()) {
+ item.removeDisposeListener(this);
+ }
+ for (TreeNode node : kids) {
+ node.close();
+ }
+ notifyRoot(new TreeNode[] { this }, TreeNode.EMPTY);
+ item = null;
+ }
+ }
+
+ TreeItemContent[] getContent(TreeNode[] nodes) {
+ List<TreeItemContent> content = new ArrayList<TreeItemContent>();
+ nodes = collect(nodes);
+ for (TreeNode node : nodes) {
+ content.addAll(node.items);
+ }
+ return content.toArray(new TreeItemContent[0]);
+ }
+
+ TreeNode[] collect(TreeNode[] items) {
+ List<TreeNode> result = new ArrayList<TreeNode>();
+ for (TreeNode item : items) {
+ collect(item, result);
+ }
+ return result.toArray(new TreeNode[0]);
+ }
+
+ void collect(TreeNode item, List<TreeNode> items) {
+ items.add(item);
+ for (TreeNode kid : item.kids) {
+ collect(kid, items);
+ }
+ }
+
+ TreeContent getRoot() {
+ TreeNode node = this;
+ while (node != null) {
+ if (node instanceof TreeContent)
+ return (TreeContent) node;
+ node = node.parent;
+ }
+ return null;
+ }
+
+ void doAdd(TreeNode node) {
+ node.recalc(index, kids.size());
+ kids.add(node);
+ node.parent = this;
+ }
+
+ void doRemove(TreeNode[] nodes) {
+ int minPos = Integer.MAX_VALUE;
+ for (TreeNode node : nodes) {
+ int pos = kids.indexOf(node);
+ if (pos >= 0) {
+ kids.remove(pos);
+ minPos = Math.min(minPos, pos);
+ }
+ }
+ for (int i = minPos; i < kids.size(); i++) {
+ kids.get(i).recalc(index, i);
+ }
+ }
+
+ void recalc(int[] parent, int cur) {
+ if (parent != null) {
+ index = new int[parent.length + 1];
+ System.arraycopy(parent, 0, index, 0, parent.length);
+ index[parent.length] = cur;
+ for (int i = 0; i < kids.size(); i++) {
+ kids.get(i).recalc(index, i);
+ }
+ }
+ }
+
+ public int compareTo(TreeNode that) {
+ int[] index = that.index;
+ for (int i = 0; i < index.length && i < this.index.length; i++) {
+ int diff = this.index[i] - index[i];
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ return this.index.length - index.length;
+ }
+
+ public String toString() {
+ int iMax = items.size() - 1;
+ if (iMax == -1)
+ return "[]";
+ StringBuilder b = new StringBuilder();
+ b.append('[');
+ for (int i = 0;; i++) {
+ b.append(items.get(i));
+ if (i == iMax)
+ return b.append(']').toString();
+ b.append(", ");
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreePath.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreePath.java
new file mode 100644
index 0000000..32d9887
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreePath.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.progress.PendingUpdateAdapter;
+
+import org.eclipse.ui.glance.controls.decor.IPath;
+
+public class TreePath implements IPath {
+
+ protected List<TreeNode> list = new ArrayList<TreeNode>();
+ protected TreeContent content;
+
+ private boolean cancel;
+
+ public TreePath(TreeNode node) {
+ content = node.getRoot();
+ TreeNode cur = node;
+ while (cur != null && cur != content) {
+ list.add(0, cur);
+ cur = cur.parent;
+ }
+ }
+
+ public void select(Composite composite) {
+ Tree tree = (Tree) composite;
+ select(tree.getItems(), 0);
+ }
+
+ public void discardSelection() {
+ this.cancel = true;
+ }
+
+ private void select(final TreeItem[] items, final int index) {
+ if (cancel) {
+ return;
+ }
+ TreeNode node = list.get(index);
+ for (final TreeItem item : items) {
+ if (getNode(item) == node) {
+ final Tree tree = item.getParent();
+ if (index == list.size() - 1) {
+ // After tree item removes tree selection restores
+ // we should set selection after this
+ tree.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ tree.setSelection(item);
+ tree.showSelection();
+ }
+ });
+ } else {
+ if (!item.getExpanded()) {
+ expand(item);
+ }
+ select(item.getItems(), index + 1);
+ }
+ return;
+ }
+ }
+ if (items.length > 0) {
+ TreeItem item = items[0];
+ final TreeItem parent = item.getParentItem();
+ if (item.getData() instanceof PendingUpdateAdapter) {
+ item.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ select(parent.getItems(), index);
+ }
+ });
+ }
+ }
+ }
+
+ private TreeNode getNode(TreeItem item) {
+ TreeCell cell = new TreeCell(item, 0);
+ TreeItemContent itemContent = content.getContent(cell);
+ return itemContent != null ? itemContent.getNode() : null;
+ }
+
+ private void expand(TreeItem item) {
+ Event event = new Event();
+ event.item = item;
+ event.type = SWT.Expand;
+ event.widget = item.getParent();
+ event.display = item.getDisplay();
+ event.widget.notifyListeners(SWT.Expand, event);
+ item.setExpanded(true);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeStructSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeStructSource.java
new file mode 100644
index 0000000..24e160f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/TreeStructSource.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+import org.eclipse.ui.glance.controls.decor.StructCell;
+import org.eclipse.ui.glance.controls.decor.StructSource;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+public abstract class TreeStructSource extends StructSource {
+
+ public TreeStructSource(Tree tree) {
+ super(tree);
+ tree.addSelectionListener(this);
+ }
+
+ @Override
+ public Tree getControl() {
+ return (Tree) super.getControl();
+ }
+
+ @Override
+ protected StructCell createCell(Item item, int column) {
+ return new TreeCell((TreeItem) item, column);
+ }
+
+ @Override
+ public void dispose() {
+ Tree tree = getControl();
+ try {
+ super.dispose();
+ } finally {
+ if (tree != null && !tree.isDisposed()) {
+ tree.removeSelectionListener(this);
+ }
+ }
+ }
+
+ @Override
+ protected abstract TreeContent createContent();
+
+ @Override
+ protected SourceSelection getSourceSelection() {
+ TreeItem[] items = getControl().getSelection();
+ if (items.length > 0) {
+ ITextBlock block = content.getContent(createCell(items[0], 0));
+ if (block != null) {
+ return new SourceSelection(block, 0, block.getText().length());
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/UnknownTreeVisitor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/UnknownTreeVisitor.java
new file mode 100644
index 0000000..b4b9529
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/controls/tree/UnknownTreeVisitor.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.controls.tree;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+
+public abstract class UnknownTreeVisitor {
+
+ public void visit(IProgressMonitor monitor) {
+ monitor.beginTask("Tree indexing", 100);
+ try {
+ Object[] roots = getRoots();
+ monitor.worked(1);
+ breadthVisit(roots, monitor, 1);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ protected abstract Object[] getRoots();
+
+ protected abstract Object[] getChildren(Object element);
+
+ private void breadthVisit(Object[] elements, IProgressMonitor monitor,
+ int level) {
+ List<Object> next = new ArrayList<Object>();
+ for (Object element : elements) {
+ if (monitor.isCanceled())
+ return;
+ next.addAll(Arrays.asList(getChildren(element)));
+ }
+ if (monitor.isCanceled())
+ return;
+ int work = (int) Math.pow(2, level);
+ monitor.worked(work);
+ int totalWork = 2 * work - 1;
+ int size = next.size();
+ if (size == 0)
+ return;
+ int remains = 100 - totalWork;
+ if (remains > size) {
+ breadthVisit(next.toArray(), monitor, level + 1);
+ } else {
+ SubProgressMonitor subMonitor = new SubProgressMonitor(monitor,
+ remains);
+ try {
+ subMonitor.beginTask("", size);
+ for (Object element : next) {
+ depthVisit(element, monitor, level + 1);
+ subMonitor.worked(1);
+ }
+ } finally {
+ subMonitor.done();
+ }
+ }
+ }
+
+ private void depthVisit(Object element, IProgressMonitor monitor, int level) {
+ if (level > 4) {
+ return;
+ }
+ for (Object child : getChildren(element)) {
+ if (monitor.isCanceled())
+ return;
+ depthVisit(child, monitor, level + 1);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceEventDispatcher.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceEventDispatcher.java
new file mode 100644
index 0000000..072ab6b
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceEventDispatcher.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jface.bindings.Binding;
+import org.eclipse.jface.bindings.BindingManager;
+import org.eclipse.jface.bindings.keys.KeySequence;
+import org.eclipse.jface.bindings.keys.KeyStroke;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.keys.BindingService;
+import org.eclipse.ui.internal.keys.WorkbenchKeyboard;
+import org.eclipse.ui.keys.IBindingService;
+
+import org.eclipse.ui.glance.internal.search.SearchManager;
+
+@SuppressWarnings("restriction")
+public class GlanceEventDispatcher {
+
+ public static final String GLANCE_CTX = "org.eclipse.ui.glance.context";
+
+ public static final String NEXT_COMMAND = "org.eclipse.ui.glance.nextResult";
+ public static final String PREV_COMMAND = "org.eclipse.ui.glance.prevResult";
+ public static final String FOCUS_COMMAND = "org.eclipse.ui.glance.commands.focus";
+ public static final String CLOSE_COMMAND = "org.eclipse.ui.glance.commands.close";
+ public static final String CLEAR_COMMAND = "org.eclipse.ui.glance.commands.clearHistory";
+
+ public static GlanceEventDispatcher INSTANCE = new GlanceEventDispatcher();
+
+ private final BindingManager bindingManager;
+
+ private GlanceEventDispatcher() {
+ bindingManager = ((BindingService) PlatformUI.getWorkbench()
+ .getService(IBindingService.class)).getBindingManager();
+ }
+
+ public void dispatchKeyPressed(Event event) {
+ @SuppressWarnings("unchecked")
+ List<Object> potentialKeyStrokes = WorkbenchKeyboard
+ .generatePossibleKeyStrokes(event);
+ if (potentialKeyStrokes.isEmpty()) {
+ return;
+ }
+
+ String commandID = getBindCommand(KeySequence
+ .getInstance((KeyStroke) potentialKeyStrokes.get(0)));
+ if (commandID == null) {
+ return;
+ } else if (FOCUS_COMMAND.equals(commandID)) {
+ SearchManager.getIntance().sourceFocus();
+ } else if (NEXT_COMMAND.equals(commandID)) {
+ SearchManager.getIntance().findNext();
+ } else if (PREV_COMMAND.equals(commandID)) {
+ SearchManager.getIntance().findPrevious();
+ } else if (CLOSE_COMMAND.equals(commandID)) {
+ SearchManager.getIntance().close();
+ } else if (CLEAR_COMMAND.equals(commandID)) {
+ SearchManager.getIntance().clearHistory();
+ }
+ }
+
+ public String getBindCommand(KeySequence keySequence) {
+ Map<?, ?> map = bindingManager.getActiveBindingsDisregardingContext();
+ List<?> bindings = (List<?>) map.get(keySequence);
+ if (bindings != null) {
+ for (Object obj : bindings) {
+ Binding binding = (Binding) obj;
+ if (GLANCE_CTX.equals(binding.getContextId())) {
+ return binding.getParameterizedCommand().getId();
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlancePlugin.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlancePlugin.java
new file mode 100644
index 0000000..9fc4d0a
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlancePlugin.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal;
+
+import java.net.URL;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class GlancePlugin extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.ui.glance";
+
+ // IMAGES
+
+ private static final String IMG_PREFIX = "icons/";
+
+ public static final String IMG_WAIT = IMG_PREFIX + "wait.gif";
+ public static final String IMG_NEXT = IMG_PREFIX + "next.gif";
+ public static final String IMG_PREV = IMG_PREFIX + "prev.gif";
+ public static final String IMG_SEARCH = IMG_PREFIX + "search.png";
+ public static final String IMG_START_INDEXING = IMG_PREFIX + "update_1.gif";
+ public static final String IMG_END_INDEXING = IMG_PREFIX + "update_2.gif";
+ public static final String IMG_INDEXING_FINISHED = IMG_PREFIX
+ + "update_done.gif";
+ public static final String[] IMG_INDEXING_LOOP = new String[] {
+ IMG_START_INDEXING, IMG_END_INDEXING };
+
+ // The shared instance
+ private static GlancePlugin plugin;
+
+ /**
+ * The constructor
+ */
+ public GlancePlugin() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext
+ * )
+ */
+ @Override
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext
+ * )
+ */
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static GlancePlugin getDefault() {
+ return plugin;
+ }
+
+ public static Image getImage(String path) {
+ Image image = getDefault().getImageRegistry().get(path);
+ if (image == null) {
+ ImageDescriptor imageDescriptor = getImageDescriptor(path, false);
+ image = imageDescriptor.createImage();
+ getDefault().getImageRegistry().put(path, image);
+ }
+ return image;
+ }
+
+ public static ImageDescriptor getImageDescriptor(String path) {
+ return getImageDescriptor(path, true);
+ }
+
+ private static ImageDescriptor getImageDescriptor(String path,
+ boolean addToRegistry) {
+ ImageDescriptor imageDescriptor = getDefault().getImageRegistry()
+ .getDescriptor(path);
+ if (imageDescriptor == null) {
+ imageDescriptor = ImageDescriptor.createFromURL(makeImageURL(path));
+ if (addToRegistry) {
+ getDefault().getImageRegistry().put(path, imageDescriptor);
+ }
+ }
+ return imageDescriptor;
+ }
+
+ public static void log(String message) {
+ log(message, null);
+ }
+
+ public static void log(Throwable t) {
+ log(t.getMessage(), t);
+ }
+
+ public static void log(String message, Throwable t) {
+ log(createStatus(message, t));
+ }
+
+ public static void log(IStatus status) {
+ getDefault().getLog().log(status);
+ }
+
+ public static IStatus createStatus(String message) {
+ return createStatus(message, null);
+ }
+
+ public static IStatus createStatus(String message, Throwable t) {
+ return new Status(Status.ERROR, PLUGIN_ID, message, t);
+ }
+
+ private static URL makeImageURL(String path) {
+ return FileLocator.find(getDefault().getBundle(), new Path(path), null);
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceStartup.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceStartup.java
new file mode 100644
index 0000000..2630015
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/GlanceStartup.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.ui.IStartup;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+import org.eclipse.ui.glance.internal.search.SearchManager;
+
+public class GlanceStartup implements IStartup, IPreferenceConstants {
+
+ public void earlyStartup() {
+ IPreferenceStore store = GlancePlugin.getDefault().getPreferenceStore();
+ if (store.getBoolean(PANEL_STARTUP)) {
+ PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ SearchManager.getIntance().startup();
+ }
+ });
+ }
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/OpenSearchPanelHandler.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/OpenSearchPanelHandler.java
new file mode 100644
index 0000000..3bdb10e
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/OpenSearchPanelHandler.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+
+import org.eclipse.ui.glance.internal.search.SearchManager;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class OpenSearchPanelHandler extends AbstractHandler {
+
+ /**
+ * The constructor.
+ */
+ public OpenSearchPanelHandler() {
+ }
+
+ /**
+ * the command has been executed, so extract extract the needed information
+ * from the application context.
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ SearchManager.getIntance().activate();
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/CheckAction.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/CheckAction.java
new file mode 100644
index 0000000..4550278
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/CheckAction.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+
+/**
+ * @author Yuri Strot
+ */
+public class CheckAction extends Action {
+
+ public CheckAction(String name, String label) {
+ super(label, AS_CHECK_BOX);
+ this.name = name;
+ setChecked(getStore().getBoolean(name));
+ }
+
+ public IPreferenceStore getStore() {
+ return GlancePlugin.getDefault().getPreferenceStore();
+ }
+
+ @Override
+ public void run() {
+ getStore().setValue(name, isChecked());
+ }
+
+ private String name;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/ImageAnimation.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/ImageAnimation.java
new file mode 100644
index 0000000..b598f76
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/ImageAnimation.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import java.net.URL;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageLoader;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.utils.UIUtils;
+
+public abstract class ImageAnimation extends Thread {
+
+ public ImageAnimation(URL url, Color bg) throws CoreException {
+ setDaemon(true);
+ this.bg = bg;
+ try {
+ imageDataArray = loader.load(url.openStream());
+ } catch (Exception e) {
+ throw new CoreException(GlancePlugin.createStatus(
+ "Can't read image '" + url.toString() + "'", e));
+ }
+ if (imageDataArray == null || imageDataArray.length <= 1) {
+ throw new CoreException(GlancePlugin.createStatus("Image '"
+ + url.toString() + "' is not an animated image"));
+ }
+ }
+
+ @Override
+ public void run() {
+ Display display = PlatformUI.getWorkbench().getDisplay();
+ /*
+ * Create an off-screen image to draw on, and fill it with the shell
+ * background.
+ */
+ final Image offScreenImage = new Image(display,
+ loader.logicalScreenWidth, loader.logicalScreenHeight);
+ GC offScreenImageGC = new GC(offScreenImage);
+ offScreenImageGC.setBackground(bg);
+ offScreenImageGC.fillRectangle(0, 0, loader.logicalScreenWidth,
+ loader.logicalScreenHeight);
+
+ Image image = null;
+
+ try {
+ /* Create the first image and draw it on the off-screen image. */
+ int imageDataIndex = 0;
+ ImageData imageData = imageDataArray[imageDataIndex];
+ image = new Image(display, imageData);
+ offScreenImageGC.drawImage(image, 0, 0, imageData.width,
+ imageData.height, imageData.x, imageData.y,
+ imageData.width, imageData.height);
+
+ /*
+ * Now loop through the images, creating and drawing each one on the
+ * off-screen image before drawing it on the shell.
+ */
+ int repeatCount = loader.repeatCount;
+ while ((loader.repeatCount == 0 || repeatCount > 0)
+ && !isTerminated()) {
+ switch (imageData.disposalMethod) {
+ case SWT.DM_FILL_BACKGROUND:
+ /* Fill with the background color before drawing. */
+ offScreenImageGC.setBackground(bg);
+ offScreenImageGC.fillRectangle(imageData.x, imageData.y,
+ imageData.width, imageData.height);
+ break;
+ case SWT.DM_FILL_PREVIOUS:
+ /* Restore the previous image before drawing. */
+ offScreenImageGC.drawImage(image, 0, 0, imageData.width,
+ imageData.height, imageData.x, imageData.y,
+ imageData.width, imageData.height);
+ break;
+ }
+
+ imageDataIndex = (imageDataIndex + 1) % imageDataArray.length;
+ imageData = imageDataArray[imageDataIndex];
+ image.dispose();
+ image = new Image(display, imageData);
+ offScreenImageGC.drawImage(image, 0, 0, imageData.width,
+ imageData.height, imageData.x, imageData.y,
+ imageData.width, imageData.height);
+ final Image newImage = image;
+
+ UIUtils.syncExec(display, new Runnable() {
+ public void run() {
+ updateImage(newImage);
+ }
+ });
+
+ /*
+ * Sleep for the specified delay time (adding commonly-used
+ * slow-down fudge factors).
+ */
+ try {
+ int ms = imageData.delayTime * 10;
+ if (ms < 20)
+ ms += 30;
+ if (ms < 30)
+ ms += 10;
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+
+ /*
+ * If we have just drawn the last image, decrement the repeat
+ * count and start again.
+ */
+ if (imageDataIndex == imageDataArray.length - 1)
+ repeatCount--;
+ }
+ } catch (SWTException ex) {
+ GlancePlugin.log("There was an error animating the GIF", ex);
+ } finally {
+ if (offScreenImage != null && !offScreenImage.isDisposed())
+ offScreenImage.dispose();
+ if (offScreenImageGC != null && !offScreenImageGC.isDisposed())
+ offScreenImageGC.dispose();
+ if (image != null && !image.isDisposed())
+ image.dispose();
+ }
+ }
+
+ protected abstract boolean isTerminated();
+
+ protected abstract void updateImage(Image image);
+
+ private final ImageLoader loader = new ImageLoader();
+ private ImageData[] imageDataArray;
+ private final Color bg;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/MoveTracker.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/MoveTracker.java
new file mode 100644
index 0000000..a009981
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/MoveTracker.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class MoveTracker implements Listener {
+
+ public MoveTracker(Control control) {
+ this.control = control;
+ cursor = new Cursor(control.getDisplay(), SWT.CURSOR_SIZEALL);
+ control.setCursor(cursor);
+ control.addListener(SWT.MouseDown, this);
+ control.addListener(SWT.Dispose, this);
+ }
+
+ public void handleEvent(Event event) {
+ if (control != null && !control.isDisposed()) {
+ switch (event.type) {
+ case SWT.MouseDown:
+ Point point = control.getDisplay().getCursorLocation();
+ handleClick(point.x, point.y);
+ break;
+ case SWT.MouseMove:
+ point = control.getDisplay().getCursorLocation();
+ handleDrag(point.x - iLocation.x, point.y - iLocation.y);
+ break;
+ case SWT.Dispose:
+ cursor.dispose();
+ cursor = null;
+ case SWT.MouseUp:
+ control.getDisplay().removeFilter(SWT.MouseMove, this);
+ control.getDisplay().removeFilter(SWT.MouseUp, this);
+ }
+
+ }
+ }
+
+ protected void handleClick(int x, int y) {
+ iLocation = new Point(x, y);
+ control.getDisplay().addFilter(SWT.MouseMove, this);
+ control.getDisplay().addFilter(SWT.MouseUp, this);
+ }
+
+ protected void handleDrag(int dx, int dy) {
+ }
+
+ private Cursor cursor;
+ private Point iLocation;
+ private Control control;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/PopupSearchDialog.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/PopupSearchDialog.java
new file mode 100644
index 0000000..f80ccfb
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/PopupSearchDialog.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import java.util.List;
+
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+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.ui.glance.panels.SearchPanel;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.utils.UIUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class PopupSearchDialog extends SearchPanel {
+
+ public PopupSearchDialog(Control target) {
+ this.target = target;
+ popup = new SearchPopup(target.getShell());
+ }
+
+ public int open() {
+ return popup.open();
+ }
+
+ public boolean isApplicable(Control control) {
+ return true;
+ }
+
+ private int matchCount;
+
+ @Override
+ public void allFound(final Match[] matches) {
+ super.allFound(matches);
+ matchCount = matches.length;
+ updateInfo();
+ }
+
+ protected void updateInfo() {
+ UIUtils.asyncExec(popup.getShell(), new Runnable() {
+
+ public void run() {
+ StringBuffer buffer = new StringBuffer();
+ if (matchCount == 0) {
+ buffer.append("No matches");
+ } else if (matchCount == 1) {
+ buffer.append("1 match");
+ } else {
+ buffer.append(matchCount);
+ buffer.append(" matches");
+ }
+ buffer.append(" found");
+
+ popup.setInfoText(buffer.toString());
+ }
+ });
+ }
+
+ @Override
+ protected void textEmpty() {
+ super.textEmpty();
+ popup.setInfoText(SearchPopup.HELP_TEXT);
+ }
+
+ @Override
+ protected Control createText(Composite parent, int style) {
+ return super.createText(parent, SWT.NONE);
+ }
+
+ @Override
+ protected Label createIcon(Composite parent) {
+ Label label = super.createIcon(parent);
+ new MoveTracker(label) {
+
+ @Override
+ protected void handleClick(int x, int y) {
+ super.handleClick(x, y);
+ location = popup.getShell().getLocation();
+ }
+
+ @Override
+ protected void handleDrag(int dx, int dy) {
+ super.handleDrag(dx, dy);
+ popup.getShell().setLocation(location.x + dx, location.y + dy);
+ }
+
+ private Point location;
+
+ };
+ return label;
+ }
+
+ @Override
+ protected void setBackground(boolean found) {
+ popup.setBackground(found);
+ }
+
+ @Override
+ public void finished() {
+ }
+
+ public void closePanel() {
+ popup.close();
+ }
+
+ @Override
+ protected void showSettings() {
+ super.showSettings();
+ // popup.showDialogMenu();
+ }
+
+ private Point getTargetLocation() {
+ Shell shell = target.getShell();
+ Display display = target.getDisplay();
+ Point location = target.getLocation();
+ location = display.map(target.getParent(), shell, location);
+ return shell.toDisplay(location);
+ }
+
+ private final SearchPopup popup;
+ private final Control target;
+
+ private class SearchPopup extends SearchDialog {
+
+ /**
+ * @param parent
+ */
+ public SearchPopup(Shell parent) {
+ super(parent);
+ }
+
+ @Override
+ protected Control createTitleMenuArea(Composite parent) {
+ PopupSearchDialog.this.createContent(parent);
+ Control control = PopupSearchDialog.this.getControl();
+ control.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ return control;
+ }
+
+ @Override
+ public void showDialogMenu() {
+ super.showDialogMenu();
+ }
+
+ @Override
+ protected void fillDialogMenu(IMenuManager dialogMenu) {
+ PopupSearchDialog.this.fillMenu(dialogMenu);
+ }
+
+ @Override
+ protected void handleClose() {
+ super.handleClose();
+ fireClose();
+ }
+
+ @Override
+ protected Point getInitialSize() {
+ return new Point(getPreferedWidth(), super.getInitialSize().y);
+ }
+
+ @Override
+ protected Point getInitialLocation(Point initialSize) {
+ Point location = getTargetLocation();
+ Point size = target.getSize();
+ int x = location.x + size.x / 2 - initialSize.x / 2;
+ int y = location.y + size.y;
+ Rectangle bounds = target.getMonitor().getBounds();
+ if (y + initialSize.y > bounds.y + bounds.height) {
+ y = location.y - initialSize.y;
+ }
+ return new Point(x, y);
+ }
+
+ @Override
+ protected Control getFocusControl() {
+ return PopupSearchDialog.this.title;
+ }
+
+ @Override
+ protected List<Control> getBackgroundColorExclusions() {
+ List<Control> list = super.getBackgroundColorExclusions();
+ list.add(PopupSearchDialog.this.title);
+ return list;
+ }
+
+ public void setBackground(boolean found) {
+ Color color = found ? getBackground() : BAD_COLOR;
+ applyBackgroundColor(color);
+ }
+
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchDialog.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchDialog.java
new file mode 100644
index 0000000..68cba09
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchDialog.java
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.PopupDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchDialog extends PopupDialog {
+
+ public SearchDialog(final Shell parent) {
+ super(parent, SWT.RESIZE, true, false, false, true, false, null, null);
+ }
+
+ @Override
+ protected Control createContents(final Composite parent) {
+ final Composite composite = new Composite(parent, SWT.NONE);
+ POPUP_LAYOUT_FACTORY.applyTo(composite);
+ LAYOUTDATA_GRAB_BOTH.applyTo(composite);
+
+ titleArea = (Composite) createTitleMenuArea(composite);
+ separator = createHorizontalSeparator(composite);
+ createInfoTextArea(composite);
+
+ applyColors(composite);
+ applyFonts(composite);
+ return composite;
+ }
+
+ @Override
+ protected Control createInfoTextArea(final Composite parent) {
+ final Composite composite = new Composite(parent, SWT.NONE);
+ final GridLayout layout = new GridLayout(3, false);
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginHeight = 0;
+ layout.marginWidth = 0;
+ composite.setLayout(layout);
+ composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ progress = new Label(composite, SWT.LEFT);
+ // Status label
+ info = new Label(composite, SWT.RIGHT);
+
+ info.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ progress.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ // factory.applyTo(info);
+ // factory.applyTo(progress);
+ final Color color = parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
+ info.setForeground(color);
+ progress.setForeground(color);
+ info.setText(HELP_TEXT);
+ return composite;
+ }
+
+ @Override
+ protected void setInfoText(final String text) {
+ info.setText(text);
+ }
+
+ protected void applyColors(final Composite composite) {
+ applyForegroundColor(getForeground(), composite);
+ applyBackgroundColor(getBackground(), composite);
+ }
+
+ protected void applyFonts(final Composite composite) {
+ Dialog.applyDialogFont(composite);
+
+ if (info != null) {
+ final Font font = info.getFont();
+ final FontData[] fontDatas = font.getFontData();
+ for (int i = 0; i < fontDatas.length; i++) {
+ fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
+ }
+ infoFont = new Font(info.getDisplay(), fontDatas);
+ info.setFont(infoFont);
+ }
+ }
+
+ @Override
+ protected void configureShell(final Shell shell) {
+ super.configureShell(shell);
+ shell.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(final DisposeEvent e) {
+ handleClose();
+ }
+ });
+ }
+
+ protected void handleClose() {
+ if (infoFont != null && !infoFont.isDisposed()) {
+ infoFont.dispose();
+ }
+ infoFont = null;
+ }
+
+ /**
+ * Create a horizontal separator for the given parent.
+ *
+ * @param parent
+ * The parent composite.
+ * @return The Control representing the horizontal separator.
+ */
+ private Control createHorizontalSeparator(final Composite parent) {
+ final Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.LINE_DOT);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(separator);
+ return separator;
+ }
+
+ @Override
+ protected List<Control> getForegroundColorExclusions() {
+ final List<Control> list = copyControls(super.getForegroundColorExclusions());
+ if (info != null)
+ list.add(info);
+ if (separator != null)
+ list.add(separator);
+ return list;
+ }
+
+ @Override
+ protected List<Control> getBackgroundColorExclusions() {
+ final List<Control> list = copyControls(super.getBackgroundColorExclusions());
+ if (separator != null)
+ list.add(separator);
+ return list;
+ }
+
+ private List<Control> copyControls(List<?> list) {
+ List<Control> result = new ArrayList<Control>(list.size());
+ for (Control control : result) {
+ result.add(control);
+ }
+ return result;
+ }
+
+ protected void applyBackgroundColor(final Color color) {
+ applyBackgroundColor(color, titleArea);
+ }
+
+ private Composite titleArea;
+
+ protected Text titleText;
+ private Font infoFont;
+ private Label info;
+ private Label progress;
+ private Control separator;
+
+ private static final GridDataFactory LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true, true);
+ private static final GridLayoutFactory POPUP_LAYOUT_FACTORY = GridLayoutFactory.fillDefaults()
+ .margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT).spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);
+
+ protected static final String HELP_TEXT = "Enter search text";
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchPanelManager.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchPanelManager.java
new file mode 100644
index 0000000..fc651d8
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchPanelManager.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IWorkbenchWindow;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+import org.eclipse.ui.glance.panels.ISearchPanel;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchPanelManager {
+
+ public static SearchPanelManager getInstance() {
+ if (instance == null)
+ instance = new SearchPanelManager();
+ return instance;
+ }
+
+ public ISearchPanel getPanel(Control control) {
+ if (showStatusLine()) {
+ IWorkbenchWindow window = SearchStatusLine.getWindow(control);
+ if (window != null) {
+ return SearchStatusLine.getSearchLine(window);
+ }
+ }
+ PopupSearchDialog dialog = new PopupSearchDialog(control);
+ dialog.open();
+ return dialog;
+ }
+
+ private boolean showStatusLine() {
+ return GlancePlugin.getDefault().getPreferenceStore().getBoolean(
+ IPreferenceConstants.PANEL_STATUS_LINE);
+ }
+
+ private static SearchPanelManager instance;
+
+ private SearchPanelManager() {
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchStatusLine.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchStatusLine.java
new file mode 100644
index 0000000..284a10f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/panels/SearchStatusLine.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.panels;
+
+import org.eclipse.jface.action.ContributionItem;
+import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.jface.action.StatusLineLayoutData;
+import org.eclipse.jface.action.StatusLineManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.WorkbenchWindow;
+import org.eclipse.ui.keys.IBindingService;
+import org.eclipse.ui.texteditor.IStatusField;
+import org.eclipse.ui.texteditor.IStatusFieldExtension;
+
+import org.eclipse.ui.glance.panels.SearchPanel;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.utils.UIUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+@SuppressWarnings("restriction")
+public class SearchStatusLine extends SearchPanel {
+
+ @Override
+ protected Control createText(Composite parent, int style) {
+ Control textControl = super.createText(parent, style);
+ textControl.addFocusListener(new FocusListener() {
+ public void focusLost(FocusEvent e) {
+ setKeyFilter(true);
+ }
+
+ public void focusGained(FocusEvent e) {
+ setKeyFilter(false);
+ }
+ });
+ textControl.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ setKeyFilter(true);
+ }
+ });
+ return textControl;
+ }
+
+ public static SearchStatusLine getSearchLine(IWorkbenchWindow window) {
+ IStatusLineManager manager = getManager(window);
+ if (manager != null) {
+ IContributionItem[] items = manager.getItems();
+ for (IContributionItem item : items) {
+ if (item instanceof SearchItem)
+ return ((SearchItem) item).getSearchPanel();
+ }
+ }
+ return new SearchStatusLine(window);
+ }
+
+ public static IWorkbenchWindow getWindow(Control control) {
+ Shell shell = control.getShell();
+ IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
+ .getWorkbenchWindows();
+ for (IWorkbenchWindow window : windows) {
+ if (shell.equals(window.getShell()))
+ return window;
+ }
+ return null;
+ }
+
+ public boolean isApplicable(Control control) {
+ return window.equals(getWindow(control));
+ }
+
+ public IWorkbenchWindow getWindow() {
+ return window;
+ }
+
+ private int matchCount;
+
+ @Override
+ public void allFound(final Match[] matches) {
+ super.allFound(matches);
+ matchCount = matches.length;
+ updateInfo();
+ }
+
+ private void updateInfo() {
+ StringBuffer buffer = new StringBuffer();
+
+ if (matchCount == 0) {
+ buffer.append(DEFAULT_MATCH_LABEL);
+ } else {
+ buffer.append(matchCount);
+ }
+
+ matchText = buffer.toString();
+ UIUtils.asyncExec(matchLabel, new Runnable() {
+
+ public void run() {
+ matchLabel.setText(matchText);
+ }
+ });
+ }
+
+ public void closePanel() {
+ if (item != null) {
+ fireClose();
+ IStatusLineManager manager = getManager();
+ if (manager != null) {
+ manager.remove(item);
+ manager.update(false);
+ }
+ item = null;
+ }
+ }
+
+ @Override
+ public void createContent(Composite parent) {
+ super.createContent(parent);
+ StatusLineLayoutData data = new StatusLineLayoutData();
+ data.widthHint = getPreferedWidth();
+ data.heightHint = getPreferredHeight();
+ getControl().setLayoutData(data);
+ createMatchLabel(parent);
+ }
+
+ @Override
+ protected void textEmpty() {
+ super.textEmpty();
+ matchText = DEFAULT_MATCH_LABEL;
+ matchLabel.setText(matchText);
+ }
+
+ private void createMatchLabel(Composite parent) {
+ Label separator = new Label(parent, SWT.SEPARATOR);
+ setLayoutData(separator);
+ matchLabel = new CLabel(parent, SWT.SHADOW_NONE);
+ StatusLineLayoutData data = new StatusLineLayoutData();
+ data.widthHint = getTextWidth(parent, 10) + 15;
+ data.heightHint = getPreferredHeight();
+ matchLabel.setLayoutData(data);
+ matchLabel.setText(matchText);
+ }
+
+ protected void setKeyFilter(boolean enabled) {
+ IBindingService service = (IBindingService) PlatformUI.getWorkbench()
+ .getService(IBindingService.class);
+ if (service != null) {
+ service.setKeyFilterEnabled(enabled);
+ }
+ }
+
+ private class SearchItem extends ContributionItem implements IStatusField,
+ IStatusFieldExtension {
+
+ public void setImage(Image image) {
+ }
+
+ public void setText(String text) {
+ }
+
+ public void setErrorImage(Image image) {
+ setImage(image);
+ }
+
+ public void setErrorText(String text) {
+ setText(text);
+ }
+
+ public void setToolTipText(String string) {
+ setText(string);
+ }
+
+ @Override
+ public void fill(Composite parent) {
+ Label separator = new Label(parent, SWT.SEPARATOR);
+ createContent(parent);
+ setLayoutData(separator);
+ }
+
+ public SearchStatusLine getSearchPanel() {
+ return SearchStatusLine.this;
+ }
+
+ @Override
+ public void dispose() {
+ fireClose();
+ }
+
+ }
+
+ private void setLayoutData(Label separator) {
+ StatusLineLayoutData data = new StatusLineLayoutData();
+ data.heightHint = getPreferredHeight();
+ separator.setLayoutData(data);
+ }
+
+ private SearchStatusLine(IWorkbenchWindow window) {
+ this.window = window;
+ init();
+ }
+
+ private void init() {
+ item = new SearchItem();
+ IStatusLineManager manager = getManager();
+ if (manager != null) {
+ manager.remove(item);
+ manager.appendToGroup(StatusLineManager.BEGIN_GROUP, item);
+ manager.update(true);
+ }
+ }
+
+ private IStatusLineManager getManager() {
+ return getManager(window);
+ }
+
+ private static IStatusLineManager getManager(IWorkbenchWindow window) {
+ if (window != null) {
+ WorkbenchWindow ww = (WorkbenchWindow) window;
+ return ww.getActionBars().getStatusLineManager();
+ }
+ return null;
+ }
+
+ private static final String DEFAULT_MATCH_LABEL = "no matches";
+
+ private String matchText = DEFAULT_MATCH_LABEL;
+ private CLabel matchLabel;
+ private SearchItem item;
+ private final IWorkbenchWindow window;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferenceInitializer.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferenceInitializer.java
new file mode 100644
index 0000000..332003f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferenceInitializer.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class GlancePreferenceInitializer extends AbstractPreferenceInitializer implements IPreferenceConstants {
+
+ @Override
+ public void initializeDefaultPreferences() {
+ IPreferenceStore preferences = GlancePlugin.getDefault().getPreferenceStore();
+ preferences.setDefault(SEARCH_CASE_SENSITIVE, false);
+ preferences.setDefault(SEARCH_CAMEL_CASE, false);
+ preferences.setDefault(SEARCH_REGEXP, false);
+ preferences.setDefault(SEARCH_WORD_PREFIX, false);
+
+ preferences.setDefault(PANEL_DIRECTIONS, true);
+ preferences.setDefault(PANEL_STATUS_LINE, true);
+ preferences.setDefault(PANEL_CLOSE, true);
+ preferences.setDefault(PANEL_TEXT_SIZE, 20);
+ preferences.setDefault(PANEL_LINK, true);
+ preferences.setDefault(PANEL_STARTUP, false);
+ preferences.setDefault(PANEL_AUTO_INDEXING, false);
+ preferences.setDefault(SEARCH_INCREMENTAL, true);
+ preferences.setDefault(PANEL_MAX_INDEXING_DEPTH, 4);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferencePage.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferencePage.java
new file mode 100644
index 0000000..aefcf09
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/GlancePreferencePage.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.preferences;
+
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.ColorFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.IntegerFieldEditor;
+import org.eclipse.swt.SWT;
+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.Group;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.search.SearchManager;
+import org.eclipse.ui.glance.sources.ColorManager;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class GlancePreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage,
+ IPreferenceConstants {
+
+ private Button currentWindow;
+
+ public GlancePreferencePage() {
+ super(GRID);
+ }
+
+ public void init(final IWorkbench workbench) {
+ }
+
+ @Override
+ protected IPreferenceStore doGetPreferenceStore() {
+ return GlancePlugin.getDefault().getPreferenceStore();
+ }
+
+ @Override
+ protected void createFieldEditors() {
+ createSearchSettings(getFieldEditorParent());
+ createColorSettings(getFieldEditorParent());
+ createPanelSettings(getFieldEditorParent());
+ }
+
+ /**
+ * Adjust the layout of the field editors so that they are properly aligned.
+ */
+ @Override
+ protected void adjustGridLayout() {
+ super.adjustGridLayout();
+ ((GridLayout) getFieldEditorParent().getLayout()).numColumns = 1;
+ }
+
+ private Group createPanelSettings(final Composite parent) {
+ final Group group = new Group(parent, SWT.NONE);
+ group.setText("Panel");
+ group.setLayout(new GridLayout(1, false));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ final Composite composite = new Composite(group, SWT.NONE);
+ composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ createCurrentWindowOption(composite);
+ addField(new BooleanFieldEditor(PANEL_STARTUP, "Show at startup", composite));
+ addField(new BooleanFieldEditor(PANEL_STATUS_LINE, "Show panel in status line when possible", composite));
+ addField(new BooleanFieldEditor(PANEL_DIRECTIONS, "Show direction buttons", composite));
+ addField(new BooleanFieldEditor(PANEL_CLOSE, "Show close button", composite));
+ addField(new BooleanFieldEditor(PANEL_AUTO_INDEXING, "Enable auto indexing", composite));
+ addField(new BooleanFieldEditor(SEARCH_INCREMENTAL, "Enable incremental search", composite));
+ final IntegerFieldEditor maxIndexingDepthEditor = new IntegerFieldEditor(PANEL_MAX_INDEXING_DEPTH,
+ "Max indexing depth for trees:", composite);
+ maxIndexingDepthEditor.setValidRange(1, Integer.MAX_VALUE);
+ addField(maxIndexingDepthEditor);
+ addField(new IntegerFieldEditor(PANEL_TEXT_SIZE, "Default box width in chars:", composite));
+
+ return group;
+ }
+
+ private void createCurrentWindowOption(final Composite composite) {
+ currentWindow = new Button(composite, SWT.CHECK);
+ currentWindow.setText("Show in the current window");
+ initCurrentWindow();
+ }
+
+ @Override
+ protected void performDefaults() {
+ super.performDefaults();
+ initCurrentWindow();
+ }
+
+ private void initCurrentWindow() {
+ final IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window == null) {
+ currentWindow.setEnabled(false);
+ } else {
+ final boolean inWindow = SearchManager.getIntance().isInWindow(window);
+ currentWindow.setSelection(inWindow);
+ }
+ }
+
+ @Override
+ public boolean performOk() {
+ final IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window != null) {
+ final boolean inWindow = SearchManager.getIntance().isInWindow(window);
+ final boolean open = currentWindow.getSelection();
+ if (open != inWindow) {
+ SearchManager.getIntance().setStatusLine(window, open);
+ }
+ }
+ return super.performOk();
+ }
+
+ private Group createSearchSettings(final Composite parent) {
+ final Group group = new Group(parent, SWT.NONE);
+ group.setText("Search");
+ group.setLayout(new GridLayout(1, false));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ final Composite composite = new Composite(group, SWT.NONE);
+ composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ addField(new BooleanFieldEditor(SEARCH_CASE_SENSITIVE, LABEL_CASE_SENSITIVE, composite));
+ addField(new BooleanFieldEditor(SEARCH_CAMEL_CASE, LABEL_CAMEL_CASE, composite));
+ addField(new BooleanFieldEditor(SEARCH_WORD_PREFIX, LABEL_WORD_PREFIX, composite));
+ addField(new BooleanFieldEditor(SEARCH_REGEXP, LABEL_REGEXP, composite));
+
+ return group;
+ }
+
+ private Group createColorSettings(final Composite parent) {
+ final Group group = new Group(parent, SWT.NONE);
+ group.setText("Colors");
+ group.setLayout(new GridLayout(1, false));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ final Composite composite = new Composite(group, SWT.NONE);
+ composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ addField(new ColorEditor(composite, "Highlight:", COLOR_HIGHLIGHT));
+ addField(new ColorEditor(composite, "Selection:", COLOR_SELECTION));
+
+ return group;
+ }
+
+ private static class ColorEditor extends ColorFieldEditor {
+
+ public ColorEditor(final Composite parent, final String text, final String prefKey) {
+ this(parent, text, prefKey, ColorManager.getStore());
+ }
+
+ public ColorEditor(final Composite parent, final String text, final String prefKey, IPreferenceStore store) {
+ super(prefKey, text, parent);
+ super.setPreferenceStore(store);
+ }
+
+ @Override
+ public void setPreferenceStore(final IPreferenceStore store) {
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/IPreferenceConstants.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/IPreferenceConstants.java
new file mode 100644
index 0000000..5e8ef21
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/IPreferenceConstants.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.preferences;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface IPreferenceConstants {
+
+ final String PREFERENCE_PAGE_ID = "org.eclipse.ui.glance.preference";
+
+ final String SEARCH_PREFIX = "search.";
+ final String PANEL_PREFIX = "panel.";
+ final String HISTORY = "searchHistory";
+
+ final String PANEL_DIRECTIONS = PANEL_PREFIX + "directions";
+ final String PANEL_CLOSE = PANEL_PREFIX + "close";
+ final String PANEL_TEXT_SIZE = PANEL_PREFIX + "textSize";
+ final String PANEL_STATUS_LINE = PANEL_PREFIX + "statusLine";
+ final String PANEL_LINK = PANEL_PREFIX + "link";
+ final String PANEL_STARTUP = PANEL_PREFIX + "startup";
+ final String PANEL_AUTO_INDEXING = PANEL_PREFIX + "autoIndexing";
+ final String PANEL_MAX_INDEXING_DEPTH = PANEL_PREFIX + "maxIndexingDepth";
+
+ final String SEARCH_CASE_SENSITIVE = SEARCH_PREFIX + "caseSensitive";
+ final String SEARCH_WORD_PREFIX = SEARCH_PREFIX + "wordPrefix";
+ final String SEARCH_REGEXP = SEARCH_PREFIX + "regexp";
+ final String SEARCH_CAMEL_CASE = SEARCH_PREFIX + "camelCase";
+ final String SEARCH_INCREMENTAL = PANEL_PREFIX + "incremental";
+
+ final String LABEL_CASE_SENSITIVE = "Case Sensitive";
+ final String LABEL_WORD_PREFIX = "Word Prefix";
+ final String LABEL_REGEXP = "Regular Expressions";
+ final String LABEL_CAMEL_CASE = "Camel Case";
+
+ final String COLOR_HIGHLIGHT = "glanceColorBackground";
+ final String COLOR_SELECTION = "glanceSelectedColorBackground";
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/TreeColors.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/TreeColors.java
new file mode 100644
index 0000000..92764d3
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/preferences/TreeColors.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.preferences;
+
+import org.eclipse.swt.graphics.RGB;
+
+public class TreeColors implements IPreferenceConstants {
+
+ public TreeColors(RGB bg, RGB fg, boolean useNative) {
+ this.bg = bg;
+ this.fg = fg;
+ this.useNative = useNative;
+ }
+
+ public RGB getBg() {
+ return bg;
+ }
+
+ public RGB getFg() {
+ return fg;
+ }
+
+ public boolean isUseNative() {
+ return useNative;
+ }
+
+ public static TreeColors getDefault() {
+ String osName = System.getProperty("os.name").toLowerCase();
+
+ if (osName.contains("windows")) {
+ if (osName.contains("7")) {
+ return new TreeColors(null, null, true);
+ }
+ } else if (osName.contains("mac")) {
+ return new TreeColors(new RGB(56, 117, 215),
+ new RGB(255, 255, 255), false);
+ }
+ return new TreeColors(null, null, false);
+ }
+
+ private final boolean useNative;
+ private final RGB bg;
+ private final RGB fg;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/AbstractSearchScope.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/AbstractSearchScope.java
new file mode 100644
index 0000000..51d70ff
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/AbstractSearchScope.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.ui.glance.internal.search.SearchScopeEntry.IMatchListener;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class AbstractSearchScope implements IMatchListener,
+ ITextSourceListener {
+
+ public AbstractSearchScope(ITextSource source) {
+ this.source = source;
+ init();
+ }
+
+ public void dispose() {
+ for (SearchScopeEntry entry : entries) {
+ entry.dispose();
+ }
+ source.removeTextSourceListener(this);
+ }
+
+ public abstract void added(SearchScopeEntry entry, Match match);
+
+ public abstract void cleared(SearchScopeEntry entry);
+
+ public void blocksReplaced(ITextBlock[] newBlocks) {
+ entries = new ArrayList<SearchScopeEntry>(newBlocks.length);
+ newBlocks(newBlocks);
+ }
+
+ public void blocksChanged(ITextBlock[] removed, ITextBlock[] added) {
+ for (int i = 0; i < entries.size(); i++) {
+ SearchScopeEntry entry = entries.get(i);
+ for (ITextBlock block : removed) {
+ if (block.equals(entry.getBlock())) {
+ entries.remove(i);
+ i--;
+ break;
+ }
+ }
+ }
+ newBlocks(added);
+ }
+
+ private void newBlocks(ITextBlock[] newBlocks) {
+ for (ITextBlock block : newBlocks) {
+ SearchScopeEntry entry = createEntry(block);
+ int index = Collections.binarySearch(entries, entry);
+ if (index < 0) {
+ index = -1 * index - 1;
+ entries.add(index, entry);
+ }
+ }
+ // TODO: need to test more without this updates
+ // updateSelection(source.getSelection());
+ }
+
+ public void selectNext() {
+ int index = getSelectedEntryIndex();
+ if (index < 0 && entries.size() > 0)
+ index = 0;
+ if (index >= 0) {
+ int offset = getOffset();
+ Match match = findNextMatch(index, entries.size(), offset,
+ Integer.MAX_VALUE);
+ if (match == null) {
+ match = findNextMatch(0, index, -1, Integer.MAX_VALUE);
+ if (match == null) {
+ match = findNextMatch(index, index + 1, -1, offset);
+ }
+ }
+ select(match);
+ }
+ }
+
+ public void selectPrev() {
+ int index = getSelectedEntryIndex();
+ if (index < 0 && entries.size() > 0)
+ index = 0;
+ if (index >= 0) {
+ int offset = getOffset();
+ Match match = findPrevMatch(index, -1, 0, offset);
+ if (match == null) {
+ match = findPrevMatch(entries.size() - 1, index, 0,
+ Integer.MAX_VALUE);
+ if (match == null) {
+ match = findPrevMatch(index, index - 1, offset,
+ Integer.MAX_VALUE);
+ }
+ }
+ select(match);
+ }
+ }
+
+ protected void select(Match match) {
+ if (match != null) {
+ selection = new SourceSelection(match.getBlock(),
+ match.getOffset(), match.getLength());
+ source.select(match);
+ }
+ }
+
+ public void showEmptyText() {
+ source.select(null);
+ }
+
+ public void showMatches() {
+ source.show(getMatches());
+ }
+
+ protected Match findNextMatch(int from, int to, int offsetStart,
+ int offsetEnd) {
+ for (int i = from; i < to; i++) {
+ SearchScopeEntry entry = entries.get(i);
+ for (Match match : entry.getMatches()) {
+ if (match.getOffset() > offsetStart
+ && match.getOffset() < offsetEnd) {
+ return match;
+ }
+ }
+ offsetStart = -1;
+ offsetEnd = Integer.MAX_VALUE;
+ }
+ return null;
+ }
+
+ protected Match findPrevMatch(int from, int to, int offsetStart,
+ int offsetEnd) {
+ for (int i = from; i > to; i--) {
+ SearchScopeEntry entry = entries.get(i);
+ List<Match> matches = entry.getMatches();
+ for (int j = matches.size() - 1; j >= 0; j--) {
+ Match match = matches.get(j);
+ if (match.getOffset() >= offsetStart
+ && match.getOffset() < offsetEnd) {
+ return match;
+ }
+ }
+ offsetStart = -1;
+ offsetEnd = Integer.MAX_VALUE;
+ }
+ return null;
+ }
+
+ public void selectionChanged(SourceSelection selection) {
+ updateSelection(selection);
+ }
+
+ public Match[] getMatches() {
+ List<Match> matches = new ArrayList<Match>();
+ for (SearchScopeEntry entry : entries) {
+ matches.addAll(entry.getMatches());
+ }
+ return matches.toArray(new Match[matches.size()]);
+ }
+
+ protected void init() {
+ source.addTextSourceListener(this);
+ ITextBlock[] blocks = source.getBlocks();
+ entries = new ArrayList<SearchScopeEntry>(blocks.length);
+ for (ITextBlock block : blocks) {
+ SearchScopeEntry entry = createEntry(block);
+ entries.add(entry);
+ }
+ Collections.sort(entries);
+ updateSelection(source.getSelection());
+ }
+
+ private void updateSelection(SourceSelection selection) {
+ if (selection != null && selection.equals(this.selection)) {
+ return;
+ }
+ if (selection == null && entries.size() > 0) {
+ ITextBlock block = entries.get(0).getBlock();
+ selection = new SourceSelection(block, 0, 0);
+ }
+ this.selection = selection;
+ updateStart();
+ }
+
+ protected void updateStart() {
+ int index = getSelectedEntryIndex();
+ if (index >= 0) {
+ SearchScopeEntry entry = entries.get(index);
+ entry.setStart(getOffset());
+ currentEntry = index;
+ }
+ }
+
+ private int getOffset() {
+ return selection == null ? 0 : selection.getOffset();
+ }
+
+ protected int getSelectedEntryIndex() {
+ if (selection != null) {
+ for (int i = 0; i < entries.size(); i++) {
+ SearchScopeEntry entry = entries.get(i);
+ ITextBlock block = entry.getBlock();
+ if (block.equals(selection.getBlock())) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ protected SearchScopeEntry createEntry(ITextBlock block) {
+ return new SearchScopeEntry(block, this);
+ }
+
+ protected List<SearchScopeEntry> entries;
+ protected ITextSource source;
+ private SourceSelection selection;
+ protected int currentEntry;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/ISearchListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/ISearchListener.java
new file mode 100644
index 0000000..f0d4ed3
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/ISearchListener.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ISearchListener {
+
+ public void firstFound(Match match);
+
+ public void allFound(Match[] matches);
+
+ public void finished();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchEngine.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchEngine.java
new file mode 100644
index 0000000..6698d8f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchEngine.java
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.regex.Matcher;
+
+import org.eclipse.ui.glance.internal.search.SearchJob.ISearchMonitor;
+import org.eclipse.ui.glance.sources.ConfigurationManager;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchEngine extends Thread {
+
+ public SearchEngine(ISearchListener listener) {
+ this.listener = listener;
+ }
+
+ public void setSource(SearchRule rule, ITextSource source, boolean paused) {
+ synchronized (monitor) {
+ if (scope != null) {
+ scope.dispose();
+ scope = null;
+ }
+ scope = new SearchScope(source);
+ this.paused = paused;
+ doSetRule(rule);
+ }
+ }
+
+ public void selectNext() {
+ if (scope != null) {
+ scope.selectNext();
+ }
+ }
+
+ public void selectPrev() {
+ if (scope != null) {
+ scope.selectPrev();
+ }
+ }
+
+ public void run() {
+ while (!exit) {
+ sleepWhileInterrupted(0);
+ if (scope == null)
+ continue;
+ if (exit)
+ break;
+ while (true) {
+ long start = System.currentTimeMillis();
+ if (!findMatches())
+ continue;
+ long waitPause = paused ? PAUSE
+ - (System.currentTimeMillis() - start) : 0;
+ if (waitPause > 0)
+ sleepWhileInterrupted(waitPause);
+ if (!scope.isCanceled())
+ break;
+ }
+ listener.finished();
+ scope.showMatches();
+ }
+ }
+
+ public void setRule(SearchRule rule) {
+ synchronized (monitor) {
+ doSetRule(rule);
+ }
+ }
+
+ protected void doSetRule(SearchRule rule) {
+ cancel = true;
+ boolean findFirst = false;
+ if (this.rule == null || !this.rule.equals(rule)) {
+ this.rule = rule;
+ findFirst = true;
+ matcher = rule.getText().length() == 0 ? null : rule.getPattern()
+ .matcher(new String());
+ }
+ if (scope != null) {
+ scope.updateMatcher(findFirst);
+ }
+ interrupt();
+ }
+
+ public void exit() {
+ synchronized (monitor) {
+ exit = true;
+ interrupt();
+ }
+ }
+
+ private boolean findMatches() {
+ cancel = false;
+ if (matcher == null) {
+ scope.showEmptyText();
+ return true;
+ }
+ SearchJob job = null;
+ while ((job = scope.getJob()) != null) {
+ if (!job.run())
+ return false;
+ }
+ scope.updateResult();
+ return true;
+ }
+
+ private void sleepWhileInterrupted(long millis) {
+ try {
+ if (millis == 0) {
+ while (true)
+ Thread.sleep(50);
+ } else
+ Thread.sleep(millis);
+ } catch (InterruptedException e1) {
+ }
+ }
+
+ private class SearchScope extends AbstractSearchScope implements
+ ISearchMonitor {
+
+ private boolean firstFound = false;
+
+ /**
+ * @param source
+ */
+ public SearchScope(ITextSource source) {
+ super(source);
+ }
+
+ public SearchJob getJob() {
+ synchronized (monitor) {
+ while (currentEntry < entries.size()) {
+ SearchJob job = (SearchJob) entries.get(currentEntry);
+ if (!job.isFinished())
+ return job;
+ currentEntry++;
+ }
+ return updateEntry();
+ }
+ }
+
+ public void updateResult() {
+ if (!firstFound) {
+ listener.firstFound(null);
+ firstFound = true;
+ }
+ listener.allFound(getMatches());
+ }
+
+ @Override
+ public void added(SearchScopeEntry entry, Match match) {
+ if (!firstFound) {
+ if (ConfigurationManager.getInstance().incremenstalSearch()){
+ select(match);
+ }
+ listener.firstFound(match);
+ firstFound = true;
+ }
+ }
+
+ @Override
+ public void cleared(SearchScopeEntry entry) {
+ synchronized (monitor) {
+ cancel = true;
+ interrupt();
+ }
+ }
+
+ public boolean isCanceled() {
+ synchronized (monitor) {
+ return cancel;
+ }
+ }
+
+ @Override
+ public void blocksChanged(ITextBlock[] removed, ITextBlock[] added) {
+ synchronized (monitor) {
+ super.blocksChanged(removed, added);
+ cancel = true;
+ interrupt();
+ }
+ }
+
+ @Override
+ public void blocksReplaced(ITextBlock[] newBlocks) {
+ synchronized (monitor) {
+ super.blocksReplaced(newBlocks);
+ cancel = true;
+ interrupt();
+ }
+ }
+
+ @Override
+ public Match[] getMatches() {
+ synchronized (monitor) {
+ return super.getMatches();
+ }
+ }
+
+ @Override
+ protected SearchScopeEntry createEntry(ITextBlock block) {
+ return new SearchJob(block, matcher, this);
+ }
+
+ protected void updateMatcher(boolean findFirst) {
+ firstFound = !findFirst;
+ for (SearchScopeEntry entry : entries) {
+ SearchJob job = (SearchJob) entry;
+ job.update(matcher);
+ }
+ updateStart();
+ }
+
+ private SearchJob updateEntry() {
+ currentEntry = 0;
+ for (SearchScopeEntry entry : entries) {
+ SearchJob job = (SearchJob) entry;
+ if (!job.isFinished())
+ return job;
+ currentEntry++;
+ }
+ currentEntry = 0;
+ return null;
+ }
+ }
+
+ private static final long PAUSE = 100 * 5;// 100 s
+
+ private boolean cancel;
+ private SearchRule rule;
+ private Matcher matcher;
+ private Object monitor = new Object();
+ private boolean exit;
+ private boolean paused;
+
+ private SearchScope scope;
+ private ISearchListener listener;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchJob.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchJob.java
new file mode 100644
index 0000000..e6d8a88
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchJob.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.regex.Matcher;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchJob extends SearchScopeEntry {
+
+ public SearchJob(ITextBlock block, Matcher matcher, ISearchMonitor monitor) {
+ super(block, monitor);
+ update(matcher);
+ }
+
+ public void update(Matcher matcher) {
+ this.matcher = matcher;
+ clear();
+ }
+
+ @Override
+ protected void doClear() {
+ super.doClear();
+ finished = false;
+ }
+
+ /**
+ * @return the finished
+ */
+ public boolean isFinished() {
+ return finished;
+ }
+
+ public boolean run() {
+ if (matcher == null)
+ return false;
+ matcher.reset(getText());
+ int from = getStart();
+ if (!find(from, getText().length()))
+ return false;
+ addMatchToBegin();
+ if (!find(0, from - 1))
+ return false;
+ finished = true;
+ setStart(0);
+ return true;
+ }
+
+ private boolean find(int from, int to) {
+ int k = 1;
+ int limit = getText().length();
+ if (from >= to || from > limit)
+ return true;
+ Match match = find(from);
+ if (getMonitor().isCanceled())
+ return false;
+ if (match != null) {
+ from = match.getOffset() + 1;
+ if (from > to || from > limit)
+ return true;
+ addMatch(match);
+ match = find(from);
+ while ((match = find(from)) != null) {
+ if (match.getOffset() >= to)
+ return true;
+ addMatch(match);
+ if (k++ == 20) {
+ if (getMonitor().isCanceled())
+ return false;
+ k = 0;
+ }
+ from = match.getOffset() + 1;
+ }
+ }
+ return true;
+ }
+
+ private Match find(int from) {
+ try {
+ if (matcher.find(from)) {
+ int start = matcher.start();
+ int end = matcher.end();
+ if (end != start) { // don't report 0-length matches
+ return new Match(getBlock(), start, end - start);
+ }
+ }
+ } catch (Exception e) {
+ // It can be an exception while we matching.
+ // So return if exception occured
+ }
+ return null;
+ }
+
+ private ISearchMonitor getMonitor() {
+ return (ISearchMonitor) getListener();
+ }
+
+ interface ISearchMonitor extends IMatchListener {
+
+ boolean isCanceled();
+
+ }
+
+ private boolean finished;
+ private Matcher matcher;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchManager.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchManager.java
new file mode 100644
index 0000000..fc34ca6
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchManager.java
@@ -0,0 +1,470 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IWindowListener;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.panels.SearchPanelManager;
+import org.eclipse.ui.glance.internal.panels.SearchStatusLine;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+import org.eclipse.ui.glance.internal.sources.ISourceProviderListener;
+import org.eclipse.ui.glance.internal.sources.TextSourceMaker;
+import org.eclipse.ui.glance.internal.sources.TextSourceManager;
+import org.eclipse.ui.glance.panels.ISearchPanel;
+import org.eclipse.ui.glance.panels.ISearchPanelListener;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+import org.eclipse.ui.glance.utils.UITextSource;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchManager {
+
+ /** Searching from the current position */
+ public static final int FIND_HERE = 0;
+ /** Searching next occurrence of the string */
+ public static final int FIND_NEXT = 1;
+ /** Searching previous occurrence of the string */
+ public static final int FIND_PREVIOUS = 2;
+
+ public static SearchManager getIntance() {
+ if (manager == null) {
+ manager = new SearchManager();
+ }
+ return manager;
+ }
+
+ public boolean activate() {
+ final TextSourceMaker source = TextSourceManager.getInstance()
+ .getSource();
+ if (update(source, true)) {
+ forceFocus();
+ return true;
+ }
+ return false;
+ }
+
+ public void startup() {
+ PlatformUI.getWorkbench().addWindowListener(new IWindowListener() {
+ public void windowOpened(final IWorkbenchWindow window) {
+ setStatusLine(window, true);
+ }
+
+ public void windowDeactivated(final IWorkbenchWindow window) {
+ }
+
+ public void windowClosed(final IWorkbenchWindow window) {
+ }
+
+ public void windowActivated(final IWorkbenchWindow window) {
+ }
+ });
+ for (final IWorkbenchWindow window : PlatformUI.getWorkbench()
+ .getWorkbenchWindows()) {
+ setStatusLine(window, true);
+ }
+ }
+
+ public void setStatusLine(final IWorkbenchWindow window, final boolean open) {
+ final ISearchPanel panel = SearchStatusLine.getSearchLine(window);
+ if (!open) {
+ panel.closePanel();
+ return;
+ }
+ panels.add(panel);
+ final SearchPanelListener listener = new SearchPanelListener(panel);
+ panel.addPanelListener(listener);
+ panelToListener.put(panel, listener);
+ updateSourceListener();
+
+ final TextSourceMaker source = TextSourceManager.getInstance()
+ .getSource();
+ if (source != null && source.getControl() != null
+ && panel.isApplicable(source.getControl())) {
+ this.panel = panel;
+ rule = panel.getRule();
+ if (setDescription(source))
+ updateEnabling();
+ }
+ }
+
+ public boolean isInWindow(final IWorkbenchWindow window) {
+ for (final ISearchPanel panel : panels) {
+ if (panel instanceof SearchStatusLine) {
+ final SearchStatusLine sl = ((SearchStatusLine) panel);
+ if (sl.getWindow() == window) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void findNext() {
+ if (panel != null) {
+ panel.findNext();
+ }
+ }
+
+ public void findPrevious() {
+ if (panel != null) {
+ panel.findPrevious();
+ }
+ }
+
+ public void clearHistory() {
+ if (panel != null) {
+ panel.clearHistory();
+ }
+ }
+
+ public void sourceFocus() {
+ ITextSource source = getSource();
+ if (source instanceof UITextSource) {
+ UITextSource uiTextSource = (UITextSource) source;
+ if (uiTextSource.getControl() != null) {
+ uiTextSource.getControl().setFocus();
+ }
+ }
+ }
+
+ public void close() {
+ if (panel != null) {
+ panel.closePanel();
+ }
+ }
+
+ public ITextSource getSource() {
+ return source;
+ }
+
+ private boolean update(final TextSourceMaker source,
+ final boolean openNewPanel) {
+ updatePanel(source, openNewPanel);
+ if (panel != null) {
+ rule = panel.getRule();
+ updateSourceListener();
+ if (setDescription(source))
+ updateEnabling();
+ return true;
+ }
+ return false;
+ }
+
+ private void updatePanel(final TextSourceMaker source,
+ final boolean openNewPanel) {
+ final Control control = source.getControl();
+ if (panel != null) {
+ if (panel.isApplicable(control))
+ return;
+ if (this.source != null) {
+ this.source.dispose();
+ this.source = null;
+ }
+ creator = null;
+ panel = null;
+ }
+ for (final ISearchPanel panel : panels) {
+ if (panel.isApplicable(control)) {
+ this.panel = panel;
+ break;
+ }
+ }
+ if (panel == null && openNewPanel) {
+ panel = SearchPanelManager.getInstance().getPanel(control);
+ if (panel != null) {
+ panels.add(panel);
+ final SearchPanelListener listener = new SearchPanelListener(
+ panel);
+ panel.addPanelListener(listener);
+ panelToListener.put(panel, listener);
+ }
+ }
+ }
+
+ private void forceFocus() {
+ String text = "";
+ if (source != null) {
+ final SourceSelection selection = source.getSelection();
+ if (selection != null) {
+ text = selection.getBlock().getText();
+ final int offset = selection.getOffset();
+ final int length = selection.getLength();
+ if (offset + length <= text.length())
+ text = text.substring(offset, offset + length);
+ else
+ text = "";
+ }
+ }
+ panel.setFocus(text);
+ }
+
+ private void find(final SearchRule rule, final int type) {
+ this.type = type;
+ if (rule != null) {
+ final boolean textEquals = rule.isTextEquals(this.rule);
+ final boolean settingsEqual = rule.isSettingsEqual(this.rule);
+ if (textEquals) {
+ if (settingsEqual) {
+ updateSelection();
+ } else {
+ updateSearch(rule, false);
+ }
+ } else {
+ updateSearch(rule, true);
+ }
+ } else {
+ updateSearch(rule, false);
+ }
+ this.rule = rule;
+ }
+
+ protected void dispose(final ISearchPanel panel) {
+ panels.remove(panel);
+ final SearchPanelListener listener = panelToListener.remove(panel);
+ panel.removePanelListener(listener);
+ if (panel == this.panel) {
+ this.panel = null;
+ if (source != null) {
+ source.dispose();
+ source = null;
+ }
+ if (creator != null) {
+ final Control control = creator.getControl();
+ if (control != null && !control.isDisposed())
+ control.forceFocus();
+ creator = null;
+ }
+ rule = null;
+ }
+ if (panels.size() == 0) {
+ if (engine != null) {
+ engine.exit();
+ engine = null;
+ }
+ if (sourceListener != null) {
+ TextSourceManager.getInstance().removeSourceProviderListener(
+ sourceListener);
+ sourceListener = null;
+ }
+ } else {
+ updateSourceListener();
+ }
+ }
+
+ protected void updateSearch(final SearchRule rule, final boolean paused) {
+ getSearchEngine().setRule(rule);
+ }
+
+ protected void updateSelection() {
+ if (type == FIND_NEXT) {
+ getSearchEngine().selectNext();
+ } else if (type == FIND_PREVIOUS) {
+ getSearchEngine().selectPrev();
+ }
+ }
+
+ protected boolean setDescription(final TextSourceMaker descriptor) {
+ // ignore panel controls
+ if (descriptor != null && panel != null && panel.getControl() != null) {
+ if (isParent(panel.getControl(), descriptor.getControl()))
+ return false;
+ }
+ // ignore the same source
+ if (descriptor == null) {
+ if (this.creator == null)
+ return false;
+ } else if (descriptor.equals(this.creator)) {
+ return false;
+ }
+ if (source != null) {
+ source.dispose();
+ source = null;
+ this.creator = null;
+ }
+ if (descriptor != null && descriptor.isValid()) {
+ this.creator = descriptor;
+ source = new UITextSource(descriptor.create(),
+ descriptor.getControl());
+ getSearchEngine().setSource(rule, source, true);
+ source.init();
+ updateIndexingState();
+ }
+ return true;
+ }
+
+ private void updateIndexingState() {
+ if (monitor != null) {
+ monitor.setCanceled(true);
+ monitor = null;
+ }
+
+ final boolean indexRequired = source != null && !source.isDisposed()
+ && source.isIndexRequired();
+ if (GlancePlugin.getDefault().getPreferenceStore()
+ .getBoolean(IPreferenceConstants.PANEL_AUTO_INDEXING)
+ && indexRequired) {
+ index();
+ } else {
+ panel.setIndexingState(indexRequired ? ISearchPanel.INDEXING_STATE_INITIAL
+ : ISearchPanel.INDEXING_STATE_DISABLE);
+ }
+ }
+
+ public void index() {
+ if (panel != null && source != null && !source.isDisposed()) {
+ monitor = new SearchProgressMonitor(panel);
+ new Thread() {
+ @Override
+ public void run() {
+ panel.setIndexingState(ISearchPanel.INDEXING_STATE_IN_PROGRESS);
+ if (source != null) {
+ source.index(monitor);
+ }
+ }
+ }.start();
+ }
+ }
+
+ private IProgressMonitor monitor;
+
+ protected void updateEnabling() {
+ if (panel != null) {
+ panel.setEnabled(source != null);
+ if (source == null) {
+ panel.setIndexingState(ISearchPanel.INDEXING_STATE_FINISHED);
+ }
+ }
+ }
+
+ protected boolean isParent(final Control parent, Control child) {
+ while (child != null) {
+ if (child.equals(parent))
+ return true;
+ child = child.getParent();
+ }
+ return false;
+ }
+
+ private SearchEngine getSearchEngine() {
+ if (engine == null) {
+ engine = new SearchEngine(searchListener);
+ engine.start();
+ }
+ return engine;
+ }
+
+ private void updateSourceListener() {
+ if (sourceListener == null) {
+ sourceListener = new SourceListener();
+ TextSourceManager.getInstance().addSourceProviderListener(
+ sourceListener);
+ }
+ }
+
+ private class SearchListener implements ISearchListener {
+
+ public void allFound(final Match[] matches) {
+ if (panel != null)
+ panel.allFound(matches);
+ }
+
+ public void finished() {
+ if (panel != null)
+ panel.finished();
+ }
+
+ public void firstFound(final Match match) {
+ if (panel != null)
+ panel.firstFound(match);
+ }
+ }
+
+ private class SourceListener implements ISourceProviderListener {
+ public void sourceChanged(final TextSourceMaker source) {
+ update(source, false);
+ }
+ }
+
+ private class SearchPanelListener implements ISearchPanelListener {
+
+ public SearchPanelListener(final ISearchPanel panel) {
+ this.panel = panel;
+ }
+
+ protected boolean isCurrent() {
+ return panel.equals(SearchManager.this.panel);
+ }
+
+ public void ruleChanged(final SearchRule rule) {
+ if (isCurrent())
+ find(rule, SearchManager.FIND_HERE);
+ }
+
+ public void findNext() {
+ if (isCurrent())
+ find(rule, SearchManager.FIND_NEXT);
+ }
+
+ public void findPrevious() {
+ if (isCurrent())
+ find(rule, SearchManager.FIND_PREVIOUS);
+ }
+
+ public void close() {
+ dispose(panel);
+ }
+
+ public void indexCanceled() {
+ if (monitor != null) {
+ monitor.setCanceled(true);
+ monitor = null;
+ }
+ }
+
+ private final ISearchPanel panel;
+
+ }
+
+ private static SearchManager manager;
+
+ private SearchManager() {
+ panels = new ArrayList<ISearchPanel>();
+ searchListener = new SearchListener();
+ panelToListener = new HashMap<ISearchPanel, SearchPanelListener>();
+ }
+
+ private final SearchListener searchListener;
+ private SourceListener sourceListener;
+ private final Map<ISearchPanel, SearchPanelListener> panelToListener;
+ private SearchEngine engine;
+ private final List<ISearchPanel> panels;
+ private ISearchPanel panel;
+ private ITextSource source;
+ private TextSourceMaker creator;
+
+ private int type;
+ private SearchRule rule;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchProgressMonitor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchProgressMonitor.java
new file mode 100644
index 0000000..927483f
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchProgressMonitor.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import org.eclipse.ui.glance.panels.ISearchPanel;
+
+public class SearchProgressMonitor implements IProgressMonitor {
+
+ private final ISearchPanel panel;
+ private double total = 1;
+ private double current;
+ private boolean cancel;
+
+ public SearchProgressMonitor(ISearchPanel panel) {
+ this.panel = panel;
+ }
+
+ public void beginTask(String name, int totalWork) {
+ total = totalWork;
+ panel.newTask(name);
+ current = 0;
+ }
+
+ public void done() {
+ if (isCanceled()) {
+ return;
+ }
+ panel.setIndexingState(ISearchPanel.INDEXING_STATE_FINISHED);
+ }
+
+ public void internalWorked(double work) {
+ if (isCanceled()) {
+ return;
+ }
+ current += work / total;
+ if (current > 1) {
+ current = 1;
+ }
+ panel.updateIndexingPercent(current);
+ }
+
+ public boolean isCanceled() {
+ return cancel || panel.getControl().isDisposed();
+ }
+
+ public void setCanceled(boolean value) {
+ this.cancel = value;
+ panel.setIndexingState(ISearchPanel.INDEXING_STATE_FINISHED);
+ }
+
+ public void setTaskName(String name) {
+ }
+
+ public void subTask(String name) {
+ }
+
+ public void worked(int work) {
+ internalWorked(work);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchRule.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchRule.java
new file mode 100644
index 0000000..741d9ec
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchRule.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchRule implements IPreferenceConstants {
+
+ /**
+ * @param text
+ * @param caseSensitive
+ * @param prefix
+ */
+ public SearchRule(String text) {
+ this.text = text;
+ loadFromPref();
+ }
+
+ /**
+ * @param text
+ * @param caseSensitive
+ * @param prefix
+ */
+ public SearchRule(String text, boolean caseSensitive, boolean camelCase,
+ boolean wordPrefix, boolean regExp) {
+ this.text = text;
+ this.caseSensitive = caseSensitive;
+ this.camelCase = camelCase;
+ this.wordPrefix = wordPrefix;
+ this.regExp = regExp;
+ }
+
+ /**
+ * @return the text
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @return the caseSensitive
+ */
+ public boolean isCaseSensitive() {
+ return caseSensitive;
+ }
+
+ /**
+ * @return the regExp
+ */
+ public boolean isRegExp() {
+ return regExp;
+ }
+
+ /**
+ * @return the prefix
+ */
+ public boolean isWordPrefix() {
+ return wordPrefix;
+ }
+
+ /**
+ * @return the camelCase
+ */
+ public boolean isCamelCase() {
+ return camelCase;
+ }
+
+ /**
+ * @return the pattern
+ */
+ public Pattern getPattern() {
+ if (pattern == null) {
+ pattern = SearchUtils.createPattern(text, caseSensitive, regExp,
+ wordPrefix, camelCase);
+ }
+ return pattern;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((text == null) ? 0 : text.hashCode());
+ result = prime * result + (caseSensitive ? 1231 : 1237);
+ result = prime * result + (regExp ? 1231 : 1237);
+ result = prime * result + (wordPrefix ? 1231 : 1237);
+ result = prime * result + (camelCase ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SearchRule other = (SearchRule) obj;
+ if (!isSettingsEqual(other))
+ return false;
+ return isTextEquals(other);
+ }
+
+ public boolean isSettingsEqual(SearchRule other) {
+ if (other == null)
+ return false;
+ if (caseSensitive != other.caseSensitive)
+ return false;
+ if (regExp != other.regExp)
+ return false;
+ if (wordPrefix != other.wordPrefix)
+ return false;
+ if (camelCase != other.camelCase)
+ return false;
+ return true;
+ }
+
+ public boolean isTextEquals(SearchRule other) {
+ if (other == null)
+ return false;
+ if (text == null) {
+ if (other.text != null)
+ return false;
+ } else if (!text.equals(other.text))
+ return false;
+ return true;
+ }
+
+ private void loadFromPref() {
+ IPreferenceStore preferences = GlancePlugin.getDefault()
+ .getPreferenceStore();
+ caseSensitive = preferences.getBoolean(SEARCH_CASE_SENSITIVE);
+ regExp = preferences.getBoolean(SEARCH_REGEXP);
+ wordPrefix = preferences.getBoolean(SEARCH_WORD_PREFIX);
+ camelCase = preferences.getBoolean(SEARCH_CAMEL_CASE);
+ }
+
+ private Pattern pattern;
+ private String text;
+ private boolean caseSensitive;
+ private boolean regExp;
+ private boolean wordPrefix;
+ private boolean camelCase;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchScopeEntry.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchScopeEntry.java
new file mode 100644
index 0000000..0ecde65
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchScopeEntry.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchScopeEntry implements ITextBlockListener, Comparable<SearchScopeEntry> {
+
+ public SearchScopeEntry(ITextBlock block, IMatchListener listener) {
+ this.block = block;
+ this.listener = listener;
+ init();
+ }
+
+ public void setStart(int start) {
+ synchronized (MONITOR) {
+ this.start = start;
+ }
+ }
+
+ public int getStart() {
+ synchronized (MONITOR) {
+ return start;
+ }
+ }
+
+ protected void addMatch(Match match) {
+ synchronized (MONITOR) {
+ matches.add(nextMatchIndex, match);
+ nextMatchIndex++;
+ listener.added(this, match);
+ }
+ }
+
+ public void dispose() {
+ block.removeTextBlockListener(this);
+ }
+
+ interface IMatchListener {
+
+ public void added(SearchScopeEntry entry, Match match);
+
+ public void cleared(SearchScopeEntry entry);
+ }
+
+ public void textChanged(TextChangedEvent event) {
+ clear();
+ listener.cleared(this);
+ }
+
+ public int compareTo(SearchScopeEntry entry) {
+ return block.compareTo(entry.block);
+ }
+
+ protected void init() {
+ clear();
+ block.addTextBlockListener(this);
+ }
+
+ protected void clear() {
+ synchronized (MONITOR) {
+ doClear();
+ }
+ }
+
+ protected void doClear() {
+ matches = new ArrayList<Match>();
+ text = block.getText();
+ nextMatchIndex = 0;
+ }
+
+ protected void addMatchToBegin() {
+ synchronized (MONITOR) {
+ nextMatchIndex = 0;
+ }
+ }
+
+ /**
+ * @return the text
+ */
+ public String getText() {
+ synchronized (MONITOR) {
+ return text;
+ }
+ }
+
+ /**
+ * @return the matches
+ */
+ public List<Match> getMatches() {
+ synchronized (MONITOR) {
+ return matches;
+ }
+ }
+
+ /**
+ * @return the block
+ */
+ public ITextBlock getBlock() {
+ return block;
+ }
+
+ /**
+ * @return the listener
+ */
+ public IMatchListener getListener() {
+ return listener;
+ }
+
+ private Object MONITOR = new Object();
+ private IMatchListener listener;
+ private String text;
+ private ITextBlock block;
+ private int start;
+ private int nextMatchIndex;
+ private List<Match> matches;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchUtils.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchUtils.java
new file mode 100644
index 0000000..95626d9
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/search/SearchUtils.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.search;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SearchUtils {
+
+ public static Pattern createPattern(String pattern, boolean caseSensitive,
+ boolean regExSearch, boolean wordPrefix, boolean camelCase) {
+ if (pattern == null || pattern.length() == 0)
+ return null;
+
+ int patternFlags = 0;
+
+ if (!caseSensitive)
+ patternFlags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
+
+ if (regExSearch) {
+ patternFlags |= Pattern.MULTILINE;
+ pattern = substituteLinebreak(pattern);
+ } else {
+ RegExpBulder builder = camelCase ? new CamelCaseBuilder()
+ : new RegExpBulder();
+ pattern = asRegPattern(pattern, builder);
+ if (wordPrefix) {
+ pattern = "\\b" + pattern; //$NON-NLS-1$
+ }
+ }
+
+ return Pattern.compile(pattern, patternFlags);
+ }
+
+ /**
+ * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
+ *
+ * @param findString
+ * the original find pattern
+ * @return the transformed find pattern
+ * @throws PatternSyntaxException
+ * if \R is added at an illegal position (e.g. in a character
+ * set)
+ */
+ private static String substituteLinebreak(String findString)
+ throws PatternSyntaxException {
+ int length = findString.length();
+ StringBuffer buf = new StringBuffer(length);
+
+ int inCharGroup = 0;
+ int inBraces = 0;
+ boolean inQuote = false;
+ for (int i = 0; i < length; i++) {
+ char ch = findString.charAt(i);
+ switch (ch) {
+ case '[':
+ buf.append(ch);
+ if (!inQuote)
+ inCharGroup++;
+ break;
+
+ case ']':
+ buf.append(ch);
+ if (!inQuote)
+ inCharGroup--;
+ break;
+
+ case '{':
+ buf.append(ch);
+ if (!inQuote && inCharGroup == 0)
+ inBraces++;
+ break;
+
+ case '}':
+ buf.append(ch);
+ if (!inQuote && inCharGroup == 0)
+ inBraces--;
+ break;
+
+ case '\\':
+ if (i + 1 < length) {
+ char ch1 = findString.charAt(i + 1);
+ if (inQuote) {
+ if (ch1 == 'E')
+ inQuote = false;
+ buf.append(ch).append(ch1);
+ i++;
+
+ } else if (ch1 == 'R') {
+ if (inCharGroup > 0 || inBraces > 0) {
+ throw new PatternSyntaxException(
+ "Illegal position for \\R", findString, i);
+ }
+ buf.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
+ i++;
+
+ } else {
+ if (ch1 == 'Q') {
+ inQuote = true;
+ }
+ buf.append(ch).append(ch1);
+ i++;
+ }
+ } else {
+ buf.append(ch);
+ }
+ break;
+
+ default:
+ buf.append(ch);
+ break;
+ }
+
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Converts a non-regex string to a pattern that can be used with the regex
+ * search engine.
+ *
+ * @param string
+ * the non-regex pattern
+ * @return the string converted to a regex pattern
+ */
+ private static String asRegPattern(String string, RegExpBulder builder) {
+ StringBuffer out = new StringBuffer(string.length());
+ boolean quoting = false;
+
+ for (int i = 0, length = string.length(); i < length; i++) {
+ char ch = string.charAt(i);
+ String re = builder.asRegExp(ch);
+ if (re != null) {
+ if (quoting) {
+ out.append("\\E"); //$NON-NLS-1$
+ quoting = false;
+ }
+ out.append(re); //$NON-NLS-1$
+ continue;
+ }
+ if (!quoting) {
+ out.append("\\Q"); //$NON-NLS-1$
+ quoting = true;
+ }
+ out.append(ch);
+ }
+ if (quoting)
+ out.append("\\E"); //$NON-NLS-1$
+
+ return out.toString();
+ }
+
+ private static class RegExpBulder {
+
+ public String asRegExp(char ch) {
+ if (ch == '\\') {
+ return "\\\\";
+ }
+ return null;
+ }
+
+ }
+
+ private static class CamelCaseBuilder extends RegExpBulder {
+
+ @Override
+ public String asRegExp(char ch) {
+ String regExp = super.asRegExp(ch);
+ if (regExp != null) {
+ wordPrev = false;
+ } else {
+ boolean word = isWord(ch);
+ if (word && wordPrev) {
+ regExp = CAMEL_CASE_SKIP + ch;
+ }
+ wordPrev = word;
+ }
+ return regExp;
+ }
+
+ private boolean isWord(char ch) {
+ return ch == '_' || Character.isLetterOrDigit(ch);
+ }
+
+ private boolean wordPrev = false;
+
+ }
+
+ private static final String CAMEL_CASE_SKIP = "\\w*";
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/ISourceProviderListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/ISourceProviderListener.java
new file mode 100644
index 0000000..1a47986
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/ISourceProviderListener.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ISourceProviderListener {
+
+ public void sourceChanged(TextSourceMaker description);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceListener.java
new file mode 100644
index 0000000..0daa2cf
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceListener.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.sources;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextSourceListener implements Listener {
+
+ public TextSourceListener(ITextSourceDescriptor[] descriptors) {
+ this.descriptors = descriptors;
+ }
+
+ public void addSourceProviderListener(ISourceProviderListener listener) {
+ if (listeners.size() == 0) {
+ getDisplay().addFilter(SWT.FocusIn, this);
+ }
+ listeners.add(listener);
+ }
+
+ public void removeSourceProviderListener(ISourceProviderListener listener) {
+ listeners.remove(listener);
+ if (listeners.size() == 0) {
+ getDisplay().removeFilter(SWT.FocusIn, this);
+ selection = null;
+ }
+ }
+
+ public void handleEvent(Event event) {
+ TextSourceMaker creator = getCreator(getDisplay().getFocusControl());
+ if (!creator.equals(selection)) {
+ selection = creator;
+ Object[] objects = listeners.getListeners();
+ for (Object object : objects) {
+ ISourceProviderListener listener = (ISourceProviderListener) object;
+ listener.sourceChanged(selection);
+ }
+ }
+ }
+
+ public TextSourceMaker getSelection() {
+ return getCreator(getDisplay().getFocusControl());
+ }
+
+ private TextSourceMaker getCreator(Control control) {
+ return new TextSourceMaker(getDescriptor(control), control);
+ }
+
+ private ITextSourceDescriptor getDescriptor(Control control) {
+ if (control != null) {
+ for (ITextSourceDescriptor descriptor : descriptors) {
+ if (descriptor.isValid(control)) {
+ return descriptor;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Display getDisplay() {
+ return PlatformUI.getWorkbench().getDisplay();
+ }
+
+ private TextSourceMaker selection;
+ private ListenerList listeners = new ListenerList();
+ private ITextSourceDescriptor[] descriptors;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceMaker.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceMaker.java
new file mode 100644
index 0000000..f65acef
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceMaker.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.sources;
+
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextSourceMaker {
+
+ public TextSourceMaker(ITextSourceDescriptor description, Control control) {
+ this.description = description;
+ this.control = control;
+ }
+
+ public boolean isValid() {
+ return description == null ? false : description.isValid(control);
+ }
+
+ public ITextSource create() {
+ return description == null ? null : description.createSource(control);
+ }
+
+ /**
+ * @return the control
+ */
+ public Control getControl() {
+ return control;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((control == null) ? 0 : control.hashCode());
+ result = prime * result
+ + ((description == null) ? 0 : description.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TextSourceMaker other = (TextSourceMaker) obj;
+ if (control == null) {
+ if (other.control != null)
+ return false;
+ } else if (!control.equals(other.control))
+ return false;
+ if (description == null) {
+ if (other.description != null)
+ return false;
+ } else if (!description.equals(other.description))
+ return false;
+ return true;
+ }
+
+ private ITextSourceDescriptor description;
+ private Control control;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceManager.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceManager.java
new file mode 100644
index 0000000..297ce85
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/sources/TextSourceManager.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.internal.sources;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.Platform;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextSourceManager {
+
+ public static TextSourceManager getInstance() {
+ if (instance == null)
+ instance = new TextSourceManager();
+ return instance;
+ }
+
+ public void addSourceProviderListener(ISourceProviderListener listener) {
+ assert listener != null;
+ getListener().addSourceProviderListener(listener);
+ }
+
+ public void removeSourceProviderListener(ISourceProviderListener listener) {
+ getListener().removeSourceProviderListener(listener);
+ }
+
+ public TextSourceMaker getSource() {
+ return getListener().getSelection();
+ }
+
+ private TextSourceListener getListener() {
+ if (listener == null) {
+ listener = new TextSourceListener(getDescriptors());
+ }
+ return listener;
+ }
+
+ private ITextSourceDescriptor[] getDescriptors() {
+ if (descriptors == null) {
+
+ List<Class<?>> excludedClasses = new ArrayList<Class<?>>();
+
+ IConfigurationElement[] excludedConfigs = Platform.getExtensionRegistry()
+ .getConfigurationElementsFor(EXT_EXCLUDED_SOURCES);
+
+ for (IConfigurationElement config : excludedConfigs) {
+ try {
+ ITextSourceDescriptor descriptor = (ITextSourceDescriptor) config
+ .createExecutableExtension(ATTR_CLASS);
+ excludedClasses.add(descriptor.getClass());
+ } catch (Exception e) {
+ GlancePlugin.log(e);
+ }
+ }
+
+ IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(
+ EXT_SOURCES);
+ List<ITextSourceDescriptor> list = new ArrayList<ITextSourceDescriptor>();
+ final Map<ITextSourceDescriptor, Integer> prior = new HashMap<ITextSourceDescriptor, Integer>();
+
+ for (IConfigurationElement config : configs) {
+ try {
+ ITextSourceDescriptor descriptor = (ITextSourceDescriptor) config
+ .createExecutableExtension(ATTR_CLASS);
+ if (excludedClasses.contains(descriptor.getClass())) {
+ continue;
+ }
+ int priority = toInt(config.getAttribute(ATTR_PRIORITY));
+ list.add(descriptor);
+ prior.put(descriptor, priority);
+ } catch (Exception e) {
+ GlancePlugin.log(e);
+ }
+ }
+
+ Collections.sort(list, new Comparator<ITextSourceDescriptor>() {
+
+ public int compare(ITextSourceDescriptor o1, ITextSourceDescriptor o2) {
+ return prior.get(o2) - prior.get(o1);
+ }
+
+ });
+ descriptors = list.toArray(new ITextSourceDescriptor[list.size()]);
+ }
+ return descriptors;
+ }
+
+ private int toInt(String priority) {
+ try {
+ if (priority == null || priority.length() == 0)
+ return 1;
+ return Integer.parseInt(priority);
+ } catch (Exception e) {
+ GlancePlugin.log(e);
+ }
+ return 1;
+ }
+
+ private TextSourceManager() {
+ }
+
+ private final static String ATTR_PRIORITY = "priority";
+ private final static String ATTR_CLASS = "class";
+ private final static String EXT_SOURCES = GlancePlugin.PLUGIN_ID + ".sources";
+ private final static String EXT_EXCLUDED_SOURCES = GlancePlugin.PLUGIN_ID + ".excludedSources";
+
+ private static TextSourceManager instance;
+
+ private TextSourceListener listener;
+ private ITextSourceDescriptor[] descriptors;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ColoredTextViewerBlock.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ColoredTextViewerBlock.java
new file mode 100644
index 0000000..cae9917
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ColoredTextViewerBlock.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.viewers;
+
+import org.eclipse.jface.text.ITextPresentationListener;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.jface.text.TextViewer;
+
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+import org.eclipse.ui.glance.utils.TextUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ColoredTextViewerBlock extends TextViewerBlock implements
+ ITextPresentationListener {
+
+ /**
+ * @param viewer
+ */
+ public ColoredTextViewerBlock(TextViewer viewer) {
+ super(viewer);
+ }
+
+ @Override
+ protected void addListeners() {
+ viewer.addTextPresentationListener(this);
+ super.addListeners();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ refresh();
+ }
+
+ @Override
+ protected void fireTextChanged(TextChangedEvent changedEvent) {
+ matches = Match.EMPTY;
+ super.fireTextChanged(changedEvent);
+ }
+
+ @Override
+ protected void removeListeners() {
+ super.removeListeners();
+ viewer.removeTextPresentationListener(this);
+ }
+
+ public void setMatches(Match[] matches) {
+ this.matches = matches;
+ refresh();
+ }
+
+ public void setSelected(Match selected) {
+ this.selected = selected;
+ refresh();
+ }
+
+
+ public Match getSelected() {
+ return selected;
+ }
+
+ private void refresh() {
+ viewer.invalidateTextPresentation();
+ }
+
+ public void applyTextPresentation(TextPresentation presentation) {
+ TextUtils.applyStyles(presentation, matches, selected);
+ }
+
+ private Match[] matches = Match.EMPTY;
+ private Match selected = null;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/SourceViewerControl.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/SourceViewerControl.java
new file mode 100644
index 0000000..f7d0c17
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/SourceViewerControl.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.viewers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelExtension;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.ui.glance.controls.text.styled.TextSelector;
+import org.eclipse.ui.glance.sources.BaseTextSource;
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SourceViewerControl extends BaseTextSource implements ISelectionChangedListener {
+
+ public static String ANNOTATION_TYPE = ColorManager.ANNOTATION_ID;
+
+ public static String SELECTED_ANNOTATION_TYPE = ColorManager.ANNOTATION_SELECTED_ID;
+
+ public SourceViewerControl(final SourceViewer viewer) {
+ this.viewer = viewer;
+ listeners = new ListenerList();
+ blocks = new TextViewerBlock[] { new TextViewerBlock(viewer) };
+ }
+
+ public void addTextSourceListener(final ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTextSourceListener(final ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void dispose() {
+ if (!disposed) {
+ selector.dispose();
+ select(null);
+ viewer.removeSelectionChangedListener(this);
+ replaceMatches(Match.EMPTY);
+ getBlock().dispose();
+ disposed = true;
+ }
+ }
+
+ public void selectionChanged(final SelectionChangedEvent event) {
+ final ISelection selection = event.getSelection();
+ if (selection instanceof TextSelection) {
+ final TextSelection tSelection = (TextSelection) selection;
+ final SourceSelection sSelection = new SourceSelection(getBlock(),
+ tSelection.getOffset(), tSelection.getLength());
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.selectionChanged(sSelection);
+ }
+ }
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public TextViewerBlock getBlock() {
+ return blocks[0];
+ }
+
+ public ITextBlock[] getBlocks() {
+ return blocks;
+ }
+
+ public SourceSelection getSelection() {
+ final Point selection = viewer.getSelectedRange();
+ return new SourceSelection(getBlock(), selection.x, selection.y);
+ }
+
+ public void select(final Match match) {
+ final Annotation[] remove = getAnnotations(true);
+ final Map<Annotation, Position> add = match != null ? createAnnotations(
+ new Match[] { match }, true)
+ : new HashMap<Annotation, Position>();
+ final IAnnotationModel model = viewer.getAnnotationModel();
+ if (model instanceof IAnnotationModelExtension) {
+ final IAnnotationModelExtension eModel = (IAnnotationModelExtension) model;
+ eModel.replaceAnnotations(remove, add);
+ } else {
+ for (final Annotation annotation : remove) {
+ model.removeAnnotation(annotation);
+ }
+ for (final Annotation annotation : add.keySet()) {
+ model.addAnnotation(annotation, add.get(annotation));
+ }
+ }
+
+ selector.setMatch(match);
+ }
+
+ public void show(final Match[] matches) {
+ replaceMatches(matches);
+ }
+
+ private void replaceMatches(final Match[] matches) {
+ final Annotation[] remove = getAnnotations(false);
+ final Map<Annotation, Position> add = createAnnotations(matches, false);
+ final IAnnotationModel model = viewer.getAnnotationModel();
+ if (model instanceof IAnnotationModelExtension) {
+ final IAnnotationModelExtension eModel = (IAnnotationModelExtension) model;
+ eModel.replaceAnnotations(remove, add);
+ } else {
+ for (final Annotation annotation : remove) {
+ model.removeAnnotation(annotation);
+ }
+ for (final Annotation annotation : add.keySet()) {
+ model.addAnnotation(annotation, add.get(annotation));
+ }
+ }
+ }
+
+ private Map<Annotation, Position> createAnnotations(final Match[] matches,
+ final boolean selected) {
+ final Map<Annotation, Position> map = new HashMap<Annotation, Position>();
+ for (final Match match : matches) {
+ final Annotation annotation = new Annotation(
+ selected ? SELECTED_ANNOTATION_TYPE : ANNOTATION_TYPE,
+ false, null);
+ final Position position = new Position(match.getOffset(),
+ match.getLength());
+ map.put(annotation, position);
+ }
+ return map;
+ }
+
+ private Annotation[] getAnnotations(final boolean selected) {
+ final String type = selected ? SELECTED_ANNOTATION_TYPE
+ : ANNOTATION_TYPE;
+ final IAnnotationModel model = viewer.getAnnotationModel();
+ final List<Annotation> annotations = new ArrayList<Annotation>();
+ if (model != null) {
+ final Iterator<?> it = model.getAnnotationIterator();
+ while (it.hasNext()) {
+ final Annotation annotation = (Annotation) it.next();
+ if (type.equals(annotation.getType())) {
+ annotations.add(annotation);
+ }
+ }
+ }
+ return annotations.toArray(new Annotation[annotations.size()]);
+ }
+
+ @Override
+ public void init() {
+ selector = new ViewerSelector(viewer);
+ viewer.addSelectionChangedListener(this);
+ }
+
+ private TextSelector selector;
+ private final ListenerList listeners;
+ private boolean disposed;
+ private final TextViewerBlock[] blocks;
+ private final SourceViewer viewer;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerBlock.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerBlock.java
new file mode 100644
index 0000000..278e953
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerBlock.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.viewers;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.text.ITextListener;
+import org.eclipse.jface.text.TextEvent;
+import org.eclipse.jface.text.TextViewer;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextViewerBlock implements ITextBlock, ITextListener {
+
+ public TextViewerBlock(TextViewer viewer) {
+ this.viewer = viewer;
+ listeners = new ListenerList();
+ addListeners();
+ }
+
+ public void dispose() {
+ removeListeners();
+ }
+
+ protected void addListeners() {
+ if (!addFirstListener()) {
+ viewer.addTextListener(this);
+ }
+ }
+
+ /**
+ * Add our text listener to the beginning of the listener list thro
+ * reflection. This method return true if this operation succeed and fail
+ * otherwise.
+ */
+ private boolean addFirstListener() {
+ try {
+ Field filed = TextViewer.class.getDeclaredField("fTextListeners");
+ filed.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) filed.get(viewer);
+ if (list == null) {
+ // no listeners, can add it usual way
+ return false;
+ }
+ if (!list.contains(this)) {
+ list.add(0, this);
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ protected void removeListeners() {
+ viewer.removeTextListener(this);
+ }
+
+ public void textChanged(TextEvent event) {
+ if (event.getDocumentEvent() != null) {
+ TextChangedEvent changedEvent = new TextChangedEvent(event.getOffset(), event.getLength(),
+ event.getReplacedText());
+ fireTextChanged(changedEvent);
+ }
+ }
+
+ protected void fireTextChanged(TextChangedEvent changedEvent) {
+ Object[] objects = listeners.getListeners();
+ for (Object object : objects) {
+ ITextBlockListener listener = (ITextBlockListener) object;
+ listener.textChanged(changedEvent);
+ }
+ }
+
+ public void addTextBlockListener(ITextBlockListener listener) {
+ listeners.add(listener);
+ }
+
+ public String getText() {
+ return viewer.getDocument().get();
+ }
+
+ public void removeTextBlockListener(ITextBlockListener listener) {
+ listeners.remove(listener);
+ }
+
+ public int compareTo(ITextBlock o) {
+ return 0;
+ }
+
+ private ListenerList listeners;
+ protected TextViewer viewer;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerControl.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerControl.java
new file mode 100644
index 0000000..5a46293
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/TextViewerControl.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.viewers;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.TextViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.ui.glance.controls.text.styled.TextSelector;
+import org.eclipse.ui.glance.sources.BaseTextSource;
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextViewerControl extends BaseTextSource implements
+ ISelectionChangedListener {
+
+ public TextViewerControl(final TextViewer viewer) {
+ this.viewer = viewer;
+ listeners = new ListenerList();
+ blocks = new ColoredTextViewerBlock[] { new ColoredTextViewerBlock(
+ viewer) };
+ }
+
+ public void addTextSourceListener(final ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTextSourceListener(final ITextSourceListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void dispose() {
+ if (!disposed) {
+ selector.dispose();
+ viewer.removeSelectionChangedListener(this);
+ getBlock().dispose();
+ disposed = true;
+ }
+ }
+
+ public void selectionChanged(final SelectionChangedEvent event) {
+ final ISelection selection = event.getSelection();
+ if (selection instanceof TextSelection) {
+ final TextSelection tSelection = (TextSelection) selection;
+ final SourceSelection sSelection = new SourceSelection(getBlock(),
+ tSelection.getOffset(), tSelection.getLength());
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.selectionChanged(sSelection);
+ }
+ }
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public ColoredTextViewerBlock getBlock() {
+ return blocks[0];
+ }
+
+ public ITextBlock[] getBlocks() {
+ return blocks;
+ }
+
+ public SourceSelection getSelection() {
+ final Point selection = viewer.getSelectedRange();
+ return new SourceSelection(getBlock(), selection.x, selection.y);
+ }
+
+ public void select(final Match match) {
+ getBlock().setSelected(match);
+ selector.setMatch(match);
+ }
+
+ public void show(final Match[] matches) {
+ getBlock().setMatches(matches);
+ }
+
+ @Override
+ public void init() {
+ selector = new ViewerSelector(viewer);
+ viewer.addSelectionChangedListener(this);
+ }
+
+ private TextSelector selector;
+ private final ListenerList listeners;
+ private boolean disposed;
+ private final ColoredTextViewerBlock[] blocks;
+ private final TextViewer viewer;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ViewerSelector.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ViewerSelector.java
new file mode 100644
index 0000000..1fc073b
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/internal/viewers/ViewerSelector.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.internal.viewers;
+
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.TextViewer;
+import org.eclipse.swt.custom.StyledText;
+
+import org.eclipse.ui.glance.controls.text.styled.TextSelector;
+
+public class ViewerSelector extends TextSelector {
+
+ private final TextViewer viewer;
+ private final StyledText control;
+
+ public ViewerSelector(TextViewer viewer) {
+ this.viewer = viewer;
+ this.control = viewer.getTextWidget();
+ init();
+ }
+
+ @Override
+ protected StyledText getControl() {
+ return control;
+ }
+
+ @Override
+ protected void setSelection(int offset, int length) {
+ viewer.setSelection(new TextSelection(offset, length));
+ }
+
+ @Override
+ protected Region getSelection() {
+ TextSelection selection = (TextSelection) viewer.getSelection();
+ return new Region(selection.getOffset(), selection.getLength());
+ }
+
+ @Override
+ protected void reveal(int offset, int length) {
+ viewer.revealRange(offset, length);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanel.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanel.java
new file mode 100644
index 0000000..9d1566c
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanel.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.panels;
+
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.internal.search.ISearchListener;
+import org.eclipse.ui.glance.internal.search.SearchRule;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ISearchPanel extends ISearchListener {
+
+ public static int INDEXING_STATE_DISABLE = 0;
+
+ public static int INDEXING_STATE_INITIAL = 1;
+
+ public static int INDEXING_STATE_IN_PROGRESS = 2;
+
+ public static int INDEXING_STATE_FINISHED = 3;
+
+ public void addPanelListener(ISearchPanelListener listener);
+
+ public void removePanelListener(ISearchPanelListener listener);
+
+ public void setEnabled(boolean enabled);
+
+ public boolean isApplicable(Control control);
+
+ public Control getControl();
+
+ public void setIndexingState(int state);
+
+ public void updateIndexingPercent(double percent);
+
+ public void newTask(String name);
+
+ /**
+ * Set focus to search panel with some initial text
+ */
+ public void setFocus(String text);
+
+ public SearchRule getRule();
+
+ public void closePanel();
+
+ public void findNext();
+
+ public void findPrevious();
+
+ public void clearHistory();
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanelListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanelListener.java
new file mode 100644
index 0000000..f8c8270
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/ISearchPanelListener.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.panels;
+
+import org.eclipse.ui.glance.internal.search.SearchRule;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ISearchPanelListener {
+
+ public void ruleChanged(SearchRule rule);
+
+ public void findNext();
+
+ public void findPrevious();
+
+ public void close();
+
+ public void indexCanceled();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/SearchPanel.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/SearchPanel.java
new file mode 100644
index 0000000..aeec1ea
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/panels/SearchPanel.java
@@ -0,0 +1,680 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.panels;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.PopupDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+
+import org.eclipse.ui.glance.internal.GlanceEventDispatcher;
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.panels.CheckAction;
+import org.eclipse.ui.glance.internal.panels.ImageAnimation;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+import org.eclipse.ui.glance.internal.search.SearchManager;
+import org.eclipse.ui.glance.internal.search.SearchRule;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.utils.SelectionAdapter;
+import org.eclipse.ui.glance.utils.UIUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class SearchPanel implements ISearchPanel,
+ IPreferenceConstants, IPropertyChangeListener {
+
+ /**
+ * @param parent
+ * @param style
+ */
+ public SearchPanel() {
+ rule = new SearchRule("");
+ getPreferences().addPropertyChangeListener(this);
+ }
+
+ public void setIndexingState(final int state) {
+ indexState = state;
+
+ if (updateInfoThread != null) {
+ final UpdateInfoThread thread = updateInfoThread;
+ thread.requestStop();
+ updateInfoThread = null;
+ }
+ if (state == INDEXING_STATE_IN_PROGRESS) {
+ indexPercent = 0;
+ try {
+ updateInfoThread = new UpdateInfoThread();
+ updateInfoThread.start();
+ } catch (final CoreException e) {
+ GlancePlugin.log(e.getStatus());
+ }
+ } else {
+ UIUtils.asyncExec(toolBar, new Runnable() {
+ public void run() {
+ updateInfo(null);
+ }
+ });
+ }
+ }
+
+ public void updateIndexingPercent(final double percent) {
+ indexPercent = percent;
+ }
+
+ public void newTask(final String name) {
+ this.taskName = name;
+ indexPercent = 0;
+ }
+
+ private static URL getWaitImageStream() throws CoreException {
+ URL url = FileLocator.find(GlancePlugin.getDefault().getBundle(),
+ new Path(GlancePlugin.IMG_WAIT), null);
+ try {
+ url = FileLocator.resolve(url);
+ return url;
+ } catch (final IOException e) {
+ throw new CoreException(GlancePlugin.createStatus(
+ "Can't find wait image", e));
+ }
+ }
+
+ private Color getWaitBGColor() {
+ final Display display = PlatformUI.getWorkbench().getDisplay();
+ final Color[] color = new Color[1];
+ display.syncExec(new Runnable() {
+ public void run() {
+ color[0] = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ }
+ });
+ return color[0];
+ }
+
+ private class UpdateInfoThread extends ImageAnimation {
+
+ public UpdateInfoThread() throws CoreException {
+ super(getWaitImageStream(), getWaitBGColor());
+ }
+
+ private volatile boolean stop = false;
+
+ @Override
+ protected boolean isTerminated() {
+ return stop || indexState != INDEXING_STATE_IN_PROGRESS;
+ }
+
+ @Override
+ protected void updateImage(final Image image) {
+ updateInfo(image);
+ }
+
+ public void requestStop() {
+ stop = true;
+ }
+ }
+
+ private void updateInfo(final Image image) {
+ if (bIndexing == null || bIndexing.isDisposed()) {
+ return;
+ }
+
+ if (indexState == INDEXING_STATE_DISABLE) {
+ bIndexing.setToolTipText("Index component");
+ bIndexing.setSelection(false);
+ bIndexing.setEnabled(false);
+ if (bIndexing.getImage() == null) {
+ bIndexing.setImage(GlancePlugin
+ .getImage(GlancePlugin.IMG_START_INDEXING));
+ }
+ } else if (indexState == INDEXING_STATE_INITIAL) {
+ bIndexing.setToolTipText("Index component");
+ bIndexing.setSelection(false);
+ bIndexing.setImage(GlancePlugin
+ .getImage(GlancePlugin.IMG_START_INDEXING));
+ bIndexing.setEnabled(true);
+ } else if (indexState == INDEXING_STATE_FINISHED) {
+ bIndexing.setToolTipText("Index finished");
+ bIndexing.setSelection(false);
+ bIndexing.setEnabled(false);
+ } else {
+ final StringBuffer buffer = new StringBuffer();
+ bIndexing.setSelection(true);
+ bIndexing.setImage(image);
+ if (taskName != null && taskName.length() > 0) {
+ buffer.append(taskName);
+ buffer.append(": ");
+ }
+ buffer.append((int) (indexPercent * 100));
+ buffer.append("%. Stop indexing.");
+ bIndexing.setToolTipText(buffer.toString());
+ }
+ }
+
+ public void propertyChange(final PropertyChangeEvent event) {
+ final String property = event.getProperty();
+ if (property != null && property.startsWith(SEARCH_PREFIX)) {
+ updateRule();
+ }
+ }
+
+ public void createContent(final Composite parent) {
+ final Composite container = createContainer(parent);
+ final GridLayout layout = new GridLayout(3, false);
+ layout.verticalSpacing = 0;
+ layout.marginHeight = 2;
+ layout.marginWidth = 0;
+ container.setLayout(layout);
+ createIcon(container);
+ createText(container, SWT.BORDER);
+ createToolBar(container);
+ initSize(container);
+ }
+
+ public Control getControl() {
+ return container;
+ }
+
+ protected Composite createContainer(final Composite parent) {
+ container = new Composite(parent, SWT.NONE);
+ return container;
+ }
+
+ public void firstFound(final Match match) {
+ UIUtils.asyncExec(title, new Runnable() {
+ public void run() {
+ setBackground(match != null);
+ }
+ });
+ }
+
+ public void allFound(final Match[] matches) {
+ result = matches;
+ UIUtils.asyncExec(title, new Runnable() {
+ public void run() {
+ setBackground(result.length > 0);
+ }
+ });
+ }
+
+ public void finished() {
+ }
+
+ protected Label createIcon(final Composite parent) {
+ final Label label = new Label(parent, SWT.NONE);
+ label.setImage(GlancePlugin.getImage(GlancePlugin.IMG_SEARCH));
+ return label;
+ }
+
+ protected Control createText(final Composite parent, final int style) {
+ title = new Combo(parent, style | SWT.DROP_DOWN);
+ final String text = rule.getText();
+ title.setText(text);
+ loadHistory();
+ if (text != null && text.length() > 0 && result.length == 0)
+ setBackground(false);
+ title.addModifyListener(modifyListener);
+ title.addListener(SWT.KeyDown, new Listener() {
+ public void handleEvent(Event event) {
+ GlanceEventDispatcher.INSTANCE.dispatchKeyPressed(event);
+ }
+ });
+ title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ title.setEnabled(titleEnabled);
+ return title;
+ }
+
+ protected ToolBar createToolBar(final Composite parent) {
+ toolBar = new ToolBar(parent, SWT.FLAT);
+ GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER)
+ .applyTo(toolBar);
+ if (getPreferences().getBoolean(PANEL_DIRECTIONS)) {
+ createNextItem(toolBar);
+ createPreviousItem(toolBar);
+ }
+ createIndexing(toolBar);
+ createSettingsMenu(toolBar);
+ if (getPreferences().getBoolean(PANEL_CLOSE)) {
+ createClose(toolBar);
+ }
+ return toolBar;
+ }
+
+ protected ToolItem createNextItem(final ToolBar bar) {
+ bNext = createTool(bar, "Next", GlancePlugin.IMG_NEXT,
+ new SelectionAdapter() {
+ @Override
+ public void selected(final SelectionEvent e) {
+ findNext();
+ }
+ });
+ return bNext;
+ }
+
+ protected ToolItem createPreviousItem(final ToolBar bar) {
+ bPrev = createTool(bar, "Previous", GlancePlugin.IMG_PREV,
+ new SelectionAdapter() {
+ @Override
+ public void selected(final SelectionEvent e) {
+ findPrevious();
+ }
+ });
+ return bPrev;
+ }
+
+ private void createIndexing(final ToolBar bar) {
+ bIndexing = new ToolItem(bar, SWT.CHECK);
+ bIndexing.setDisabledImage(GlancePlugin
+ .getImage(GlancePlugin.IMG_INDEXING_FINISHED));
+ bIndexing.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void selected(final SelectionEvent e) {
+ if (indexState == INDEXING_STATE_INITIAL) {
+ SearchManager.getIntance().index();
+ } else if (indexState != INDEXING_STATE_FINISHED) {
+ fireIndexCanceled();
+ }
+ }
+ });
+ }
+
+ private ToolItem createTool(final ToolBar bar, final String tip,
+ final String image, final SelectionListener listener) {
+ final ToolItem item = new ToolItem(bar, SWT.PUSH);
+ item.setToolTipText(tip);
+ item.setImage(GlancePlugin.getImage(image));
+ item.addSelectionListener(listener);
+ return item;
+ }
+
+ protected ToolItem createSettingsMenu(final ToolBar bar) {
+ final ToolItem settings = new ToolItem(bar, SWT.PUSH);
+ settings.setImage(JFaceResources.getImage(PopupDialog.POPUP_IMG_MENU));
+ settings.setDisabledImage(JFaceResources
+ .getImage(PopupDialog.POPUP_IMG_MENU_DISABLED));
+ settings.setToolTipText("Settings"); //$NON-NLS-1$
+ settings.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void selected(final SelectionEvent e) {
+ showSettings();
+ }
+ });
+ return settings;
+ }
+
+ protected ToolItem createClose(final ToolBar bar) {
+ final ToolItem close = new ToolItem(bar, SWT.PUSH);
+ final ImageDescriptor image = PlatformUI.getWorkbench()
+ .getSharedImages()
+ .getImageDescriptor(ISharedImages.IMG_TOOL_DELETE);
+ if (image != null)
+ close.setImage(image.createImage());
+ close.setToolTipText("Close"); //$NON-NLS-1$
+ close.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void selected(final SelectionEvent e) {
+ closePanel();
+ }
+ });
+ return close;
+ }
+
+ protected void showSettings() {
+ final MenuManager manager = new MenuManager();
+ fillMenu(manager);
+ final Menu menu = manager.createContextMenu(getControl());
+
+ final Point location = getControl().getDisplay().getCursorLocation();
+ menu.setLocation(location);
+ menu.setVisible(true);
+ }
+
+ protected void fillMenu(final IMenuManager menu) {
+ menu.add(new Separator());
+ newAction(menu, SEARCH_CASE_SENSITIVE, LABEL_CASE_SENSITIVE, true);
+ final boolean regExp = newAction(menu, SEARCH_REGEXP, LABEL_REGEXP,
+ true).isChecked();
+ newAction(menu, SEARCH_CAMEL_CASE, LABEL_CAMEL_CASE, !regExp);
+ newAction(menu, SEARCH_WORD_PREFIX, LABEL_WORD_PREFIX, !regExp);
+ menu.add(new Separator());
+ menu.add(new Action("Clear search history") {
+ @Override
+ public void run() {
+ clearHistory();
+ }
+ });
+ menu.add(new Separator());
+ menu.add(new Action("Preferences...") {
+ @Override
+ public void run() {
+ PreferencesUtil.createPreferenceDialogOn(container.getShell(),
+ PREFERENCE_PAGE_ID, null, null).open();
+ }
+ });
+ }
+
+ private CheckAction newAction(final IMenuManager menu, final String name,
+ final String label, final boolean enable) {
+ return newAction(menu, name, label, enable, null);
+ }
+
+ private CheckAction newAction(final IMenuManager menu, final String name,
+ final String label, final boolean enable, final String path) {
+ final CheckAction action = new CheckAction(name, label);
+ if (path != null) {
+ action.setImageDescriptor(GlancePlugin.getImageDescriptor(path));
+ }
+ action.setEnabled(enable);
+ menu.add(action);
+ return action;
+ }
+
+ protected void textChanged() {
+ final String text = title.getText();
+ final boolean empty = text.length() == 0;
+ if (empty)
+ textEmpty();
+ if (bNext != null && !bNext.isDisposed())
+ bNext.setEnabled(!empty);
+ if (bPrev != null && !bPrev.isDisposed())
+ bPrev.setEnabled(!empty);
+ updateRule();
+ }
+
+ protected void textEmpty() {
+ setBackground(true);
+ }
+
+ protected void updateRule() {
+ rule = new SearchRule(title.getText());
+ fireRuleChanged(rule);
+ }
+
+ /**
+ * @return the rule
+ */
+ public SearchRule getRule() {
+ return rule;
+ }
+
+ public void setFocus(String text) {
+ if (isReady()) {
+ if (text == null || text.length() == 0)
+ text = rule.getText();
+ if (text != null && text.length() > 0) {
+ title.setText(text);
+ title.setSelection(new Point(0, text.length()));
+ textChanged();
+ }
+ title.forceFocus();
+ }
+ }
+
+ public void setEnabled(final boolean enabled) {
+ if (isReady()) {
+ title.setEnabled(enabled);
+ titleEnabled = enabled;
+ }
+ }
+
+ private boolean isReady() {
+ return title != null && !title.isDisposed();
+ }
+
+ public void addPanelListener(final ISearchPanelListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removePanelListener(final ISearchPanelListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void fireRuleChanged(final SearchRule rule) {
+ historyDirty = true;
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ISearchPanelListener listener = (ISearchPanelListener) object;
+ listener.ruleChanged(rule);
+ }
+ }
+
+ protected void fireIndexCanceled() {
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ISearchPanelListener listener = (ISearchPanelListener) object;
+ listener.indexCanceled();
+ }
+ }
+
+ public void findNext() {
+ updateHistory();
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ISearchPanelListener listener = (ISearchPanelListener) object;
+ listener.findNext();
+ }
+ }
+
+ public void findPrevious() {
+ updateHistory();
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ISearchPanelListener listener = (ISearchPanelListener) object;
+ listener.findPrevious();
+ }
+ }
+
+ public void clearHistory() {
+ if (title != null && !title.isDisposed()) {
+ title.removeModifyListener(modifyListener);
+ try {
+ title.removeAll();
+ findHistory.clear();
+ } finally {
+ title.addModifyListener(modifyListener);
+ historyDirty = false;
+ }
+ }
+ }
+
+ protected void fireClose() {
+ updateHistory();
+ saveHistory();
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ISearchPanelListener listener = (ISearchPanelListener) object;
+ listener.close();
+ }
+ getPreferences().removePropertyChangeListener(this);
+ if (updateInfoThread != null) {
+ updateInfoThread.requestStop();
+ }
+ }
+
+ /**
+ * @return the preferedWidth
+ */
+ protected int getPreferedWidth() {
+ return preferredWidth;
+ }
+
+ /**
+ * @return the preferredHeight
+ */
+ protected int getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ private void initSize(final Composite composite) {
+ final Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ preferredWidth = size.x;
+ preferredHeight = size.y;
+ preferredWidth -= title.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+ final int widthInChars = getPreferences().getInt(PANEL_TEXT_SIZE);
+ preferredWidth += getTextWidth(title, widthInChars) + 15;
+ }
+
+ protected int getTextWidth(final Control control, final int width) {
+ final GC gc = new GC(control);
+ try {
+ gc.setFont(title.getFont());
+ return gc.getFontMetrics().getAverageCharWidth() * width;
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
+ * Updates history.
+ */
+ private void updateHistory() {
+ if (title != null && !title.isDisposed() && historyDirty) {
+ title.removeModifyListener(modifyListener);
+ try {
+ String findString = title.getText();
+ final Point sel = title.getSelection();
+ findString = fixItem(findString);
+ if (findString != null) {
+ findHistory.remove(findString);
+ findHistory.add(0, findString);
+ title.removeAll();
+ for (final String string : findHistory) {
+ title.add(string);
+ }
+ title.setText(findString);
+ title.setSelection(sel);
+ }
+ } finally {
+ title.addModifyListener(modifyListener);
+ historyDirty = false;
+ }
+ }
+ }
+
+ private void saveHistory() {
+ final StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < findHistory.size() && i < 8; i++) {
+ final String item = findHistory.get(i);
+ if (i > 0) {
+ buffer.append("\n");
+ }
+ buffer.append(item);
+ }
+ getPreferences().putValue(HISTORY, buffer.toString());
+ }
+
+ private void loadHistory() {
+ findHistory = new ArrayList<String>();
+ final String content = getPreferences().getString(HISTORY);
+ final String[] items = content.split("\n");
+ for (final String item : items) {
+ findHistory.add(item);
+ title.add(item);
+ }
+ }
+
+ private String fixItem(final String item) {
+ if (item.length() == 0) {
+ return null;
+ }
+ final int index = item.indexOf("\n");
+ if (index == 0) {
+ return null;
+ } else if (index > 0) {
+ return item.substring(0, index);
+ } else {
+ return item;
+ }
+ }
+
+ private IPreferenceStore getPreferences() {
+ return GlancePlugin.getDefault().getPreferenceStore();
+ }
+
+ protected void setBackground(final boolean found) {
+ title.setBackground(found ? GOOD_COLOR : BAD_COLOR);
+ }
+
+ protected static final Color GOOD_COLOR = Display.getDefault()
+ .getSystemColor(SWT.COLOR_WHITE);
+ protected static final Color BAD_COLOR = new Color(Display.getDefault(),
+ 255, 102, 102);
+
+ protected Composite container;
+ protected Combo title;
+ private boolean titleEnabled = true;
+
+ private final ListenerList listeners = new ListenerList();
+ private final ModifyListener modifyListener = new ModifyListener() {
+ public void modifyText(final ModifyEvent e) {
+ textChanged();
+ }
+ };
+
+ private List<String> findHistory;
+ private boolean historyDirty = true;
+ private ToolItem bNext;
+ private ToolItem bPrev;
+ private ToolItem bIndexing;
+ private ToolBar toolBar;
+ private SearchRule rule;
+ private Match[] result = Match.EMPTY;
+
+ private double indexState;
+ private double indexPercent;
+ private String taskName;
+
+ private int preferredHeight;
+ private int preferredWidth;
+
+ private UpdateInfoThread updateInfoThread;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/BaseTextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/BaseTextSource.java
new file mode 100644
index 0000000..fc5a203
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/BaseTextSource.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public abstract class BaseTextSource implements ITextSource {
+
+ public void index(IProgressMonitor monitor) {
+ monitor.done();
+ }
+
+ public boolean isIndexRequired() {
+ return false;
+ }
+
+ public void init() {
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ColorManager.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ColorManager.java
new file mode 100644
index 0000000..43dc946
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ColorManager.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+import static org.eclipse.jface.preference.PreferenceConverter.getColor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.editors.text.EditorsUI;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+import org.eclipse.ui.glance.internal.preferences.TreeColors;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class ColorManager implements IPropertyChangeListener, IPreferenceConstants {
+
+ public static final String ANNOTATION_ID = "org.eclipse.ui.glance.highlight";
+ public static final String ANNOTATION_SELECTED_ID = "org.eclipse.ui.glance.select";
+
+ private ColorManager() {
+ getStore().addPropertyChangeListener(this);
+ GlancePlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
+ updateColors();
+ }
+
+ public static IPreferenceStore getStore() {
+ return EditorsUI.getPreferenceStore();
+ }
+
+ public static ColorManager getInstance() {
+ if (INSTANCE == null)
+ INSTANCE = new ColorManager();
+ return INSTANCE;
+ }
+
+ public Color getBackgroundColor() {
+ return selection;
+ }
+
+ public Color getSelectedBackgroundColor() {
+ return highlight;
+ }
+
+ public Color getTreeSelectionBg() {
+ return treeBg;
+ }
+
+ public Color getTreeSelectionFg() {
+ return treeFg;
+ }
+
+ public boolean isUseNative() {
+ return useNative;
+ }
+
+ public void propertyChange(final PropertyChangeEvent event) {
+ if (COLOR_HIGHLIGHT.equals(event.getProperty()) || COLOR_SELECTION.equals(event.getProperty())) {
+ updateColors();
+ }
+ }
+
+ public static Color lighten(Color color, int delta) {
+ int r = ensureColor(color.getRed() + delta);
+ int g = ensureColor(color.getGreen() + delta);
+ int b = ensureColor(color.getBlue() + delta);
+ return new Color(color.getDevice(), r, g, b);
+ }
+
+ private static int ensureColor(int value) {
+ return value > 255 ? 255 : value;
+ }
+
+ private void updateColors() {
+ for (Color color : toDispose) {
+ color.dispose();
+ }
+ toDispose = new ArrayList<Color>();
+
+ final Display display = PlatformUI.getWorkbench().getDisplay();
+ final IPreferenceStore store = getStore();
+
+ selection = new Color(display, getColor(store, COLOR_HIGHLIGHT));
+ highlight = new Color(display, getColor(store, COLOR_SELECTION));
+
+ TreeColors colors = TreeColors.getDefault();
+ useNative = colors.isUseNative();
+ if (colors.getBg() != null) {
+ treeBg = new Color(display, colors.getBg());
+ toDispose.add(treeBg);
+ } else {
+ treeBg = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+ }
+
+ if (colors.getFg() != null) {
+ treeFg = new Color(display, colors.getFg());
+ toDispose.add(treeFg);
+ } else {
+ treeFg = display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+ }
+
+ toDispose.add(selection);
+ toDispose.add(highlight);
+ }
+
+ private static ColorManager INSTANCE;
+
+ private Color selection;
+ private Color highlight;
+ private Color treeBg;
+ private Color treeFg;
+ private boolean useNative;
+
+ private List<Color> toDispose = new ArrayList<Color>();
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ConfigurationManager.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ConfigurationManager.java
new file mode 100644
index 0000000..21e0359
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ConfigurationManager.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+import org.eclipse.ui.glance.internal.GlancePlugin;
+import org.eclipse.ui.glance.internal.preferences.IPreferenceConstants;
+
+public final class ConfigurationManager {
+
+ private static ConfigurationManager INSTANCE;
+
+ private ConfigurationManager() {
+ }
+
+ public static ConfigurationManager getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new ConfigurationManager();
+ }
+ return INSTANCE;
+ }
+
+ public int getMaxIndexingDepth() {
+ return GlancePlugin.getDefault().getPreferenceStore().getInt(
+ IPreferenceConstants.PANEL_MAX_INDEXING_DEPTH);
+ }
+
+ public boolean incremenstalSearch(){
+ return GlancePlugin.getDefault().getPreferenceStore().getBoolean(
+ IPreferenceConstants.SEARCH_INCREMENTAL);
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlock.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlock.java
new file mode 100644
index 0000000..13b75fc
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlock.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ITextBlock extends Comparable<ITextBlock> {
+
+ public String getText();
+
+ public void addTextBlockListener(ITextBlockListener listener);
+
+ public void removeTextBlockListener(ITextBlockListener listener);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlockListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlockListener.java
new file mode 100644
index 0000000..c3f8729
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextBlockListener.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ITextBlockListener {
+
+ public void textChanged(TextChangedEvent event);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSource.java
new file mode 100644
index 0000000..3824354
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSource.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ITextSource {
+
+ /**
+ * Return text blocks associated with this source
+ *
+ * @return text blocks
+ */
+ public ITextBlock[] getBlocks();
+
+ /**
+ * Add text source listener
+ *
+ * @param listener
+ * text source listener
+ */
+ public void addTextSourceListener(ITextSourceListener listener);
+
+ /**
+ * Remove text source listener
+ *
+ * @param listener
+ * text source listener
+ */
+ public void removeTextSourceListener(ITextSourceListener listener);
+
+ /**
+ * Return current source selection. This selection using to identify where
+ * start search
+ *
+ * @return source selection
+ */
+ public SourceSelection getSelection();
+
+ /**
+ * Focus match
+ *
+ * @param match
+ * match to focus
+ */
+ public void select(Match match);
+
+ /**
+ * Highlight matches
+ *
+ * @param matches
+ */
+ public void show(Match[] matches);
+
+ /**
+ * Called before search started
+ */
+ public void init();
+
+ /**
+ * @param monitor
+ */
+ public void index(IProgressMonitor monitor);
+
+ public boolean isIndexRequired();
+
+ public boolean isDisposed();
+
+ public void dispose();
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceDescriptor.java
new file mode 100644
index 0000000..e54cee5
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceDescriptor.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ITextSourceDescriptor {
+
+ /**
+ * Return a boolean indicating whether text source can be created for this
+ * control
+ *
+ * @return <code>true</code> if the text source can be created, and
+ * <code>false</code> otherwise
+ */
+ public boolean isValid(Control control);
+
+ /**
+ * Creates text source for specified control
+ *
+ * @param control
+ * @return
+ */
+ public ITextSource createSource(Control control);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceListener.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceListener.java
new file mode 100644
index 0000000..e608808
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/ITextSourceListener.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public interface ITextSourceListener {
+
+ /**
+ * Notification about block changing
+ *
+ * @param removed
+ * @param added
+ */
+ public void blocksChanged(ITextBlock[] removed, ITextBlock[] added);
+
+ /**
+ * Notification about all blocks removed and added abother blocks
+ *
+ * @param newBlocks
+ */
+ public void blocksReplaced(ITextBlock[] newBlocks);
+
+ /**
+ * Notification about selection changing
+ *
+ * @param selection
+ */
+ public void selectionChanged(SourceSelection selection);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/Match.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/Match.java
new file mode 100644
index 0000000..22eb62e
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/Match.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class Match implements Comparable<Match> {
+
+ public static final Match[] EMPTY = new Match[0];
+
+ public Match(ITextBlock block, int offset, int length) {
+ this.block = block;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * @return the block
+ */
+ public ITextBlock getBlock() {
+ return block;
+ }
+
+ /**
+ * @return the offset
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * @return the length
+ */
+ public int getLength() {
+ return length;
+ }
+
+ public int compareTo(Match match) {
+ if (block != null && match.block != null) {
+ int diff = block.compareTo(match.block);
+ if (diff != 0)
+ return diff;
+ }
+ return offset - match.offset;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((block == null) ? 0 : block.hashCode());
+ result = prime * result + length;
+ result = prime * result + offset;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Match other = (Match) obj;
+ if (block == null) {
+ if (other.block != null)
+ return false;
+ } else if (!block.equals(other.block))
+ return false;
+ if (length != other.length)
+ return false;
+ if (offset != other.offset)
+ return false;
+ return true;
+ }
+
+ private ITextBlock block;
+ private int offset;
+ private int length;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/SourceSelection.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/SourceSelection.java
new file mode 100644
index 0000000..627707c
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/SourceSelection.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SourceSelection {
+
+ public SourceSelection(ITextBlock block, int offset, int length) {
+ this.block = block;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * @return the block
+ */
+ public ITextBlock getBlock() {
+ return block;
+ }
+
+ /**
+ * @return the length
+ */
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * @return the offset
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ @Override
+ public String toString() {
+ return block + ": (" + offset + ", " + length + ")";
+ }
+
+ private ITextBlock block;
+ private int offset;
+ private int length;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/TextChangedEvent.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/TextChangedEvent.java
new file mode 100644
index 0000000..1e29dbc
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/sources/TextChangedEvent.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.sources;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextChangedEvent {
+
+ public TextChangedEvent(int start, int length, String replacedText) {
+ this.start = start;
+ this.length = length;
+ this.replacedText = replacedText;
+ }
+
+ /**
+ * @return the start
+ */
+ public int getStart() {
+ return start;
+ }
+
+ /**
+ * @return the length
+ */
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * @return the replacedText
+ */
+ public String getReplacedText() {
+ return replacedText;
+ }
+
+ /** start offset of the new text */
+ private int start;
+ /** length of the new text */
+ private int length;
+ /** replaced text or empty string if no text was replaced */
+ private String replacedText;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/SelectionAdapter.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/SelectionAdapter.java
new file mode 100644
index 0000000..9f8e8b9
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/SelectionAdapter.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ *
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.utils;
+
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class SelectionAdapter implements SelectionListener {
+
+ public final void widgetDefaultSelected(SelectionEvent e) {
+ selected(e);
+ }
+
+ public final void widgetSelected(SelectionEvent e) {
+ selected(e);
+ }
+
+ public abstract void selected(SelectionEvent e);
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/TextUtils.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/TextUtils.java
new file mode 100644
index 0000000..a82f5b2
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/TextUtils.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.utils;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.TextPresentation;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.graphics.Color;
+
+import org.eclipse.ui.glance.sources.ColorManager;
+import org.eclipse.ui.glance.sources.Match;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextUtils {
+
+ public static StyleRange[] copy(final StyleRange[] ranges) {
+ final StyleRange[] result = new StyleRange[ranges.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = copy(ranges[i]);
+ }
+ return result;
+ }
+
+ public static StyleRange copy(final StyleRange range) {
+ final StyleRange result = new StyleRange(range);
+ result.start = range.start;
+ result.length = range.length;
+ result.fontStyle = range.fontStyle;
+ return result;
+ }
+
+ public static StyleRange[] getStyles(final TextPresentation presentation) {
+ final StyleRange[] ranges = new StyleRange[presentation
+ .getDenumerableRanges()];
+ final Iterator<?> e = presentation.getAllStyleRangeIterator();
+ for (int i = 0; e.hasNext(); i++) {
+ ranges[i] = (StyleRange) e.next();
+ }
+ return ranges;
+ }
+
+ public static void applyStyles(final TextPresentation presentation,
+ final Match[] matches, final Match selected) {
+
+ final StyleRange[] ranges = createStyleRanges(presentation.getExtent(), matches, ColorManager
+ .getInstance().getBackgroundColor());
+
+ presentation.mergeStyleRanges(ranges);
+
+ if (selected != null) {
+ final StyleRange[] selectedRanges = createStyleRanges(presentation.getExtent(),
+ new Match[] { selected }, ColorManager.getInstance().getSelectedBackgroundColor());
+ presentation.mergeStyleRanges(selectedRanges);
+
+ }
+ }
+
+ private static StyleRange[] createStyleRanges(final IRegion region, final Match[] matches,
+ final Color color) {
+ final Match[] regionMatches = getRangeMatches(region.getOffset(), region.getLength(), matches);
+ final StyleRange[] ranges = new StyleRange[regionMatches.length];
+ for (int i = 0; i < regionMatches.length; i++) {
+ final StyleRange range = new StyleRange(regionMatches[i].getOffset(),
+ regionMatches[i].getLength(), null, color);
+ ranges[i] = range;
+ }
+ return ranges;
+ }
+
+ private static Match[] getRangeMatches(final int start, final int length,
+ final Match[] matches) {
+ int from = getPosition(start, matches);
+ if (from >= matches.length)
+ return Match.EMPTY;
+ if (from > 0) {
+ final Match border = matches[from - 1];
+ if (border.getLength() + border.getOffset() > start)
+ from--;
+ }
+ final int to = getPosition(start + length, matches) - 1;
+ if (from <= to) {
+ final Match[] result = new Match[to - from + 1];
+ System.arraycopy(matches, from, result, 0, result.length);
+ return result;
+ }
+ return Match.EMPTY;
+ }
+
+ private static int getPosition(final int offset, final Match[] matches) {
+ final int index = Arrays.binarySearch(matches, new Match(null, offset, 0));
+ if (index >= 0)
+ return index;
+ return -index - 1;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextBlock.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextBlock.java
new file mode 100644
index 0000000..5cc8ac6
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextBlock.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.utils;
+
+import org.eclipse.core.runtime.ListenerList;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextBlockListener;
+import org.eclipse.ui.glance.sources.TextChangedEvent;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class UITextBlock implements ITextBlock, ITextBlockListener {
+
+ public UITextBlock(ITextBlock block) {
+ this.block = block;
+ text = block.getText();
+ block.addTextBlockListener(this);
+ }
+
+ public void dispose() {
+ block.removeTextBlockListener(this);
+ }
+
+ /**
+ * @return the block
+ */
+ public ITextBlock getBlock() {
+ return block;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.glance.sources.ITextBlock#getText()
+ */
+ public String getText() {
+ return text;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextBlock#addTextBlockListener(org.eclipse.ui
+ * .glance.sources.ITextBlockListener)
+ */
+ public void addTextBlockListener(ITextBlockListener listener) {
+ listeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextBlock#removeTextBlockListener(org.eclipse.ui
+ * .glance.sources.ITextBlockListener)
+ */
+ public void removeTextBlockListener(ITextBlockListener listener) {
+ listeners.remove(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextBlockListener#textChanged(org.eclipse.ui.
+ * glance.sources.TextChangedEvent)
+ */
+ public void textChanged(TextChangedEvent event) {
+ text = block.getText();
+ Object[] objects = listeners.getListeners();
+ for (Object object : objects) {
+ ITextBlockListener listener = (ITextBlockListener) object;
+ listener.textChanged(event);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(ITextBlock block) {
+ return this.block.compareTo(((UITextBlock) block).block);
+ }
+
+ @Override
+ public String toString() {
+ return "UI(" + block + ")";
+ }
+
+ private ListenerList listeners = new ListenerList();
+ private ITextBlock block;
+ private String text;
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextSource.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextSource.java
new file mode 100644
index 0000000..df2b0ca
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UITextSource.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.glance.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.sources.ITextBlock;
+import org.eclipse.ui.glance.sources.ITextSource;
+import org.eclipse.ui.glance.sources.ITextSourceListener;
+import org.eclipse.ui.glance.sources.Match;
+import org.eclipse.ui.glance.sources.SourceSelection;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class UITextSource implements ITextSource, ITextSourceListener {
+
+ public UITextSource(final ITextSource source, final Control control) {
+ this.source = source;
+ this.control = control;
+
+ blocks = new ArrayList<UITextBlock>();
+ blockToBlock = new HashMap<ITextBlock, UITextBlock>();
+ listeners = new ListenerList();
+ source.addTextSourceListener(this);
+ addBlocks(source.getBlocks());
+ updateSelection();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.glance.sources.ITextSource#getSelection()
+ */
+ public SourceSelection getSelection() {
+ return selection;
+ }
+
+ public Control getControl() {
+ return control;
+ }
+
+ public boolean isIndexRequired() {
+ return source.isIndexRequired();
+ }
+
+ public void dispose() {
+ synchronized (blocks) {
+ for (final UITextBlock block : blocks) {
+ block.dispose();
+ }
+ }
+ source.removeTextSourceListener(this);
+ source.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.glance.sources.ITextSource#isDisposed()
+ */
+ public boolean isDisposed() {
+ return source.isDisposed();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.glance.sources.ITextSource#getBlocks()
+ */
+ public ITextBlock[] getBlocks() {
+ return blocks.toArray(new ITextBlock[blocks.size()]);
+ }
+
+ public void index(final IProgressMonitor monitor) {
+ source.index(monitor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSource#select(org.eclipse.ui.glance.sources
+ * .Match)
+ */
+ public void select(final Match match) {
+ UIUtils.asyncExec(control, new Runnable() {
+
+ public void run() {
+ if (!source.isDisposed()) {
+ if (match == null)
+ source.select(null);
+ else {
+ final UITextBlock block = (UITextBlock) match.getBlock();
+ source.select(new Match(block.getBlock(), match.getOffset(), match.getLength()));
+ }
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSource#show(org.eclipse.ui.glance.sources
+ * .Match[])
+ */
+ public void show(final Match[] matches) {
+ UIUtils.asyncExec(control, new Runnable() {
+
+ public void run() {
+ if (!source.isDisposed()) {
+ final Match[] newMatches = new Match[matches.length];
+ for (int i = 0; i < matches.length; i++) {
+ final Match match = matches[i];
+ final UITextBlock block = (UITextBlock) match.getBlock();
+ newMatches[i] = new Match(block.getBlock(), match.getOffset(), match.getLength());
+ }
+ source.show(newMatches);
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSource#addTextSourceListener(org.eclipse.ui
+ * .glance.sources.ITextSourceListener)
+ */
+ public void addTextSourceListener(final ITextSourceListener listener) {
+ listeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSource#removeTextSourceListener(org.
+ * eclipse.ui.glance.sources.ITextSourceListener)
+ */
+ public void removeTextSourceListener(final ITextSourceListener listener) {
+ listeners.remove(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSourceListener#blocksChanged(org.eclipse.ui
+ * .glance.sources.ITextBlock[],
+ * org.eclipse.ui.glance.sources.ITextBlock[])
+ */
+ public void blocksChanged(final ITextBlock[] removed, final ITextBlock[] added) {
+ final ITextBlock[] uiRemoved = removeBlocks(removed);
+ final ITextBlock[] uiAdded = addBlocks(added);
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.blocksChanged(uiRemoved, uiAdded);
+ }
+ }
+
+ public void blocksReplaced(final ITextBlock[] newBlocks) {
+ synchronized (this.blocks) {
+ for (final UITextBlock uiBlock : blockToBlock.values()) {
+ uiBlock.dispose();
+ }
+ blockToBlock = new HashMap<ITextBlock, UITextBlock>();
+ blocks = new ArrayList<UITextBlock>();
+ }
+ final ITextBlock[] uiAdded = addBlocks(newBlocks);
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.blocksReplaced(uiAdded);
+ }
+ selection = source.getSelection();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.ui.glance.sources.ITextSourceListener#selectionChanged(org.
+ * eclipse.ui.glance.sources.SourceSelection)
+ */
+ public void selectionChanged(final SourceSelection selection) {
+ final SourceSelection newSelection = updateSelection();
+ final Object[] objects = listeners.getListeners();
+ for (final Object object : objects) {
+ final ITextSourceListener listener = (ITextSourceListener) object;
+ listener.selectionChanged(newSelection);
+ }
+ }
+
+ protected ITextBlock[] addBlocks(final ITextBlock[] blocks) {
+ synchronized (this.blocks) {
+ final ITextBlock[] added = new ITextBlock[blocks.length];
+ for (int i = 0; i < blocks.length; i++) {
+ final ITextBlock block = blocks[i];
+ final UITextBlock uiBlock = new UITextBlock(block);
+ added[i] = uiBlock;
+ this.blocks.add(uiBlock);
+ blockToBlock.put(block, uiBlock);
+ }
+ return added;
+ }
+ }
+
+ protected ITextBlock[] removeBlocks(final ITextBlock[] blocks) {
+ synchronized (this.blocks) {
+ final List<ITextBlock> removed = new ArrayList<ITextBlock>(blocks.length);
+ for (int i = 0; i < blocks.length; i++) {
+ final ITextBlock block = blocks[i];
+ final UITextBlock uiBlock = blockToBlock.remove(block);
+ if (uiBlock != null) {
+ removed.add(uiBlock);
+ this.blocks.remove(uiBlock);
+ uiBlock.dispose();
+ }
+ }
+ return removed.toArray(new ITextBlock[removed.size()]);
+ }
+ }
+
+ protected SourceSelection updateSelection() {
+ final SourceSelection sourceSelection = source.getSelection();
+ if (sourceSelection == null) {
+ selection = null;
+ } else {
+ selection = new SourceSelection(blockToBlock.get(sourceSelection.getBlock()),
+ sourceSelection.getOffset(), sourceSelection.getLength());
+ }
+ return selection;
+ }
+
+ public void init() {
+ if (source != null) {
+ source.init();
+ }
+ }
+
+ private SourceSelection selection;
+ private Map<ITextBlock, UITextBlock> blockToBlock;
+ private final ListenerList listeners;
+ private List<UITextBlock> blocks;
+ private final ITextSource source;
+ private final Control control;
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UIUtils.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UIUtils.java
new file mode 100644
index 0000000..082728d
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/utils/UIUtils.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.utils;
+
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class UIUtils {
+
+ public static Display getDisplay() {
+ return PlatformUI.getWorkbench().getDisplay();
+ }
+
+ public static void asyncExec(final Control control, final Runnable runnable) {
+ if (control != null && !control.isDisposed()) {
+ control.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ if (!control.isDisposed())
+ runnable.run();
+ }
+ });
+ }
+ }
+
+ public static void asyncExec(final Display display, final Runnable runnable) {
+ if (display != null && !display.isDisposed()) {
+ display.asyncExec(new Runnable() {
+ public void run() {
+ if (!display.isDisposed())
+ runnable.run();
+ }
+ });
+ }
+ }
+
+ public static void syncExec(final Display display, final Runnable runnable) {
+ if (display != null && !display.isDisposed()) {
+ display.syncExec(new Runnable() {
+ public void run() {
+ if (!display.isDisposed())
+ runnable.run();
+ }
+ });
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/AbstractViewerDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/AbstractViewerDescriptor.java
new file mode 100644
index 0000000..e244949
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/AbstractViewerDescriptor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.viewers.descriptors;
+
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.sources.ITextSourceDescriptor;
+import org.eclipse.ui.glance.viewers.utils.ViewerUtils;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public abstract class AbstractViewerDescriptor implements ITextSourceDescriptor {
+
+ protected ITextViewer getTextViewer(Control control) {
+ if (control instanceof StyledText) {
+ return getTextViewer((StyledText) control);
+ }
+ return null;
+ }
+
+ protected ITextViewer getTextViewer(StyledText text) {
+ return ViewerUtils.getTextViewer(text);
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/SourceViewerDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/SourceViewerDescriptor.java
new file mode 100644
index 0000000..66b51f7
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/SourceViewerDescriptor.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.viewers.descriptors;
+
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.internal.viewers.SourceViewerControl;
+import org.eclipse.ui.glance.sources.ITextSource;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class SourceViewerDescriptor extends AbstractViewerDescriptor {
+
+ public boolean isValid(Control control) {
+ ITextViewer viewer = getTextViewer(control);
+ if (viewer instanceof SourceViewer) {
+ SourceViewer sViewer = (SourceViewer) viewer;
+ if (sViewer.getAnnotationModel() != null
+ && sViewer.getDocument() != null)
+ return true;
+ }
+ return false;
+ }
+
+ public ITextSource createSource(Control control) {
+ return new SourceViewerControl((SourceViewer) getTextViewer(control));
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/TextViewerDescriptor.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/TextViewerDescriptor.java
new file mode 100644
index 0000000..f57675e
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/descriptors/TextViewerDescriptor.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.viewers.descriptors;
+
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.TextViewer;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.ui.glance.internal.viewers.TextViewerControl;
+import org.eclipse.ui.glance.sources.ITextSource;
+
+/**
+ * @author Yuri Strot
+ *
+ */
+public class TextViewerDescriptor extends AbstractViewerDescriptor {
+
+ public boolean isValid(Control control) {
+ ITextViewer viewer = getTextViewer(control);
+ return viewer instanceof TextViewer && viewer.getDocument() != null;
+ }
+
+ public ITextSource createSource(Control control) {
+ return new TextViewerControl((TextViewer) getTextViewer(control));
+ }
+}
diff --git a/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/utils/ViewerUtils.java b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/utils/ViewerUtils.java
new file mode 100644
index 0000000..121235c
--- /dev/null
+++ b/bundles/org.eclipse.ui.glance/src/org/eclipse/ui/glance/viewers/utils/ViewerUtils.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Exyte
+ * 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:
+ * Yuri Strot - initial API and Implementation
+ ******************************************************************************/
+package org.eclipse.ui.glance.viewers.utils;
+
+import java.lang.reflect.Field;
+
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TypedListener;
+
+public class ViewerUtils {
+
+ public static ITextViewer getTextViewer(StyledText text) {
+ return getViewer(ITextViewer.class, text, SWT.Selection);
+ }
+
+ public static TreeViewer getTreeViewer(Tree tree) {
+ return getViewer(TreeViewer.class, tree, SWT.Expand);
+ }
+
+ private static <T> T getViewer(Class<T> clazz, Control control, int event) {
+ Listener[] listeners = control.getListeners(event);
+ for (Listener listener : listeners) {
+ Object lookFor = listener;
+ if (listener instanceof TypedListener) {
+ lookFor = ((TypedListener) listener).getEventListener();
+ }
+ try {
+ Field this$0 = lookFor.getClass().getDeclaredField("this$0");
+ this$0.setAccessible(true);
+ Object viewer = this$0.get(lookFor);
+ if (clazz.isInstance(viewer)) {
+ return clazz.cast(viewer);
+ }
+ } catch (Exception e) {
+ // ignore exceptions
+ }
+ }
+ return null;
+ }
+
+}