Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWim Jongman2018-02-15 12:10:39 +0000
committerWim Jongman2018-02-28 10:43:23 +0000
commit855e377e4232cf456912f5d4d0088e86d157eabd (patch)
tree3792982215a3b81d3a30a9c9bc48bc1e2054d562 /org.eclipse.tips.core
parenta5d490a262a6e2f6f3dfcb36372dba07b98566a7 (diff)
downloadeclipse.platform.ua-855e377e4232cf456912f5d4d0088e86d157eabd.tar.gz
eclipse.platform.ua-855e377e4232cf456912f5d4d0088e86d157eabd.tar.xz
eclipse.platform.ua-855e377e4232cf456912f5d4d0088e86d157eabd.zip
Bug 307889 - [Intro] having "tip of the day" functionalityI20180228-2000
This is the initial contribution of a Tip of the Day framework to Eclipse. It provides a dialog with tips that can be started from the Help menu. Optionally is is show at IDE or RCP application starts. Extenders can provide their own tips by implementing a TipProvider. TipProviders can be added to the tips extension point and/or can be initialized from a JSon file. TipProviders can get priority based on an enabled when expression similar to commands and handlers. For example, EMF tips may be shown when the modeling perspective is open. Tips can be created from straight up HTML, hosted on a separate web page (e.g. Eclipse wiki), created from a Json file or be a full blown SWT implementation. Tips can provide actions to enable the user to open a view, set some preferences or whatever. The framework is UI agnostic but comes with a separate SWT implementation. Examples on how to create a Tips and TipProviders are available. Documentation is on the wiki: https://wiki.eclipse.org/Tip_of_the_Day The Tips framework uses null annotations, by default all methods cannot return null, except if they annotated with @Nullable. Changes done by Lars in cooperation with Wim ==Wim 16/feb== * removed annotations in core for now * Fixed some javadoc == Lars 16/feb== Removed the TipThemeManager Remove TipProvider from the Tip API, the Tip does not need to know its provider Tip now require the TipID Tips hashcode and equals methods are based on summary, providerID and creation date Removed the special getImage64 methods and refactored getImage48 to getImage == Wim 27 feb == Removed json plugin Change-Id: Ib65e150bdeb5f3f38075d8c6432e5b476bfa064d Signed-off-by: Wim Jongman <wim.jongman@remainsoftware.com>
Diffstat (limited to 'org.eclipse.tips.core')
-rw-r--r--org.eclipse.tips.core/.classpath7
-rw-r--r--org.eclipse.tips.core/.gitignore2
-rw-r--r--org.eclipse.tips.core/.project28
-rw-r--r--org.eclipse.tips.core/.settings/org.eclipse.jdt.core.prefs7
-rw-r--r--org.eclipse.tips.core/META-INF/MANIFEST.MF11
-rw-r--r--org.eclipse.tips.core/build.properties17
-rw-r--r--org.eclipse.tips.core/images/nomoretips.pngbin0 -> 123004 bytes
-rw-r--r--org.eclipse.tips.core/plugin.xml16
-rw-r--r--org.eclipse.tips.core/pom.xml25
-rw-r--r--org.eclipse.tips.core/schema/tips.exsd455
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/IHtmlTip.java37
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/ITipManager.java74
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/IUrlTip.java30
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/Tip.java109
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipAction.java81
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipImage.java298
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipManager.java260
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java332
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListener.java34
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListenerManager.java67
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/internal/FinalTip.java65
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/internal/ImageUtil.java50
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/internal/LogUtil.java69
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/internal/package-info.java14
-rw-r--r--org.eclipse.tips.core/src/org/eclipse/tips/core/package-info.java20
25 files changed, 2108 insertions, 0 deletions
diff --git a/org.eclipse.tips.core/.classpath b/org.eclipse.tips.core/.classpath
new file mode 100644
index 000000000..eca7bdba8
--- /dev/null
+++ b/org.eclipse.tips.core/.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/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.tips.core/.gitignore b/org.eclipse.tips.core/.gitignore
new file mode 100644
index 000000000..09e3bc9b2
--- /dev/null
+++ b/org.eclipse.tips.core/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/org.eclipse.tips.core/.project b/org.eclipse.tips.core/.project
new file mode 100644
index 000000000..85a0fc8d3
--- /dev/null
+++ b/org.eclipse.tips.core/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.tips.core</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/org.eclipse.tips.core/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.tips.core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..0c68a61dc
--- /dev/null
+++ b/org.eclipse.tips.core/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/org.eclipse.tips.core/META-INF/MANIFEST.MF b/org.eclipse.tips.core/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..201ad389f
--- /dev/null
+++ b/org.eclipse.tips.core/META-INF/MANIFEST.MF
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Tip of the Day core plugin
+Bundle-SymbolicName: org.eclipse.tips.core;singleton:=true
+Bundle-Version: 0.1.0.qualifier
+Bundle-Vendor: Eclipse Foundation
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: org.eclipse.tips.core,
+ org.eclipse.tips.core.internal;x-internal:=true
+Require-Bundle: org.eclipse.core.runtime;bundle-version="3.13.0"
+Automatic-Module-Name: org.eclipse.tips.core
diff --git a/org.eclipse.tips.core/build.properties b/org.eclipse.tips.core/build.properties
new file mode 100644
index 000000000..39b31f3b5
--- /dev/null
+++ b/org.eclipse.tips.core/build.properties
@@ -0,0 +1,17 @@
+###############################################################################
+# Copyright (c) 2018 Remain Software
+# 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:
+# wim.jongman@remainsoftware.com - initial API and implementation
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ images/
+src.includes = schema/
diff --git a/org.eclipse.tips.core/images/nomoretips.png b/org.eclipse.tips.core/images/nomoretips.png
new file mode 100644
index 000000000..d77582876
--- /dev/null
+++ b/org.eclipse.tips.core/images/nomoretips.png
Binary files differ
diff --git a/org.eclipse.tips.core/plugin.xml b/org.eclipse.tips.core/plugin.xml
new file mode 100644
index 000000000..b29e93cdb
--- /dev/null
+++ b/org.eclipse.tips.core/plugin.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<!--
+ Copyright (c) 2018 Remain Software
+ 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:
+ wim.jongman@remainsoftware.com - initial API and implementation
+ -->
+
+<plugin>
+ <extension-point id="tips" name="Tips" schema="schema/tips.exsd"/>
+</plugin>
diff --git a/org.eclipse.tips.core/pom.xml b/org.eclipse.tips.core/pom.xml
new file mode 100644
index 000000000..173ccf367
--- /dev/null
+++ b/org.eclipse.tips.core/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2018 Remain Software
+ 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:
+ wim.jongman@remainsoftware.com - 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>
+ <groupId>eclipse.platform.ua</groupId>
+ <artifactId>eclipse.platform.ua</artifactId>
+ <version>4.8.0-SNAPSHOT</version>
+ </parent>
+ <groupId>org.eclipse.ui</groupId>
+ <artifactId>org.eclipse.tips.core</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <packaging>eclipse-plugin</packaging>
+</project>
diff --git a/org.eclipse.tips.core/schema/tips.exsd b/org.eclipse.tips.core/schema/tips.exsd
new file mode 100644
index 000000000..93236edf6
--- /dev/null
+++ b/org.eclipse.tips.core/schema/tips.exsd
@@ -0,0 +1,455 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.tips.core" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appinfo>
+ <meta.schema plugin="org.eclipse.tips.core" id="tips" name="Tips"/>
+ </appinfo>
+ <documentation>
+ &lt;p&gt;
+This extension point allows bundles to hook into the eclipse tips framework.
+&lt;/p&gt;
+@see Examples
+ </documentation>
+ </annotation>
+
+ <include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/>
+
+ <element name="extension">
+ <annotation>
+ <appinfo>
+ <meta.element />
+ </appinfo>
+ </annotation>
+ <complexType>
+ <choice>
+ <element ref="provider" minOccurs="1" maxOccurs="unbounded"/>
+ <element ref="category" minOccurs="1" maxOccurs="unbounded"/>
+ </choice>
+ <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="provider">
+ <complexType>
+ <sequence>
+ <element ref="enablement" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ <attribute name="id" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="description" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="java" basedOn="org.eclipse.tips.core.TipProvider:"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ <attribute name="category" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="identifier" basedOn="org.eclipse.tips.core.tips/category/@id"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="category">
+ <annotation>
+ <documentation>
+ A category to group tips in the UI.
+ </documentation>
+ </annotation>
+ <complexType>
+ <attribute name="id" type="string" use="required">
+ <annotation>
+ <documentation>
+ a unique name that will be used to identify this category
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string" use="required">
+ <annotation>
+ <documentation>
+ a translatable name that will be used in the UI for this category
+ </documentation>
+ <appinfo>
+ <meta.attribute translatable="true"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ <attribute name="parentCategory" type="string">
+ <annotation>
+ <documentation>
+ an optional path composed of category IDs separated by &apos;/&apos;. This
+allows the creation of a hierarchy of categories.
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="identifier" basedOn="org.eclipse.tips.core.tips/category/@id"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+
+ <annotation>
+ <appinfo>
+ <meta.section type="examples"/>
+ </appinfo>
+ <documentation>
+ &lt;p&gt;
+This extension defines a parent category &quot;Java&quot;and a child category &quot;Java9&quot;. Then the provider
+is defined within category &quot;Java9&quot;.
+&lt;pre&gt;
+ &lt;extension
+ point=&quot;org.eclipse.tips.ide.tips&quot;&gt;
+ &lt;category
+ id=&quot;org.eclipse.tips.examples.java&quot;
+ name=&quot;Java&quot;&gt;
+ &lt;/category&gt;
+ &lt;category
+ id=&quot;org.eclipse.tips.examples.java9&quot;
+ name=&quot;Java9&quot;
+ parentCategory=&quot;org.eclipse.tips.examples.java9&quot;&gt;
+ &lt;/category&gt;
+ &lt;provider
+ category=&quot;org.eclipse.tips.examples.java9&quot;
+ class=&quot;org.eclipse.tips.examples.java.java9.Java9TipProvider&quot;
+ description=&quot;Java 9 Tips&quot;
+ id=&quot;org.eclipse.tips.examples.java.java9.Java9TipProvider&quot;&gt;
+ &lt;enablement&gt;
+ &lt;with
+ variable=&quot;activeWorkbenchWindow.activePerspective&quot;&gt;
+ &lt;equals
+ value=&quot;org.eclipse.jdt.ui.JavaPerspective&quot;&gt;
+ &lt;/equals&gt;
+ &lt;/with&gt;
+ &lt;/enablement&gt;
+ &lt;/provider&gt;
+ &lt;/extension&gt;
+&lt;/pre&gt;
+&lt;/p&gt;
+
+&lt;p&gt;
+&lt;b&gt;Sample implementation TipProvider&lt;/b&gt;
+&lt;/p&gt;
+
+&lt;p&gt;
+
+&lt;h1&gt;The Implementation of a TipProvider&lt;/h1&gt;
+This is an example of a TipProvider. The framework also contains an JSon TipProvider that fetches tips inside a JSon file from a remote URL.
+&lt;b&gt;
+The example below instantiates Tips from Java.
+&lt;pre&gt;
+
+/**
+ * Class to provide tips to the tip framework. It is the job of this provider to
+ * manage its tips. Examples of managing tips are:
+ *
+ * &lt;ul&gt;
+ * &lt;li&gt;Loading tips from the internet&lt;/li&gt;
+ * &lt;li&gt;Serve next, previous and current tip on request&lt;/li&gt;
+ * &lt;/ul&gt;
+ *
+ * After the TipProvider is instantiated by the {@link TipManager}, the
+ * TipManager will insert itself by calling {@link #setManager(TipManager)}.
+ * Then the TipManager will asynchronous call this providers&apos; {@link #load()}
+ * method. The job of the load() method is to do long work like fetching new
+ * tips from the internet and storing them locally. There is no defined method
+ * on how tips should be stored locally, implementers are free to do what is
+ * needed.
+ *
+ * The constructor must return fast, meaning that tips may not be fetched from
+ * the internet in the constructor. This should be done in the
+ * {@link #load(IProgressMonitor)} method.
+ *
+ * To indicate that this provider is ready to serve tips, it should call the
+ * {@link #setTips(List)} method which then sets its &lt;code&gt;ready&lt;/code&gt; flag.
+ *
+ */
+public class Java9TipProvider extends TipProvider {
+
+ @Override
+ /**
+ * The Id is used (for example) to manage the read state of this providers tips.
+ * It should be unique.
+ *
+ * @return the ID of this provider
+ */
+ public String getID() {
+ return &quot;org.eclipse.tips.examples.Java9TipProvider&quot;;
+ }
+
+ /**
+ * The 48x48 image may be used by the UI for low resolution displays.
+ *
+ * @return a 48x48 {@link TipImage}
+ */
+ @Override
+ public TipImage getImage48() {
+ Bundle bundle = FrameworkUtil.getBundle(getClass());
+ try {
+ return new TipImage(bundle.getEntry(&quot;icons/48/java.png&quot;)).setAspectRatio(1);
+ } catch (IOException e) {
+ getManager().log(getClass(), e);
+ }
+ return null;
+
+ }
+
+ /**
+ * The 64x64 image may be used by the UI for higher resolution displays.
+ *
+ * @return a 64x64 {@link TipImage}
+ */
+ @Override
+ public TipImage getImage64() {
+ Bundle bundle = FrameworkUtil.getBundle(getClass());
+ try {
+ return new TipImage(bundle.getEntry(&quot;icons/64/java.png&quot;)).setAspectRatio(1);
+ } catch (IOException e) {
+ getManager().log(getClass(), e);
+ }
+ return null;
+
+ }
+
+ /**
+ * Is called asynchronously during startup of the TipManager to gather new tips.
+ * The provider is not available to the UI unless it has called it&apos;s
+ * {@link #setTips(List)} method. It is therefore possible that the provider is
+ * not immediately visible in the tip UI but will be added later.
+ *
+ * One strategy is to do a long running fetch in this method and then store the
+ * tips locally. On the next run of the TipManager, the fetched tips can be
+ * served from the constructor (i.e. by calling {@link #setTips(List)}), making
+ * it immediately available.
+ *
+ * @param pMonitor
+ * The monitor to report back progress.
+ * @see TipProvider#setTips(List)
+ * @see TipProvider#isReady()
+ */
+ @Override
+ public synchronized void load(IProgressMonitor pMonitor) {
+ SubMonitor subMonitor = SubMonitor.convert(pMonitor);
+ subMonitor.beginTask(&quot;Loading Tips&quot;, -1);
+ List&lt;Tip&gt; tips = new ArrayList&lt;&gt;();
+ tips.add(new Tip1(this));
+ tips.add(new Navigate2Tip(this));
+ setTips(tips);
+ subMonitor.done();
+ }
+
+ /**
+ * @return the short description of this provider.
+ */
+ @Override
+ public String getDescription() {
+ return &quot;Java and Java Dev Tools Tips&quot;;
+ }
+
+ /**
+ * Provides the opportunity to release all held resources.
+ */
+ @Override
+ public void dispose() {
+ }
+}
+
+&lt;/pre&gt;
+
+&lt;h1&gt;The Implementation of a Tip&lt;/h1&gt;
+This is an example implementation of a Tip
+&lt;pre&gt;
+/**
+ * This is an example Tip class.
+ *
+ */
+public class Tip1 extends Tip {
+
+ /**
+ * Tips should be created fast.
+ *
+ * @param pProvider
+ * the associated {@link TipProvider}
+ */
+ public Tip1(TipProvider pProvider) {
+ super(pProvider);
+ }
+
+ /**
+ * A getter for a {@link Runnable} action for this tip. Clients may override or
+ * call {@link #setAction(Runnable)}.
+ *
+ * @return the action or null if not set by {@link Tip#setAction(Runnable)}.
+ * @see Tip#setAction(Runnable)
+ */
+ @Override
+ public Runnable getAction() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ MessageDialog.openConfirm(null, getSubject(), &quot;We can start an action from a Tip!&quot;);
+ }
+ });
+ }
+ };
+ }
+
+ /**
+ * Return the publish date of the tip. The {@link TipProvider} could decide to
+ * server newer tips first.
+ *
+ * @return the date this tip was published which may not be null.
+ */
+ @Override
+ public Date getCreationDate() {
+ return DateUtil.getDateFromYYMMDD(&quot;10/01/2018&quot;);
+ }
+
+ /**
+ * @return the subject which may not be null.
+ */
+ @Override
+ public String getSubject() {
+ return &quot;Java tip 1&quot;;
+ }
+
+ /**
+ * This method may both return the string representation of an URL or the
+ * descriptive HTML of the tip. If the html of the text is returned then an
+ * effort is made to also inline the image URL. If you supply an URL then
+ * {@link #getImage()} should return null.
+ *
+ * @return the HMTL or URL of the tip which is displayed in a browser widget.
+ * @see #getImage()
+ */
+ @Override
+ public String getHTML() {
+ return &quot;&lt;h2&gt;Javatip 1&lt;/h2&gt;You see this tip because the Tip UI was opened when the java perspective &quot;
+ + &quot;was active or because you selected the Java icon below.&quot; + &quot;&lt;br&gt;&lt;br&gt;&quot;
+ + &quot;More java tips will be displayed here in the near future. For now, select one of the other providers&quot;
+ + &quot; by clicking on the icons below.&quot;;
+ }
+
+ /**
+ * A getter for the {@link TipImage}. Subclasses may override, the default
+ * implementation returns null.
+ *
+ * @return a TipImage with information about the image or null if no image is
+ * provided.
+ */
+ @Override
+ public TipImage getImage() {
+ Bundle bundle = FrameworkUtil.getBundle(getClass());
+ try {
+ return new TipImage(bundle.getEntry(&quot;images/java/duke.png&quot;)).setAspectRatio(1);
+ } catch (IOException e) {
+ getProvider().getManager().log(getClass(), e);
+ }
+ return null;
+ }
+}
+&lt;/pre&gt;
+
+&lt;h1&gt;Example of a TipImage&lt;/h1&gt;
+TipImage objects are UI agnostic representations of an Image. You may pass a URL to a TipImage or a full base64 string (e.g. &quot;data:image/png;base64,iVBORw0KGgo.....CYII=&quot;).
+&lt;br&gt;
+One import aspect of the TipImage is it&apos;s aspect ratio. The Tip UI may use a browser to display it&apos;s UI. Therefore it can be scaled to the available space. Images look best when they use a 3:2 (1.5) ratio.
+&lt;br&gt;
+&lt;h4&gt;Set an image that is a square (aspect ratio = 1:1 (1))
+&lt;pre&gt;
+TipImage img = new TipImage(bundle.getEntry(&quot;images/java/duke.png&quot;)).setAspectRatio(1)
+&lt;/pre&gt;
+
+&lt;h4&gt;Set an image that is a square (aspect ratio = 1:1 (1))
+&lt;pre&gt;
+ /**
+ * Sets the aspect ratio of this image. If the image is 300 wide and 600 high
+ * then the aspect ratio is 300/600 = 0,5 (1:2). If your image is 1200 wide and
+ * 250 high then the aspect ratio (1200/250) = 4,8. With the supplied values the
+ * best dimensions for the image can be calculated give the available space in
+ * the UI.
+ * &lt;p&gt;
+ * In case you pass true for &lt;code&gt;pSetAsMax&lt;/code&gt; then the image can not be
+ * up-scaled beyond the specified size. So if your image is 200x200 and you want
+ * a maximum up-scale of 2 then pass 400x400 to this method to maintain the
+ * aspect ratio but allow the image to be resized to maximum it&apos;s double size.
+ * &lt;p&gt;
+ * The recommended aspect ratio is around 3:2 (1.5) to be comfortably displayed
+ * in the Tip UI.
+ **
+ TipImage img = new TipImage(&quot;data:image/png;base64,3ff4..eFW&quot;);
+ img.setAspectRatio(800, 400, true); // Image can not be scaled up higer than 800:400
+&lt;/pre&gt;
+
+&lt;/p&gt;
+ </documentation>
+ </annotation>
+
+
+
+ <annotation>
+ <appinfo>
+ <meta.section type="copyright"/>
+ </appinfo>
+ <documentation>
+ Copyright (c) 2017 Remain Software
+
+ 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:
+ Wim Jongman &lt;wim.jongman@remainsoftware.com&gt; - Initial implementation
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/IHtmlTip.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/IHtmlTip.java
new file mode 100644
index 000000000..b6373111a
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/IHtmlTip.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core;
+
+
+/**
+ * Decoration of {@link Tip} that enables HTML content.
+ *
+ */
+public interface IHtmlTip {
+
+ /**
+ * Returns the HTML of the tip to be rendered in the tip UI, together or without
+ * the {@link #getImage()}.
+ *
+ * @return the HMTL of the tip
+ * @see #getImage()
+ */
+ public String getHTML();
+
+ /**
+ * Returns the {@link TipImage}.
+ *
+ * @return a TipImage with information about the image or <code>null</code> if the this information cannot be created
+ * @see #getHTML()
+ */
+ public TipImage getImage();
+
+}
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/ITipManager.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/ITipManager.java
new file mode 100644
index 000000000..a722914b5
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/ITipManager.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * The model maintained by the TipManager.
+ *
+ */
+public interface ITipManager {
+
+ /**
+ * Indicates whether already read tips must be served or not.
+ *
+ * @return true or false
+ * @see TipManager#setServeReadTips(boolean)
+ */
+ public boolean mustServeReadTips();
+
+ /**
+ * Consults TipManager to determine the Tip's read status.
+ *
+ * @param tip
+ * the tip to query for its read status
+ * @return true if the tip is read, false otherwise.
+ */
+ public abstract boolean isRead(Tip tip);
+
+ /**
+ * Instructs the TipManager to mark this tip as read.
+ *
+ * @param tip
+ * the tip to set as read.
+ * @return this
+ */
+ public abstract ITipManager setAsRead(Tip tip);
+
+ /**
+ * Central place of logging for the Tip Framework.
+ *
+ * @param status
+ * the {@link IStatus} which may not be null
+ * @return this
+ */
+ public ITipManager log(IStatus status);
+
+ /**
+ * Binds the passed provider to this manager. After registration, ITipManager
+ * implementations should asynchronously call the
+ * {@link TipProvider#loadNewTips(org.eclipse.core.runtime.IProgressMonitor)}
+ * method.
+ *
+ * @param provider
+ * the {@link TipProvider} to register which may not be null.
+ * @return this
+ */
+ public ITipManager register(TipProvider provider);
+
+ /**
+ * Returns the disposed stated.
+ *
+ * @return true if this manager is disposed, false otherwise.
+ */
+ public boolean isDisposed();
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/IUrlTip.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/IUrlTip.java
new file mode 100644
index 000000000..01ec04a59
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/IUrlTip.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core;
+
+import java.net.URL;
+
+/**
+ * Decoration of {@link Tip} that enables URL content.
+ *
+ */
+public interface IUrlTip {
+
+ /**
+ * Return an URL with the primary goal to be rendered by the tip manager.
+ * Implementations of Tip may also use the URL to aid the rendering (e.g. by
+ * providing other data than HTML, e.g. a text file).
+ *
+ * @return the URL to the (remote) content
+ *
+ */
+ public URL getURL();
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/Tip.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/Tip.java
new file mode 100644
index 000000000..8d5cf112c
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/Tip.java
@@ -0,0 +1,109 @@
+/****************************************************************************
+ * Copyright (c) 2017, 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * This is the base Tip class of the UI agnostic Tip framework. You might want
+ * to check specializations of this class that may make your life easier.
+ *
+ */
+public abstract class Tip {
+
+ private String providerId;
+
+ private final List<TipAction> fActions = new ArrayList<>();
+
+ /**
+ * Constructor for a Tip. For the best user experience, Tips should be created
+ * really fast.
+ *
+ */
+ public Tip(String providerId) {
+ this.providerId = providerId;
+ }
+
+ /**
+ * For the sanity of the Framework, Tips should be created really fast.
+ *
+ * @param pProvider
+ * the associated {@link TipProvider}
+ * @param actions
+ * the list of actions which may not be null
+ */
+ public Tip(List<TipAction> actions) {
+ fActions.addAll(actions);
+ }
+
+ /**
+ * A getter for a list of {@link TipAction}s for this tip. Clients may override
+ * or provide the actions through the constructor.
+ *
+ * @return the list of actions, never null but could be empty.
+ */
+ public List<TipAction> getActions() {
+ return fActions;
+ }
+
+ /**
+ * Return the publish date of the tip. The UI could decide to server newer tips
+ * first.
+ *
+ * @return the date this tip was published which may not be null.
+ */
+ public abstract Date getCreationDate();
+
+
+ /**
+ * @return the subject which may not be null.
+ */
+ public abstract String getSubject();
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((getCreationDate() == null) ? 0 : getCreationDate().hashCode());
+ result = prime * result + ((providerId == null) ? 0 : providerId.hashCode());
+ result = prime * result + ((getSubject() == null) ? 0 : getSubject().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;
+ Tip other = (Tip) obj;
+ if (getCreationDate() == null) {
+ if (other.getCreationDate() != null)
+ return false;
+ } else if (!getCreationDate().equals(other.getCreationDate()))
+ return false;
+ if (providerId == null) {
+ if (other.providerId != null)
+ return false;
+ } else if (!providerId.equals(other.providerId))
+ return false;
+ if (getSubject() == null) {
+ if (other.getSubject() != null)
+ return false;
+ } else if (!getSubject().equals(other.getSubject()))
+ return false;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipAction.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipAction.java
new file mode 100644
index 000000000..e73a2ac72
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipAction.java
@@ -0,0 +1,81 @@
+/****************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+/**
+ * Provides an action to be executed by a Tip.
+ *
+ */
+public class TipAction {
+
+ private final String fText;
+ private final TipImage fTipImage;
+ private final Runnable fRunner;
+ private final String fTooltip;
+
+ /**
+ * Creates a new TipAction. Tip actions can be executed in the tip UI when the
+ * associated Tip is displayed.
+ *
+ * @param text
+ * a very short description to be used on buttons and menus.
+ * @param tooltip
+ * a longer description to be shown as tool tip when possible.
+ * @param runner
+ * the actual code to run
+ * @param image
+ * the image to be shown when possible.
+ *
+ */
+ public TipAction(String text, String tooltip, Runnable runner, TipImage image) {
+ fText = text;
+ fTooltip = tooltip;
+ fRunner = runner;
+ fTipImage = image;
+ }
+
+ /**
+ * The short description of the action to be shown as button text or menu entry
+ * when possible.
+ *
+ * @return the text
+ */
+ public String getText() {
+ return fText;
+ }
+
+ /**
+ * A longer description to be shown as tool tip when possible.
+ *
+ * @return the tool tip.
+ */
+ public String getTooltip() {
+ return fTooltip;
+ }
+
+ /**
+ * The icon of the image wrapped in a TipImage.
+ *
+ * @return the icon
+ */
+ public TipImage getTipImage() {
+ return fTipImage;
+ }
+
+ /**
+ * The actual code to run when this action is executed.
+ *
+ * @return the runner.
+ */
+ public Runnable getRunner() {
+ return fRunner;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipImage.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipImage.java
new file mode 100644
index 000000000..18a12e791
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipImage.java
@@ -0,0 +1,298 @@
+/****************************************************************************
+ * Copyright (c) 2017, 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Base64;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.tips.core.internal.ImageUtil;
+
+/**
+ * Provides more information about the image to be used in the tip. The image
+ * aspect ratio must be around 3:2 to be comfortably displayed in the Tip UI.
+ *
+ */
+public class TipImage {
+
+ private static final double THREE_TO_TWO = 1.5;
+
+ /**
+ * Value to indicate that the height or width are to be determined by the Tip
+ * framework.
+ */
+ public static final int UNDEFINED = -1;
+
+ private String fExtension = null;
+ private int fMaxWidth = UNDEFINED;
+ private int fMaxHeight = UNDEFINED;
+ final private URL fURL;
+ private double fAspectRatio = THREE_TO_TWO;
+
+ final private String fBase64Image;
+
+ private static final int _4KB = 4096;
+
+ /**
+ * Creates a new TipImage with the specified URL which gets read into a base 64
+ * string.
+ *
+ * @param url
+ * the image URL which may not be null
+ * @throws IOException
+ * in case the stream of the passed URL could not be opened or read.
+ *
+ */
+ public TipImage(URL url) throws IOException {
+ Assert.isNotNull(url);
+ fURL = url;
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] chunk = new byte[_4KB];
+ int bytesRead;
+ InputStream stream = url.openStream();
+ while ((bytesRead = stream.read(chunk)) > 0) {
+ outputStream.write(chunk, 0, bytesRead);
+ }
+ fBase64Image = "data:image/" //
+ + getExtension() //
+ + ";base64," //
+ + Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ }
+
+ /**
+ * @return true if the URL constructor was used and not the base64 constructor.
+ */
+ public boolean isURLSet() {
+ return fURL != null;
+ }
+
+ /**
+ * Creates a new {@link TipImage} with the specified base64 string which must be
+ * a valid RFC-2397 string thats begins with
+ * <code>"data:image/&lt;subtype&gt;;base64"</code> where <code>subtype</code>
+ * must be a valid image type (pmg, bmp, gif, etc..).
+ *
+ * @param base64Image
+ * the non-null base64 encoded image according to RFC-2397.
+ *
+ * @throws RuntimeException
+ * if the string is not valid
+ * @see TipImage
+ * @see <a href="https://tools.ietf.org/search/rfc2397">RFC-2397
+ * (https://tools.ietf.org/search/rfc2397)</a>
+ *
+ */
+ public TipImage(String base64Image) {
+ Assert.isNotNull(base64Image);
+ fURL = null;
+ if (base64Image.matches("^data:image\\/.*?;base64,.*$")) {
+ fBase64Image = base64Image;
+ int from = base64Image.indexOf("/") + 1;
+ int to = base64Image.indexOf(";");
+ setExtension(base64Image.substring(from, to).trim());
+ setExtension(base64Image.substring(from, to).trim());
+ } else {
+ int length = base64Image.length();
+ throw new RuntimeException("Wrong base64 data " + base64Image.substring(0, length < 50 ? length : 50));
+ }
+ }
+
+ /**
+ * Sets the maximum height that this image can display. For example, if you have
+ * a 32x32 image the framework will blow it up to a larger size which will not
+ * work for the image and you might want to pass 64 to indicate that the image
+ * cannot be resized passed 64 pixels. If the height is not set or set to
+ * {@link #UNDEFINED}, then it is automatically resized based on aspect ratio
+ * and maximum width.
+ *
+ * @param maxHeight
+ * the maximum height for this image or {@link #UNDEFINED}
+ * @return this
+ * @see #setAspectRatio(double)
+ * @see #setAspectRatio(int, int, boolean)
+ * @see #setMaxWidth(int)
+ */
+ public TipImage setMaxHeight(int maxHeight) {
+ fMaxHeight = maxHeight;
+ return this;
+ }
+
+ /**
+ * Sets the maximum width that this image can display. For example, if you have
+ * a 32x32 image the framework will blow it up to a larger size which will not
+ * work for the image and you might want to pass 64 to indicate that the image
+ * cannot be resized passed 64 pixels. If the width is not set or set to
+ * {@link #UNDEFINED}, it is automatically resized based on aspect ratio and
+ * maximum height.
+ *
+ * @param maxWidth
+ * the maximum width for this image or {@link #UNDEFINED}
+ * @return this
+ * @see #setAspectRatio(double)
+ * @see #setAspectRatio(int, int, boolean)
+ * @see #setMaxHeight(int)
+ */
+ public TipImage setMaxWidth(int maxWidth) {
+ fMaxWidth = maxWidth;
+ return this;
+ }
+
+ /**
+ * Sets the aspect ratio of this image. If the image is 300 wide and 600 high
+ * then the aspect ratio is 300/600 = 0,5 (1:2). If your image is 1200 wide and
+ * 250 high then the aspect ratio (1200/250) = 4,8. With the supplied values the
+ * best dimensions for the image can be calculated give the available space in
+ * the UI.
+ * <p>
+ * In case you pass true for <code>pSetAsMax</code> then the image can not be
+ * up-scaled beyond the specified size. So if your image is 200x200 and you want
+ * a maximum up-scale of 2 then pass 400x400 to this method to maintain the
+ * aspect ratio but allow the image to be resized to maximum it's double size.
+ * <p>
+ * The recommended aspect ratio is around 3:2 (1.5) to be comfortably displayed
+ * in the Tip UI.
+ *
+ * @param width
+ * the width of the image, must be greater than 0
+ * @param height
+ * the height of the image, must be greater than 0
+ * @param setAsMax
+ * true to set the passed width and height as the maximum width and
+ * height for the image
+ * @return this
+ * @see #setAspectRatio(double)
+ * @see #getIMGAttributes(int, int)
+ * @see #setMaxHeight(int)
+ * @see #setMaxWidth(int)
+ */
+ public TipImage setAspectRatio(int width, int height, boolean setAsMax) {
+ Assert.isTrue(width > 0);
+ Assert.isTrue(height > 0);
+ fAspectRatio = (double) width / (double) height;
+ if (setAsMax) {
+ setMaxHeight(height);
+ setMaxWidth(width);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the aspect ratio of your image which is defined by width divided by
+ * height.
+ * <p>
+ * The recommended aspect ratio is around 3:2 (1.5) to be comfortably displayed
+ * in the Tip UI.
+ *
+ *
+ * @param aspectRatio
+ * the aspect ration
+ * @return this
+ * @see #setAspectRatio(int, int, boolean)
+ * @see #getIMGAttributes(int, int)
+ */
+ public TipImage setAspectRatio(double aspectRatio) {
+ fAspectRatio = aspectRatio;
+ return this;
+ }
+
+ /**
+ * Changes the default value "null" to the passed value which commonly is "png",
+ * "gif" and such.
+ *
+ * @param extension
+ * the extension of this file
+ * @return this
+ * @see #getExtension()
+ */
+ public TipImage setExtension(String extension) {
+ fExtension = extension;
+ return this;
+ }
+
+ /**
+ * Returns the base64 encoded image string according to RFC-2397 or null. The
+ * recommended aspect ratio is around 3:2.
+ *
+ * @return the base64 encoded image string according to RFC-2397 or null
+ */
+ public String getBase64Image() {
+ return fBase64Image;
+ }
+
+ /**
+ * Returns the width and height attributes of the HTML IMG tag.
+ *
+ * <pre>
+ * &lt;img src="smiley.gif" height="42" width="42"&gt;
+ * </pre>
+ *
+ * The available space in the UI is passed and with it the best size of the
+ * image will be calculated based on the aspect ratio of this image.
+ *
+ * Clients may override if they can provide better information.
+ *
+ * @param widthHint
+ * the available width which must be greater than 0
+ * @param heightHint
+ * the available height which must be greater than 0
+ * @return the attributes in the HTML img tag
+ * @see TipImage#setAspectRatio(double)
+ * @see TipImage#setAspectRatio(int, int, boolean)
+ * @see TipImage#setMaxHeight(int)
+ * @see TipImage#setMaxWidth(int)
+ */
+ public String getIMGAttributes(int widthHint, int heightHint) {
+
+ int myWidthHint = (fMaxWidth == UNDEFINED) ? widthHint : Math.min(widthHint, fMaxWidth);
+ int myHeightHint = (fMaxHeight == UNDEFINED) ? heightHint : Math.min(heightHint, fMaxHeight);
+
+ int width = ImageUtil.getWidth(fAspectRatio, myWidthHint, myHeightHint);
+ int height = ImageUtil.getHeight(fAspectRatio, myWidthHint, myHeightHint);
+
+ String result = "";
+ if (fMaxWidth == UNDEFINED) {
+ result += " width=\"" + width + "\"";
+ } else {
+ result += " width=\"" + Math.min(fMaxWidth, width) + "\"";
+ }
+
+ if (fMaxHeight == UNDEFINED) {
+ result += " height=\"" + height + "\"";
+ } else {
+ result += " height=\"" + Math.min(fMaxHeight, height) + "\"";
+ }
+ return result;
+ }
+
+ /**
+ * Returns the image extension for use in the IMG tag for the data attribute
+ * (<code>data:image/???</code>). If the extension is not set in this object,
+ * then the URL is examined to find the extension. If that can not be determined
+ * then "png" is returned.
+ *
+ * @return the extension
+ */
+ private String getExtension() {
+ if (fExtension != null) {
+ return fExtension;
+ }
+ String[] split = fURL.getPath().split("\\.");
+ if (split.length > 1) {
+ fExtension = split[split.length - 1];
+ } else {
+ fExtension = "png";
+ }
+ return fExtension;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipManager.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipManager.java
new file mode 100644
index 000000000..2101f160c
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipManager.java
@@ -0,0 +1,260 @@
+/****************************************************************************
+ * Copyright (c) 2017, 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.tips.core.internal.LogUtil;
+
+/**
+ * An abstract implementation of ITipManager with additional control API. While
+ * the rest of the framework must work with ITipManager, this class provides API
+ * to open the dialog and do low level housekeeping that is of no concern to
+ * external participants (Tip and TipProvider).
+ *
+ */
+public abstract class TipManager implements ITipManager {
+
+ private Map<String, TipProvider> fProviders = new HashMap<>();
+ private Map<Integer, List<String>> fProviderPrio = new TreeMap<>();
+ protected boolean fOpen;
+ private boolean fServeReadTips = false;
+ private TipProviderListenerManager fListenerManager = new TipProviderListenerManager();
+ private boolean fIsDiposed;
+
+ /**
+ * Instantiates a new TipManager.
+ */
+ public TipManager() {
+ }
+
+ /**
+ * Gets the provider with the specified ID.
+ *
+ * @param providerID
+ * the id of the provider to fetch
+ * @return the provider with the specified ID or null if no such provider
+ * exists.
+ * @see TipProvider#getID()
+ */
+ public TipProvider getProvider(String providerID) {
+ checkDisposed();
+ return fProviders.get(providerID);
+ }
+
+ /**
+ * Binds the passed provider to this manager. Implementations should override,
+ * call super and the asynchronously call the
+ * {@link TipProvider#loadNewTips(org.eclipse.core.runtime.IProgressMonitor)}
+ * method.
+ *
+ * @param provider
+ * the {@link TipProvider} to register.
+ *
+ * @return this
+ */
+ @Override
+ public ITipManager register(TipProvider provider) {
+ checkDisposed();
+ log(LogUtil.info("Registering provider: " + provider.getID() + " : " + provider.getDescription()));
+ provider.setManager(this);
+ addToMaps(provider, new Integer(getPriority(provider)));
+ provider.getListenerManager().addProviderListener(
+ myProvider -> getListenerManager().notifyListeners(TipProviderListener.EVENT_READY, myProvider));
+ return this;
+ }
+
+ private void checkDisposed() {
+ if (isDisposed()) {
+ throw new RuntimeException("This TipManager is disposed.");
+ }
+
+ }
+
+ /**
+ * Calculates the priority that this provider has in the Tips framework. The
+ * {@link TipProvider#getExpression()} was purposed to aid in the calculation of
+ * the priority.
+ *
+ * @param provider
+ * the provider
+ * @return the priority, lower is higher, never negative.
+ */
+ public abstract int getPriority(TipProvider provider);
+
+ private synchronized void addToMaps(TipProvider pProvider, Integer pPriorityHint) {
+ removeFromMaps(pProvider);
+ addToProviderMaps(pProvider, pPriorityHint);
+ addToPriorityMap(pProvider, pPriorityHint);
+ }
+
+ private void addToPriorityMap(TipProvider provider, Integer priorityHint) {
+ if (!fProviderPrio.get(priorityHint).contains(provider.getID())) {
+ if (!fProviderPrio.get(priorityHint).contains(provider.getID())) {
+ fProviderPrio.get(priorityHint).add(provider.getID());
+ }
+ }
+ }
+
+ private void addToProviderMaps(TipProvider provider, Integer priorityHint) {
+ fProviders.put(provider.getID(), provider);
+ if (fProviderPrio.get(priorityHint) == null) {
+ fProviderPrio.put(priorityHint, new ArrayList<>());
+ }
+ }
+
+ private void removeFromMaps(TipProvider provider) {
+ if (fProviders.containsKey(provider.getID())) {
+ for (Map.Entry<Integer, List<String>> entry : fProviderPrio.entrySet()) {
+ entry.getValue().remove(provider.getID());
+ }
+ fProviders.remove(provider.getID());
+ }
+ }
+
+ /**
+ * The returned list contains providers ready to serve tips and is guaranteed to
+ * be in a prioritised order according the implementation of this manager.
+ *
+ * @return the prioritised list of ready providers with tips in an immutable
+ * list.
+ */
+ public List<TipProvider> getProviders() {
+ checkDisposed();
+ if (fProviders == null) {
+ return Collections.emptyList();
+ }
+ ArrayList<TipProvider> result = new ArrayList<>();
+ for (Map.Entry<Integer, List<String>> entry : fProviderPrio.entrySet()) {
+ for (String id : entry.getValue()) {
+ if (fProviders.get(id).isReady()) {
+ result.add(fProviders.get(id));
+ }
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Determines if the Tips framework must run at startup. The default
+ * implementation returns true, subclasses should probably override this.
+ *
+ * @return true if the Tips framework should run at startup.
+ * @see TipManager#setRunAtStartup(boolean)
+ */
+ public boolean isRunAtStartup() {
+ checkDisposed();
+ return true;
+ }
+
+ /**
+ * Determines if the Tips framework must run at startup.
+ *
+ * @param shouldRun
+ * true if the tips should be displayed at startup, false otherwise.
+ *
+ * @return this
+ *
+ * @see #isRunAtStartup()
+ */
+ public abstract TipManager setRunAtStartup(boolean shouldRun);
+
+ /**
+ * Opens the Tip of the Day dialog.
+ *
+ * @param startUp
+ * When called from a startup situation, true must be passed for
+ * <code>pStartup</code>. If in a manual starting situation, false
+ * must be passed. This enables the manager to decide to skip opening
+ * the dialog at startup (e.g., no new tip items).
+ *
+ * @return this
+ *
+ * @see #isOpen()
+ */
+ public abstract TipManager open(boolean startUp);
+
+ /**
+ * The default implementation disposes of this manager and all the TipProviders
+ * when the dialog is disposed. Subclasses may override but must call super.
+ */
+ public void dispose() {
+ checkDisposed();
+ try {
+ for (TipProvider provider : fProviders.values()) {
+ try {
+ provider.dispose();
+ } catch (Exception e) {
+ log(LogUtil.error(e));
+ }
+ }
+ } finally {
+ fProviders.clear();
+ fProviderPrio.clear();
+ fIsDiposed = true;
+ }
+ }
+
+ /**
+ * @return returns true if the tips are currently being displayed in some way.
+ */
+ public boolean isOpen() {
+ checkDisposed();
+ return fOpen;
+ }
+
+ /**
+ * Indicates whether read tips must be served or not. Subclasses could override,
+ * to save the state somewhere, but must call super.
+ *
+ * @param serveRead
+ * true of read tips may be served by the {@link TipProvider}s
+ * @return this
+ * @see TipManager#mustServeReadTips()
+ */
+ public TipManager setServeReadTips(boolean serveRead) {
+ checkDisposed();
+ fServeReadTips = serveRead;
+ return this;
+ }
+
+ /**
+ * Indicates whether already read tips must be served or not.
+ *
+ * @return true or false
+ * @see #setServeReadTips(boolean)
+ */
+ @Override
+ public boolean mustServeReadTips() {
+ checkDisposed();
+ return fServeReadTips;
+ }
+
+ /**
+ * Gets the listener manager so that interested parties can subscribe to the
+ * events of this provider.
+ *
+ * @return the listener manager, never null.
+ */
+ public TipProviderListenerManager getListenerManager() {
+ return fListenerManager;
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return fIsDiposed;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java
new file mode 100644
index 000000000..fde1da7b3
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java
@@ -0,0 +1,332 @@
+/****************************************************************************
+ * Copyright (c) 2017, 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.tips.core.internal.FinalTip;
+
+/**
+ * Class to provide tips to the tip framework. It is the job of this provider to
+ * manage its tips. Examples of managing tips are:
+ *
+ * <ul>
+ * <li>Loading tips from the internet</li>
+ * <li>Serve next, previous and current tip on request</li>
+ * </ul>
+ *
+ * After the TipProvider is instantiated by the {@link ITipManager}, the
+ * TipManager will insert itself by calling {@link #setManager(ITipManager)}.
+ * Then the TipManager will asynchronous call this providers'
+ * {@link #loadNewTips(IProgressMonitor)} method. The job of the load() method
+ * is to do long work like fetching new tips from the internet and storing them
+ * locally. There is no defined method on how tips should be stored locally,
+ * implementers are free to do what is needed.
+ *
+ * The constructor must return fast, meaning that tips may not be fetched from
+ * the Internet in the constructor. This should be done in the
+ * {@link #loadNewTips(IProgressMonitor)} method.
+ *
+ * To indicate that this provider is ready to serve tips, it should call the
+ * {@link #setTips(List)} method which then sets its <code>ready</code> flag.
+ *
+ */
+public abstract class TipProvider {
+
+ private ITipManager fTipManager;
+ private int fTipIndex;
+ protected List<Tip> fTips = new ArrayList<>();
+ private Tip fCurrentTip;
+ private boolean fReady;
+ private TipProviderListenerManager fListenerManager = new TipProviderListenerManager();
+ private Tip fFinalTip = new FinalTip(getID());
+ private String fExpression;
+
+ /**
+ * The zero argument constructor must be able to instantiate the TipProvider.
+ * This method may also be used to quickly set the available tips by calling the
+ * {@link #setTips(List)} method. The constructor may not be used to load tips
+ * from the internet. Use the {@link #loadNewTips(IProgressMonitor)} method for
+ * this purpose.
+ *
+ * @see #loadNewTips(IProgressMonitor)
+ * @see #setTips(List)
+ */
+ public TipProvider() {
+ }
+
+ /**
+ * Provides the opportunity to release all held resources.
+ */
+ public abstract void dispose();
+
+ /**
+ * @return the short description of this provider.
+ */
+ public abstract String getDescription();
+
+ /**
+ * @return the ID of this provider
+ */
+ public abstract String getID();
+
+ /**
+ * The image used by the UI for low resolution
+ *
+ * @return a 48x48 {@link TipImage}
+ */
+ public abstract TipImage getImage();
+
+ /**
+ * Get a list of tips. The default implementation returns tips based on the
+ * following conditions: <br>
+ * <dl>
+ * <dt><code>pFilter</code> is false</dt>
+ * <dd>Return all read and unread tips.</dd>
+ * <dt><code>pFilter</code> is true</dt>
+ * <dd>Return read and unread tips if the tipManager may serve unread tips,
+ * otherwise return only unread tips.</dd>
+ * </dl>
+ * <p>
+ * Subclasses may override (calling super(false) to fetch the list) if they want
+ * to serve or sort the list of tips in a different way.
+ *
+ * @param filter
+ * false or true, see description above.
+ * @return an unmodifiable list of tips.
+ */
+ public synchronized List<Tip> getTips(boolean filter) {
+ if (filter) {
+ return Collections.unmodifiableList(fTips //
+ .stream() //
+ .filter(tip -> getManager().mustServeReadTips() || !getManager().isRead(tip)) //
+ .sorted(Comparator.comparing(Tip::getCreationDate).reversed()) //
+ .collect(Collectors.toList()));
+ }
+ return Collections.unmodifiableList(fTips);
+ }
+
+ /**
+ * @return the {@link Tip} that was last returned by {@link #getNextTip()} or
+ * {@link #getPreviousTip()}
+ */
+ public synchronized Tip getCurrentTip() {
+ if (fCurrentTip == null) {
+ return getNextTip();
+ }
+ return fCurrentTip;
+ }
+
+ /**
+ * The next {@link Tip} is returned based on the read status of the Tip and the
+ * fact if already read tips must be served or not which is known by the
+ * {@link ITipManager}: ({@link ITipManager#mustServeReadTips()}).
+ *
+ * @return the next {@link Tip}
+ * @see #getPreviousTip()
+ * @see #getCurrentTip()
+ */
+ public synchronized Tip getNextTip() {
+ boolean unreadOnly = !getManager().mustServeReadTips();
+ List<Tip> list = getTips(unreadOnly);
+ if (list.isEmpty()) {
+ return setCurrentTip(fFinalTip);
+ }
+ if (!unreadOnly && fCurrentTip != null) {
+ fTipIndex++;
+ } else if (fCurrentTip != null && getManager().isRead(fCurrentTip)) {
+ fTipIndex++;
+ }
+ if (fTipIndex >= list.size()) {
+ fTipIndex = 0;
+ }
+ return setCurrentTip(list.get(fTipIndex));
+ }
+
+ /**
+ * @return the previous {@link Tip}
+ * @see #getNextTip()
+ * @see #getCurrentTip()
+ */
+ public Tip getPreviousTip() {
+ List<Tip> list = getTips(!getManager().mustServeReadTips());
+ if (list.isEmpty()) {
+ return setCurrentTip(fFinalTip);
+ }
+ fTipIndex--;
+ if (fTipIndex < 0) {
+ fTipIndex = list.size() - 1;
+ }
+ return setCurrentTip(list.get(fTipIndex));
+ }
+
+ /**
+ * @return the {@link ITipManager} of this provider, never null.
+ */
+ public synchronized ITipManager getManager() {
+ return fTipManager;
+ }
+
+ /**
+ * @return true if the provider is ready to deliver tips
+ */
+ public final boolean isReady() {
+ return fReady;
+ }
+
+ /**
+ * Is called asynchronously during startup of the TipManager to gather new tips.
+ *
+ * The provider is not available to the UI unless it has called it's
+ * {@link #setTips(List)} method. It is therefore possible that the provider is
+ * not immediately visible in the tip UI but will be added later.
+ * <p>
+ * If you run out of tips and you feel that you should load more tips on your
+ * own then you can also asynchronously call this method. A good place would be
+ * to override {@link #getTips(boolean)}, check if the supply of tips is
+ * sufficient and then call this method asynchronously.
+ * <p>
+ * One strategy is to do a long running fetch in this method and then store the
+ * tips locally. On the next run of the TipManager, the fetched tips can be
+ * served from the constructor (i.e. by calling {@link #setTips(List)}), making
+ * them available immediately
+ *
+ * @param monitor
+ * The monitor to report back progress.
+ * @return the status in case you want to report problems.
+ * @see TipProvider#setTips(List)
+ * @see TipProvider#isReady()
+ */
+ public abstract IStatus loadNewTips(IProgressMonitor monitor);
+
+ private synchronized Tip setCurrentTip(Tip pTip) {
+ fCurrentTip = pTip;
+ return fCurrentTip;
+ }
+
+ /**
+ * Sets the TipManager. You should probably not call this method directly. This
+ * method is normally called after the provider is instantiated by the
+ * {@link ITipManager}. If you create the provider yourself you should register
+ * the provider with {@link ITipManager#register(TipProvider)} which in turn
+ * will call this method. Subclasses may override but must not forget to call
+ * super in order to save the {@link ITipManager}.
+ *
+ * @param tipManager
+ * the {@link ITipManager}
+ * @return this
+ */
+ public synchronized TipProvider setManager(ITipManager tipManager) {
+ fTipManager = tipManager;
+ return this;
+ }
+
+ /**
+ * Sets the tips for this provider, replacing the current set of tips, and sets
+ * the <code>ready</code> flag to true. This method is typically called from the
+ * constructor of the {@link TipProvider} but may also be called from the
+ * asynchronous {@link #loadNewTips(IProgressMonitor)} method.
+ *
+ * @param tips
+ * a list of {@link Tip} objects
+ * @return this
+ * @see #addTips(List)
+ * @see #isReady()
+ * @see #loadNewTips(IProgressMonitor)
+ */
+ public TipProvider setTips(List<Tip> tips) {
+ if(getManager().isDisposed()) {
+ return this;
+ }
+ doSetTips(tips, true);
+ fReady = true;
+ fListenerManager.notifyListeners(TipProviderListener.EVENT_READY, this);
+ return this;
+ }
+
+ /**
+ * Adds the passed tips to the set of tips this provider already has sets the
+ * <code>ready</code> flag to true. This method is typically called from the
+ * constructor of the {@link TipProvider} but may also be called from the
+ * asynchronous {@link #loadNewTips(IProgressMonitor)} method.
+ *
+ * @param tips
+ * a list of {@link Tip} objects
+ * @return this
+ * @see #setTips(List)
+ * @see #isReady()
+ * @see #loadNewTips(IProgressMonitor)
+ */
+ public TipProvider addTips(List<Tip> tips) {
+ doSetTips(tips, false);
+ fReady = true;
+ fListenerManager.notifyListeners(TipProviderListener.EVENT_READY, this);
+ return this;
+ }
+
+ private synchronized void doSetTips(List<Tip> tips, boolean replace) {
+ if (replace) {
+ fTips.clear();
+ }
+ fTips.addAll(tips);
+ }
+
+ /**
+ * Gets the listener manager so that interested parties can subscribe to the
+ * events of this provider.
+ *
+ * @return the {@link TipProviderListenerManager}
+ */
+ public TipProviderListenerManager getListenerManager() {
+ return fListenerManager;
+ }
+
+ /**
+ * Returns an expression that is used by the {@link ITipManager} to determine
+ * the priority of this provider. The expression can be used to advice the
+ * TipManager when the tips of this provider deserve priority. The Eclipse IDE
+ * TipManager uses the core expression from the o.e.core.runtime bundle.
+ * Example: The expression
+ *
+ * <pre>
+ * &lt;with
+ * variable="activeWorkbenchWindow.activePerspective"&gt;
+ * &lt;equals value="org.eclipse.jdt.ui.JavaPerspective"&gt;&lt;/equals&gt;
+ * &lt;/with&gt;
+ * </pre>
+ *
+ * will give the provider priority when the java perspective is active in the
+ * IDE
+ *
+ * @return the expression which can be empty or null.
+ */
+ public String getExpression() {
+ return fExpression;
+ }
+
+ /**
+ * Sets the expression to determine the priority of the provider.
+ *
+ * @param expression
+ * the expression, may be null.
+ *
+ * @see #getExpression()
+ */
+ public void setExpression(String expression) {
+ fExpression = expression;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListener.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListener.java
new file mode 100644
index 000000000..f9d4b59d3
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListener.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core;
+
+/**
+ * Provides notifications on TipProvider events.
+ *
+ */
+@FunctionalInterface
+public interface TipProviderListener {
+
+ /**
+ * Event ready. The TipProvider is ready to serve tips.
+ */
+ public static final int EVENT_READY = 1;
+
+ /**
+ * When the subject is ready to serve tips (e.g. fetched new tips from
+ * somewhere) it will The provider is ready to serve tips. The default
+ * implementation does nothing, subclasses may override.
+ *
+ * @param provider
+ * the provider.
+ */
+ public void providerReady(TipProvider provider);
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListenerManager.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListenerManager.java
new file mode 100644
index 000000000..985cb0ecd
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProviderListenerManager.java
@@ -0,0 +1,67 @@
+/****************************************************************************
+ * Copyright (c) 2017, 2018 Remain Software
+ * 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:
+ * Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
+ *****************************************************************************/
+package org.eclipse.tips.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to manage provider listeners.
+ */
+public class TipProviderListenerManager {
+
+ protected List<TipProviderListener> fListeners = new ArrayList<>();
+
+ /**
+ * Notifies all listeners that an event occurred.
+ *
+ * @param event
+ * the event
+ * @param provider
+ * the provider
+ */
+ public void notifyListeners(int event, TipProvider provider) {
+ synchronized (fListeners) {
+ for (TipProviderListener tipProviderListener : fListeners) {
+ switch (event) {
+ case TipProviderListener.EVENT_READY:
+ tipProviderListener.providerReady(provider);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a listener to the list of listeners.
+ *
+ * @param pProviderListener
+ * the listener to be notified
+ * @return this
+ */
+ public TipProviderListenerManager addProviderListener(TipProviderListener pProviderListener) {
+ fListeners.remove(pProviderListener);
+ fListeners.add(pProviderListener);
+ return this;
+ }
+
+ /**
+ * Removes a listener from the list of listeners.
+ *
+ * @param pProviderListener
+ * the listener to remove
+ * @return this
+ */
+ public TipProviderListenerManager removeProviderListener(TipProviderListener pProviderListener) {
+ fListeners.remove(pProviderListener);
+ return this;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/FinalTip.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/FinalTip.java
new file mode 100644
index 000000000..fbc8b8c26
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/FinalTip.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core.internal;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.eclipse.tips.core.IHtmlTip;
+import org.eclipse.tips.core.Tip;
+import org.eclipse.tips.core.TipImage;
+import org.eclipse.tips.core.TipProvider;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Special generic tip that tells the user that there are no more tips.
+ *
+ */
+public class FinalTip extends Tip implements IHtmlTip {
+
+ /**
+ * Constructor.
+ */
+ public FinalTip(String providerId) {
+ super(providerId);
+ }
+
+ @Override
+ public Date getCreationDate() {
+ Calendar instance = Calendar.getInstance();
+ return instance.getTime();
+ }
+
+ @Override
+ public String getSubject() {
+ return "No more tips";
+ }
+
+ @Override
+ public String getHTML() {
+ return "<h1>There are no more tips</h1>" //
+ + "This provider has no more new tips. You can toggle the " //
+ + "<b>Unread</b> checkbox below or select another provider.";
+ }
+
+ @Override
+ public TipImage getImage() {
+ Bundle bundle = FrameworkUtil.getBundle(getClass());
+ try {
+ return new TipImage(bundle.getEntry("images/nomoretips.png")).setAspectRatio(417, 640, false);
+ } catch (IOException e) {
+// getManager().log(LogUtil.error(getClass(), e));
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/ImageUtil.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/ImageUtil.java
new file mode 100644
index 000000000..0a338de6c
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/ImageUtil.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core.internal;
+
+/**
+ * Image helper class.
+ *
+ */
+public class ImageUtil {
+
+ /**
+ * Calculate a height that will fit in the proposed rectangle given the passed
+ * aspect ratio.
+ *
+ * @param aspectRatio
+ * the aspect ration, e.g. 1.5 for a 3/2 ratio (width/height).
+ * @param widthHint
+ * the available width in the viewport
+ * @param heightHint
+ * the available height in the viewport
+ * @return the maximum height that will fit in the passed rectangle.
+ */
+ public static int getHeight(double aspectRatio, int widthHint, int heightHint) {
+ return (int) Math.min(heightHint, widthHint / aspectRatio);
+ }
+
+ /**
+ * Calculate a width that will fit in the proposed rectangle given the passed
+ * aspect ratio.
+ *
+ * @param aspectRatio
+ * the aspect ration, e.g. 1.5 for a 3/2 ratio (width/height).
+ * @param widthHint
+ * the available width in the viewport
+ * @param heightHint
+ * the available height in the viewport
+ * @return the maximum width that will fit in the rectangle.
+ */
+ public static int getWidth(double aspectRatio, int widthHint, int heightHint) {
+ return (int) Math.min(widthHint, heightHint * aspectRatio);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/LogUtil.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/LogUtil.java
new file mode 100644
index 000000000..976f4e03e
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/LogUtil.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.core.internal;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.FrameworkUtil;
+
+public class LogUtil {
+
+ public static IStatus getStatus(int severity, String bundleId, String message, Throwable throwable) {
+ return new Status(severity, bundleId, message, throwable);
+ }
+
+ public static IStatus error(Throwable throwable) {
+ return getStatus(IStatus.ERROR, "org.eclipse.tips.core", throwable.getMessage(), throwable);
+ }
+
+ public static IStatus warn(Throwable throwable) {
+ return getStatus(IStatus.WARNING, "org.eclipse.tips.core", throwable.getMessage(), throwable);
+ }
+
+ public static IStatus info(Throwable throwable) {
+ return getStatus(IStatus.INFO, "org.eclipse.tips.core", throwable.getMessage(), throwable);
+ }
+
+ public static IStatus error(Class<?> clazz, Throwable throwable) {
+ return getStatus(IStatus.ERROR, getBundleId(clazz), throwable.getMessage(), throwable);
+ }
+
+ public static IStatus warn(Class<?> clazz, Throwable throwable) {
+ return getStatus(IStatus.WARNING, getBundleId(clazz), throwable.getMessage(), throwable);
+ }
+
+ public static IStatus info(Class<?> clazz, Throwable throwable) {
+ return getStatus(IStatus.INFO, getBundleId(clazz), throwable.getMessage(), throwable);
+ }
+
+ public static IStatus info(Class<?> clazz, String message) {
+ return getStatus(IStatus.INFO, getBundleId(clazz), message, null);
+ }
+
+ public static IStatus warn(Class<?> clazz, String pessage) {
+ return getStatus(IStatus.WARNING, getBundleId(clazz), pessage, null);
+ }
+
+ public static IStatus error(Class<?> clazz, String message) {
+ return getStatus(IStatus.ERROR, getBundleId(clazz), message, null);
+ }
+
+ private static String getBundleId(Class<?> clazz) {
+ if (FrameworkUtil.getBundle(clazz) != null) {
+ return FrameworkUtil.getBundle(clazz).getSymbolicName();
+ }
+ return "osgi.not.running";
+ }
+
+ public static IStatus info(String message) {
+ return getStatus(IStatus.INFO, "org.eclipse.tips.core", message, null);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/package-info.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/package-info.java
new file mode 100644
index 000000000..c790f96d4
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/internal/package-info.java
@@ -0,0 +1,14 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+/**
+ * Tip Framework API internal classes.
+ */
+package org.eclipse.tips.core.internal; \ No newline at end of file
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/package-info.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/package-info.java
new file mode 100644
index 000000000..f63aef9af
--- /dev/null
+++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/package-info.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Remain Software
+ * 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:
+ * wim.jongman@remainsoftware.com - initial API and implementation
+ *******************************************************************************/
+/**
+ * Tip Framework core API. A UI agnostic API for the Tip of the Day. Known
+ * implementation is in package org.eclipse.tips.ui. If you run in the Eclipse
+ * IDE you may use the tips extension point to register a new
+ * {@link org.eclipse.tips.core.TipProvider}.If you want to run the Tip
+ * framework outside of the IDE (e.g. in an RCP application) then you should
+ * also implement your own {@link org.eclipse.tips.core.TipManager} and start
+ * that with some action or at startup of your application.
+ */
+package org.eclipse.tips.core; \ No newline at end of file

Back to the top