[gnome-applets] invest-applet: rewrite in c



commit 025f69cd4cd0bfcab2d0aabb31b79b7135faa72b
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Wed Sep 21 13:27:04 2016 +0300

    invest-applet: rewrite in c

 Makefile.am                                        |    1 -
 configure.ac                                       |   16 +-
 gnome-applets.doap                                 |    1 -
 invest-applet/data/Makefile.am                     |   36 +-
 invest-applet/data/financialchart.ui               |  120 +-
 ...g.gnome.applets.InvestApplet.panel-applet.in.in |    3 +-
 ...rg.gnome.gnome-applets.invest.gschema.xml.in.in |   20 +
 ...ome.panel.applet.InvestAppletFactory.service.in |    3 -
 invest-applet/data/prefs-dialog.ui                 |   37 +-
 invest-applet/invest/Makefile.am                   |  103 +-
 invest-applet/invest/__init__.py                   |  226 ---
 invest-applet/invest/about.py                      |   35 -
 invest-applet/invest/applet.py                     |  251 ----
 invest-applet/invest/chart.py                      |  258 ----
 invest-applet/invest/currencies.py                 |  145 --
 invest-applet/invest/defs.py.in                    |   10 -
 invest-applet/invest/help.py                       |    8 -
 invest-applet/invest/invest-applet.c               |  483 +++++++
 invest-applet/invest/invest-applet.h               |   46 +
 invest-applet/invest/invest-applet.py              |   82 --
 invest-applet/invest/invest-cache.c                |  290 ++++
 invest-applet/invest/invest-cache.h                |   66 +
 invest-applet/invest/invest-chart                  |   15 -
 invest-applet/invest/invest-chart.c                |  473 +++++++
 invest-applet/invest/invest-chart.h                |   39 +
 invest-applet/invest/invest-currencies.c           |  190 +++
 invest-applet/invest/invest-currencies.h           |   44 +
 invest-applet/invest/invest-image-retriever.c      |  248 ++++
 invest-applet/invest/invest-image-retriever.h      |   44 +
 invest-applet/invest/invest-preferences.c          | 1137 ++++++++++++++++
 invest-applet/invest/invest-preferences.h          |   43 +
 invest-applet/invest/invest-quotes-retriever.c     |  372 +++++
 invest-applet/invest/invest-quotes-retriever.h     |   45 +
 invest-applet/invest/invest-quotes.c               | 1427 ++++++++++++++++++++
 invest-applet/invest/invest-quotes.h               |   48 +
 invest-applet/invest/invest-widget.c               |  638 +++++++++
 invest-applet/invest/invest-widget.h               |   40 +
 invest-applet/invest/invest-window.c               |  166 +++
 invest-applet/invest/invest-window.h               |   42 +
 invest-applet/invest/networkmanager.py             |   24 -
 invest-applet/invest/preferences.py                |  391 ------
 invest-applet/invest/quotes.py                     |  627 ---------
 invest-applet/invest/test.py                       |   30 -
 invest-applet/invest/widgets.py                    |  173 ---
 po/POTFILES.in                                     |   13 +-
 po/POTFILES.skip                                   |    1 +
 46 files changed, 6047 insertions(+), 2463 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 536daa0..e1e6a29 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -108,7 +108,6 @@ MAINTAINERCLEANFILES = \
        $(GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL) \
        `find "$(srcdir)/m4" -type f -name "*.m4" -print` \
        $(srcdir)/INSTALL \
-       $(srcdir)/build-aux/py-compile \
        $(srcdir)/config.h.in~ \
        $(srcdir)/configure \
        $(NULL)
diff --git a/configure.ac b/configure.ac
index 0643ceb..c04453a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -59,7 +59,7 @@ LT_LIB_M
 dnl ***************************************************************************
 dnl *** Minimum library versions for GNOME-APPLETS                          ***
 dnl ***************************************************************************
-GTK_REQUIRED=3.15.2
+GTK_REQUIRED=3.20.0
 GLIB_REQUIRED=2.44.0
 GIO_REQUIRED=2.26.0
 LIBPANEL_REQUIRED=3.18.0
@@ -88,7 +88,6 @@ AC_PROG_CC
 AC_ISC_POSIX
 AC_STDC_HEADERS
 AC_PATH_XTRA
-AM_PATH_PYTHON(3)
 
 X_LIBS="$X_LIBS $X_PRE_LIBS -lX11 $X_EXTRA_LIBS"
 AC_SUBST(X_LIBS)
@@ -598,19 +597,6 @@ if test "$gtk_ok" = "yes"; then
             [Define if _NL_MEASUREMENT_MEASUREMENT is available])
 fi
 
-dnl ***************************************************************************
-dnl *** Set install directories                                             ***
-dnl ***************************************************************************
-AC_ARG_WITH([pythondir],
-       AS_HELP_STRING([--with-pythondir=DIR], [installation path for private Python modules @<:@auto@:>@]),
-       [ac_with_pythondir=$withval], [ac_with_pythondir=""])
-if test "$ac_with_pythondir" != ""; then
-       pythondir=${ac_with_pythondir}
-fi
-
-AC_MSG_NOTICE([installing private Python modules in $pythondir])
-AC_SUBST(pythondir)
-
 dnl **************************************************************************
 dnl Process .in files
 dnl **************************************************************************
diff --git a/gnome-applets.doap b/gnome-applets.doap
index 4c7978c..2b102ff 100644
--- a/gnome-applets.doap
+++ b/gnome-applets.doap
@@ -12,7 +12,6 @@
   <bug-database rdf:resource="https://bugzilla.gnome.org/browse.cgi?product=gnome-applets"; />
 
   <programming-language>C</programming-language>
-  <programming-language>Python</programming-language>
 
   <maintainer>
     <foaf:Person>
diff --git a/invest-applet/data/Makefile.am b/invest-applet/data/Makefile.am
index af13377..24b82e8 100644
--- a/invest-applet/data/Makefile.am
+++ b/invest-applet/data/Makefile.am
@@ -3,31 +3,25 @@ SUBDIRS = art
 # ******************************************************************************
 # Panel Applet stuff
 # ******************************************************************************
-servicedir       = $(datadir)/dbus-1/services
-service_in_files = org.gnome.panel.applet.InvestAppletFactory.service.in
-service_DATA     = $(service_in_files:.service.in=.service)
-
-org.gnome.panel.applet.InvestAppletFactory.service: $(service_in_files)
-       $(AM_V_GEN)sed \
-            -e "s|\@LIBEXECDIR\@|$(libexecdir)|" \
-            $< > $@
 
 appletdir       = $(LIBPANEL_APPLET_DIR)
 applet_in_files = org.gnome.applets.InvestApplet.panel-applet.in
 applet_DATA     = $(applet_in_files:.panel-applet.in=.panel-applet)
 
+LOCATION=$(pkglibdir)/$(LIBPANEL_APPLET_API_VERSION)/libinvest-applet.so
+
 $(applet_in_files): $(applet_in_files).in Makefile
        $(AM_V_GEN)sed \
-            -e "s|\@LIBEXECDIR\@|$(libexecdir)|" \
+            -e "s|\@LOCATION\@|$(LOCATION)|" \
             -e "s|\@VERSION\@|$(PACKAGE_VERSION)|" \
             $< > $@
 
 %.panel-applet: %.panel-applet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) 
$(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
 
-
 # ******************************************************************************
 # Misc data
 # ******************************************************************************
+
 uidir = $(pkgdatadir)/ui
 ui_DATA = \
        invest-applet-menu.xml
@@ -42,20 +36,30 @@ investbindir = $(libdir)/invest-applet
 # ******************************************************************************
 # Build rules
 # ******************************************************************************
-@INTLTOOL_SERVER_RULE@
+
 @INTLTOOL_SCHEMAS_RULE@
 
+gsettings_schemas_in_in = \
+       org.gnome.gnome-applets.invest.gschema.xml.in.in
+
+@INTLTOOL_XML_NOMERGE_RULE@
+
+gsettings_schemas_in = $(gsettings_schemas_in_in:.xml.in.in=.xml.in)
+gsettings_SCHEMAS = $(gsettings_schemas_in:.xml.in=.xml)
 
+%.gschema.xml.in: %.gschema.xml.in.in Makefile
+       $(AM_V_GEN) $(SED) -e 's^\@GETTEXT_PACKAGE\@^$(GETTEXT_PACKAGE)^g' < $< > $@
 
-CLEANFILES = $(applet_DATA) $(applet_DATA).in $(service_DATA)
+@GSETTINGS_RULES@
 
-DISTCLEANFILES = \
-       $(server_in_files) \
-       $(server_in_files:.server.in=.server)
+CLEANFILES = $(applet_DATA) $(applet_DATA).in \
+       $(gsettings_SCHEMAS_in) \
+       $(gsettings_SCHEMAS) \
+       *.gschema.valid
 
 EXTRA_DIST = \
        org.gnome.applets.InvestApplet.panel-applet.in.in \
-       $(service_in_files) \
+       $(gsettings_schemas_in_in) \
        $(ui_DATA) \
        $(builder_DATA)
 
diff --git a/invest-applet/data/financialchart.ui b/invest-applet/data/financialchart.ui
index da326a8..6f1c071 100644
--- a/invest-applet/data/financialchart.ui
+++ b/invest-applet/data/financialchart.ui
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.18.3 -->
+<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.12"/>
-  <object class="GtkWindow" id="window">
+  <requires lib="gtk+" version="3.20"/>
+  <template class="InvestChart" parent="GtkWindow">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="border_width">6</property>
@@ -41,6 +41,8 @@
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="has_focus">True</property>
+                <signal name="activate" handler="s_activate_cb" object="InvestChart" swapped="no"/>
+                <signal name="changed" handler="s_changed_cb" object="InvestChart" swapped="no"/>
               </object>
               <packing>
                 <property name="expand">True</property>
@@ -50,13 +52,12 @@
             </child>
             <child>
               <object class="GtkCheckButton" id="autorefresh">
-                <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
-                <property name="xalign">0.5</property>
                 <property name="active">True</property>
                 <property name="draw_indicator">True</property>
+                <signal name="toggled" handler="autorefresh_toggled_cb" object="InvestChart" swapped="no"/>
                 <child>
                   <object class="GtkAlignment" id="alignment1">
                     <property name="visible">True</property>
@@ -108,15 +109,17 @@
               <object class="GtkComboBoxText" id="t">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
+                <property name="active">0</property>
                 <items>
-                  <item translatable="yes">Today</item>
-                  <item translatable="yes">5 Days</item>
-                  <item translatable="yes">3 Months</item>
-                  <item translatable="yes">6 Months</item>
-                  <item translatable="yes">1 Year</item>
-                  <item translatable="yes">5 Years</item>
-                  <item translatable="yes">Maximum</item>
+                  <item id="1d" translatable="yes">Today</item>
+                  <item id="5d" translatable="yes">5 Days</item>
+                  <item id="3m" translatable="yes">3 Months</item>
+                  <item id="6m" translatable="yes">6 Months</item>
+                  <item id="1y" translatable="yes">1 Year</item>
+                  <item id="5y" translatable="yes">5 Years</item>
+                  <item id="my" translatable="yes">Maximum</item>
                 </items>
+                <signal name="changed" handler="changed_cb" object="InvestChart" swapped="no"/>
               </object>
               <packing>
                 <property name="expand">False</property>
@@ -196,7 +199,6 @@
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="halign">end</property>
-                    <property name="xalign">1</property>
                     <property name="label" translatable="yes" comments="see 
http://biz.yahoo.com/charts/guide10.html and 
http://en.wikipedia.org/wiki/Technical_indicator#Charting_terms_and_indicators";>Indicators: </property>
                   </object>
                   <packing>
@@ -212,11 +214,13 @@
                       <object class="GtkComboBoxText" id="q">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="active">0</property>
                         <items>
-                          <item translatable="yes">Line</item>
-                          <item translatable="yes">Bar</item>
-                          <item translatable="yes">Candle</item>
+                          <item id="l" translatable="yes">Line</item>
+                          <item id="b" translatable="yes">Bar</item>
+                          <item id="c" translatable="yes">Candle</item>
                         </items>
+                        <signal name="changed" handler="changed_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -247,10 +251,12 @@
                       <object class="GtkComboBoxText" id="l">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="active">0</property>
                         <items>
-                          <item translatable="yes">Linear</item>
-                          <item translatable="yes">Logarithmic</item>
+                          <item id="off" translatable="yes">Linear</item>
+                          <item id="on" translatable="yes">Logarithmic</item>
                         </items>
+                        <signal name="changed" handler="changed_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -283,13 +289,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pm5">
                         <property name="label" translatable="yes">5</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -300,13 +305,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pm10">
                         <property name="label" translatable="yes">10</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -317,14 +321,13 @@
                     <child>
                       <object class="GtkCheckButton" id="pm20">
                         <property name="label" translatable="yes">20</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -335,13 +338,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pm50">
                         <property name="label" translatable="yes">50</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -352,13 +354,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pm100">
                         <property name="label" translatable="yes">100</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -369,13 +370,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pm200">
                         <property name="label" translatable="yes">200</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -397,14 +397,13 @@
                     <child>
                       <object class="GtkCheckButton" id="pe5">
                         <property name="label" translatable="yes">5</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -415,13 +414,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pe10">
                         <property name="label" translatable="yes">10</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -432,14 +430,13 @@
                     <child>
                       <object class="GtkCheckButton" id="pe20">
                         <property name="label" translatable="yes">20</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -450,13 +447,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pe50">
                         <property name="label" translatable="yes">50</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -467,13 +463,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pe100">
                         <property name="label" translatable="yes">100</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -484,13 +479,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pe200">
                         <property name="label" translatable="yes">200</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -512,14 +506,13 @@
                     <child>
                       <object class="GtkCheckButton" id="pb">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://biz.yahoo.com/charts/guide13.html and 
http://en.wikipedia.org/wiki/Bollinger_bands";>Bollinger</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -530,13 +523,12 @@
                     <child>
                       <object class="GtkCheckButton" id="pp">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://biz.yahoo.com/charts/guide16.html and 
http://en.wikipedia.org/wiki/Parabolic_SAR";>SAR</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -547,13 +539,12 @@
                     <child>
                       <object class="GtkCheckButton" id="ps">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://biz.yahoo.com/charts/guide6.html and 
http://en.wikipedia.org/wiki/Stock_split";>Splits</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -564,14 +555,13 @@
                     <child>
                       <object class="GtkCheckButton" id="pv">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://biz.yahoo.com/charts/guide20.html and 
http://en.wikipedia.org/wiki/Volume_%28finance%29";>Volumes</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -594,14 +584,13 @@
                     <child>
                       <object class="GtkCheckButton" id="am">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/MACD";>MACD</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">0</property>
@@ -611,14 +600,13 @@
                     <child>
                       <object class="GtkCheckButton" id="af">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Money_flow_index";>MFI</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
@@ -628,13 +616,12 @@
                     <child>
                       <object class="GtkCheckButton" id="ap">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Rate_of_change_%28technical_analysis%29";>ROC</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">2</property>
@@ -644,13 +631,12 @@
                     <child>
                       <object class="GtkCheckButton" id="ar">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Relative_Strength_Index";>RSI</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">0</property>
@@ -660,13 +646,12 @@
                     <child>
                       <object class="GtkCheckButton" id="av">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Volume_%28finance%29";>Vol</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">0</property>
@@ -676,14 +661,13 @@
                     <child>
                       <object class="GtkCheckButton" id="ass">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Stochastic_oscillator";>Slow stoch</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
@@ -693,13 +677,12 @@
                     <child>
                       <object class="GtkCheckButton" id="avm">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Volume_%28finance%29";>Vol+MA</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
@@ -709,13 +692,12 @@
                     <child>
                       <object class="GtkCheckButton" id="afs">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Stochastic_oscillator";>Fast stoch</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">2</property>
@@ -725,14 +707,13 @@
                     <child>
                       <object class="GtkCheckButton" id="aw">
                         <property name="label" translatable="yes" comments="Please keep this term short. For 
its meaning, see http://en.wikipedia.org/wiki/Williams_%25R";>W%R</property>
-                        <property name="use_action_appearance">False</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
                         <property name="use_underline">True</property>
-                        <property name="xalign">0.5</property>
                         <property name="active">True</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="toggled_cb" object="InvestChart" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">2</property>
@@ -792,7 +773,6 @@
         </child>
         <child>
           <object class="GtkLabel" id="progress">
-            <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="label" translatable="yes">Downloading chart from 
&lt;b&gt;Yahoo!&lt;/b&gt;</property>
             <property name="use_markup">True</property>
@@ -806,5 +786,5 @@
         </child>
       </object>
     </child>
-  </object>
+  </template>
 </interface>
diff --git a/invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in.in 
b/invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in.in
index afedcdb..7116c40 100644
--- a/invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in.in
+++ b/invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in.in
@@ -1,6 +1,7 @@
 [Applet Factory]
 Id=InvestAppletFactory
-Location=@LIBEXECDIR@/invest-applet
+InProcess=true
+Location=@LOCATION@
 _Name=Invest Applet Factory
 _Description=Factory for creating the invest applet.
 
diff --git a/invest-applet/data/org.gnome.gnome-applets.invest.gschema.xml.in.in 
b/invest-applet/data/org.gnome.gnome-applets.invest.gschema.xml.in.in
new file mode 100644
index 0000000..a112cd4
--- /dev/null
+++ b/invest-applet/data/org.gnome.gnome-applets.invest.gschema.xml.in.in
@@ -0,0 +1,20 @@
+<schemalist gettext-domain="@GETTEXT_PACKAGE@">
+  <schema id="org.gnome.gnome-applets.invest">
+    <key name="currency" type="s">
+      <default>""</default>
+      <_summary>Currency</_summary>
+    </key>
+    <key name="hidecharts" type="b">
+      <default>false</default>
+      <_summary>Hide charts in quotes list</_summary>
+    </key>
+    <key name="indexexpansion" type="b">
+      <default>true</default>
+      <_summary>Show stocks of index values</_summary>
+    </key>
+    <key type="a{bv}" name="stocks">
+      <default>{}</default>
+      <summary>Stocks</summary>
+    </key>
+  </schema>
+</schemalist>
diff --git a/invest-applet/data/prefs-dialog.ui b/invest-applet/data/prefs-dialog.ui
index c7cc9d9..a137e89 100644
--- a/invest-applet/data/prefs-dialog.ui
+++ b/invest-applet/data/prefs-dialog.ui
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.8"/>
-  <object class="GtkDialog" id="preferences">
+  <requires lib="gtk+" version="3.20"/>
+  <template class="InvestPreferences" parent="GtkDialog">
     <property name="can_focus">False</property>
     <property name="border_width">5</property>
     <property name="title" translatable="yes">Invest Preferences</property>
-    <property name="modal">True</property>
     <property name="window_position">center</property>
     <property name="default_height">450</property>
     <property name="destroy_with_parent">True</property>
@@ -25,7 +25,6 @@
             <child>
               <object class="GtkButton" id="help">
                 <property name="label">gtk-help</property>
-                <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
@@ -40,7 +39,6 @@
             <child>
               <object class="GtkButton" id="close">
                 <property name="label">gtk-close</property>
-                <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="can_default">True</property>
@@ -92,17 +90,16 @@
                   <object class="GtkLabel" id="stocklabel">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
                     <property name="label" translatable="yes">Stocks</property>
                     <property name="use_underline">True</property>
-                    <property name="justify">center</property>
+                    <property name="xalign">0</property>
                     <attributes>
                       <attribute name="weight" value="bold"/>
                     </attributes>
                   </object>
                   <packing>
                     <property name="expand">False</property>
-                    <property name="fill">False</property>
+                    <property name="fill">True</property>
                     <property name="position">1</property>
                   </packing>
                 </child>
@@ -133,6 +130,7 @@
                                 <property name="reorderable">True</property>
                                 <property name="rules_hint">True</property>
                                 <property name="enable_tree_lines">True</property>
+                                <signal name="key-press-event" handler="stocks_key_press_event_cb" 
swapped="no"/>
                                 <child internal-child="selection">
                                   <object class="GtkTreeSelection" id="treeview-selection1"/>
                                 </child>
@@ -146,7 +144,7 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkHBox" id="hbox1">
+                          <object class="GtkBox" id="hbox1">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
                             <property name="spacing">6</property>
@@ -154,11 +152,11 @@
                             <child>
                               <object class="GtkButton" id="addstock">
                                 <property name="label">gtk-add</property>
-                                <property name="use_action_appearance">False</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                                 <property name="use_stock">True</property>
+                                <signal name="clicked" handler="addstock_clicked_cb" swapped="no"/>
                               </object>
                               <packing>
                                 <property name="expand">True</property>
@@ -169,10 +167,10 @@
                             <child>
                               <object class="GtkButton" id="addgroup">
                                 <property name="label" translatable="yes" context=" " comments="Instead of 
adding a single stock to the list of stocks, the 'Add Group' button adds a group (kind of a sub folder) to 
which numerous stocks can be added. A group here refers to a group of stocks.">Add Group</property>
-                                <property name="use_action_appearance">False</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">True</property>
+                                <signal name="clicked" handler="addgroup_clicked_cb" swapped="no"/>
                               </object>
                               <packing>
                                 <property name="expand">True</property>
@@ -183,11 +181,11 @@
                             <child>
                               <object class="GtkButton" id="remove">
                                 <property name="label">gtk-remove</property>
-                                <property name="use_action_appearance">False</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                                 <property name="use_stock">True</property>
+                                <signal name="clicked" handler="remove_clicked_cb" swapped="no"/>
                               </object>
                               <packing>
                                 <property name="expand">True</property>
@@ -205,13 +203,11 @@
                         <child>
                           <object class="GtkCheckButton" id="indexexpansion">
                             <property name="label" translatable="yes" comments="An index value (for instance 
the NASDAQ Composite) is based on a number of stocks. This option allows to also show the quotes of the 
stocks an index is based on. ">Show stocks of index values</property>
-                            <property name="use_action_appearance">False</property>
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">False</property>
                             <property name="has_tooltip">True</property>
                             <property name="tooltip_markup" translatable="yes">An index value, for instance 
the &lt;i&gt;NASDAQ Composite&lt;/i&gt; (^IXIC), is based on a number of stocks. This option allows to also 
show the quotes of the &lt;i&gt;&lt;b&gt;stocks&lt;/b&gt;&lt;/i&gt; an index is based on.</property>
-                            <property name="xalign">0</property>
                             <property name="active">True</property>
                             <property name="draw_indicator">True</property>
                           </object>
@@ -224,13 +220,11 @@
                         <child>
                           <object class="GtkCheckButton" id="hidecharts">
                             <property name="label" translatable="yes">Hide charts in quotes list</property>
-                            <property name="use_action_appearance">False</property>
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">False</property>
                             <property name="tooltip_text" translatable="yes">A small chart image is shown 
next to each quote. The retrieval of each chart image causes network traffic. Hiding charts reduces the 
network bandwidth demand significantly.</property>
                             <property name="relief">none</property>
-                            <property name="xalign">0</property>
                             <property name="draw_indicator">True</property>
                           </object>
                           <packing>
@@ -243,9 +237,9 @@
                           <object class="GtkLabel" id="default_info">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
                             <property name="label" 
translatable="yes">&lt;i&gt;&lt;small&gt;&lt;b&gt;Source:&lt;/b&gt; &lt;a 
href="http://finance.yahoo.com/"&gt;Yahoo! Finance&lt;/a&gt; (at least 15 minutes 
delayed)&lt;/small&gt;&lt;/i&gt;</property>
                             <property name="use_markup">True</property>
+                            <property name="xalign">0</property>
                           </object>
                           <packing>
                             <property name="expand">False</property>
@@ -266,17 +260,16 @@
                   <object class="GtkLabel" id="currencylabel">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
                     <property name="label" translatable="yes">Currency</property>
                     <property name="use_underline">True</property>
-                    <property name="justify">center</property>
+                    <property name="xalign">0</property>
                     <attributes>
                       <attribute name="weight" value="bold"/>
                     </attributes>
                   </object>
                   <packing>
                     <property name="expand">False</property>
-                    <property name="fill">False</property>
+                    <property name="fill">True</property>
                     <property name="position">3</property>
                   </packing>
                 </child>
@@ -290,6 +283,8 @@
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="tooltip_text" translatable="yes">Type the target currency to which 
all stock quotes will be converted to.</property>
+                        <signal name="activate" handler="currency_activate_cb" swapped="no"/>
+                        <signal name="focus-out-event" handler="currency_focus_out_event_cb" swapped="no"/>
                       </object>
                     </child>
                   </object>
@@ -320,5 +315,5 @@
       <action-widget response="1">help</action-widget>
       <action-widget response="-7">close</action-widget>
     </action-widgets>
-  </object>
+  </template>
 </interface>
diff --git a/invest-applet/invest/Makefile.am b/invest-applet/invest/Makefile.am
index 4b0d656..774d8e7 100644
--- a/invest-applet/invest/Makefile.am
+++ b/invest-applet/invest/Makefile.am
@@ -1,55 +1,52 @@
-invest-applet: invest-applet.py
-       sed -e "s|\@PYTHONDIR\@|$(pythondir)/|" $< > $@
-
-defs.py: defs.py.in
-       sed \
-               -e "s|\@DATADIR\@|$(datadir)|" \
-               -e "s|\@LIBDIR\@|$(libdir)|" \
-               -e "s|\@VERSION\@|$(VERSION)|" \
-               -e "s|\@PACKAGE\@|$(PACKAGE)|" \
-               -e "s|\@PKGDATADIR\@|$(pkgdatadir)|" \
-               -e "s|\@PYTHONDIR\@|$(pythondir)|" \
-               -e "s|\@GETTEXT_PACKAGE\@|$(GETTEXT_PACKAGE)|" \
-               -e "s|\@GNOMELOCALEDIR\@|$(localedir)|" \
-               -e "s|\@BUILDERDIR\@|$(pkgdatadir)/builder|" \
-               -e "s|\@LIBPANEL_APPLET_API_VERSION\@|$(LIBPANEL_APPLET_API_VERSION)|" \
-               $< > $@
-
-bin_SCRIPTS = invest-chart
-
-libexec_SCRIPTS = invest-applet
-
-investdir = $(pythondir)/invest
-invest_PYTHON = \
-       __init__.py \
-       about.py \
-       help.py \
-       applet.py \
-       chart.py \
-       currencies.py \
-       widgets.py \
-       quotes.py \
-       networkmanager.py \
-       preferences.py
-nodist_invest_PYTHON = \
-       defs.py
-
-BUILT_SOURCES = \
-       invest-applet
-
-CLEANFILES = \
-       $(BUILT_SOURCES)
-
-DISTCLEANFILES = \
-       defs.py \
-       $(CLEANFILES)
-
-EXTRA_DIST = \
-       defs.py.in \
-       invest-applet.py \
-       invest-chart \
-       test.py
-
-#TESTS = test.py
+NULL =
+
+invest_applet_libdir = $(pkglibdir)/$(LIBPANEL_APPLET_API_VERSION)
+invest_applet_lib_LTLIBRARIES = libinvest-applet.la
+
+libinvest_applet_la_SOURCES = \
+       invest-applet.c \
+       invest-applet.h \
+       invest-cache.c \
+       invest-cache.h \
+       invest-chart.c \
+       invest-chart.h \
+       invest-currencies.c \
+       invest-currencies.h \
+       invest-image-retriever.c \
+       invest-image-retriever.h \
+       invest-preferences.c \
+       invest-preferences.h \
+       invest-quotes.c \
+       invest-quotes.h \
+       invest-quotes-retriever.c \
+       invest-quotes-retriever.h \
+       invest-widget.c \
+       invest-widget.h \
+       invest-window.c \
+       invest-window.h \
+       $(NULL)
+
+libinvest_applet_la_CPPFLAGS = \
+       -DARTDIR=\""$(pkgdatadir)/invest-applet"\" \
+       -DBUILDERDIR=\""$(pkgdatadir)/builder"\" \
+       -DGNOMELOCALEDIR=\""$(localedir)"\" \
+       -DUIDIR=\""$(pkgdatadir)/ui"\" \
+       $(NULL)
+
+libinvest_applet_la_CFLAGS = \
+       $(GNOME_APPLETS_CFLAGS) \
+       $(WARN_CFLAGS) \
+       $(AM_CFLAGS) \
+       $(NULL)
+
+libinvest_applet_la_LIBADD = \
+       $(GNOME_APPLETS_LIBS) \
+       $(NULL)
+
+libinvest_applet_la_LDFLAGS = \
+       -module -avoid-version \
+       $(WARN_LDFLAGS) \
+       $(AM_LDFLAGS) \
+       $(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/invest-applet/invest/invest-applet.c b/invest-applet/invest/invest-applet.c
new file mode 100644
index 0000000..79d268e
--- /dev/null
+++ b/invest-applet/invest/invest-applet.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "invest-applet.h"
+#include "invest-preferences.h"
+#include "invest-quotes.h"
+#include "invest-window.h"
+
+struct _InvestApplet
+{
+  PanelApplet   parent;
+
+  GSettings    *settings;
+
+  GtkWidget    *icon;
+  GtkWidget    *preferences;
+
+  InvestQuotes *quotes;
+
+  GtkWidget    *window;
+};
+
+struct _InvestAppletClass
+{
+  PanelAppletClass parent_class;
+};
+
+G_DEFINE_TYPE (InvestApplet, invest_applet, PANEL_TYPE_APPLET)
+
+static void
+update_popup_position (InvestApplet *applet)
+{
+  GtkWindow *popup_window;
+  GdkDisplay *display;
+  GdkWindow *applet_window;
+  GdkMonitor *monitor;
+  GdkRectangle geometry;
+  GtkAllocation allocation;
+  gint x, y, w, h;
+  GdkGravity gravity;
+
+  g_assert (applet->window != NULL);
+
+  popup_window = GTK_WINDOW (applet->window);
+
+  display = gdk_display_get_default ();
+  applet_window = gtk_widget_get_window (GTK_WIDGET (applet));
+  monitor = gdk_display_get_monitor_at_window (display, applet_window);
+
+  gdk_monitor_get_geometry (monitor, &geometry);
+
+  gtk_widget_get_allocation (GTK_WIDGET (applet), &allocation);
+  gdk_window_get_origin (applet_window, &x, &y);
+  gtk_window_get_size (popup_window, &w, &h);
+
+  gravity = GDK_GRAVITY_NORTH_WEST;
+  switch (panel_applet_get_orient (PANEL_APPLET (applet)))
+    {
+      case PANEL_APPLET_ORIENT_RIGHT:
+        x += allocation.width;
+        if ((y + h) > geometry.y + geometry.height)
+          y -= (y + h) - (geometry.y + geometry.height);
+
+        if ((y + h) > (geometry.height / 2))
+          gravity = GDK_GRAVITY_SOUTH_WEST;
+        else
+          gravity = GDK_GRAVITY_NORTH_WEST;
+        break;
+
+      case PANEL_APPLET_ORIENT_LEFT:
+        x -= w;
+        if ((y + h) > geometry.y + geometry.height)
+          y -= (y + h) - (geometry.y + geometry.height);
+
+        if ((y + h) > (geometry.height / 2))
+          gravity = GDK_GRAVITY_SOUTH_EAST;
+        else
+          gravity = GDK_GRAVITY_NORTH_EAST;
+        break;
+
+      case PANEL_APPLET_ORIENT_DOWN:
+        y += allocation.height;
+        if ((x + w) > geometry.x + geometry.width)
+          x -= (x + w) - (geometry.x + geometry.width);
+
+        gravity = GDK_GRAVITY_NORTH_WEST;
+        break;
+
+      case PANEL_APPLET_ORIENT_UP:
+        y -= h;
+        if ((x + w) > geometry.x + geometry.width)
+          x -= (x + w) - (geometry.x + geometry.width);
+
+        gravity = GDK_GRAVITY_SOUTH_WEST;
+        break;
+
+      default:
+        break;
+    }
+
+  gtk_window_move (popup_window, x, y);
+  gtk_window_set_gravity (popup_window, gravity);
+}
+
+static void
+preferences_destroy_cb (GtkWidget    *widget,
+                        InvestApplet *applet)
+{
+  applet->preferences = NULL;
+}
+
+static void
+show_preferences (InvestApplet *applet,
+                  const gchar  *explanation)
+{
+  InvestPreferences *preferences;
+
+  if (applet->preferences != NULL)
+    {
+      preferences = INVEST_PREFERENCES (applet->preferences);
+
+      invest_preferences_set_explanation (preferences, explanation);
+      gtk_window_present (GTK_WINDOW (applet->preferences));
+
+      return;
+    }
+
+  applet->preferences = invest_preferences_new (applet->settings);
+  preferences = INVEST_PREFERENCES (applet->preferences);
+
+  g_signal_connect (preferences, "destroy",
+                    G_CALLBACK (preferences_destroy_cb), applet);
+
+  invest_preferences_set_explanation (preferences, explanation);
+  gtk_window_present (GTK_WINDOW (applet->preferences));
+}
+
+static void
+set_icon (InvestApplet *applet,
+          gint          change)
+{
+  GError *error;
+  GdkPixbuf *pixbuf;
+
+  error = NULL;
+
+  if (change == 1)
+    {
+      pixbuf = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest-22_up.png",
+                                                 -1, -1, &error);
+    }
+  else if (change == 0)
+    {
+      pixbuf = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest-22_neutral.png",
+                                                 -1, -1, &error);
+    }
+  else
+    {
+      pixbuf = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest-22_down.png",
+                                                 -1, -1, &error);
+    }
+
+  if (error)
+    {
+      g_warning ("%s", error->message);
+      g_error_free (error);
+    }
+
+  gtk_image_set_from_pixbuf (GTK_IMAGE (applet->icon), pixbuf);
+  g_clear_object (&pixbuf);
+}
+
+static void
+setup_applet (InvestApplet *applet)
+{
+  GtkWidget *hbox;
+
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_container_add (GTK_CONTAINER (applet), hbox);
+
+  applet->icon = gtk_image_new ();
+  gtk_container_add (GTK_CONTAINER (hbox), applet->icon);
+  gtk_widget_show (applet->icon);
+
+  set_icon (applet, 0);
+}
+
+static void
+refresh_cb (GSimpleAction *action,
+            GVariant      *parameter,
+            gpointer       user_data)
+{
+  InvestApplet *applet;
+
+  applet = INVEST_APPLET (user_data);
+
+  invest_quotes_refresh (applet->quotes);
+}
+
+static void
+preferences_cb (GSimpleAction *action,
+                GVariant      *parameter,
+                gpointer       user_data)
+{
+  InvestApplet *applet;
+
+  applet = INVEST_APPLET (user_data);
+
+  show_preferences (applet, NULL);
+}
+
+static void
+help_cb (GSimpleAction *action,
+         GVariant      *parameter,
+         gpointer       user_data)
+{
+  GdkScreen *screen;
+
+  screen = gdk_screen_get_default ();
+
+  gtk_show_uri (screen, "help:invest-applet", GDK_CURRENT_TIME, NULL);
+}
+
+static const gchar *authors[] = {
+  "Raphael Slinckx <raphael slinckx net>",
+  "Enrico Minack <enrico-minack gmx de>",
+  NULL
+};
+
+static void
+about_cb (GSimpleAction *action,
+          GVariant      *parameter,
+          gpointer       user_data)
+{
+  const gchar *copyright;
+  GdkPixbuf *logo;
+
+  copyright = "Copyright © 2004-2005 Raphael Slinckx.\nCopyright © 2009-2011 Enrico Minack.";
+  logo = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest_neutral.svg", 96, 96, NULL);
+
+  gtk_show_about_dialog (NULL,
+                         "authors", authors,
+                         "comments", _("Track your invested money."),
+                         "copyright", copyright,
+                         "logo", logo,
+                         "program-name", _("Invest"),
+                         "version", VERSION,
+                         "translator-credits", _("translator-credits"),
+                         NULL);
+
+  g_clear_object (&logo);
+}
+
+static const GActionEntry menu_actions[] = {
+  { "refresh",     refresh_cb },
+  { "preferences", preferences_cb },
+  { "help",        help_cb },
+  { "about",       about_cb }
+};
+
+static void
+setup_menu (PanelApplet *applet)
+{
+  GSimpleActionGroup *action_group;
+  const gchar *file;
+
+  action_group = g_simple_action_group_new ();
+  file = UIDIR "/invest-applet-menu.xml";
+
+  g_action_map_add_action_entries (G_ACTION_MAP (action_group), menu_actions,
+                                   G_N_ELEMENTS (menu_actions), applet);
+
+  panel_applet_setup_menu_from_file (applet, file, action_group,
+                                     GETTEXT_PACKAGE);
+
+  gtk_widget_insert_action_group (GTK_WIDGET (applet), "invest",
+                                  G_ACTION_GROUP (action_group));
+
+  g_object_unref (action_group);
+}
+
+static void
+update_icon_cb (InvestQuotes *quotes,
+                gint          icon,
+                InvestApplet *applet)
+{
+  set_icon (applet, icon);
+}
+
+static void
+update_tooltip_cb (InvestQuotes *quotes,
+                   const gchar  *text,
+                   InvestApplet *applet)
+{
+  gtk_widget_set_tooltip_text (GTK_WIDGET (applet), text);
+}
+
+static void
+invest_applet_setup (PanelApplet *papplet)
+{
+  InvestApplet *applet;
+  const gchar *schema;
+
+  applet = INVEST_APPLET (papplet);
+
+  schema = "org.gnome.gnome-applets.invest";
+  applet->settings = panel_applet_settings_new (papplet, schema);
+
+  setup_applet (applet);
+  setup_menu (papplet);
+
+  applet->quotes = invest_quotes_new (applet->settings);
+
+  g_signal_connect (applet->quotes, "update-icon",
+                    G_CALLBACK (update_icon_cb), applet);
+
+  g_signal_connect (applet->quotes, "update-tooltip",
+                    G_CALLBACK (update_tooltip_cb), applet);
+
+  gtk_widget_show_all (GTK_WIDGET (applet));
+}
+
+static void
+popup_size_allocate_cb (GtkWidget     *widget,
+                        GtkAllocation *allocation,
+                        InvestApplet  *applet)
+{
+  update_popup_position (applet);
+}
+
+static gboolean
+button_press_event_cb (GtkWidget      *widget,
+                       GdkEventButton *event,
+                       InvestApplet   *applet)
+{
+  if (event->button != 1 || event->type != GDK_BUTTON_PRESS)
+    return FALSE;
+
+  if (!invest_quotes_has_stocks (applet->quotes))
+    {
+      const gchar *explanation;
+
+      explanation = _("<b>You have not entered any stock information yet</b>");
+
+      g_clear_pointer (&applet->window, gtk_widget_destroy);
+      show_preferences (applet, explanation);
+    }
+  else if (!invest_quotes_is_valid (applet->quotes))
+    {
+      GtkWidget *dialog;
+      GtkMessageDialog *message;
+      const gchar *markup;
+      const gchar *text;
+
+      dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_INFO,
+                                       GTK_BUTTONS_CLOSE, NULL);
+
+      message = GTK_MESSAGE_DIALOG (dialog);
+      markup = _("<b>No stock quotes are currently available</b>");
+      text = _("The server could not be contacted. The computer is either "
+               "offline or the servers are down. Try again later.");
+
+      gtk_message_dialog_set_markup (message, markup);
+      gtk_message_dialog_format_secondary_text (message, "%s", text);
+
+      g_signal_connect_swapped (dialog, "response",
+                                G_CALLBACK (gtk_widget_destroy),
+                                dialog);
+
+      g_clear_pointer (&applet->window, gtk_widget_destroy);
+      gtk_window_present (GTK_WINDOW (dialog));
+    }
+  else
+    {
+      if (applet->window != NULL)
+        {
+          gtk_widget_destroy (applet->window);
+          applet->window = NULL;
+        }
+      else
+        {
+          applet->window = invest_window_new (applet->settings,
+                                              applet->quotes);
+
+          g_signal_connect (applet->window, "size-allocate",
+                            G_CALLBACK (popup_size_allocate_cb),
+                            applet);
+
+          gtk_window_present (GTK_WINDOW (applet->window));
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+change_orient_cb (PanelApplet       *papplet,
+                  PanelAppletOrient  orient,
+                  InvestApplet      *applet)
+{
+  if (applet->window != NULL)
+    update_popup_position (applet);
+}
+
+static void
+invest_applet_dispose (GObject *object)
+{
+  InvestApplet *applet;
+
+  applet = INVEST_APPLET (object);
+
+  g_clear_object (&applet->quotes);
+  g_clear_pointer (&applet->preferences, gtk_widget_destroy);
+  g_clear_pointer (&applet->window, gtk_widget_destroy);
+  g_clear_object (&applet->settings);
+
+  G_OBJECT_CLASS (invest_applet_parent_class)->dispose (object);
+}
+
+static void
+invest_applet_class_init (InvestAppletClass *applet_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (applet_class);
+
+  object_class->dispose = invest_applet_dispose;
+}
+
+static void
+invest_applet_init (InvestApplet *applet)
+{
+  panel_applet_set_flags (PANEL_APPLET (applet), PANEL_APPLET_EXPAND_MINOR);
+
+  g_signal_connect (applet, "button-press-event",
+                    G_CALLBACK (button_press_event_cb),
+                    applet);
+
+  g_signal_connect (applet, "change-orient",
+                    G_CALLBACK (change_orient_cb),
+                    applet);
+}
+
+static gboolean
+invest_applet_factory (PanelApplet *applet,
+                       const gchar *iid,
+                       gpointer     data)
+{
+  if (g_strcmp0 (iid, "InvestApplet") != 0)
+    return FALSE;
+
+  invest_applet_setup (applet);
+
+  return TRUE;
+}
+
+PANEL_APPLET_IN_PROCESS_FACTORY ("InvestAppletFactory", INVEST_TYPE_APPLET,
+                                 invest_applet_factory, NULL)
diff --git a/invest-applet/invest/invest-applet.h b/invest-applet/invest/invest-applet.h
new file mode 100644
index 0000000..b2fe06e
--- /dev/null
+++ b/invest-applet/invest/invest-applet.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_APPLET_H
+#define INVEST_APPLET_H
+
+#include <panel-applet.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_APPLET         (invest_applet_get_type ())
+#define INVEST_APPLET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), INVEST_TYPE_APPLET, InvestApplet))
+#define INVEST_APPLET_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    INVEST_TYPE_APPLET, InvestAppletClass))
+#define INVEST_IS_APPLET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), INVEST_TYPE_APPLET))
+#define INVEST_IS_APPLET_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    INVEST_TYPE_APPLET))
+#define INVEST_APPLET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o),   INVEST_TYPE_APPLET, InvestAppletClass))
+
+typedef struct _InvestApplet      InvestApplet;
+typedef struct _InvestAppletClass InvestAppletClass;
+
+GType invest_applet_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-cache.c b/invest-applet/invest/invest-cache.c
new file mode 100644
index 0000000..48d5271
--- /dev/null
+++ b/invest-applet/invest/invest-cache.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <string.h>
+
+#include "invest-cache.h"
+#include "invest-currencies.h"
+
+typedef enum
+{
+  QUOTE_STATE_UNQUOTED,
+  QUOTE_STATE_QUOTED,
+  QUOTE_STATE_QUOTE,
+} QuoteState;
+
+static gchar **
+get_fields (const gchar *line)
+{
+  GPtrArray *fields;
+  GString *field;
+  QuoteState state;
+  guint i;
+
+  fields = g_ptr_array_new ();
+  field = g_string_new (NULL);
+  state = QUOTE_STATE_UNQUOTED;
+
+  for (i = 0; line[i] != '\0'; i++)
+    {
+      gchar c;
+
+      c = line[i];
+
+      if (c == ',' && state != QUOTE_STATE_QUOTED)
+        {
+          g_string_append_c (field, '\0');
+          g_ptr_array_add (fields, g_string_free (field, FALSE));
+          field = g_string_new (NULL);
+          state = QUOTE_STATE_UNQUOTED;
+          continue;
+        }
+
+      if (c == '"')
+        {
+          if (state == QUOTE_STATE_UNQUOTED)
+            {
+              state = QUOTE_STATE_QUOTED;
+              continue;
+            }
+
+          if (state == QUOTE_STATE_QUOTED)
+            {
+              state = QUOTE_STATE_QUOTE;
+              continue;
+            }
+
+          if (state == QUOTE_STATE_QUOTE)
+            state = QUOTE_STATE_QUOTED;
+        }
+
+      if (state == QUOTE_STATE_QUOTE)
+        state = QUOTE_STATE_UNQUOTED;
+
+      field = g_string_append_c (field, c);
+    }
+
+  g_string_append_c (field, '\0');
+  g_ptr_array_add (fields, g_string_free (field, FALSE));
+  g_ptr_array_add (fields, NULL);
+
+  return (gchar **) g_ptr_array_free (fields, FALSE);
+}
+
+static InvestQuote *
+invest_quote_new (const gchar *str)
+{
+  gchar **fields;
+  InvestQuote *quote;
+  gdouble tmp;
+
+  if (str == NULL || *str == '\0')
+    return NULL;
+
+  fields = get_fields (str);
+  if (g_strv_length (fields) != 11)
+    {
+      g_strfreev (fields);
+      return NULL;
+    }
+
+  quote = g_new0 (InvestQuote, 1);
+
+  quote->symbol = g_strdup (fields[0]);
+  quote->name = g_strdup (fields[1]);
+  quote->currency = g_utf8_strup (fields[2], -1);
+  quote->last_trade = g_ascii_strtod (fields[3], NULL);
+  quote->last_trade_date = g_strdup (fields[4]);
+  quote->last_trade_time = g_strdup (fields[5]);
+  quote->change = g_ascii_strtod (fields[6], NULL);
+  quote->open = g_ascii_strtod (fields[7], NULL);
+  quote->hight = g_ascii_strtod (fields[8], NULL);
+  quote->low = g_ascii_strtod (fields[9], NULL);
+  quote->volume = (gint) g_ascii_strtoll (fields[10], NULL, 10);
+
+  /* FIXME: use p2 instead of c1 to get change in percent */
+  tmp = quote->last_trade - quote->change_pct;
+  quote->change_pct = tmp != 0.0 ? (quote->change / tmp * 100) : 0.0;
+
+  /* The currency of currency conversion rates like EURUSD=X is wrong in
+   * csv this can be fixed easily by reusing the latter currency in the
+   * symbol.
+   */
+  if (strlen (quote->symbol) == 8 && g_str_has_suffix (quote->symbol, "=X"))
+    {
+      g_free (quote->currency);
+      quote->currency = g_strndup (quote->symbol + 3, 3);
+    }
+
+  /* Indices should not have a currency, though yahoo says so.
+   */
+  if (g_str_has_prefix (quote->symbol, "^"))
+    {
+      g_free (quote->currency);
+      quote->currency = g_strdup ("");
+    }
+
+  /* Sometimes, funny currencies are returned (special characters), only
+   * consider known currencies.
+   */
+  if (!invest_currencies_contains (quote->currency))
+    {
+      g_free (quote->currency);
+      quote->currency = g_strdup ("");
+    }
+
+  g_strfreev (fields);
+  return quote;
+}
+
+static GPtrArray *
+parse_csv (const gchar *csv)
+{
+  GPtrArray *array;
+  gchar **lines;
+  guint i;
+
+  array = g_ptr_array_new ();
+  lines = g_strsplit (csv, "\n", -1);
+
+  for (i = 0; lines[i] != NULL; i++)
+    {
+      InvestQuote *quote;
+
+      quote = invest_quote_new (lines[i]);
+
+      if (quote != NULL)
+        g_ptr_array_add (array, quote);
+    }
+
+  g_strfreev (lines);
+
+  return array;
+}
+
+static void
+get_mtime (GFile    *file,
+           GTimeVal *mtime)
+{
+  GFileInfo *info;
+
+  info = g_file_query_info (file, "time::modified", 0, NULL, NULL);
+  if (info == NULL)
+    return;
+
+  g_file_info_get_modification_time (info, mtime);
+  g_object_unref (info);
+}
+
+static void
+invest_quote_free (InvestQuote *quote)
+{
+  g_free (quote->symbol);
+  g_free (quote->name);
+  g_free (quote->currency);
+  g_free (quote->last_trade_date);
+  g_free (quote->last_trade_time);
+
+  g_free (quote);
+}
+
+InvestCache *
+invest_cache_read (const gchar *filename)
+{
+  const gchar *dir;
+  gchar *path;
+  GFile *file;
+  gchar *csv;
+  GError *error;
+  GPtrArray *array;
+  InvestCache *cache;
+
+  dir = g_get_user_cache_dir ();
+  path = g_build_filename (dir, "gnome-applets", "invest-applet",
+                           filename, NULL);
+
+  file = g_file_new_for_path (path);
+  g_free (path);
+
+  csv = NULL;
+  error = NULL;
+
+  if (!g_file_load_contents (file, NULL, &csv, NULL, NULL, &error))
+    {
+      g_warning ("Could not load cached file %s: %s",
+                 filename, error->message);
+
+      g_free (csv);
+      g_error_free (error);
+      g_object_unref (file);
+
+      return NULL;
+    }
+
+  array = parse_csv (csv);
+  g_free (csv);
+
+  cache = g_new0 (InvestCache, 1);
+
+  cache->n_quotes = array->len;
+  cache->quotes = (InvestQuote **) g_ptr_array_free (array, FALSE);
+
+  get_mtime (file, &cache->mtime);
+  g_object_unref (file);
+
+  return cache;
+}
+
+void
+invest_cache_free (InvestCache *cache)
+{
+  guint i;
+
+  if (cache == NULL)
+    return;
+
+  for (i = 0; i < cache->n_quotes; i++)
+    invest_quote_free (cache->quotes[i]);
+
+  g_free (cache);
+}
+
+InvestQuote *
+invest_cache_get (InvestCache *cache,
+                  const gchar *symbol)
+
+{
+  guint i;
+
+  for (i = 0; i < cache->n_quotes; i++)
+    {
+      if (g_strcmp0 (cache->quotes[i]->symbol, symbol) == 0)
+        return cache->quotes[i];
+    }
+
+  return NULL;
+}
diff --git a/invest-applet/invest/invest-cache.h b/invest-applet/invest/invest-cache.h
new file mode 100644
index 0000000..452cd33
--- /dev/null
+++ b/invest-applet/invest/invest-cache.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_CACHE_H
+#define INVEST_CACHE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  gchar   *symbol;
+  gchar   *name;
+  gchar   *currency;
+  gdouble  last_trade;
+  gchar   *last_trade_date;
+  gchar   *last_trade_time;
+  gdouble  change;
+  gdouble  open;
+  gdouble  hight;
+  gdouble  low;
+  gint     volume;
+
+  gdouble  change_pct;
+} InvestQuote;
+
+typedef struct
+{
+  InvestQuote **quotes;
+  guint         n_quotes;
+
+  GTimeVal      mtime;
+} InvestCache;
+
+InvestCache *invest_cache_read (const gchar *filename);
+
+void         invest_cache_free (InvestCache *cache);
+
+InvestQuote *invest_cache_get  (InvestCache *cache,
+                                const gchar *symbol);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-chart.c b/invest-applet/invest/invest-chart.c
new file mode 100644
index 0000000..32e6797
--- /dev/null
+++ b/invest-applet/invest/invest-chart.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+/*
+ * p:
+ *  eX = Exponential Moving Average
+ *  mX = Moving Average
+ *  b = Bollinger Bands Overlay
+ *  v = Volume Overlay
+ *  p = Parabolic SAR overlay
+ *  s = Splits Overlay
+ * q:
+ *  l = Line
+ *  c = Candles
+ *  b = Bars
+ * l:
+ *  on = Logarithmic
+ *  off = Linear
+ * z:
+ *  l = Large
+ *  m = Medium
+ * t:
+ *  Xd = X Days
+ *  Xm = X Months
+ *  Xy = X Years
+ * a:
+ *  fX = MFI X days
+ *  ss = Slow Stochastic
+ *  fs = Fast Stochastic
+ *  wX = W%R X Days
+ *  mX-Y-Z = MACD X Days, Y Days, Signal
+ *  pX = ROC X Days
+ *  rX = RSI X Days
+ *  v = Volume
+ *  vm = Volume +MA
+ * c:
+ *  X = compare with X
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "invest-chart.h"
+#include "invest-image-retriever.h"
+
+#define AUTOREFRESH_TIMEOUT 20 * 60 * 1000
+#define CHART_URL "http://chart.finance.yahoo.com/z?s=%s&c=%s&t=%s&q=%s&l=%s&z=l&p=%s&a=%s";
+
+struct _InvestChart
+{
+  GtkWindow             parent;
+
+  GtkWidget            *s;
+  GtkWidget            *autorefresh;
+  GtkWidget            *plot;
+  GtkWidget            *progress;
+  GtkWidget            *t;
+  GtkWidget            *q;
+  GtkWidget            *l;
+  GtkWidget            *pm5;
+  GtkWidget            *pm10;
+  GtkWidget            *pm20;
+  GtkWidget            *pm50;
+  GtkWidget            *pm100;
+  GtkWidget            *pm200;
+  GtkWidget            *pe5;
+  GtkWidget            *pe10;
+  GtkWidget            *pe20;
+  GtkWidget            *pe50;
+  GtkWidget            *pe100;
+  GtkWidget            *pe200;
+  GtkWidget            *pb;
+  GtkWidget            *pp;
+  GtkWidget            *ps;
+  GtkWidget            *pv;
+  GtkWidget            *ar;
+  GtkWidget            *af;
+  GtkWidget            *ap;
+  GtkWidget            *aw;
+  GtkWidget            *am;
+  GtkWidget            *ass;
+  GtkWidget            *afs;
+  GtkWidget            *av;
+  GtkWidget            *avm;
+
+  guint                 autorefresh_id;
+
+  InvestImageRetriever *retriever;
+};
+
+G_DEFINE_TYPE (InvestChart, invest_chart, GTK_TYPE_WINDOW)
+
+static void
+completed_cb (InvestImageRetriever *retriever,
+              GdkPixbuf            *pixbuf,
+              InvestChart          *chart)
+{
+  GtkImage *image;
+  GtkLabel *label;
+
+  image = GTK_IMAGE (chart->plot);
+  label = GTK_LABEL (chart->progress);
+
+  if (pixbuf != NULL)
+    {
+      gtk_image_set_from_pixbuf (image, pixbuf);
+      gtk_label_set_text (label, _("Chart downloaded"));
+    }
+  else
+    {
+      GdkPixbuf *logo;
+
+      logo = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest_neutral.svg",
+                                               96, 96, NULL);
+
+      gtk_image_set_from_pixbuf (image, logo);
+      gtk_label_set_text (label, _("Chart could not be downloaded"));
+
+      g_clear_object (&logo);
+    }
+}
+
+static gchar *
+get_p (InvestChart *chart)
+{
+  GPtrArray *array;
+  gchar **elements;
+  gchar *p;
+
+  array = g_ptr_array_new ();
+
+#define ADD_P(variable, value) \
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chart->variable))) \
+    g_ptr_array_add (array, g_strdup (value));
+
+  ADD_P (pm5, "m5")
+  ADD_P (pm10, "m10")
+  ADD_P (pm20, "m20")
+  ADD_P (pm50, "m50")
+  ADD_P (pm100, "m100")
+  ADD_P (pm200, "m200")
+  ADD_P (pe5, "e5")
+  ADD_P (pe10, "e10")
+  ADD_P (pe20, "e20")
+  ADD_P (pe50, "e50")
+  ADD_P (pe100, "e100")
+  ADD_P (pe200, "e200")
+  ADD_P (pb, "b")
+  ADD_P (pp, "p")
+  ADD_P (ps, "s")
+  ADD_P (pv, "v")
+
+#undef ADD_p
+
+  g_ptr_array_add (array, NULL);
+  elements = (gchar **) g_ptr_array_free (array, FALSE);
+
+  p = g_strjoinv (",", elements);
+  g_strfreev (elements);
+
+  return p;
+}
+
+static gchar *
+get_a (InvestChart *chart)
+{
+  GPtrArray *array;
+  gchar **elements;
+  gchar *a;
+
+  array = g_ptr_array_new ();
+
+#define ADD_A(variable, value) \
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chart->variable))) \
+    g_ptr_array_add (array, g_strdup (value));
+
+  ADD_A (ar, "r14")
+  ADD_A (af, "f14")
+  ADD_A (ap, "p12")
+  ADD_A (aw, "w14")
+  ADD_A (am, "m26-12-9")
+  ADD_A (ass, "ss")
+  ADD_A (afs, "fs")
+  ADD_A (av, "v")
+  ADD_A (avm, "vm")
+
+#undef ADD_A
+
+  g_ptr_array_add (array, NULL);
+  elements = (gchar **) g_ptr_array_free (array, FALSE);
+
+  a = g_strjoinv (",", elements);
+  g_strfreev (elements);
+
+  return a;
+}
+
+static gboolean
+refresh_chart (InvestChart *chart)
+{
+  const gchar *text;
+  gchar *tickers;
+  gchar **symbols;
+  gchar *tail;
+  gchar *title;
+  const gchar *t, *q, *l;
+  gchar *s, *c, *p, *a;
+  gchar *url;
+
+  text = gtk_entry_get_text (GTK_ENTRY (chart->s));
+  tickers = g_strstrip (g_utf8_strup (text, -1));
+
+  if (!tickers || *tickers == '\0')
+    return TRUE;
+
+  symbols = g_strsplit (tickers, " ", -1);
+  g_free (tickers);
+
+  tail = g_strjoinv (" / ", symbols);
+  title = g_strdup_printf (_("Financial Chart - %s"), tail);
+  g_free (tail);
+
+  gtk_window_set_title (GTK_WINDOW (chart), title);
+  g_free (title);
+
+  t = gtk_combo_box_get_active_id (GTK_COMBO_BOX (chart->t));
+  q = gtk_combo_box_get_active_id (GTK_COMBO_BOX (chart->q));
+  l = gtk_combo_box_get_active_id (GTK_COMBO_BOX (chart->l));
+
+  s = g_strdup (symbols[0]);
+  c = g_strdup ("");
+
+  if (g_strv_length (symbols) > 1)
+    {
+      guint i;
+
+      for (i = 1; i < g_strv_length (symbols); i++)
+        {
+          gchar *tmp;
+
+          if (*c == '\0')
+            tmp = g_strdup (symbols[i]);
+          else
+            tmp = g_strdup_printf ("%s,%s", c, symbols[i]);
+
+          g_free (c);
+          c = tmp;
+        }
+    }
+
+  g_strfreev (symbols);
+
+  p = get_p (chart);
+  a = get_a (chart);
+
+  url = g_strdup_printf (CHART_URL, s, c, t, q, l, p, a);
+  g_free (s); g_free (c); g_free (p); g_free (a);
+
+  gtk_label_set_text (GTK_LABEL (chart->progress), _("Opening Chart"));
+  gtk_widget_show (chart->progress);
+
+  g_clear_object (&chart->retriever);
+  chart->retriever = invest_image_retriever_new (url);
+  g_free (url);
+
+  g_signal_connect (chart->retriever, "completed",
+                    G_CALLBACK (completed_cb), chart);
+
+  invest_image_retriever_start (chart->retriever);
+
+  return TRUE;
+}
+
+static void
+autorefresh_toggled_cb (GtkToggleButton *togglebutton,
+                        InvestChart     *chart)
+{
+  gboolean active;
+
+  active = gtk_toggle_button_get_active (togglebutton);
+
+  if (!active && chart->autorefresh_id != 0)
+    {
+      g_source_remove (chart->autorefresh_id);
+      chart->autorefresh_id = 0;
+    }
+
+  if (active && chart->autorefresh_id == 0)
+    {
+      chart->autorefresh_id = g_timeout_add (AUTOREFRESH_TIMEOUT,
+                                             (GSourceFunc) refresh_chart,
+                                             chart);
+
+      refresh_chart (chart);
+    }
+}
+
+static void
+s_activate_cb (GtkEntry    *entry,
+               InvestChart *chart)
+{
+  refresh_chart (chart);
+}
+
+static void
+s_changed_cb (GtkEditable *editable,
+              InvestChart *chart)
+{
+  refresh_chart (chart);
+}
+
+static void
+changed_cb (GtkComboBox *widget,
+            InvestChart *chart)
+{
+  refresh_chart (chart);
+}
+
+static void
+toggled_cb (GtkToggleButton *togglebutton,
+            InvestChart     *chart)
+{
+  refresh_chart (chart);
+}
+
+static void
+invest_chart_constructed (GObject *object)
+{
+  InvestChart *chart;
+  GdkPixbuf *logo;
+
+  chart = INVEST_CHART (object);
+
+  G_OBJECT_CLASS (invest_chart_parent_class)->constructed (object);
+
+  gtk_window_set_title (GTK_WINDOW (chart), _("Financial Chart"));
+
+  logo = gdk_pixbuf_new_from_file_at_size (ARTDIR "/invest_neutral.svg", 96, 96, NULL);
+  gtk_image_set_from_pixbuf (GTK_IMAGE (chart->plot), logo);
+  g_clear_object (&logo);
+
+  autorefresh_toggled_cb (GTK_TOGGLE_BUTTON (chart->autorefresh), chart);
+}
+
+static void
+invest_chart_dispose (GObject *object)
+{
+  InvestChart *chart;
+
+  chart = INVEST_CHART (object);
+
+  if (chart->autorefresh_id != 0)
+    {
+      g_source_remove (chart->autorefresh_id);
+      chart->autorefresh_id = 0;
+    }
+
+  g_clear_object (&chart->retriever);
+
+  G_OBJECT_CLASS (invest_chart_parent_class)->dispose (object);
+}
+
+static void
+invest_chart_bind_template (GtkWidgetClass *widget_class)
+{
+  gchar *contents;
+  gsize length;
+  GBytes *bytes;
+
+  g_file_get_contents (BUILDERDIR "/financialchart.ui",
+                       &contents, &length, NULL);
+
+  bytes = g_bytes_new_take (contents, length);
+  gtk_widget_class_set_template (widget_class, bytes);
+  g_bytes_unref (bytes);
+
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, s);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, autorefresh);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, plot);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, progress);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, t);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, q);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, l);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm5);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm10);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm20);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm50);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm100);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pm200);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe5);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe10);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe20);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe50);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe100);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pe200);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pb);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pp);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, ps);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, pv);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, ar);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, af);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, ap);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, aw);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, am);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, ass);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, afs);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, av);
+  gtk_widget_class_bind_template_child (widget_class, InvestChart, avm);
+
+  gtk_widget_class_bind_template_callback (widget_class, autorefresh_toggled_cb);
+  gtk_widget_class_bind_template_callback (widget_class, s_activate_cb);
+  gtk_widget_class_bind_template_callback (widget_class, s_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, toggled_cb);
+}
+
+static void
+invest_chart_class_init (InvestChartClass *chart_class)
+{
+  GObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+
+  object_class = G_OBJECT_CLASS (chart_class);
+  widget_class = GTK_WIDGET_CLASS (chart_class);
+
+  object_class->constructed = invest_chart_constructed;
+  object_class->dispose = invest_chart_dispose;
+
+  invest_chart_bind_template (widget_class);
+}
+
+static void
+invest_chart_init (InvestChart *chart)
+{
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (chart);
+
+  gtk_widget_init_template (widget);
+}
+
+void
+invest_chart_show_chart (const gchar *symbol)
+{
+  InvestChart *chart;
+
+  chart = g_object_new (INVEST_TYPE_CHART, NULL);
+
+  gtk_entry_set_text (GTK_ENTRY (chart->s), symbol);
+  gtk_window_present (GTK_WINDOW (chart));
+}
diff --git a/invest-applet/invest/invest-chart.h b/invest-applet/invest/invest-chart.h
new file mode 100644
index 0000000..e3ed87a
--- /dev/null
+++ b/invest-applet/invest/invest-chart.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_CHART_H
+#define INVEST_CHART_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_CHART invest_chart_get_type ()
+G_DECLARE_FINAL_TYPE (InvestChart, invest_chart, INVEST, CHART, GtkWindow)
+
+void invest_chart_show_chart (const gchar *symbol);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-currencies.c b/invest-applet/invest/invest-currencies.c
new file mode 100644
index 0000000..e14faa8
--- /dev/null
+++ b/invest-applet/invest/invest-currencies.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+#include "invest-currencies.h"
+
+const InvestCurrencies currencies[] =
+  {
+    { "BZD", "Belize Dollar" },
+    { "NLG", "Dutch Guilder" },
+    { "SLL", "Sierra Leone Leone" },
+    { "FRF", "French Franc" },
+    { "NGN", "Nigerian Naira" },
+    { "CRC", "Costa Rican Colon" },
+    { "LAK", "Laos Kip" },
+    { "CLP", "Chilean Peso" },
+    { "DZD", "Algerian Dinar" },
+    { "SZL", "Swaziland Lilangeni" },
+    { "MUR", "Mauritius Rupee" },
+    { "WST", "Western Samoa Tala" },
+    { "MMK", "Myanmar Kyat" },
+    { "IDR", "Indonesian Rupiah" },
+    { "GTQ", "Guatemala Quetzal" },
+    { "CAD", "Canadian Dollar" },
+    { "AWG", "Aruban Florin" },
+    { "TTD", "Trinidad Dollar" },
+    { "PKR", "Pakistani Rupee" },
+    { "XCD", "East Caribbean Dollar" },
+    { "VUV", "Vanuatu Vatu" },
+    { "XOF", "CFA Franc (BCEAO)" },
+    { "ROL", "Romanian Leu" },
+    { "KMF", "Comoros Franc" },
+    { "SIT", "Slovenian Tolar" },
+    { "VEB", "Venezuelan Bolivar" },
+    { "ANG", "Netherlands Antilles Guilder" },
+    { "MNT", "Mongolian Tugrik" },
+    { "LBP", "Lebanese Pound" },
+    { "KES", "Kenyan Shilling" },
+    { "BTN", "Bhutan Ngultrum" },
+    { "GBP", "British Pound" },
+    { "SEK", "Swedish Krona" },
+    { "ZMK", "Zambia Kwacha" },
+    { "SKK", "Slovak Koruna" },
+    { "DKK", "Danish Krone" },
+    { "AFA", "Afganistan Afghani" },
+    { "CYP", "Cypriot Pound" },
+    { "SCR", "Seychelles Rupee" },
+    { "FJD", "Fiji Dollar" },
+    { "SRG", "Surinam Guilder" },
+    { "SHP", "St. Helena Pound" },
+    { "ALL", "Albanian Lek" },
+    { "TOP", "Tonga Isl Pa'anga" },
+    { "UGX", "Ugandan Shilling" },
+    { "OMR", "Oman Rial" },
+    { "DJF", "Djibouti Franc" },
+    { "BND", "Brunei Dollar" },
+    { "TND", "Tunisian Dinar" },
+    { "PTE", "Portuguese Escudo" },
+    { "IEP", "Irish Punt" },
+    { "SBD", "Salomon Islands Dollar" },
+    { "GNF", "Guinea Franc" },
+    { "BOB", "Bolivian Boliviano" },
+    { "CVE", "Cape Verde Escudo" },
+    { "ARS", "Argentinian Peso" },
+    { "GMD", "Gambia Dalasi" },
+    { "ZWD", "Zimbabwean Dollar" },
+    { "MWK", "Malawi Kwacha" },
+    { "BDT", "Bangladesh Taka" },
+    { "GRD", "Greek Drachma" },
+    { "KWD", "Kuwaiti Dinar" },
+    { "EUR", "Euro" },
+    { "TRL", "Turkish Lira" },
+    { "CHF", "Swiss Franc" },
+    { "DOP", "Dominican Peso" },
+    { "PEN", "Peruvian Sol" },
+    { "SVC", "El Salvador Colon" },
+    { "SGD", "Singapore Dollar" },
+    { "TWD", "Taiwan New Dollar" },
+    { "USD", "US Dollar" },
+    { "BGN", "Bulgarian Lev" },
+    { "MAD", "Moroccan Dirham" },
+    { "SAR", "Saudi Arabian Riyal" },
+    { "AUD", "Australian Dollar" },
+    { "KYD", "Cayman Islands Dollar" },
+    { "GHC", "Ghanaian Cedi" },
+    { "KRW", "South Korean Won" },
+    { "GIP", "Gibraltar Pound" },
+    { "NAD", "Namibian Dollar" },
+    { "CZK", "Czech Koruna" },
+    { "JMD", "Jamaican Dollar" },
+    { "MXN", "Mexican Peso" },
+    { "BWP", "Botswana Pula" },
+    { "GYD", "Guyana Dollar" },
+    { "EGP", "Egyptian Pound" },
+    { "THB", "Thai Baht" },
+    { "AED", "United Arab Emirates Dirham" },
+    { "JPY", "Japanese Yen" },
+    { "JOD", "Jordanian Dinar" },
+    { "HRK", "Croatian Kuna" },
+    { "ZAR", "South African Rand" },
+    { "CUP", "Cuban Peso" },
+    { "BBD", "Barbados Dollar" },
+    { "PGK", "Papua New Guinea Kina" },
+    { "LKR", "Sri Lanka Rupee" },
+    { "BEF", "Belgian Franc" },
+    { "PLN", "Polish Zloty" },
+    { "MYR", "Malaysian Ringgit" },
+    { "FIM", "Finnish Markka" },
+    { "CNY", "Renmimbi Yuan" },
+    { "SDD", "Sudanese Dinar" },
+    { "LVL", "Latvian Lats" },
+    { "ITL", "Italian Lira" },
+    { "INR", "Indian Rupee" },
+    { "NIO", "Nicaraguan Cordoba" },
+    { "PHP", "Philippines Peso" },
+    { "HNL", "Honduras Lempira" },
+    { "HKD", "Hong Kong Dollar" },
+    { "NZD", "New Zealand Dollar" },
+    { "BRL", "Brazilian Real" },
+    { "MTL", "Maltese Pound" },
+    { "ATS", "Austrian Schilling" },
+    { "EEK", "Estonian Kroon" },
+    { "NOK", "Norwegian Krone" },
+    { "ISK", "Iceland Krona" },
+    { "ILS", "Israeli Shekel" },
+    { "LSL", "Lesotho Loti" },
+    { "HUF", "Hungarian Forint" },
+    { "ESP", "Spanish Peseta" },
+    { "UAH", "Ukraine Hryvnia" },
+    { "RUB", "Russian Ruble" },
+    { "BMD", "Bermuda Dollar" },
+    { "MVR", "Maldives Rufiyan" },
+    { "QAR", "Qatari Rial" },
+    { "VND", "Vietnam Dong" },
+    { "MRO", "Mauritania Ouguiya" },
+    { "MZM", "Mozambique Metical" },
+    { "NPR", "Nepal Rupee" },
+    { "COP", "Colombian Peso" },
+    { "TZS", "Tanzanian Shilling" },
+    { "MGF", "Malagasy Franc" },
+    { "KHR", "Cambodian Riel" },
+    { "SYP", "Syria Pound" },
+    { "HTG", "Haitian Gourde" },
+    { "DEM", "German Mark" },
+    { "BHD", "Bahraini Dinar" },
+    { "XAF", "CFA Franc(BEAC)" },
+    { "STD", "Sao Tome & Principe Dobra" },
+    { "LTL", "Lithuanian Litas" },
+    { "ETB", "Ethiopian Birr" },
+    { "XPF", "CFP Franc" },
+    { NULL, NULL }
+  };
+
+gboolean
+invest_currencies_contains (const gchar *currency)
+{
+  guint i;
+
+  if (currency == NULL)
+    return FALSE;
+
+  for (i = 0; currencies[i].code != NULL; i++)
+    {
+      if (g_strcmp0 (currencies[i].code, currency) == 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/invest-applet/invest/invest-currencies.h b/invest-applet/invest/invest-currencies.h
new file mode 100644
index 0000000..3c125c1
--- /dev/null
+++ b/invest-applet/invest/invest-currencies.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_CURRENCIES_H
+#define INVEST_CURRENCIES_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  const gchar *code;
+  const gchar *label;
+} InvestCurrencies;
+
+extern const InvestCurrencies currencies[];
+
+gboolean invest_currencies_contains (const gchar *currency);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-image-retriever.c b/invest-applet/invest/invest-image-retriever.c
new file mode 100644
index 0000000..4927bb6
--- /dev/null
+++ b/invest-applet/invest/invest-image-retriever.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "invest-image-retriever.h"
+
+#define CHUNK_SIZE 512 * 1024
+
+struct _InvestImageRetriever
+{
+  GObject       parent;
+
+  GCancellable *cancellable;
+
+  gchar        *url;
+
+  guint8       *chunk;
+  GByteArray   *array;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_URL,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+enum
+{
+  COMPLETED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (InvestImageRetriever, invest_image_retriever, G_TYPE_OBJECT)
+
+static void
+download_cb (GObject      *object,
+             GAsyncResult *res,
+             gpointer      user_data)
+{
+  GError *error;
+  GdkPixbuf *pixbuf;
+  InvestImageRetriever *retriever;
+
+  error = NULL;
+  pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_IMAGE_RETRIEVER (user_data);
+
+  if (error != NULL)
+    {
+      g_signal_emit (retriever, signals[COMPLETED], 0, NULL);
+      g_error_free (error);
+      return;
+    }
+
+  g_signal_emit (retriever, signals[COMPLETED], 0, pixbuf);
+  g_object_unref (pixbuf);
+}
+
+static void
+ready_cb (GObject      *object,
+          GAsyncResult *res,
+          gpointer      user_data)
+{
+  GError *error;
+  GFileInputStream *stream;
+  InvestImageRetriever *retriever;
+
+  error = NULL;
+  stream = g_file_read_finish (G_FILE (object), res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_IMAGE_RETRIEVER (user_data);
+
+  if (error != NULL)
+    {
+      g_signal_emit (retriever, signals[COMPLETED], 0, NULL);
+      g_error_free (error);
+      return;
+    }
+
+  gdk_pixbuf_new_from_stream_async (G_INPUT_STREAM (stream),
+                                    retriever->cancellable,
+                                    download_cb, retriever);
+
+  g_object_unref (stream);
+}
+
+static void
+invest_image_retriever_dispose (GObject *object)
+{
+  InvestImageRetriever *retriever;
+
+  retriever = INVEST_IMAGE_RETRIEVER (object);
+
+  if (retriever->cancellable != NULL)
+    {
+      g_cancellable_cancel (retriever->cancellable);
+      g_clear_object (&retriever->cancellable);
+    }
+
+  G_OBJECT_CLASS (invest_image_retriever_parent_class)->dispose (object);
+}
+
+static void
+invest_image_retriever_finalize (GObject *object)
+{
+  InvestImageRetriever *retriever;
+
+  retriever = INVEST_IMAGE_RETRIEVER (object);
+
+  g_free (retriever->url);
+
+  g_free (retriever->chunk);
+  g_byte_array_free (retriever->array, TRUE);
+
+  G_OBJECT_CLASS (invest_image_retriever_parent_class)->finalize (object);
+}
+
+static void
+invest_image_retriever_set_property (GObject      *object,
+                                     guint         property_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  InvestImageRetriever *retriever;
+
+  retriever = INVEST_IMAGE_RETRIEVER (object);
+
+  switch (property_id)
+    {
+      case PROP_URL:
+        retriever->url = g_value_dup_string (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_image_retriever_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_URL] =
+    g_param_spec_string ("url", "url", "url", NULL,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_image_retriever_install_signals (GObjectClass *object_class)
+{
+  signals[COMPLETED] =
+    g_signal_new ("completed", INVEST_TYPE_IMAGE_RETRIEVER, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 1, GDK_TYPE_PIXBUF);
+}
+
+static void
+invest_image_retriever_class_init (InvestImageRetrieverClass *retriever_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (retriever_class);
+
+  object_class->dispose = invest_image_retriever_dispose;
+  object_class->finalize = invest_image_retriever_finalize;
+  object_class->set_property = invest_image_retriever_set_property;
+
+  invest_image_retriever_install_properties (object_class);
+  invest_image_retriever_install_signals (object_class);
+}
+
+static void
+invest_image_retriever_init (InvestImageRetriever *retriever)
+{
+  retriever->cancellable = g_cancellable_new ();
+
+  retriever->chunk = g_malloc0 (CHUNK_SIZE + 1);
+  retriever->array = g_byte_array_new ();
+}
+
+InvestImageRetriever *
+invest_image_retriever_new (const gchar *url)
+{
+  return g_object_new (INVEST_TYPE_IMAGE_RETRIEVER, "url", url, NULL);
+}
+
+void
+invest_image_retriever_start (InvestImageRetriever *retriever)
+{
+  GFile *file;
+
+  file = g_file_new_for_uri (retriever->url);
+  g_file_read_async (file, 0, retriever->cancellable, ready_cb, retriever);
+  g_object_unref (file);
+}
+
+const gchar *
+invest_image_retriever_get_url (InvestImageRetriever *retriever)
+{
+  return retriever->url;
+}
diff --git a/invest-applet/invest/invest-image-retriever.h b/invest-applet/invest/invest-image-retriever.h
new file mode 100644
index 0000000..402bc90
--- /dev/null
+++ b/invest-applet/invest/invest-image-retriever.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_IMAGE_RETRIEVER_H
+#define INVEST_IMAGE_RETRIEVER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_IMAGE_RETRIEVER invest_image_retriever_get_type ()
+G_DECLARE_FINAL_TYPE (InvestImageRetriever, invest_image_retriever,
+                      INVEST, IMAGE_RETRIEVER, GObject)
+
+InvestImageRetriever *invest_image_retriever_new     (const gchar          *url);
+
+void                  invest_image_retriever_start   (InvestImageRetriever *retriever);
+
+const gchar          *invest_image_retriever_get_url (InvestImageRetriever *retriever);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-preferences.c b/invest-applet/invest/invest-preferences.c
new file mode 100644
index 0000000..bf8ac2b
--- /dev/null
+++ b/invest-applet/invest/invest-preferences.c
@@ -0,0 +1,1137 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "invest-currencies.h"
+#include "invest-preferences.h"
+
+struct _InvestPreferences
+{
+  GtkDialog     parent;
+
+  GSettings    *settings;
+  gulong        settings_changed_id;
+  gboolean      ignore_changed_signal;
+
+  GtkWidget    *explanation;
+
+  GtkTreeStore *store;
+  GtkWidget    *stocks;
+
+  GtkWidget    *indexexpansion;
+  GtkWidget    *hidecharts;
+
+  GtkWidget    *currency;
+  gchar        *currency_code;
+};
+
+enum
+{
+  COL_SYMBOL,
+  COL_LABEL,
+  COL_AMOUNT,
+  COL_PRICE,
+  COL_COMMISION,
+  COL_CURRENCY_RATE,
+
+  NUM_COLS
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SETTINGS,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (InvestPreferences, invest_preferences, GTK_TYPE_DIALOG)
+
+static gchar *
+format_currency (const gchar *label,
+                 const gchar *code)
+{
+  if (code == NULL)
+    return g_strdup (label);
+
+  return g_strdup_printf ("%s (%s)", label, code);
+}
+
+static gboolean
+is_group (InvestPreferences *preferences,
+          GtkTreeIter       *iter)
+{
+  gchar *label;
+  gboolean is_group;
+
+  label = NULL;
+  gtk_tree_model_get (GTK_TREE_MODEL (preferences->store), iter, 1, &label, -1);
+
+  is_group = label == NULL ? TRUE : FALSE;
+  g_free (label);
+
+  return is_group;
+}
+
+static gboolean
+is_stock (InvestPreferences *preferences,
+          GtkTreeIter       *iter)
+{
+  return !is_group (preferences, iter);
+}
+
+static GVariant *
+get_stocks (InvestPreferences *preferences,
+            GtkTreeModel      *model,
+            GtkTreeIter       *parent)
+{
+  GVariantBuilder builder;
+  GtkTreeIter iter;
+  gboolean valid;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{bv}"));
+
+  valid = gtk_tree_model_iter_children (model, &iter, parent);
+
+  while (valid)
+    {
+      if (is_group (preferences, &iter))
+        {
+          gchar *name;
+
+          gtk_tree_model_get (model, &iter, COL_SYMBOL, &name, -1);
+
+          g_variant_builder_add (&builder, "{bv}", TRUE,
+                                 g_variant_new ("(sv)", name,
+                                                get_stocks (preferences,
+                                                            model, &iter)));
+
+          g_free (name);
+        }
+      else
+        {
+          gchar *symbol;
+          gchar *label;
+          gdouble amount;
+          gdouble price;
+          gdouble comission;
+          gdouble currency_rate;
+
+          gtk_tree_model_get (model, &iter,
+                              COL_SYMBOL, &symbol,
+                              COL_LABEL, &label,
+                              COL_AMOUNT, &amount,
+                              COL_PRICE, &price,
+                              COL_COMMISION, &comission,
+                              COL_CURRENCY_RATE, &currency_rate,
+                              -1);
+
+          g_variant_builder_add (&builder, "{bv}", FALSE,
+                                 g_variant_new ("(ssdddd)", symbol, label,
+                                                amount, price, comission,
+                                                currency_rate));
+
+          g_free (label);
+          g_free (symbol);
+        }
+
+      valid = gtk_tree_model_iter_next (model, &iter);
+    }
+
+  return g_variant_builder_end (&builder);
+}
+
+static void
+save_stocks (InvestPreferences *preferences)
+{
+  GtkTreeModel *model;
+  GVariant *stocks;
+
+  model = GTK_TREE_MODEL (preferences->store);
+  stocks = get_stocks (preferences, model, NULL);
+
+  preferences->ignore_changed_signal = TRUE;
+  g_settings_set_value (preferences->settings, "stocks", stocks);
+  preferences->ignore_changed_signal = FALSE;
+}
+
+static void
+add_clicked_cb (InvestPreferences *preferences,
+                gboolean           group,
+                gboolean           save)
+{
+  GtkTreeView *tree_view;
+  GtkTreeSelection *selection;
+  GtkTreeStore *store;
+  GtkTreeModel *model;
+  gboolean has_parent;
+  GtkTreeIter parent;
+  GtkTreePath *path;
+  GtkTreeViewColumn *column;
+
+  tree_view = GTK_TREE_VIEW (preferences->stocks);
+  selection = gtk_tree_view_get_selection (tree_view);
+  store = preferences->store;
+  has_parent = FALSE;
+  path = NULL;
+  column = NULL;
+
+  if (gtk_tree_selection_get_selected (selection, &model, &parent))
+    {
+      GtkTreeIter iter;
+
+      has_parent = TRUE;
+
+      while (has_parent && is_stock (preferences, &parent))
+        {
+          has_parent = gtk_tree_model_iter_parent (model, &iter, &parent);
+
+          if (has_parent)
+            {
+              parent = iter;
+            }
+        }
+    }
+
+  if (group == TRUE)
+    {
+      GtkTreeIter iter;
+
+      gtk_tree_store_append (store, &iter, has_parent ? &parent : NULL);
+      gtk_tree_store_set (store, &iter, COL_SYMBOL, _("Stock Group"), -1);
+
+      path = gtk_tree_model_get_path (model, &iter);
+      gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
+      add_clicked_cb (preferences, FALSE, FALSE);
+
+      gtk_tree_view_expand_row (tree_view, path, FALSE);
+    }
+  else
+    {
+      GtkTreeIter iter;
+
+      gtk_tree_store_append (store, &iter, has_parent ? &parent : NULL);
+      gtk_tree_store_set (store, &iter,
+                          COL_SYMBOL, "YHOO",
+                          COL_LABEL, "Yahoo! Inc.",
+                          COL_AMOUNT, 0.0,
+                          COL_PRICE, 0.0,
+                          COL_COMMISION, 0.0,
+                          COL_CURRENCY_RATE, 0.0,
+                          -1);
+
+      if (has_parent)
+        {
+          path = gtk_tree_model_get_path (model, &parent);
+          gtk_tree_view_expand_row (tree_view, path, FALSE);
+        }
+
+      path = gtk_tree_model_get_path (model, &iter);
+    }
+
+  column = gtk_tree_view_get_column (tree_view, COL_SYMBOL);
+  gtk_tree_view_set_cursor (tree_view, path, column, TRUE);
+
+  if (save == TRUE)
+    {
+      save_stocks (preferences);
+    }
+}
+
+static void
+addstock_clicked_cb (GtkButton         *button,
+                     InvestPreferences *preferences)
+{
+  add_clicked_cb (preferences, FALSE, TRUE);
+}
+
+static void
+addgroup_clicked_cb (GtkButton         *button,
+                     InvestPreferences *preferences)
+{
+  add_clicked_cb (preferences, TRUE, TRUE);
+}
+
+static void
+remove_clicked_cb (GtkButton         *button,
+                   InvestPreferences *preferences)
+{
+  gboolean removed;
+  GtkTreeView *tree_view;
+  GtkTreeSelection *selection;
+  GList *selected_rows;
+  GtkTreeModel *model;
+  GList *row;
+
+  removed = FALSE;
+  tree_view = GTK_TREE_VIEW (preferences->stocks);
+  selection = gtk_tree_view_get_selection (tree_view);
+
+  selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+  for (row = selected_rows; row != NULL; row = row->next)
+    {
+      GtkTreePath *path;
+      GtkTreeIter iter;
+
+      path = (GtkTreePath *) row->data;
+
+      if (!gtk_tree_model_get_iter (model, &iter, path))
+        continue;
+
+      if (gtk_tree_model_iter_n_children (model, &iter) > 0)
+        {
+          GtkDialogFlags flags;
+          GtkWidget *dialog;
+          GtkWidget *content_area;
+          const gchar *text;
+          GtkWidget *label;
+          gint response;
+
+          flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
+
+          /* Translators: Asks the user to confirm deletion of a group of stocks */
+          dialog = gtk_dialog_new_with_buttons (_("Delete entire stock group?"),
+                                                GTK_WINDOW (preferences), flags,
+                                                _("_Cancel"), GTK_RESPONSE_REJECT,
+                                                _("_OK"), GTK_RESPONSE_ACCEPT,
+                                                NULL);
+
+          content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+          gtk_widget_set_margin_start (content_area, 10);
+          gtk_widget_set_margin_end (content_area, 10);
+          gtk_widget_set_margin_bottom (content_area, 10);
+
+          /* Translators: stocks can be grouped together into a "stock group".
+           * The user wants to delete a group, but the group still contains
+           * stocks. By deleting the group, also all stocks will be removed
+           * from configuration.
+           */
+          text = _("This removes all stocks contained in this stock group!\n"
+                   "Do you really want to remove this stock group?");
+          label = gtk_label_new (text);
+
+          gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 10);
+          gtk_widget_show (label);
+
+          response = gtk_dialog_run (GTK_DIALOG (dialog));
+          gtk_widget_destroy (dialog);
+
+          if (response == GTK_RESPONSE_REJECT)
+            continue;
+        }
+
+      gtk_tree_store_remove (preferences->store, &iter);
+      removed = TRUE;
+    }
+
+  g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
+
+  if (removed == TRUE)
+    {
+      save_stocks (preferences);
+    }
+}
+
+static gboolean
+stocks_key_press_event_cb (GtkWidget         *widget,
+                           GdkEventKey       *event,
+                           InvestPreferences *preferences)
+{
+  if (event->keyval == 65535)
+    remove_clicked_cb (NULL, preferences);
+
+  return FALSE;
+}
+
+typedef struct
+{
+  InvestPreferences *preferences;
+  gint               column;
+  GType              type;
+} CellData;
+
+static CellData *
+cell_data_new (InvestPreferences *preferences,
+               gint               column,
+               GType              type)
+{
+  CellData *data;
+
+  data = g_new0 (CellData, 1);
+
+  data->preferences = preferences;
+  data->column = column;
+  data->type = type;
+
+  return data;
+}
+
+static void
+cell_data_free (gpointer  data,
+                GClosure *closure)
+{
+  g_free (data);
+}
+
+static void
+edited_cb (GtkCellRendererText *renderer,
+           gchar               *path,
+           gchar               *new_text,
+           CellData            *data)
+{
+  InvestPreferences *preferences;
+  GtkTreeStore *store;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  preferences = data->preferences;
+  store = preferences->store;
+  model = GTK_TREE_MODEL (store);
+
+  if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
+    return;
+
+  if (data->column != 0 && is_group (preferences, &iter))
+    return;
+
+  if (data->column == 0 && is_stock (preferences, &iter))
+    {
+      gchar *text;
+
+      text = g_utf8_strup (new_text, -1);
+
+      if (text != NULL && text[0] != '\0')
+        gtk_tree_store_set (store, &iter, data->column, text, -1);
+      g_free (text);
+    }
+  else if (data->column < 2)
+    {
+      if (new_text != NULL && new_text[0] != '\0')
+        gtk_tree_store_set (store, &iter, data->column, new_text, -1);
+    }
+  else
+    {
+      gdouble value;
+
+      value = g_ascii_strtod (new_text, NULL);
+
+                       gtk_tree_store_set (store, &iter, data->column, value, -1);
+    }
+
+  save_stocks (preferences);
+}
+
+static void
+cell_data_func (GtkTreeViewColumn *tree_column,
+                GtkCellRenderer   *cell,
+                GtkTreeModel      *tree_model,
+                GtkTreeIter       *iter,
+                gpointer           data)
+{
+  CellData *cell_data;
+  InvestPreferences *preferences;
+
+  cell_data = (CellData *) data;
+  preferences = cell_data->preferences;
+
+  if (is_group (preferences, iter))
+    {
+      if (cell_data->column == 0)
+        {
+          gchar *text;
+          gchar *markup;
+
+          gtk_tree_model_get (tree_model, iter, cell_data->column, &text, -1);
+
+          markup = g_strdup_printf ("<b>%s</b>", text);
+          g_free (text);
+
+          g_object_set (cell, "markup", markup, NULL);
+          g_free (markup);
+        }
+      else
+        {
+          g_object_set (cell, "text", "", NULL);
+        }
+    }
+  else
+    {
+      gchar *text;
+
+      if (cell_data->type == G_TYPE_DOUBLE)
+        {
+          gdouble val;
+
+          gtk_tree_model_get (tree_model, iter, cell_data->column, &val, -1);
+
+          text = g_strdup_printf ("%.2f", val);
+        }
+      else
+        {
+          gtk_tree_model_get (tree_model, iter, cell_data->column, &text, -1);
+        }
+
+      g_object_set (cell, "text", text, NULL);
+      g_free (text);
+    }
+}
+
+static void
+create_cell (InvestPreferences *preferences,
+             GtkTreeView       *tree_view,
+             gint               column,
+             const gchar       *name,
+             GType              type)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *view_column;
+  CellData *data;
+
+  renderer = gtk_cell_renderer_text_new ();
+  view_column = gtk_tree_view_column_new_with_attributes (name, renderer,
+                                                          "text", column,
+                                                          NULL);
+
+  if (type == G_TYPE_DOUBLE)
+    gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
+
+  g_object_set (renderer, "editable", TRUE, NULL);
+
+  data = cell_data_new (preferences, column, type);
+  g_signal_connect_data (renderer, "edited", G_CALLBACK (edited_cb),
+                         data, cell_data_free, G_CONNECT_AFTER);
+
+  if (type == G_TYPE_STRING)
+    gtk_tree_view_column_set_sort_column_id (view_column, column);
+
+  data = cell_data_new (preferences, column, type);
+  gtk_tree_view_column_set_cell_data_func (view_column, renderer,
+                                           cell_data_func, data, g_free);
+
+  gtk_tree_view_append_column (tree_view, view_column);
+}
+
+static void
+add_exchange_column (InvestPreferences *preferences)
+{
+  GtkTreeView *tree_view;
+
+  tree_view = GTK_TREE_VIEW (preferences->stocks);
+
+  if (gtk_tree_view_get_n_columns (tree_view) != NUM_COLS)
+    {
+      create_cell (preferences, tree_view,
+                   COL_CURRENCY_RATE, _("Currency Rate"), G_TYPE_DOUBLE);
+    }
+}
+
+static void
+remove_exchange_column (InvestPreferences *preferences)
+{
+  GtkTreeView *tree_view;
+
+  tree_view = GTK_TREE_VIEW (preferences->stocks);
+
+  if (gtk_tree_view_get_n_columns (tree_view) == NUM_COLS)
+    {
+      GtkTreeViewColumn *column;
+
+      column = gtk_tree_view_get_column (tree_view, COL_CURRENCY_RATE);
+
+      gtk_tree_view_remove_column (tree_view, column);
+    }
+}
+
+static void
+pick_currency (InvestPreferences *preferences,
+               const gchar       *currency,
+               gboolean           save)
+{
+  gchar *code;
+  gchar *label;
+
+  code = g_strdup (currency);
+
+  if (code == NULL)
+    {
+      label = g_strdup ("");
+      remove_exchange_column (preferences);
+    }
+  else
+    {
+      gint i;
+
+      for (i = 0; currencies[i].code != NULL; i++)
+        {
+          if (g_strcmp0 (code, currencies[i].code) == 0)
+            {
+              label = format_currency (currencies[i].label, code);
+              break;
+            }
+        }
+
+      if (label == NULL)
+        label = g_strdup ("");
+
+      add_exchange_column (preferences);
+    }
+
+  g_free (preferences->currency_code);
+  preferences->currency_code = code;
+
+  gtk_entry_set_text (GTK_ENTRY (preferences->currency), label);
+  g_free (label);
+
+  if (save)
+    {
+      g_settings_set_string (preferences->settings, "currency",
+                             code != NULL ? code : "");
+    }
+}
+
+static void
+match_currency (InvestPreferences *preferences)
+{
+  GtkEntry *entry;
+  gchar *text;
+  guint16 length;
+  gint i;
+
+  entry = GTK_ENTRY (preferences->currency);
+  length = gtk_entry_get_text_length (entry);
+
+  if (length == 0)
+    {
+      pick_currency (preferences, NULL, TRUE);
+
+      return;
+    }
+  else if (length == 3)
+    {
+      text = g_utf8_strup (gtk_entry_get_text (entry), -1);
+
+      for (i = 0; currencies[i].code != NULL; i++)
+        {
+          if (g_strcmp0 (text, currencies[i].code) == 0)
+            {
+              pick_currency (preferences, text, TRUE);
+              g_free (text);
+
+              return;
+            }
+        }
+
+      g_free (text);
+    }
+  else
+    {
+      text = g_utf8_strup (gtk_entry_get_text (entry), -1);
+
+      for (i = 0; currencies[i].code != NULL; i++)
+        {
+          gchar *label;
+          gchar *formatted;
+          gboolean found;
+
+          label = g_utf8_strup (currencies[i].label, -1);
+          formatted = format_currency (label, currencies[i].code);
+          found = FALSE;
+
+          if (g_strcmp0 (label, text) == 0 ||
+              g_strcmp0 (formatted, text) == 0)
+            {
+              pick_currency (preferences, currencies[i].code, TRUE);
+              found = TRUE;
+            }
+
+          g_free (formatted);
+          g_free (label);
+
+          if (found)
+            {
+              g_free (text);
+              return;
+            }
+        }
+
+      g_free (text);
+    }
+
+  pick_currency (preferences, preferences->currency_code, TRUE);
+}
+
+static gboolean
+currency_focus_out_event_cb (GtkWidget         *widget,
+                             GdkEventFocus     *event,
+                             InvestPreferences *preferences)
+{
+  match_currency (preferences);
+
+  return FALSE;
+}
+
+static void
+currency_activate_cb (GtkEntry          *entry,
+                      InvestPreferences *preferences)
+{
+  match_currency (preferences);
+}
+
+static void
+prefs_dialog_response_cb (GtkDialog *dialog,
+                          gint       response_id,
+                          gpointer   user_data)
+{
+  GdkScreen *screen;
+
+  screen = gdk_screen_get_default ();
+
+  switch (response_id)
+    {
+      case 1:
+        gtk_show_uri (screen, "help:invest-applet/invest-applet-usage",
+                      GDK_CURRENT_TIME, NULL);
+        break;
+
+      default:
+        gtk_widget_destroy (GTK_WIDGET (dialog));
+        break;
+    }
+}
+
+static void
+setup_stocks (InvestPreferences *preferences)
+{
+  GtkTreeView *tree_view;
+  GtkTreeSortable *sortable;
+
+  tree_view = GTK_TREE_VIEW (preferences->stocks);
+
+  preferences->store = gtk_tree_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING,
+                                           G_TYPE_DOUBLE, G_TYPE_DOUBLE,
+                                           G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+  sortable = GTK_TREE_SORTABLE (preferences->store);
+  gtk_tree_sortable_set_sort_column_id (sortable, 0, GTK_SORT_ASCENDING);
+
+  gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (preferences->store));
+
+  create_cell (preferences, tree_view, COL_SYMBOL, _("Symbol"), G_TYPE_STRING);
+  create_cell (preferences, tree_view, COL_LABEL, _("Label"), G_TYPE_STRING);
+  create_cell (preferences, tree_view, COL_AMOUNT, _("Amount"), G_TYPE_DOUBLE);
+  create_cell (preferences, tree_view, COL_PRICE, _("Price"), G_TYPE_DOUBLE);
+  create_cell (preferences, tree_view, COL_COMMISION, _("Commission"), G_TYPE_DOUBLE);
+
+  if (preferences->currency_code != NULL)
+    add_exchange_column (preferences);
+}
+
+static gboolean
+match_selected_cb (GtkEntryCompletion *widget,
+                   GtkTreeModel       *model,
+                   GtkTreeIter        *iter,
+                   InvestPreferences  *preferences)
+{
+  GValue value = G_VALUE_INIT;
+
+  gtk_tree_model_get_value (model, iter, 1, &value);
+
+  pick_currency (preferences, g_value_get_string (&value), TRUE);
+
+  return TRUE;
+}
+
+static gboolean
+match_func (GtkEntryCompletion *completion,
+            const gchar        *key,
+            GtkTreeIter        *iter,
+            gpointer            user_data)
+{
+  GValue value = G_VALUE_INIT;
+  GtkTreeModel *model;
+  gchar *label;
+  gchar **tokens;
+  gchar **words;
+  gboolean match;
+  gint i;
+
+  model = gtk_entry_completion_get_model (completion);
+  gtk_tree_model_get_value (model, iter, 0, &value);
+
+  label = g_utf8_strdown (g_value_get_string (&value), -1);
+  tokens = g_strsplit (label, " ", -1);
+  g_free (label);
+
+  words = g_strsplit (key, " ", -1);
+  match = TRUE;
+
+  for (i = 0; words[i] != NULL; i++)
+    {
+      gboolean found;
+      gint j;
+
+      found = FALSE;
+
+      for (j = 0; tokens[j] != NULL; j++)
+        {
+          const gchar *token;
+
+          token = tokens[j];
+          if (g_str_has_prefix (token, "("))
+            token++;
+
+          if (g_str_has_prefix (token, words[i]))
+            {
+              found = TRUE;
+              break;
+            }
+        }
+
+      if (found == FALSE)
+        {
+          match = FALSE;
+          break;
+        }
+    }
+
+  g_strfreev (tokens);
+  g_strfreev (words);
+
+  return match;
+}
+
+static void
+setup_completion (InvestPreferences *preferences)
+{
+  GtkListStore *store;
+  GtkEntryCompletion *completion;
+  gint i;
+
+  store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+  completion = gtk_entry_completion_new ();
+
+  for (i = 0; currencies[i].code != NULL; i++)
+    {
+      gchar *formatted;
+      GtkTreeIter iter;
+
+      formatted = format_currency (currencies[i].label, currencies[i].code);
+
+      gtk_list_store_append (store, &iter);
+      gtk_list_store_set (store, &iter, 0, formatted,
+                          1, currencies[i].code, -1);
+
+      g_free (formatted);
+    }
+
+  g_signal_connect (completion, "match-selected",
+                    G_CALLBACK (match_selected_cb), preferences);
+
+  gtk_entry_completion_set_match_func (completion, match_func, NULL, NULL);
+  gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
+  gtk_entry_completion_set_text_column (completion, 0);
+  gtk_entry_set_completion (GTK_ENTRY (preferences->currency), completion);
+}
+
+static void
+add_to_store (GtkTreeStore *store,
+              GVariant     *stocks,
+              GtkTreeIter  *parent)
+{
+  GVariantIter iter;
+  gboolean group;
+  GVariant *variant;
+
+  g_variant_iter_init (&iter, stocks);
+  while (g_variant_iter_loop (&iter, "{bv}", &group, &variant))
+    {
+      if (group == TRUE)
+        {
+          const gchar *name;
+          GVariant *list;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&sv)", &name, &list);
+
+          gtk_tree_store_append (store, &row, parent);
+          gtk_tree_store_set (store, &row, COL_SYMBOL, name, -1);
+
+          add_to_store (store, list, &row);
+          g_variant_unref (list);
+        }
+      else
+        {
+          const gchar *symbol;
+          const gchar *label;
+          gdouble amount;
+          gdouble price;
+          gdouble comission;
+          gdouble currency_rate;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&s&sdddd)", &symbol, &label,
+                         &amount, &price, &comission, &currency_rate);
+
+          gtk_tree_store_append (store, &row, parent);
+          gtk_tree_store_set (store, &row,
+                              COL_SYMBOL, symbol,
+                              COL_LABEL, label,
+                              COL_AMOUNT, amount,
+                              COL_PRICE, price,
+                              COL_COMMISION, comission,
+                              COL_CURRENCY_RATE, currency_rate,
+                              -1);
+        }
+    }
+}
+
+static void
+settings_changed_cb (GSettings         *settings,
+                     const gchar       *key,
+                     InvestPreferences *preferences)
+{
+  if (preferences->ignore_changed_signal == TRUE)
+    return;
+
+  if (key == NULL || g_strcmp0 (key, "currency") == 0)
+    {
+      gboolean found;
+      gchar *currency;
+      gint i;
+
+      found = FALSE;
+      currency = g_settings_get_string (settings, "currency");
+
+      for (i = 0; currencies[i].code != NULL; i++)
+        {
+          if (g_strcmp0 (currency, currencies[i].code) == 0)
+            {
+              found = TRUE;
+              pick_currency (preferences, currency, FALSE);
+              break;
+            }
+        }
+
+      if (!found)
+        {
+          pick_currency (preferences, NULL, FALSE);
+        }
+
+      g_free (currency);
+    }
+
+  if (key == NULL || g_strcmp0 (key, "stocks") == 0)
+    {
+      GVariant *stocks;
+
+      stocks = g_settings_get_value (preferences->settings, "stocks");
+
+      gtk_tree_store_clear (preferences->store);
+      add_to_store (preferences->store, stocks, NULL);
+      g_variant_unref (stocks);
+    }
+}
+
+static void
+invest_preferences_constructed (GObject *object)
+{
+  InvestPreferences *preferences;
+
+  preferences = INVEST_PREFERENCES (object);
+
+  G_OBJECT_CLASS (invest_preferences_parent_class)->constructed (object);
+
+  setup_stocks (preferences);
+  setup_completion (preferences);
+
+  preferences->settings_changed_id =
+    g_signal_connect (preferences->settings, "changed",
+                      G_CALLBACK (settings_changed_cb), preferences);
+
+  g_settings_bind (preferences->settings, "indexexpansion",
+                   preferences->indexexpansion, "active",
+                   G_SETTINGS_BIND_DEFAULT);
+
+  g_settings_bind (preferences->settings, "hidecharts",
+                   preferences->hidecharts, "active",
+                   G_SETTINGS_BIND_DEFAULT);
+
+  settings_changed_cb (preferences->settings, NULL, preferences);
+}
+
+static void
+invest_preferences_dispose (GObject *object)
+{
+  InvestPreferences *prefs;
+
+  prefs = INVEST_PREFERENCES (object);
+
+  if (prefs->settings_changed_id > 0)
+    {
+      g_signal_handler_disconnect (prefs->settings, prefs->settings_changed_id);
+      prefs->settings_changed_id = 0;
+    }
+
+  g_clear_object (&prefs->store);
+  g_clear_object (&prefs->settings);
+
+  G_OBJECT_CLASS (invest_preferences_parent_class)->dispose (object);
+}
+
+static void
+invest_preferences_finalize (GObject *object)
+{
+  InvestPreferences *preferences;
+
+  preferences = INVEST_PREFERENCES (object);
+
+  g_free (preferences->currency_code);
+
+  G_OBJECT_CLASS (invest_preferences_parent_class)->finalize (object);
+}
+
+static void
+invest_preferences_set_property (GObject      *object,
+                                 guint         property_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  InvestPreferences *preferences;
+
+  preferences = INVEST_PREFERENCES (object);
+
+  switch (property_id)
+    {
+      case PROP_SETTINGS:
+        preferences->settings = g_value_dup_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_preferences_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_SETTINGS] =
+    g_param_spec_object ("settings", "settings", "settings", G_TYPE_SETTINGS,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_preferences_bind_template (GtkWidgetClass *widget_class)
+{
+  gchar *contents;
+  gsize length;
+  GBytes *bytes;
+
+  g_file_get_contents (BUILDERDIR "/prefs-dialog.ui", &contents, &length, NULL);
+
+  bytes = g_bytes_new_take (contents, length);
+  gtk_widget_class_set_template (widget_class, bytes);
+  g_bytes_unref (bytes);
+
+  gtk_widget_class_bind_template_child (widget_class, InvestPreferences, explanation);
+
+  gtk_widget_class_bind_template_callback (widget_class, addstock_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, addgroup_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, remove_clicked_cb);
+
+  gtk_widget_class_bind_template_child (widget_class, InvestPreferences, stocks);
+  gtk_widget_class_bind_template_callback (widget_class, stocks_key_press_event_cb);
+
+  gtk_widget_class_bind_template_child (widget_class, InvestPreferences, indexexpansion);
+  gtk_widget_class_bind_template_child (widget_class, InvestPreferences, hidecharts);
+
+  gtk_widget_class_bind_template_child (widget_class, InvestPreferences, currency);
+  gtk_widget_class_bind_template_callback (widget_class, currency_focus_out_event_cb);
+  gtk_widget_class_bind_template_callback (widget_class, currency_activate_cb);
+
+  gtk_widget_class_bind_template_callback (widget_class, prefs_dialog_response_cb);
+}
+
+static void
+invest_preferences_class_init (InvestPreferencesClass *preferences_class)
+{
+  GObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+
+  object_class = G_OBJECT_CLASS (preferences_class);
+  widget_class = GTK_WIDGET_CLASS (preferences_class);
+
+  object_class->constructed = invest_preferences_constructed;
+  object_class->dispose = invest_preferences_dispose;
+  object_class->finalize = invest_preferences_finalize;
+  object_class->set_property = invest_preferences_set_property;
+
+  invest_preferences_install_properties (object_class);
+  invest_preferences_bind_template (widget_class);
+}
+
+static void
+invest_preferences_init (InvestPreferences *preferences)
+{
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (preferences);
+
+  gtk_widget_init_template (widget);
+}
+
+GtkWidget *
+invest_preferences_new (GSettings *settings)
+{
+  return g_object_new (INVEST_TYPE_PREFERENCES,
+                       "settings", settings,
+                       NULL);
+}
+
+void
+invest_preferences_set_explanation (InvestPreferences *preferences,
+                                    const gchar       *explanation)
+{
+  if (explanation == NULL)
+    {
+      gtk_widget_hide (preferences->explanation);
+    }
+  else
+    {
+      gtk_label_set_markup (GTK_LABEL (preferences->explanation), explanation);
+      gtk_widget_show (preferences->explanation);
+    }
+}
diff --git a/invest-applet/invest/invest-preferences.h b/invest-applet/invest/invest-preferences.h
new file mode 100644
index 0000000..fe96da1
--- /dev/null
+++ b/invest-applet/invest/invest-preferences.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_PREFERENCES_H
+#define INVEST_PREFERENCES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_PREFERENCES invest_preferences_get_type ()
+G_DECLARE_FINAL_TYPE (InvestPreferences, invest_preferences,
+                      INVEST, PREFERENCES, GtkDialog)
+
+GtkWidget *invest_preferences_new             (GSettings         *settings);
+
+void       invest_preferences_set_explanation (InvestPreferences *preferences,
+                                               const gchar       *explanation);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-quotes-retriever.c b/invest-applet/invest/invest-quotes-retriever.c
new file mode 100644
index 0000000..e25a4d4
--- /dev/null
+++ b/invest-applet/invest/invest-quotes-retriever.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "invest-quotes-retriever.h"
+
+#define QUOTES_URL "http://finance.yahoo.com/d/quotes.csv?s=%s&f=snc4l1d1t1c1ohgv";
+#define CHUNK_SIZE 512 * 1024
+
+struct _InvestQuotesRetriever
+{
+  GObject       parent;
+
+  GCancellable *cancellable;
+
+  gchar        *symbol;
+  gchar        *filename;
+
+  guint8       *chunk;
+  GByteArray   *array;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SYMBOL,
+  PROP_FILENAME,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+enum
+{
+  COMPLETED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (InvestQuotesRetriever, invest_quotes_retriever, G_TYPE_OBJECT)
+
+static void
+write_cb (GObject      *object,
+          GAsyncResult *res,
+          gpointer      user_data)
+{
+  GError *error;
+  gsize bytes_written;
+  gboolean result;
+  InvestQuotesRetriever *retriever;
+
+  error = NULL;
+  result = g_output_stream_write_all_finish (G_OUTPUT_STREAM (object), res,
+                                             &bytes_written, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_QUOTES_RETRIEVER (user_data);
+
+  g_output_stream_close (G_OUTPUT_STREAM (object), NULL, NULL);
+  g_signal_emit (retriever, signals[COMPLETED], 0, result);
+  g_clear_error (&error);
+}
+
+static void
+save_cb (GObject      *object,
+         GAsyncResult *res,
+         gpointer      user_data)
+{
+  GError *error;
+  GFileOutputStream *stream;
+  InvestQuotesRetriever *retriever;
+
+  error = NULL;
+  stream = g_file_replace_finish (G_FILE (object), res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_QUOTES_RETRIEVER (user_data);
+
+  if (error != NULL)
+    {
+      g_signal_emit (retriever, signals[COMPLETED], 0, FALSE);
+      g_error_free (error);
+      return;
+    }
+
+  g_output_stream_write_all_async (G_OUTPUT_STREAM (stream),
+                                   (const void *) retriever->array->data,
+                                   retriever->array->len,
+                                   0, retriever->cancellable,
+                                   write_cb, retriever);
+
+  g_object_unref (stream);
+}
+
+static void
+download_cb (GObject      *object,
+             GAsyncResult *res,
+             gpointer      user_data)
+{
+  GError *error;
+  gssize done;
+  InvestQuotesRetriever *retriever;
+
+  error = NULL;
+  done = g_input_stream_read_finish (G_INPUT_STREAM (object), res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_QUOTES_RETRIEVER (user_data);
+
+  if (error != NULL)
+    {
+      g_signal_emit (retriever, signals[COMPLETED], 0, FALSE);
+      g_error_free (error);
+      return;
+    }
+
+  if (done > 0)
+    {
+      g_byte_array_append (retriever->array, retriever->chunk, done);
+
+      g_free (retriever->chunk);
+      retriever->chunk = g_malloc0 (CHUNK_SIZE + 1);
+
+      g_input_stream_read_async (G_INPUT_STREAM (object),
+                                 retriever->chunk, CHUNK_SIZE,
+                                 0, retriever->cancellable,
+                                 download_cb, retriever);
+    }
+  else if (done == 0)
+    {
+      const gchar *dir;
+      gchar *path;
+      GFile *file;
+
+      dir = g_get_user_cache_dir ();
+
+      path = g_build_filename (dir, "gnome-applets", "invest-applet", NULL);
+      g_mkdir_with_parents (path, 0700);
+      g_free (path);
+
+      path = g_build_filename (dir, "gnome-applets", "invest-applet",
+                               retriever->filename, NULL);
+
+      file = g_file_new_for_path (path);
+      g_free (path);
+
+      g_file_replace_async (file, NULL, FALSE, 0, 0,
+                            retriever->cancellable,
+                            save_cb, retriever);
+
+      g_object_unref (file);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static void
+ready_cb (GObject      *object,
+          GAsyncResult *res,
+          gpointer      user_data)
+{
+  GError *error;
+  GFileInputStream *stream;
+  InvestQuotesRetriever *retriever;
+
+  error = NULL;
+  stream = g_file_read_finish (G_FILE (object), res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  retriever = INVEST_QUOTES_RETRIEVER (user_data);
+
+  if (error != NULL)
+    {
+      g_signal_emit (retriever, signals[COMPLETED], 0, FALSE);
+      g_error_free (error);
+      return;
+    }
+
+  g_input_stream_read_async (G_INPUT_STREAM (stream),
+                             retriever->chunk, CHUNK_SIZE,
+                             0, retriever->cancellable,
+                             download_cb, retriever);
+
+  g_object_unref (stream);
+}
+
+static void
+invest_quotes_retriever_dispose (GObject *object)
+{
+  InvestQuotesRetriever *retriever;
+
+  retriever = INVEST_QUOTES_RETRIEVER (object);
+
+  if (retriever->cancellable != NULL)
+    {
+      g_cancellable_cancel (retriever->cancellable);
+      g_clear_object (&retriever->cancellable);
+    }
+
+  G_OBJECT_CLASS (invest_quotes_retriever_parent_class)->dispose (object);
+}
+
+static void
+invest_quotes_retriever_finalize (GObject *object)
+{
+  InvestQuotesRetriever *retriever;
+
+  retriever = INVEST_QUOTES_RETRIEVER (object);
+
+  g_free (retriever->symbol);
+  g_free (retriever->filename);
+
+  g_free (retriever->chunk);
+  g_byte_array_free (retriever->array, TRUE);
+
+  G_OBJECT_CLASS (invest_quotes_retriever_parent_class)->finalize (object);
+}
+
+static void
+invest_quotes_retriever_set_property (GObject      *object,
+                                      guint         property_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  InvestQuotesRetriever *retriever;
+
+  retriever = INVEST_QUOTES_RETRIEVER (object);
+
+  switch (property_id)
+    {
+      case PROP_SYMBOL:
+        retriever->symbol = g_value_dup_string (value);
+        break;
+
+      case PROP_FILENAME:
+        retriever->filename = g_value_dup_string (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_quotes_retriever_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_SYMBOL] =
+    g_param_spec_string ("symbol", "symbol", "symbol", NULL,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_FILENAME] =
+    g_param_spec_string ("filename", "filename", "filename", NULL,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_quotes_retriever_install_signals (GObjectClass *object_class)
+{
+  signals[COMPLETED] =
+    g_signal_new ("completed", INVEST_TYPE_QUOTES_RETRIEVER, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+}
+
+static void
+invest_quotes_retriever_class_init (InvestQuotesRetrieverClass *retriever_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (retriever_class);
+
+  object_class->dispose = invest_quotes_retriever_dispose;
+  object_class->finalize = invest_quotes_retriever_finalize;
+  object_class->set_property = invest_quotes_retriever_set_property;
+
+  invest_quotes_retriever_install_properties (object_class);
+  invest_quotes_retriever_install_signals (object_class);
+}
+
+static void
+invest_quotes_retriever_init (InvestQuotesRetriever *retriever)
+{
+  retriever->cancellable = g_cancellable_new ();
+
+  retriever->chunk = g_malloc0 (CHUNK_SIZE + 1);
+  retriever->array = g_byte_array_new ();
+}
+
+InvestQuotesRetriever *
+invest_quotes_retriever_new (const gchar *symbol,
+                             const gchar *filename)
+{
+  return g_object_new (INVEST_TYPE_QUOTES_RETRIEVER,
+                       "symbol", symbol,
+                       "filename", filename,
+                       NULL);
+}
+
+void
+invest_quotes_retriever_start (InvestQuotesRetriever *retriever)
+{
+  gchar *uri;
+  GFile *file;
+
+  uri = g_strdup_printf (QUOTES_URL, retriever->symbol);
+  file = g_file_new_for_uri (uri);
+  g_free (uri);
+
+  g_file_read_async (file, 0, retriever->cancellable, ready_cb, retriever);
+  g_object_unref (file);
+}
+
+const gchar *
+invest_quotes_retriever_get_symbol (InvestQuotesRetriever *retriever)
+{
+  return retriever->symbol;
+}
diff --git a/invest-applet/invest/invest-quotes-retriever.h b/invest-applet/invest/invest-quotes-retriever.h
new file mode 100644
index 0000000..d033278
--- /dev/null
+++ b/invest-applet/invest/invest-quotes-retriever.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_QUOTES_RETRIEVER_H
+#define INVEST_QUOTES_RETRIEVER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_QUOTES_RETRIEVER invest_quotes_retriever_get_type ()
+G_DECLARE_FINAL_TYPE (InvestQuotesRetriever, invest_quotes_retriever,
+                      INVEST, QUOTES_RETRIEVER, GObject)
+
+InvestQuotesRetriever *invest_quotes_retriever_new        (const gchar           *symbol,
+                                                           const gchar           *filename);
+
+void                   invest_quotes_retriever_start      (InvestQuotesRetriever *retriever);
+
+const gchar           *invest_quotes_retriever_get_symbol (InvestQuotesRetriever *retriever);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-quotes.c b/invest-applet/invest/invest-quotes.c
new file mode 100644
index 0000000..d5d1f10
--- /dev/null
+++ b/invest-applet/invest/invest-quotes.c
@@ -0,0 +1,1427 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "invest-cache.h"
+#include "invest-image-retriever.h"
+#include "invest-quotes.h"
+#include "invest-quotes-retriever.h"
+
+#define AUTOREFRESH_TIMEOUT 15 * 60 * 1000
+
+struct _InvestQuotes
+{
+  GtkTreeStore   parent;
+
+  GSettings     *settings;
+  GHashTable    *retrievers;
+
+  GVariant      *stocks;
+  gchar        **symbols;
+
+  gboolean       simple_quotes_only;
+
+  guint          update_id;
+
+  gint           count;
+  gdouble        change;
+  gdouble        avg_change;
+  gchar         *updated;
+
+  GPtrArray     *currencies;
+  GHashTable    *statistics;
+
+  gboolean       valid;
+};
+
+enum
+{
+  COL_SYMBOL,
+  COL_LABEL,
+  COL_CURRENCY,
+  COL_TICKER_ONLY,
+  COL_BALANCE,
+  COL_BALANCE_PCT,
+  COL_VALUE,
+  COL_VARIATION_PCT,
+  COL_PIXBUF,
+  COL_VARIANT,
+
+  NUM_COLS
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SETTINGS,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+enum
+{
+  UPDATE_ICON,
+  UPDATE_TOOLTIP,
+
+  RELOADED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (InvestQuotes, invest_quotes, GTK_TYPE_TREE_STORE)
+
+typedef struct
+{
+  InvestQuotes *quotes;
+  GtkTreeIter   row;
+} ImageData;
+
+static void
+image_completed_cb (InvestImageRetriever *retriever,
+                    GdkPixbuf            *pixbuf,
+                    ImageData            *data)
+{
+  GtkTreeStore *store;
+  const gchar *url;
+
+  store = GTK_TREE_STORE (data->quotes);
+  url = invest_image_retriever_get_url (retriever);
+
+  gtk_tree_store_set (store, &data->row, COL_PIXBUF, pixbuf, -1);
+
+  g_hash_table_remove (data->quotes->retrievers, url);
+}
+
+static void
+retrieve_image (InvestQuotes *quotes,
+                const gchar  *symbol,
+                GtkTreeIter  *row)
+{
+  gchar *url;
+  InvestImageRetriever *retriever;
+  ImageData *data;
+
+  if (g_settings_get_boolean (quotes->settings, "hidecharts"))
+    return;
+
+  url = g_strdup_printf ("http://ichart.yahoo.com/h?s=%s";, symbol);
+  retriever = invest_image_retriever_new (url);
+
+  data = g_new0 (ImageData, 1);
+
+  data->quotes = quotes;
+  data->row = *row;
+
+  g_signal_connect_data (retriever, "completed",
+                         G_CALLBACK (image_completed_cb),
+                         data, (GClosureNotify) g_free,
+                         G_CONNECT_AFTER);
+
+  g_hash_table_replace (quotes->retrievers, url, retriever);
+  invest_image_retriever_start (retriever);
+}
+
+typedef struct
+{
+  gdouble balance;
+  gdouble paid;
+} StatisticsData;
+
+static void
+update_tooltip (InvestQuotes *quotes)
+{
+  GPtrArray *array;
+  gchar *tmp;
+  gchar **tooltip;
+  gchar *text;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  array = g_ptr_array_new ();
+
+  if (quotes->count > 0)
+    {
+      /* Translators: This is share-market jargon. It is the average
+       * percentage change of all stock prices. The %s gets replaced
+       * with the string value of the change (localized), including
+       * the percent sign.
+       */
+      tmp = g_strdup_printf (_("Average change: %+.2f%%"), quotes->avg_change);
+      g_ptr_array_add (array, tmp);
+    }
+
+  g_hash_table_iter_init (&iter, quotes->statistics);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const gchar *currency;
+      StatisticsData *data;
+      gdouble change;
+
+      currency = (const gchar *) key;
+      data = (StatisticsData *) value;
+
+      change = data->balance / data->paid * 100;
+
+      /* Translators: This is share-market jargon. It refers to the total
+       * difference between the current price and purchase price for all
+       * the shares put together for a particular currency. i.e. How much
+       * money would be earned if they were sold right now. The first string
+       * is the change value, the second the currency, and the third value
+       * is the percentage of the change, formatted using user's locale.
+       */
+      tmp = g_strdup_printf (_("Positions balance: %'+.2f %s (%'+.2f%%)"),
+                             data->balance, currency, change);
+      g_ptr_array_add (array, tmp);
+    }
+
+  if (quotes->updated != NULL)
+    {
+      tmp = g_strdup_printf (_("Updated at %s"), quotes->updated);
+      g_ptr_array_add (array, tmp);
+    }
+
+  g_ptr_array_add (array, NULL);
+  tooltip = (gchar **) g_ptr_array_free (array, FALSE);
+
+  text = g_strjoinv ("\n", tooltip);
+  g_strfreev (tooltip);
+
+  g_signal_emit (quotes, signals[UPDATE_TOOLTIP], 0, text);
+  g_free (text);
+}
+
+static void
+add_balance_change (InvestQuotes *quotes,
+                    gdouble       balance,
+                    gdouble       change,
+                    const gchar  *currency)
+{
+  StatisticsData *data;
+
+  if (balance == 0.0 && change == 0.0)
+    return;
+
+  if (currency == NULL)
+    currency = "";
+
+  data = (StatisticsData *) g_hash_table_lookup (quotes->statistics, currency);
+
+  if (data != NULL)
+    {
+      data->balance += balance;
+      data->paid += balance / change * 100;
+    }
+  else
+    {
+      data = g_new0 (StatisticsData, 1);
+
+      data->balance = balance;
+      data->paid = balance / change * 100;
+
+      g_hash_table_insert (quotes->statistics, g_strdup (currency), data);
+    }
+}
+
+static InvestQuote *
+get_quote (InvestQuotes *quotes,
+           InvestCache  *cache,
+           const gchar  *symbol)
+{
+  InvestQuote *quote;
+
+  quote = invest_cache_get (cache, symbol);
+  if (quote == NULL)
+    return NULL;
+
+  if (quote->currency != NULL)
+    {
+      gboolean found;
+      guint i;
+
+      found = FALSE;
+      for (i = 0; i < quotes->currencies->len; i++)
+        {
+          const gchar *currency;
+
+          currency = (const gchar *) quotes->currencies->pdata[i];
+
+          if (g_strcmp0 (currency, quote->currency) == 0)
+            {
+              found = TRUE;
+              break;
+            }
+        }
+
+      if (found == FALSE)
+        g_ptr_array_add (quotes->currencies, g_strdup (quote->currency));
+    }
+
+  return quote;
+}
+
+static void
+add_quotes (InvestQuotes *quotes,
+            InvestCache  *cache,
+            GVariant     *stocks,
+            GtkTreeIter  *parent)
+{
+  GtkTreeStore *store;
+  GVariantIter iter;
+  gboolean group;
+  GVariant *variant;
+
+  store = GTK_TREE_STORE (quotes);
+
+  g_variant_iter_init (&iter, stocks);
+  while (g_variant_iter_loop (&iter, "{bv}", &group, &variant))
+    {
+      if (group == TRUE)
+        {
+          const gchar *name;
+          GVariant *list;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&sv)", &name, &list);
+
+          gtk_tree_store_append (store, &row, parent);
+          gtk_tree_store_set (store, &row,
+                              COL_LABEL, name,
+                              COL_TICKER_ONLY, TRUE,
+                              -1);
+
+          add_quotes (quotes, cache, list, &row);
+          g_variant_unref (list);
+        }
+      else
+        {
+          const gchar *symbol;
+          const gchar *label;
+          gdouble amount;
+          gdouble price;
+          gdouble comission;
+          gdouble currency_rate;
+          InvestQuote *quote;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&s&sdddd)", &symbol, &label,
+                         &amount, &price, &comission, &currency_rate);
+
+          quote = get_quote (quotes, cache, symbol);
+          if (quote == NULL)
+            continue;
+
+          if (strlen (label) == 0)
+            {
+              if (strlen (quote->name) != 0)
+                label = quote->name;
+              else
+                label = symbol;
+            }
+
+          gtk_tree_store_append (store, &row, parent);
+
+          if (amount == 0.0)
+            {
+              gtk_tree_store_set (store, &row,
+                                  COL_SYMBOL, symbol,
+                                  COL_LABEL, label,
+                                  COL_CURRENCY, quote->currency,
+                                  COL_TICKER_ONLY, TRUE,
+                                  COL_BALANCE, 0.0,
+                                  COL_BALANCE_PCT, 0.0,
+                                  COL_VALUE, quote->last_trade,
+                                  COL_VARIATION_PCT, quote->change_pct,
+                                  COL_PIXBUF, NULL,
+                                  COL_VARIANT, variant,
+                                  -1);
+            }
+          else
+            {
+              gdouble current;
+              gdouble paid;
+              gdouble balance;
+              gdouble change;
+
+              current = amount * quote->last_trade;
+              paid = amount * price + comission;
+              balance = current - paid;
+
+              if (paid != 0.0)
+                {
+                  change = 100 * balance / paid;
+                }
+              else
+                {
+                  /* Not technically correct, but it will look more
+                   * intuitive than the real result of infinity.
+                   */
+                  change = 100;
+                }
+
+              gtk_tree_store_set (store, &row,
+                                  COL_SYMBOL, symbol,
+                                  COL_LABEL, label,
+                                  COL_CURRENCY, quote->currency,
+                                  COL_TICKER_ONLY, FALSE,
+                                  COL_BALANCE, balance,
+                                  COL_BALANCE_PCT, change,
+                                  COL_VALUE, quote->last_trade,
+                                  COL_VARIATION_PCT, quote->change_pct,
+                                  COL_PIXBUF, NULL,
+                                  COL_VARIANT, variant,
+                                  -1);
+
+              add_balance_change (quotes, balance,change, quote->currency);
+            }
+
+          quotes->change += quote->change_pct;
+          quotes->count++;
+
+          retrieve_image (quotes, symbol, &row);
+        }
+    }
+}
+
+static void
+populate (InvestQuotes *quotes,
+          InvestCache  *cache)
+{
+  add_quotes (quotes, cache, quotes->stocks, NULL);
+
+  if (quotes->count > 0)
+    {
+      gint icon;
+
+      quotes->avg_change = quotes->change / quotes->count;
+
+      icon = 0;
+      if (quotes->avg_change != 0.0)
+        icon = quotes->avg_change / ABS (quotes->avg_change);
+
+      g_signal_emit (quotes, signals[UPDATE_ICON], 0, icon);
+    }
+  else
+    {
+      quotes->avg_change = 0.0;
+    }
+
+  quotes->valid = TRUE;
+}
+
+static void
+add_indices (GPtrArray   *symbols,
+             GVariant    *stocks,
+             GtkTreeIter *parent)
+{
+  GVariantIter iter;
+  gboolean group;
+  GVariant *variant;
+
+  g_variant_iter_init (&iter, stocks);
+  while (g_variant_iter_loop (&iter, "{bv}", &group, &variant))
+    {
+      if (group == TRUE)
+        {
+          const gchar *name;
+          GVariant *list;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&sv)", &name, &list);
+
+          add_indices (symbols, list, &row);
+          g_variant_unref (list);
+        }
+      else
+        {
+          const gchar *symbol;
+          const gchar *label;
+          gdouble amount;
+          gdouble price;
+          gdouble comission;
+          gdouble currency_rate;
+          gboolean found;
+          guint i;
+
+          g_variant_get (variant, "(&s&sdddd)", &symbol, &label,
+                         &amount, &price, &comission, &currency_rate);
+
+          if (!g_str_has_prefix (symbol, "^"))
+            continue;
+
+          found = FALSE;
+          for (i = 0; i < symbols->len; i++)
+            {
+              const gchar *tmp;
+
+              tmp = (const gchar *) symbols->pdata[i];
+
+              if (g_strcmp0 (symbol, tmp) == 0)
+                {
+                  found = TRUE;
+                  break;
+                }
+            }
+
+          if (!found)
+            g_ptr_array_add (symbols, g_strdup (symbol));
+        }
+    }
+}
+
+static gchar **
+get_indices (InvestQuotes *quotes)
+{
+  GPtrArray *indices;
+
+  indices = g_ptr_array_new ();
+
+  add_indices (indices, quotes->stocks, NULL);
+  g_ptr_array_add (indices, NULL);
+
+  return (gchar **) g_ptr_array_free (indices, FALSE);
+}
+
+static void
+add_symbols (InvestQuotes *quotes,
+             GPtrArray    *symbols,
+             GVariant     *stocks,
+             GtkTreeIter  *parent)
+{
+  GVariantIter iter;
+  gboolean group;
+  GVariant *variant;
+
+  g_variant_iter_init (&iter, stocks);
+  while (g_variant_iter_loop (&iter, "{bv}", &group, &variant))
+    {
+      if (group == TRUE)
+        {
+          const gchar *name;
+          GVariant *list;
+          GtkTreeIter row;
+
+          g_variant_get (variant, "(&sv)", &name, &list);
+
+          add_symbols (quotes, symbols, list, &row);
+          g_variant_unref (list);
+        }
+      else
+        {
+          const gchar *symbol;
+          const gchar *label;
+          gdouble amount;
+          gdouble price;
+          gdouble comission;
+          gdouble currency_rate;
+          gboolean found;
+          guint i;
+
+          g_variant_get (variant, "(&s&sdddd)", &symbol, &label,
+                         &amount, &price, &comission, &currency_rate);
+
+          found = FALSE;
+          for (i = 0; i < symbols->len; i++)
+            {
+              const gchar *tmp;
+
+              tmp = (const gchar *) symbols->pdata[i];
+
+              if (g_strcmp0 (symbol, tmp) == 0)
+                {
+                  found = TRUE;
+                  break;
+                }
+            }
+
+          if (amount != 0.0)
+            quotes->simple_quotes_only = FALSE;
+
+          if (!found)
+            g_ptr_array_add (symbols, g_strdup (symbol));
+        }
+    }
+}
+
+static void
+load_symbols (InvestQuotes *quotes)
+{
+  GPtrArray *symbols;
+
+  symbols = g_ptr_array_new ();
+
+  quotes->simple_quotes_only = TRUE;
+
+  add_symbols (quotes, symbols, quotes->stocks, NULL);
+  g_ptr_array_add (symbols, NULL);
+
+  g_strfreev (quotes->symbols);
+  quotes->symbols = (gchar **) g_ptr_array_free (symbols, FALSE);
+}
+
+static void
+load_quotes (InvestQuotes *quotes)
+{
+  GtkTreeStore *store;
+  InvestCache *cache;
+  GDateTime *time;
+
+  store = GTK_TREE_STORE (quotes);
+
+  gtk_tree_store_clear (store);
+
+  quotes->count = 0;
+  quotes->change = 0.0;
+  quotes->avg_change = 0.0;
+  quotes->valid = FALSE;
+
+  if (quotes->currencies != NULL)
+    g_ptr_array_free (quotes->currencies, TRUE);
+  quotes->currencies = g_ptr_array_new ();
+
+  g_hash_table_remove_all (quotes->statistics);
+
+  cache = invest_cache_read ("quotes.csv");
+  if (cache == NULL)
+    return;
+
+  populate (quotes, cache);
+
+  time = g_date_time_new_from_timeval_local (&cache->mtime);
+  if (time != NULL)
+    {
+      g_free (quotes->updated);
+      quotes->updated = g_date_time_format (time, "%H:%M");
+      g_date_time_unref (time);
+    }
+  else
+    {
+      g_free (quotes->updated);
+      quotes->updated = NULL;
+    }
+
+  update_tooltip (quotes);
+}
+
+typedef struct
+{
+  gchar     *symbol;
+  GPtrArray *paths;
+} ForeachData;
+
+static gboolean
+find_stock_cb (GtkTreeModel *model,
+               GtkTreePath  *path,
+               GtkTreeIter  *iter,
+               gpointer      user_data)
+{
+  ForeachData *data;
+  gchar *symbol;
+
+  data = (ForeachData *) user_data;
+
+  gtk_tree_model_get (model, iter, COL_SYMBOL, &symbol, -1);
+
+  if (g_strcmp0 (symbol, data->symbol) == 0)
+    g_ptr_array_add (data->paths, gtk_tree_path_to_string (path));
+
+  g_free (symbol);
+
+  return FALSE;
+}
+
+static gboolean
+expand_index (InvestQuotes *quotes,
+              const gchar  *index)
+{
+  gchar *filename;
+  InvestCache *cache;
+  GtkTreeModel *model;
+  ForeachData *data;
+  gchar **paths;
+  gboolean valid;
+  guint i;
+
+  filename = g_strdup_printf ("quotes.%s.csv", index);
+  cache = invest_cache_read (filename);
+  g_free (filename);
+
+  if (cache == NULL)
+    return FALSE;
+
+  model = GTK_TREE_MODEL (quotes);
+  data = g_new0 (ForeachData, 1);
+
+  data->symbol = g_strdup (index);
+  data->paths = g_ptr_array_new ();
+
+  gtk_tree_model_foreach (model, find_stock_cb, data);
+  g_ptr_array_add (data->paths, NULL);
+
+  g_free (data->symbol);
+  paths = (gchar **) g_ptr_array_free (data->paths, FALSE);
+  g_free (data);
+
+  valid = FALSE;
+  for (i = 0; paths[i] != NULL; i++)
+    {
+      guint j;
+
+      for (j = 0; j < cache->n_quotes; j++)
+        {
+          const gchar *symbol;
+          InvestQuote *quote;
+
+          symbol = cache->quotes[j]->symbol;
+          quote = get_quote (quotes, cache, symbol);
+
+          if (quote != NULL)
+            {
+              GtkTreePath *path;
+              GtkTreeIter iter;
+
+              path = gtk_tree_path_new_from_string (paths[i]);
+              if (gtk_tree_model_get_iter (model, &iter, path))
+                {
+                  GtkTreeIter row;
+
+                  gtk_tree_store_insert (GTK_TREE_STORE (quotes), &row, &iter, 0);
+                  gtk_tree_store_set (GTK_TREE_STORE (quotes), &row,
+                                      COL_SYMBOL, symbol,
+                                      COL_LABEL, quote->name,
+                                      COL_CURRENCY, quote->currency,
+                                      COL_TICKER_ONLY, TRUE,
+                                      COL_BALANCE, 0.0,
+                                      COL_BALANCE_PCT, 0.0,
+                                      COL_VALUE, quote->last_trade,
+                                      COL_VARIATION_PCT, quote->change_pct,
+                                      COL_PIXBUF, NULL,
+                                      COL_VARIANT, NULL,
+                                      -1);
+
+                  retrieve_image (quotes, symbol, &row);
+                }
+
+              gtk_tree_path_free (path);
+            }
+        }
+
+      valid = TRUE;
+    }
+
+  g_strfreev (paths);
+  invest_cache_free (cache);
+
+  return valid;
+}
+
+static void
+load_all_index_quotes (InvestQuotes *quotes)
+{
+  const gchar *cache;
+  gchar *path;
+  GDir *dir;
+  GRegex *regex;
+  const gchar *filename;
+
+  if (!g_settings_get_boolean (quotes->settings, "indexexpansion"))
+    return;
+
+  cache = g_get_user_cache_dir ();
+  path = g_build_filename (cache, "gnome-applets", "invest-applet", NULL);
+  dir = g_dir_open (path, 0, NULL);
+
+  if (dir == NULL)
+    {
+      g_free (path);
+      return;
+    }
+
+  regex = g_regex_new ("quotes.([^.]+).csv", 0, 0, NULL);
+  filename = g_dir_read_name (dir);
+
+  while (filename != NULL)
+    {
+      GMatchInfo *match_info;
+
+      g_regex_match (regex, filename, 0, &match_info);
+
+      while (g_match_info_matches (match_info))
+        {
+          gchar *index;
+
+          index = g_match_info_fetch (match_info, 1);
+
+          if (expand_index (quotes, index) == 0)
+            {
+              gchar *delete_filename;
+
+              delete_filename = g_build_filename (path, filename, NULL);
+
+              g_unlink (delete_filename);
+              g_free (delete_filename);
+            }
+
+          g_free (index);
+
+          g_match_info_next (match_info, NULL);
+        }
+
+      g_match_info_free (match_info);
+      filename = g_dir_read_name (dir);
+    }
+
+  g_regex_unref (regex);
+  g_dir_close (dir);
+  g_free (path);
+}
+
+typedef struct
+{
+  gdouble rate;
+} RateData;
+
+static void
+convert_currencies (InvestQuotes *quotes,
+                    InvestCache  *cache,
+                    const gchar  *target_currency)
+{
+  GHashTable *rates;
+  guint i;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gboolean valid;
+
+  /* reset the overall balance */
+  g_hash_table_remove_all (quotes->statistics);
+
+  /* collect the rates for the currencies */
+  rates = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  for (i = 0; i < cache->n_quotes; i++)
+    {
+      InvestQuote *quote;
+      RateData *data;
+
+      quote = cache->quotes[i];
+
+      data = g_new0 (RateData, 1);
+      data->rate = quote->last_trade;
+
+      g_hash_table_insert (rates, g_strndup (quote->symbol, 3), data);
+    }
+
+  /* convert all non target currencies */
+  model = GTK_TREE_MODEL (quotes);
+  valid = gtk_tree_model_get_iter_first (model, &iter);
+
+  while (valid)
+    {
+      gchar *currency;
+      gchar *symbol;
+      gdouble balance;
+      gdouble change;
+
+      gtk_tree_model_get (model, &iter, COL_CURRENCY, &currency, -1);
+
+      if (currency == NULL)
+        {
+          valid = gtk_tree_model_iter_next (model, &iter);
+          continue;
+        }
+
+      gtk_tree_model_get (model, &iter, COL_SYMBOL, &symbol, -1);
+
+      /* Ignore stocks that are currency conversions and only convert
+       * stocks that are not in the target currency and if we have a
+       * conversion rate.
+       */
+      if (!(strlen (symbol) == 8 && g_str_has_suffix (symbol, "=X")) &&
+          g_strcmp0 (currency, target_currency) != 0 &&
+          g_hash_table_lookup (rates, currency) != NULL)
+        {
+          RateData *data;
+          GtkTreeStore *store;
+          gboolean ticker_only;
+          gdouble value;
+
+          data = (RateData *) g_hash_table_lookup (rates, currency);
+          store = GTK_TREE_STORE (quotes);
+
+          gtk_tree_model_get (model, &iter,
+                              COL_TICKER_ONLY, &ticker_only,
+                              COL_VALUE, &value,
+                              -1);
+
+          /* first convert the balance, it needs the original value */
+          if (ticker_only == FALSE)
+            {
+              GVariant *variant;
+              gdouble amount;
+              gdouble price;
+              gdouble comission;
+              gdouble currency_rate;
+              gdouble current;
+              gdouble paid;
+
+              gtk_tree_model_get (model, &iter, COL_VARIANT, &variant, -1);
+              g_variant_get (variant, "(&s&sdddd)", NULL, NULL, &amount,
+                             &price, &comission, &currency_rate);
+
+              current = 0.0;
+              paid = 0.0;
+
+              if (amount != 0.0)
+                {
+                  /* if the buy rate is invalid, use 1.0 */
+                  if (currency_rate <= 0.0)
+                    currency_rate = 1.0;
+
+                  /* current value is the current rate * amount * value */
+                  current = data->rate * amount * value;
+
+                  /* paid is buy rate * (amount * price + commission) */
+                  paid = currency_rate * (amount * price + comission);
+                }
+
+              balance = current - paid;
+
+              if (paid != 0.0)
+                {
+                  change = 100 * balance / paid;
+                }
+              else
+                {
+                  /* Not technically correct, but it will look more
+                   * intuitive than the real result of infinity.
+                   */
+                  change = 100;
+                }
+
+              gtk_tree_store_set (store, &iter,
+                                  COL_BALANCE, balance,
+                                  COL_BALANCE_PCT, change,
+                                  -1);
+
+              add_balance_change (quotes, balance, change, target_currency);
+            }
+
+          /* now, convert the value */
+          gtk_tree_store_set (store, &iter,
+                              COL_VALUE, value * data->rate,
+                              COL_CURRENCY, target_currency,
+                              -1);
+        }
+      else
+        {
+          gtk_tree_model_get (model, &iter,
+                              COL_BALANCE, &balance,
+                              COL_BALANCE_PCT, &change,
+                              -1);
+
+          add_balance_change (quotes, balance, change, currency);
+        }
+
+      g_free (currency);
+      g_free (symbol);
+
+      valid = gtk_tree_model_iter_next (model, &iter);
+    }
+
+  g_hash_table_destroy (rates);
+}
+
+static void
+load_currencies (InvestQuotes *quotes)
+{
+  gchar *target_currency;
+  InvestCache *cache;
+
+  /* If there is no target currency, this method should never have
+   * been called.
+   */
+  target_currency = g_settings_get_string (quotes->settings, "currency");
+  if (!target_currency || *target_currency == '\0')
+    {
+      g_free (target_currency);
+      return;
+    }
+
+  cache = invest_cache_read ("currencies.csv");
+  if (cache == NULL)
+    {
+      g_free (target_currency);
+      return;
+    }
+
+  convert_currencies (quotes, cache, target_currency);
+
+  invest_cache_free (cache);
+  g_free (target_currency);
+}
+
+static void
+currencies_completed_cb (InvestQuotesRetriever *retriever,
+                         gboolean               retrieved,
+                         InvestQuotes          *quotes)
+{
+  if (!retrieved)
+    g_warning ("Failed to retrieve currency rates!");
+  else
+    load_currencies (quotes);
+
+  update_tooltip (quotes);
+
+  g_hash_table_remove (quotes->retrievers, "currencies");
+}
+
+static void
+retrieve_currencies (InvestQuotes *quotes)
+{
+  gchar *target_currency;
+  GPtrArray *array;
+  guint i;
+  gchar **symbols;
+
+  target_currency = g_settings_get_string (quotes->settings, "currency");
+  if (!target_currency || *target_currency == '\0')
+    {
+      g_free (target_currency);
+      return;
+    }
+
+  array = g_ptr_array_new ();
+  for (i = 0; i < quotes->currencies->len; i++)
+    {
+      const gchar *currency;
+      gchar *symbol;
+
+      currency = (const gchar *) quotes->currencies->pdata[i];
+
+      if (g_strcmp0 (currency, target_currency) == 0)
+        continue;
+
+      symbol = g_strdup_printf ("%s%s=X", currency, target_currency);
+      g_ptr_array_add (array, symbol);
+    }
+
+  g_free (target_currency);
+
+  g_ptr_array_add (array, NULL);
+  symbols = (gchar **) g_ptr_array_free (array, FALSE);
+
+  if (g_strv_length (symbols) > 0)
+    {
+      gchar *tmp;
+      InvestQuotesRetriever *retriever;
+
+      tmp = g_strjoinv (",", symbols);
+      retriever = invest_quotes_retriever_new (tmp, "currencies.csv");
+      g_free (tmp);
+
+      g_signal_connect (retriever, "completed",
+                        G_CALLBACK (currencies_completed_cb),
+                        quotes);
+
+      g_hash_table_replace (quotes->retrievers, g_strdup ("currencies"), retriever);
+      invest_quotes_retriever_start (retriever);
+    }
+
+  g_strfreev (symbols);
+}
+
+static void
+index_completed_cb (InvestQuotesRetriever *retriever,
+                    gboolean               retrieved,
+                    InvestQuotes          *quotes)
+{
+  const gchar *index;
+
+  index = invest_quotes_retriever_get_symbol (retriever);
+
+  if (!retrieved)
+    {
+      g_warning ("Failed to retrieve quotes for index %s!", index);
+      return;
+    }
+
+  expand_index (quotes, index);
+  retrieve_currencies (quotes);
+
+  g_hash_table_remove (quotes->retrievers, index);
+}
+
+static void
+expand_indices (InvestQuotes *quotes)
+{
+  gchar **indices;
+  guint i;
+
+  if (!g_settings_get_boolean (quotes->settings, "indexexpansion"))
+    {
+      retrieve_currencies (quotes);
+      return;
+    }
+
+  indices = get_indices (quotes);
+
+  for (i = 0; indices[i] != NULL; i++)
+    {
+      gchar *filename;
+      InvestQuotesRetriever *retriever;
+
+      filename = g_strdup_printf ("quotes.%s.csv", indices[i]);
+      retriever = invest_quotes_retriever_new (indices[i], filename);
+      g_free (filename);
+
+      g_signal_connect (retriever, "completed",
+                        G_CALLBACK (index_completed_cb),
+                        quotes);
+
+      g_hash_table_replace (quotes->retrievers, g_strdup (indices[i]), retriever);
+      invest_quotes_retriever_start (retriever);
+    }
+
+  g_strfreev (indices);
+}
+
+static void
+quotes_completed_cb (InvestQuotesRetriever *retriever,
+                     gboolean               retrieved,
+                     InvestQuotes          *quotes)
+{
+  if (retrieved)
+    {
+      load_quotes (quotes);
+      expand_indices (quotes);
+    }
+  else
+    {
+      const gchar *text;
+
+      text = _("Invest could not connect to Yahoo! Finance");
+
+      g_signal_emit (quotes, signals[UPDATE_TOOLTIP], 0, text);
+    }
+
+  g_hash_table_remove (quotes->retrievers, "quotes");
+}
+
+static void
+add_update_timeout (InvestQuotes *quotes)
+{
+  gint interval;
+  GSourceFunc func;
+
+  if (quotes->update_id != 0)
+    return;
+
+  interval = AUTOREFRESH_TIMEOUT;
+  func = (GSourceFunc) invest_quotes_refresh;
+
+  quotes->update_id = g_timeout_add (interval, func, quotes);
+}
+
+static void
+invest_quotes_reload (InvestQuotes *quotes)
+{
+  g_clear_pointer (&quotes->stocks, g_variant_unref);
+  quotes->stocks = g_settings_get_value (quotes->settings, "stocks");
+
+  g_hash_table_remove_all (quotes->retrievers);
+
+  load_symbols (quotes);
+  load_quotes (quotes);
+  load_all_index_quotes (quotes);
+  load_currencies (quotes);
+
+  g_signal_emit (quotes, signals[RELOADED], 0);
+
+  add_update_timeout (quotes);
+  invest_quotes_refresh (quotes);
+}
+
+static void
+settings_changed_cb (GSettings    *settings,
+                     const gchar  *key,
+                     InvestQuotes *quotes)
+{
+  invest_quotes_reload (quotes);
+}
+
+static void
+network_changed_cb (GNetworkMonitor *monitor,
+                    gboolean         available,
+                    InvestQuotes    *quotes)
+{
+  if (!available)
+    return;
+
+  add_update_timeout (quotes);
+  invest_quotes_refresh (quotes);
+}
+
+static void
+invest_quotes_constructed (GObject *object)
+{
+  InvestQuotes *quotes;
+
+  quotes = INVEST_QUOTES (object);
+
+  G_OBJECT_CLASS (invest_quotes_parent_class)->constructed (object);
+
+  g_signal_connect (quotes->settings, "changed",
+                    G_CALLBACK (settings_changed_cb), quotes);
+
+  g_signal_connect (g_network_monitor_get_default (), "network-changed",
+                    G_CALLBACK (network_changed_cb), quotes);
+
+  invest_quotes_reload (quotes);
+}
+
+static void
+invest_quotes_dispose (GObject *object)
+{
+  InvestQuotes *quotes;
+
+  quotes = INVEST_QUOTES (object);
+
+  if (quotes->update_id != 0)
+    {
+      g_source_remove (quotes->update_id);
+      quotes->update_id = 0;
+    }
+
+  g_clear_pointer (&quotes->stocks, g_variant_unref);
+  g_clear_pointer (&quotes->retrievers, g_hash_table_destroy);
+  g_clear_pointer (&quotes->statistics, g_hash_table_destroy);
+  g_clear_object (&quotes->settings);
+
+  G_OBJECT_CLASS (invest_quotes_parent_class)->dispose (object);
+}
+
+static void
+invest_quotes_finalize (GObject *object)
+{
+  InvestQuotes *quotes;
+
+  quotes = INVEST_QUOTES (object);
+
+  g_strfreev (quotes->symbols);
+  g_free (quotes->updated);
+
+  if (quotes->currencies != NULL)
+    g_ptr_array_free (quotes->currencies, TRUE);
+
+  G_OBJECT_CLASS (invest_quotes_parent_class)->finalize (object);
+}
+
+static void
+invest_quotes_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  InvestQuotes *quotes;
+
+  quotes = INVEST_QUOTES (object);
+
+  switch (property_id)
+    {
+      case PROP_SETTINGS:
+        quotes->settings = g_value_dup_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_quotes_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_SETTINGS] =
+    g_param_spec_object ("settings", "settings", "settings", G_TYPE_SETTINGS,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_quotes_install_signals (GObjectClass *object_class)
+{
+  signals[UPDATE_ICON] =
+    g_signal_new ("update-icon", INVEST_TYPE_QUOTES, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);
+
+  signals[UPDATE_TOOLTIP] =
+    g_signal_new ("update-tooltip", INVEST_TYPE_QUOTES, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+  signals[RELOADED] =
+    g_signal_new ("reloaded", INVEST_TYPE_QUOTES, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+invest_quotes_class_init (InvestQuotesClass *quotes_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (quotes_class);
+
+  object_class->constructed = invest_quotes_constructed;
+  object_class->dispose = invest_quotes_dispose;
+  object_class->finalize = invest_quotes_finalize;
+  object_class->set_property = invest_quotes_set_property;
+
+  invest_quotes_install_properties (object_class);
+  invest_quotes_install_signals (object_class);
+}
+
+static void
+invest_quotes_init (InvestQuotes *quotes)
+{
+  GtkTreeStore *store;
+  GType *types;
+
+  store = GTK_TREE_STORE (quotes);
+  types = g_new0 (GType, NUM_COLS);
+
+  types[COL_SYMBOL] = G_TYPE_STRING;
+  types[COL_LABEL] = G_TYPE_STRING;
+  types[COL_CURRENCY] = G_TYPE_STRING;
+  types[COL_TICKER_ONLY] = G_TYPE_BOOLEAN;
+  types[COL_BALANCE] = G_TYPE_DOUBLE;
+  types[COL_BALANCE_PCT] = G_TYPE_DOUBLE;
+  types[COL_VALUE] = G_TYPE_DOUBLE;
+  types[COL_VARIATION_PCT] = G_TYPE_DOUBLE;
+  types[COL_PIXBUF] = GDK_TYPE_PIXBUF;
+  types[COL_VARIANT] = G_TYPE_VARIANT;
+
+  gtk_tree_store_set_column_types (store, NUM_COLS, types);
+  g_free (types);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), COL_LABEL,
+                                        GTK_SORT_ASCENDING);
+
+  quotes->retrievers = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                              g_free, g_object_unref);
+
+  quotes->statistics = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                              g_free, g_free);
+}
+
+InvestQuotes *
+invest_quotes_new (GSettings *settings)
+{
+  return g_object_new (INVEST_TYPE_QUOTES,
+                       "settings", settings,
+                       NULL);
+}
+
+gboolean
+invest_quotes_has_stocks (InvestQuotes *quotes)
+{
+  gboolean valid;
+  gint i;
+
+  if (!quotes->symbols)
+    return FALSE;
+
+  valid = FALSE;
+  for (i = 0; quotes->symbols[i] != NULL; i++)
+    {
+      const gchar *symbol;
+
+      symbol = quotes->symbols[i];
+
+      if (symbol != NULL && symbol[0] != '\0')
+        {
+          valid = TRUE;
+          break;
+        }
+    }
+
+  return valid;
+}
+
+gboolean
+invest_quotes_is_valid (InvestQuotes *quotes)
+{
+  return quotes->valid;
+}
+
+gboolean
+invest_quotes_simple_quotes_only (InvestQuotes *quotes)
+{
+  return quotes->simple_quotes_only;
+}
+
+gboolean
+invest_quotes_refresh (InvestQuotes *quotes)
+{
+  GNetworkMonitor *nm;
+  gchar *symbols;
+  InvestQuotesRetriever *retriever;
+
+  nm = g_network_monitor_get_default ();
+
+  if (!g_network_monitor_get_network_available (nm))
+    {
+      if (quotes->update_id != 0)
+        {
+          g_source_remove (quotes->update_id);
+          quotes->update_id = 0;
+        }
+
+      return FALSE;
+    }
+
+  if (!invest_quotes_has_stocks (quotes))
+    return TRUE;
+
+  symbols = g_strjoinv (",", quotes->symbols);
+  retriever = invest_quotes_retriever_new (symbols, "quotes.csv");
+  g_free (symbols);
+
+  g_signal_connect (retriever, "completed",
+                    G_CALLBACK (quotes_completed_cb),
+                    quotes);
+
+  g_hash_table_replace (quotes->retrievers, g_strdup ("quotes"), retriever);
+  invest_quotes_retriever_start (retriever);
+
+  return TRUE;
+}
diff --git a/invest-applet/invest/invest-quotes.h b/invest-applet/invest/invest-quotes.h
new file mode 100644
index 0000000..36e824e
--- /dev/null
+++ b/invest-applet/invest/invest-quotes.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_QUOTES_H
+#define INVEST_QUOTES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_QUOTES invest_quotes_get_type ()
+G_DECLARE_FINAL_TYPE (InvestQuotes, invest_quotes,
+                      INVEST, QUOTES, GtkTreeStore)
+
+InvestQuotes *invest_quotes_new                (GSettings    *settings);
+
+gboolean      invest_quotes_has_stocks         (InvestQuotes *quotes);
+
+gboolean      invest_quotes_is_valid           (InvestQuotes *quotes);
+
+gboolean      invest_quotes_simple_quotes_only (InvestQuotes *quotes);
+
+gboolean      invest_quotes_refresh            (InvestQuotes *quotes);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-widget.c b/invest-applet/invest/invest-widget.c
new file mode 100644
index 0000000..24af592
--- /dev/null
+++ b/invest-applet/invest/invest-widget.c
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "invest-chart.h"
+#include "invest-widget.h"
+
+#define LIGHT -3
+#define MEDIUM -1
+
+struct _InvestWidget
+{
+  GtkTreeView        parent;
+
+  GSettings         *settings;
+  InvestQuotes      *quotes;
+
+  GtkTreeViewColumn *chart;
+  GtkTreeViewColumn *gain;
+  GtkTreeViewColumn *gain_pct;
+
+  gulong             changed_id;
+  gulong             reloaded_id;
+};
+
+enum
+{
+  COL_SYMBOL,
+  COL_LABEL,
+  COL_CURRENCY,
+  COL_TICKER_ONLY,
+  COL_BALANCE,
+  COL_BALANCE_PCT,
+  COL_VALUE,
+  COL_VARIATION_PCT,
+  COL_PIXBUF,
+  COL_VARIANT,
+
+  NUM_COLS
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SETTINGS,
+  PROP_QUOTES,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (InvestWidget, invest_widget, GTK_TYPE_TREE_VIEW)
+
+static const gchar *positive[] =
+  {
+    "#ffffff", "#ad7fa8", "#75507b", "#5c3566", "#729fcf",
+    "#3465a4", "#204a87", "#8ae234", "#73d216", "#4e9a06"
+  };
+
+static const gchar *negative[] =
+  {
+    "#ffffff", "#fce94f", "#e9b96e", "#fcaf3e", "#c17d11",
+    "#f57900", "#ce5c00", "#ef2929", "#cc0000", "#a40000"
+  };
+
+static void
+row_activated_cb (GtkTreeView       *tree_view,
+                  GtkTreePath       *path,
+                  GtkTreeViewColumn *column,
+                  InvestWidget      *widget)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gchar *symbol;
+
+  model = GTK_TREE_MODEL (widget->quotes);
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    return;
+
+  gtk_tree_model_get (model, &iter, COL_SYMBOL, &symbol, -1);
+
+  if (symbol == NULL)
+    return;
+
+  invest_chart_show_chart (symbol);
+  g_free (symbol);
+}
+
+static gboolean
+is_selected (InvestWidget *widget,
+             GtkTreeIter  *iter)
+{
+  GtkTreeView *tree_view;
+  GtkTreeSelection *selection;
+  GtkTreeModel *model;
+  GtkTreeIter selected;
+  GtkTreePath *a;
+  GtkTreePath *b;
+  gboolean ret;
+
+  tree_view = GTK_TREE_VIEW (widget);
+  selection = gtk_tree_view_get_selection (tree_view);
+
+  if (!gtk_tree_selection_get_selected (selection, &model, &selected))
+    return FALSE;
+
+  a = gtk_tree_model_get_path (GTK_TREE_MODEL (widget->quotes), iter);
+  b = gtk_tree_model_get_path (model, &selected);
+
+  ret = FALSE;
+  if (gtk_tree_path_compare (a, b) == 0)
+    ret = TRUE;
+
+  gtk_tree_path_free (a);
+  gtk_tree_path_free (b);
+
+  return ret;
+}
+
+static const gchar *
+get_color (InvestWidget *widget,
+           GtkTreeIter  *iter,
+           gint          field)
+{
+  gdouble value;
+  gint intensity;
+
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      field, &value, -1);
+
+  intensity = MEDIUM;
+  if (is_selected (widget, iter))
+    intensity = LIGHT;
+
+  if (value < 0.0)
+    return negative[G_N_ELEMENTS (negative) + intensity];
+
+  return positive[G_N_ELEMENTS (positive) + intensity];
+}
+
+static void
+changed_hidecharts_cb (GSettings    *settings,
+                       const gchar  *key,
+                       InvestWidget *widget)
+{
+  gboolean hidecharts;
+
+  hidecharts = g_settings_get_boolean (widget->settings, key);
+  gtk_tree_view_column_set_visible (widget->chart, !hidecharts);
+}
+
+static void
+reloaded_cb (InvestQuotes *quotes,
+             InvestWidget *widget)
+{
+  gboolean simple_quotes_only;
+
+  simple_quotes_only = invest_quotes_simple_quotes_only (quotes);
+
+  gtk_tree_view_column_set_visible (widget->gain, !simple_quotes_only);
+  gtk_tree_view_column_set_visible (widget->gain_pct, !simple_quotes_only);
+}
+
+static gboolean
+is_group (InvestWidget *widget,
+          GtkTreeIter  *iter)
+{
+  gchar *symbol;
+  gboolean is_group;
+
+  symbol = NULL;
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_SYMBOL, &symbol, -1);
+
+  is_group = symbol == NULL ? TRUE : FALSE;
+  g_free (symbol);
+
+  return is_group;
+}
+
+static gboolean
+is_stock (InvestWidget *widget,
+          GtkTreeIter  *iter)
+{
+  return !is_group (widget, iter);
+}
+
+static void
+ticker_cell_data_func (GtkTreeViewColumn *tree_column,
+                       GtkCellRenderer   *cell,
+                       GtkTreeModel      *tree_model,
+                       GtkTreeIter       *iter,
+                       gpointer           data)
+{
+  InvestWidget *widget;
+  gchar *label;
+
+  widget = INVEST_WIDGET (data);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_LABEL, &label, -1);
+
+  if (is_stock (widget, iter))
+    {
+      g_object_set (cell, "text", label, NULL);
+      g_free (label);
+    }
+  else
+    {
+      gchar *markup;
+
+      markup = g_strdup_printf ("<b>%s</b>", label);
+      g_free (label);
+
+      g_object_set (cell, "markup", markup, NULL);
+      g_free (markup);
+    }
+}
+
+static void
+add_column_ticker (InvestWidget *widget,
+                   GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Ticker"), renderer, NULL);
+
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+                                           ticker_cell_data_func,
+                                           widget, NULL);
+
+  gtk_tree_view_column_set_sort_column_id (column, COL_LABEL);
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+last_cell_data_func (GtkTreeViewColumn *tree_column,
+                     GtkCellRenderer   *cell,
+                     GtkTreeModel      *tree_model,
+                     GtkTreeIter       *iter,
+                     gpointer           data)
+{
+  InvestWidget *widget;
+  gdouble value;
+  gchar *currency;
+
+  widget = INVEST_WIDGET (data);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_VALUE, &value, COL_CURRENCY, &currency, -1);
+
+  if (currency == NULL)
+    {
+      g_object_set (cell, "text", "", NULL);
+    }
+  else
+    {
+      gchar *text;
+
+      text = g_strdup_printf ("%'.2f %s", value, currency);
+
+      g_object_set (cell, "text", text, NULL);
+      g_free (text);
+    }
+
+  g_free (currency);
+}
+
+static void
+add_column_last (InvestWidget *widget,
+                 GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Last"), renderer, NULL);
+
+  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
+
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+                                           last_cell_data_func,
+                                           widget, NULL);
+
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+change_cell_data_func (GtkTreeViewColumn *tree_column,
+                       GtkCellRenderer   *cell,
+                       GtkTreeModel      *tree_model,
+                       GtkTreeIter       *iter,
+                       gpointer           data)
+{
+  InvestWidget *widget;
+  const gchar *color;
+  gdouble value;
+  gchar *markup;
+
+  widget = INVEST_WIDGET (data);
+
+  if (is_group (widget, iter))
+    {
+      g_object_set (cell, "text", "", NULL);
+      return;
+    }
+
+  color = get_color (widget, iter, COL_VARIATION_PCT);
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_VARIATION_PCT, &value, -1);
+
+  markup = g_strdup_printf ("<span foreground='%s'>%'+.2f%%</span>",
+                            color, value);
+
+  g_object_set (cell, "markup", markup, NULL);
+  g_free (markup);
+}
+
+static void
+add_column_change (InvestWidget *widget,
+                   GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Change %"), renderer, NULL);
+
+  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
+
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+                                           change_cell_data_func,
+                                           widget, NULL);
+
+  gtk_tree_view_column_set_sort_column_id (column, COL_VARIATION_PCT);
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+add_column_chart (InvestWidget *widget,
+                  GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_pixbuf_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Chart"), renderer,
+                                                     "pixbuf", COL_PIXBUF,
+                                                     NULL);
+
+  widget->chart = column;
+
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+gain_cell_data_func (GtkTreeViewColumn *tree_column,
+                     GtkCellRenderer   *cell,
+                     GtkTreeModel      *tree_model,
+                     GtkTreeIter       *iter,
+                     gpointer           data)
+{
+  InvestWidget *widget;
+  gboolean ticker_only;
+
+  widget = INVEST_WIDGET (data);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_TICKER_ONLY, &ticker_only, -1);
+
+  if (ticker_only)
+    {
+      g_object_set (cell, "text", "", NULL);
+    }
+  else
+    {
+      const gchar *color;
+      gdouble value;
+      gchar *currency;
+      gchar *markup;
+
+      color = get_color (widget, iter, COL_BALANCE);
+      gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                          COL_BALANCE, &value, COL_CURRENCY, &currency,
+                          -1);
+
+      markup = g_strdup_printf ("<span foreground='%s'>%'+.2f %s</span>",
+                                color, value, currency);
+
+      g_free (currency);
+
+      g_object_set (cell, "markup", markup, NULL);
+      g_free (markup);
+    }
+}
+
+static void
+add_column_gain (InvestWidget *widget,
+                 GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Gain"), renderer, NULL);
+
+  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
+
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+                                           gain_cell_data_func,
+                                           widget, NULL);
+
+  widget->gain = column;
+
+  gtk_tree_view_column_set_sort_column_id (column, COL_BALANCE);
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+gain_pct_cell_data_func (GtkTreeViewColumn *tree_column,
+                         GtkCellRenderer   *cell,
+                         GtkTreeModel      *tree_model,
+                         GtkTreeIter       *iter,
+                         gpointer           data)
+{
+  InvestWidget *widget;
+  gboolean ticker_only;
+
+  widget = INVEST_WIDGET (data);
+
+  gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                      COL_TICKER_ONLY, &ticker_only, -1);
+
+  if (ticker_only)
+    {
+      g_object_set (cell, "text", "", NULL);
+    }
+  else
+    {
+      const gchar *color;
+      gdouble value;
+      gchar *markup;
+
+      color = get_color (widget, iter, COL_BALANCE_PCT);
+      gtk_tree_model_get (GTK_TREE_MODEL (widget->quotes), iter,
+                          COL_BALANCE_PCT, &value, -1);
+
+      markup = g_strdup_printf ("<span foreground='%s'>%'+.2f%%</span>",
+                                color, value);
+
+      g_object_set (cell, "markup", markup, NULL);
+      g_free (markup);
+    }
+}
+
+static void
+add_column_gain_pct (InvestWidget *widget,
+                     GtkTreeView  *tree_view)
+{
+  GtkCellRenderer *renderer;
+  GtkTreeViewColumn *column;
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Gain %"), renderer, NULL);
+
+  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
+
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+                                           gain_pct_cell_data_func,
+                                           widget, NULL);
+
+  widget->gain_pct = column;
+
+  gtk_tree_view_column_set_sort_column_id (column, COL_BALANCE_PCT);
+  gtk_tree_view_append_column (tree_view, column);
+}
+
+static void
+invest_widget_constructed (GObject *object)
+{
+  InvestWidget *widget;
+  GtkTreeView *tree_view;
+  GtkTreeModel *model;
+
+  G_OBJECT_CLASS (invest_widget_parent_class)->constructed (object);
+
+  widget = INVEST_WIDGET (object);
+  tree_view = GTK_TREE_VIEW (widget);
+  model = GTK_TREE_MODEL (widget->quotes);
+
+  add_column_ticker (widget, tree_view);
+  add_column_last (widget, tree_view);
+  add_column_change (widget, tree_view);
+  add_column_chart (widget, tree_view);
+  add_column_gain (widget, tree_view);
+  add_column_gain_pct (widget, tree_view);
+
+  widget->changed_id =
+    g_signal_connect (widget->settings,"changed::hidecharts",
+                      G_CALLBACK (changed_hidecharts_cb), widget);
+
+  widget->reloaded_id =
+    g_signal_connect (widget->quotes, "reloaded",
+                      G_CALLBACK (reloaded_cb), widget);
+
+  changed_hidecharts_cb (widget->settings, "hidecharts", widget);
+  reloaded_cb (widget->quotes, widget);
+
+  gtk_tree_view_set_hover_selection (tree_view, TRUE);
+  gtk_tree_view_set_model (tree_view, model);
+}
+
+static void
+invest_widget_dispose (GObject *object)
+{
+  InvestWidget *widget;
+
+  widget = INVEST_WIDGET (object);
+
+  if (widget->changed_id != 0)
+    {
+      g_signal_handler_disconnect (widget->settings, widget->changed_id);
+      widget->changed_id = 0;
+    }
+
+  if (widget->reloaded_id != 0)
+    {
+      g_signal_handler_disconnect (widget->quotes, widget->reloaded_id);
+      widget->reloaded_id = 0;
+    }
+
+  g_clear_object (&widget->settings);
+  g_clear_object (&widget->quotes);
+
+  G_OBJECT_CLASS (invest_widget_parent_class)->dispose (object);
+}
+
+static void
+invest_widget_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  InvestWidget *widget;
+
+  widget = INVEST_WIDGET (object);
+
+  switch (property_id)
+    {
+      case PROP_SETTINGS:
+        widget->settings = g_value_dup_object (value);
+        break;
+
+      case PROP_QUOTES:
+        widget->quotes = g_value_dup_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_widget_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_SETTINGS] =
+    g_param_spec_object ("settings", "settings", "settings", G_TYPE_SETTINGS,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_QUOTES] =
+    g_param_spec_object ("quotes", "quotes", "quotes", INVEST_TYPE_QUOTES,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_widget_class_init (InvestWidgetClass *widget_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (widget_class);
+
+  object_class->constructed = invest_widget_constructed;
+  object_class->dispose = invest_widget_dispose;
+  object_class->set_property = invest_widget_set_property;
+
+  invest_widget_install_properties (object_class);
+}
+
+static void
+invest_widget_init (InvestWidget *widget)
+{
+  g_signal_connect (widget, "row-activated",
+                    G_CALLBACK (row_activated_cb), widget);
+}
+
+GtkWidget *
+invest_widget_new (GSettings    *settings,
+                   InvestQuotes *quotes)
+{
+  return g_object_new (INVEST_TYPE_WIDGET,
+                       "settings", settings,
+                       "quotes", quotes,
+                       NULL);
+}
diff --git a/invest-applet/invest/invest-widget.h b/invest-applet/invest/invest-widget.h
new file mode 100644
index 0000000..6d11b6c
--- /dev/null
+++ b/invest-applet/invest/invest-widget.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_WIDGET_H
+#define INVEST_WIDGET_H
+
+#include "invest-quotes.h"
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_WIDGET invest_widget_get_type ()
+G_DECLARE_FINAL_TYPE (InvestWidget, invest_widget, INVEST, WIDGET, GtkTreeView)
+
+GtkWidget *invest_widget_new (GSettings    *settings,
+                              InvestQuotes *quotes);
+
+G_END_DECLS
+
+#endif
diff --git a/invest-applet/invest/invest-window.c b/invest-applet/invest/invest-window.c
new file mode 100644
index 0000000..900d6cc
--- /dev/null
+++ b/invest-applet/invest/invest-window.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#include "config.h"
+#include "invest-widget.h"
+#include "invest-window.h"
+
+struct _InvestWindow
+{
+  GtkWindow     parent;
+
+  GSettings    *settings;
+  InvestQuotes *quotes;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_SETTINGS,
+  PROP_QUOTES,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (InvestWindow, invest_window, GTK_TYPE_WINDOW)
+
+static void
+invest_window_constructed (GObject *object)
+{
+  InvestWindow *window;
+  GtkWidget *scrolled;
+  GtkWidget *invest;
+
+  G_OBJECT_CLASS (invest_window_parent_class)->constructed (object);
+
+  window = INVEST_WINDOW (object);
+
+  scrolled = gtk_scrolled_window_new (NULL, NULL);
+  gtk_container_add (GTK_CONTAINER (window), scrolled);
+  gtk_widget_show (scrolled);
+
+  invest = invest_widget_new (window->settings, window->quotes);
+  gtk_container_add (GTK_CONTAINER (scrolled), invest);
+  gtk_widget_show (invest);
+
+  gtk_widget_set_size_request (GTK_WIDGET (window), 520, 220);
+  gtk_container_set_border_width (GTK_CONTAINER (window), 4);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+}
+
+static void
+invest_window_dispose (GObject *object)
+{
+  InvestWindow *window;
+
+  window = INVEST_WINDOW (object);
+
+  g_clear_object (&window->settings);
+  g_clear_object (&window->quotes);
+
+  G_OBJECT_CLASS (invest_window_parent_class)->dispose (object);
+}
+
+static void
+invest_window_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  InvestWindow *window;
+
+  window = INVEST_WINDOW (object);
+
+  switch (property_id)
+    {
+      case PROP_SETTINGS:
+        window->settings = g_value_dup_object (value);
+        break;
+
+      case PROP_QUOTES:
+        window->quotes = g_value_dup_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+invest_window_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_SETTINGS] =
+    g_param_spec_object ("settings", "settings", "settings", G_TYPE_SETTINGS,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_QUOTES] =
+    g_param_spec_object ("quotes", "quotes", "quotes", INVEST_TYPE_QUOTES,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+invest_window_class_init (InvestWindowClass *window_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (window_class);
+
+  object_class->constructed = invest_window_constructed;
+  object_class->dispose = invest_window_dispose;
+  object_class->set_property = invest_window_set_property;
+
+  invest_window_install_properties (object_class);
+}
+
+static void
+invest_window_init (InvestWindow *window)
+{
+  GtkWindow *gtk_window;
+
+  gtk_window = GTK_WINDOW (window);
+
+  gtk_window_set_decorated (gtk_window, FALSE);
+  gtk_window_set_type_hint (gtk_window, GDK_WINDOW_TYPE_HINT_DOCK);
+  gtk_window_stick (gtk_window);
+}
+
+GtkWidget *
+invest_window_new (GSettings    *settings,
+                   InvestQuotes *quotes)
+{
+  return g_object_new (INVEST_TYPE_WINDOW,
+                       "type", GTK_WINDOW_TOPLEVEL,
+                       "settings", settings,
+                       "quotes", quotes,
+                       NULL);
+}
diff --git a/invest-applet/invest/invest-window.h b/invest-applet/invest/invest-window.h
new file mode 100644
index 0000000..dfe3edf
--- /dev/null
+++ b/invest-applet/invest/invest-window.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004-2005 Raphael Slinckx
+ * Copyright (C) 2009-2011 Enrico Minack
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Alberts Muktupāvels <alberts muktupavels gmail com>
+ *     Enrico Minack <enrico-minack gmx de>
+ *     Raphael Slinckx <raphael slinckx net>
+ */
+
+#ifndef INVEST_WINDOW_H
+#define INVEST_WINDOW_H
+
+#include <gtk/gtk.h>
+
+#include "invest-quotes.h"
+
+G_BEGIN_DECLS
+
+#define INVEST_TYPE_WINDOW invest_window_get_type ()
+G_DECLARE_FINAL_TYPE (InvestWindow, invest_window, INVEST, WINDOW, GtkWindow)
+
+GtkWidget *invest_window_new (GSettings    *settings,
+                              InvestQuotes *quotes);
+
+G_END_DECLS
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8ac7554..71aed84 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -57,14 +57,13 @@ inhibit/src/inhibit-applet.c
 [type: gettext/glade]invest-applet/data/financialchart.ui
 [type: gettext/glade]invest-applet/data/invest-applet-menu.xml
 [type: gettext/ini]invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in.in
+invest-applet/data/org.gnome.gnome-applets.invest.gschema.xml.in.in
 [type: gettext/glade]invest-applet/data/prefs-dialog.ui
-invest-applet/invest/about.py
-invest-applet/invest/applet.py
-invest-applet/invest/chart.py
-invest-applet/invest/invest-applet.py
-invest-applet/invest/preferences.py
-invest-applet/invest/quotes.py
-invest-applet/invest/widgets.py
+invest-applet/invest/invest-applet.c
+invest-applet/invest/invest-chart.c
+invest-applet/invest/invest-preferences.c
+invest-applet/invest/invest-quotes.c
+invest-applet/invest/invest-widget.c
 mini-commander/src/about.c
 mini-commander/src/cmd_completion.c
 mini-commander/src/command_line.c
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 35b1029..4740000 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -21,6 +21,7 @@ gweather/org.gnome.applets.GWeatherApplet.panel-applet.in
 gweather/org.gnome.gnome-applets.gweather.gschema.xml.in
 inhibit/org.gnome.InhibitApplet.panel-applet.in
 invest-applet/data/org.gnome.applets.InvestApplet.panel-applet.in
+invest-applet/data/org.gnome.gnome-applets.invest.gschema.xml.in
 mini-commander/src/org.gnome.applets.MiniCommanderApplet.panel-applet.in
 mini-commander/src/org.gnome.gnome-applets.mini-commander.gschema.xml.in
 modem-lights/org.gnome.applets.ModemApplet.panel-applet.in


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]