Added initial revision of stock widget to repo.
authorJonathan Parr <jon@jdtp>
Sat, 19 Mar 2011 19:07:26 +0000 (19:07 +0000)
committerJonathan Parr <jon@jdtp>
Sat, 19 Mar 2011 19:07:26 +0000 (19:07 +0000)
TODO: Transparent background for widget
TODO: Debian package generation.

Makefile [new file with mode: 0644]
lib-stock-home-widget.c [new file with mode: 0644]
lib-stock-home-widget.h [new file with mode: 0644]
lib-stock-settings.c [new file with mode: 0644]
lib-stock-settings.h [new file with mode: 0644]
stock-home-widget.desktop [new file with mode: 0644]
stockgetter.c [new file with mode: 0644]
stockgetter.h [new file with mode: 0644]
welcome [deleted file]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..709015a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+#
+# Makefile for stock widget
+# TODO: Generate debian package for easy installation.
+# Jon Parr
+#
+
+all: stock-home-widget
+
+SOURCES=lib-stock-home-widget.c lib-stock-settings.c stockgetter.c
+TARGET =lib-stock-home-widget.so
+DESKTOP=stock-home-widget.desktop
+
+lib-stock-home-widget.c : lib-stock-home-widget.h
+stockgetter.c : stockgetter.h
+
+stock-home-widget: $(SOURCES)
+       @gcc -Wall -ansi -pedantic -shared `pkg-config --libs --cflags hildon-1 libhildondesktop-1` $(SOURCES) -o $(TARGET) -lcurl
+
+clean:
+       @rm -rf *.o *~
+
+clobber:
+       @rm -rf $(TARGET) *.o *~
+
+install:
+       @cp -f $(TARGET) `pkg-config libhildondesktop-1 --variable=hildondesktoplibdir`/.
+       @cp -f $(DESKTOP) `pkg-config libhildondesktop-1 --variable=hildonhomedesktopentrydir`/.
diff --git a/lib-stock-home-widget.c b/lib-stock-home-widget.c
new file mode 100644 (file)
index 0000000..9f9e054
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Simple stock widget
+ * Jon Parr
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <string.h>
+#include <hildon/hildon.h>
+#include "lib-stock-home-widget.h"
+#include "lib-stock-settings.h"
+
+HD_DEFINE_PLUGIN_MODULE (StockPlugin, stock_plugin, HD_TYPE_HOME_PLUGIN_ITEM)
+
+static void
+OnClickRefresh(GtkWidget *widget, StockPluginContext *psContext)
+{
+       DebugOut(("OnClickRefresh"));
+       
+       /* Disable button while getting latest stocks */
+       gtk_widget_set_sensitive(GTK_WIDGET(psContext->psButton),FALSE);
+       {
+               float   fPrice;
+               float   fChange;
+               char    szBuffer[1024];         
+
+               if(RetrieveStockPrice(psContext->hSG,psContext->psSettings->ppszTickers[0],&fPrice,NULL,&fChange) == SG_OK)
+               {
+                       char             szTime[80];
+                       time_t           rawtime;
+                       struct tm       *timeinfo;
+
+                       time(&rawtime);
+                       timeinfo = localtime(&rawtime);
+                       strftime(szTime,80,"(%I:%M%p)",timeinfo);
+                       
+                       /* Update to latest price */
+                       sprintf(szBuffer,"%s %.2f %.2f%% %s",psContext->psSettings->ppszTickers[0],fPrice,fChange,szTime);
+
+                       hildon_button_set_title(HILDON_BUTTON(psContext->psButton),szBuffer);
+                       gtk_widget_queue_draw(GTK_WIDGET(psContext->psBox));
+               }
+               /*
+                 If it fails, leave the old price as the user can see the timestamp
+                 and know it's not up-to-date. So just notify them of the failure.
+                */
+               else
+               {
+                       hildon_banner_show_information(GTK_WIDGET(widget),NULL,"Failed to retrieve stock information, check ticker symbol and try again.");
+               }
+       }
+       gtk_widget_set_sensitive(GTK_WIDGET(psContext->psButton),TRUE);
+}
+
+static gboolean
+UpdateStockOnTimeout(gpointer data)
+{
+       StockPluginContext *psContext = (StockPluginContext*)data;
+
+       DebugOut(("UpdateStockOnTimeout"));
+       
+       if(!psContext)
+               return FALSE;
+
+       /* If no longer a timeout, don't do anything and stop */
+       if(!psContext->psSettings->uiUpdateTime)
+               return FALSE;
+
+       /* Update stock price */
+       OnClickRefresh(GTK_WIDGET(psContext->psButton),psContext);
+       return TRUE;
+}
+
+static void
+stock_show_settings_dialog(GtkWidget* widget, gpointer data)
+{
+       StockPluginContext      *psContext = (StockPluginContext*)data;
+       GtkWidget                       *dialog;
+       GtkWidget                       *content;
+       GtkSizeGroup            *group;
+       GtkWidget                       *addselector;
+       GtkWidget                       *addbutton;
+       GtkWidget                       *timeselector;
+       GtkWidget                       *timebutton;
+
+       DebugOut(("stock_show_settings_dialog"));
+       
+       /* Stop timer while settings are one */
+       if(psContext->nTimerID != -1)
+       {
+               g_source_remove(psContext->nTimerID);
+               psContext->nTimerID = -1;
+       }
+       
+       dialog  = gtk_dialog_new_with_buttons("Settings",NULL,0,"Save",GTK_RESPONSE_ACCEPT,NULL);
+       content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       group   = GTK_SIZE_GROUP(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL));
+
+       /* Add new stock symbol */
+       {
+               int i;
+               
+               addbutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
+                                                                                         HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
+
+               addselector = hildon_touch_selector_entry_new_text();
+
+               for(i=0; i < psContext->psSettings->uiNumTickers; i++)
+               {
+                       hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(addselector),psContext->psSettings->ppszTickers[i]);
+               }
+
+               /* First ticker in ppszTickers is the chosen one */
+               hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(addselector), 0, 0);
+               
+               hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (addbutton), HILDON_TOUCH_SELECTOR (addselector));
+               
+               hildon_button_set_title(HILDON_BUTTON(addbutton), "Select Ticker");
+       }
+
+       /* Set auto update time */
+       {
+               int i;
+               int sel = 0;
+
+               char *aszUpdateTimes[] =
+               {
+                       "No update",
+                       "1 minute",
+                       "15 minutes",
+                       "30 minutes",
+                       "1 hour"
+               };
+               
+               timebutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
+                                                                                         HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
+
+               timeselector = hildon_touch_selector_new_text();
+
+               for(i=0; i < sizeof(aszUpdateTimes)/sizeof(aszUpdateTimes[0]); i++)
+               {
+                       hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(timeselector),aszUpdateTimes[i]);
+               }
+
+               switch(psContext->psSettings->uiUpdateTime)
+               {
+                       case 0:  sel = 0; break;
+                       case 1:  sel = 1; break;
+                       case 15: sel = 2; break;
+                       case 30: sel = 3; break;
+                       case 60: sel = 4; break;
+               }
+               
+               hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(timeselector), 0, sel);
+               hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (timebutton), HILDON_TOUCH_SELECTOR (timeselector));
+               hildon_button_set_title(HILDON_BUTTON(timebutton), "Automatic Update Period");
+       }
+
+       gtk_container_add(GTK_CONTAINER(content),addbutton);
+       gtk_container_add(GTK_CONTAINER(content),timebutton);
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) 
+       {
+               const char *pszNewTicker;
+               char aszOldTicker[255]={0};
+               guint uiUpdateSelection = 0;
+
+               /* Only perform ops if they actually changed the ticker */
+               if(strcmp(aszOldTicker,psContext->psSettings->ppszTickers[0]))
+               {
+                       /* Save off old ticker */
+                       strcpy(aszOldTicker,psContext->psSettings->ppszTickers[0]);
+               
+                       /* Get new ticker */
+                       pszNewTicker = hildon_button_get_value(HILDON_BUTTON(addbutton));
+
+                       /* Check if ticker already exists */
+                       {
+                               int i;
+                               gboolean bFound = FALSE;
+                       
+                               /* Check it doesn't exist already */
+                               for(i=0; i < psContext->psSettings->uiNumTickers; i++)
+                               {
+                                       if(!strcmp(pszNewTicker,psContext->psSettings->ppszTickers[i]))
+                                       {
+                                               bFound = TRUE;
+                                               break;
+                                       }
+                               }
+
+                               /* If they just chose another one, just swap the pointers so new one is at the top:
+                                */
+                               if(bFound)
+                               {
+                                       /* Swapped ith and 0th element */
+                                       char *psTmp = psContext->psSettings->ppszTickers[i];
+                                       psContext->psSettings->ppszTickers[i] = psContext->psSettings->ppszTickers[0];
+                                       psContext->psSettings->ppszTickers[0] = psTmp;
+
+                                       DebugOut(("Swapped ticker %d (%s) with 0 (%s)",
+                                                         i,
+                                                         psContext->psSettings->ppszTickers[i],
+                                                         psContext->psSettings->ppszTickers[0]));
+                               }
+                               /* If it doesn't add it to the list, placing chosen one at the front */
+                               else
+                               {
+                                       gchar **ppszTickers = g_malloc0(sizeof(char*) * (psContext->psSettings->uiNumTickers+2));
+
+                                       /* Place the selected one first */
+                                       ppszTickers[0] = g_strdup(pszNewTicker);
+                               
+                                       /* Copy across old tickers */
+                                       for(i=0; i < psContext->psSettings->uiNumTickers; i++)
+                                       {
+                                               /* offset by one as the selected one is first */
+                                               ppszTickers[i+1] = g_strdup(psContext->psSettings->ppszTickers[i]);
+                                       }
+
+                                       /* Free current array */
+                                       g_strfreev(psContext->psSettings->ppszTickers);
+
+                                       /* Assign new array */
+                                       psContext->psSettings->ppszTickers = ppszTickers;
+
+                                       /* Update count */
+                                       psContext->psSettings->uiNumTickers++;
+                               }
+                       }
+
+                       /* Force a refresh as the ticker changed */
+                       OnClickRefresh(GTK_WIDGET(psContext->psButton),psContext);
+               }
+
+               uiUpdateSelection = hildon_touch_selector_get_active(HILDON_TOUCH_SELECTOR(timeselector),0);
+               
+               switch(uiUpdateSelection)
+               {
+                       /* No update */
+                       case 0: psContext->psSettings->uiUpdateTime =  0; break;
+                       case 1: psContext->psSettings->uiUpdateTime =  1; break;
+                       case 2: psContext->psSettings->uiUpdateTime = 15; break;
+                       case 3: psContext->psSettings->uiUpdateTime = 30; break;
+                       case 4: psContext->psSettings->uiUpdateTime = 60; break;
+               }
+
+               if(psContext->psSettings->uiUpdateTime != 0)
+               {
+                       /* Remove previous timeout if there was one */
+                       if(psContext->nTimerID != -1)
+                       {
+                               g_source_remove(psContext->nTimerID);
+                       }
+                       
+                       /* Add timer (in seconds) */
+                       psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60,
+                                                                                                               UpdateStockOnTimeout,
+                                                                                                               psContext);
+               }
+
+               /* Save Settings */
+               stock_save_settings(psContext->psSettings);
+       }
+       
+       gtk_widget_destroy (dialog);
+}
+
+static GtkWidget *stock_create_gui(gchar                               *pszStockLabel,
+                                                                  StockPluginContext   *psContext)
+{
+       HildonButton *psButton = NULL;
+
+       DebugOut(("stock_create_gui"));
+       
+       psButton = (HildonButton*)hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
+                                                                                       HILDON_BUTTON_ARRANGEMENT_VERTICAL,
+                                                                                       pszStockLabel,
+                                                                                       NULL);
+
+       psContext->psButton = psButton;
+
+       /* Setup button click signal for refreshing stocks */
+       gtk_signal_connect (GTK_OBJECT(psButton),"clicked",GTK_SIGNAL_FUNC(OnClickRefresh),(gpointer)psContext);
+
+       /* Display */
+       gtk_widget_show_all(GTK_WIDGET(psButton));
+       
+       return GTK_WIDGET(psButton);
+}
+
+static void
+stock_plugin_finalize (GObject *object)
+{
+       StockPluginContext *psContext = ((StockPlugin*)object)->context;
+
+       DebugOut(("stock_plugin_finalize"));
+       
+       if(psContext)
+       {
+               if(psContext->psSettings)
+               {
+                       if(psContext->psSettings)
+                               g_free(psContext->psSettings->iD);
+                       
+                       stock_free_settings(psContext->psSettings);
+                       g_free(psContext->psSettings);
+               }
+
+               if(psContext->nTimerID != -1)
+               {
+                       g_source_remove(psContext->nTimerID);
+                       psContext->nTimerID = -1;
+               }
+
+               if(psContext->hSG)
+                       FreeStockGetter(psContext->hSG);
+               
+               g_free(psContext);
+       }
+}
+
+static void
+stock_plugin_init (StockPlugin *desktop_plugin)
+{
+       StockPluginContext *psContext;
+       SGHandle hSG;
+
+       DebugOut(("stock_plugin_init"));
+       
+       hSG = InitStockGetter();
+
+       if(!hSG)
+       {
+               return;
+       }
+
+       /* Create plugin context */
+       psContext = g_new0(StockPluginContext,1);
+
+       /* Always create a settings */
+       psContext->psSettings     = g_new0(StockPluginSettings,1);
+       psContext->psSettings->iD = g_strdup(STOCK_PLUGIN_APPLET_ID);
+
+       /* Attempt to load settings (defaulted if not present) */
+       stock_read_settings(psContext->psSettings);
+
+       /* Create GUI widget interface (and sets links in context) */
+       psContext->psBox = stock_create_gui("Click to Update",psContext);
+       
+       gtk_container_add (GTK_CONTAINER (desktop_plugin), psContext->psBox);
+       
+       psContext->hSG          = hSG;
+       psContext->nTimerID = -1;
+
+       /* Assign private data to plugin */
+       desktop_plugin->context = psContext;
+       
+       /* Remove previous timeout if there was one */
+       if(psContext->nTimerID != -1)
+       {
+               g_source_remove(psContext->nTimerID);
+       }
+
+       /* Setup new one if required */
+       if(psContext->psSettings->uiUpdateTime > 0)
+       {
+               psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60,
+                                                                                                       UpdateStockOnTimeout,
+                                                                                                       psContext);
+       }
+
+       /* Show a settings button */
+       hd_home_plugin_item_set_settings (HD_HOME_PLUGIN_ITEM (desktop_plugin), TRUE);
+
+       /* Connect to the settings button */
+       g_signal_connect(desktop_plugin, "show-settings", G_CALLBACK(stock_show_settings_dialog), psContext);
+}
+
+static void
+stock_plugin_class_init (StockPluginClass *klass)
+{
+       G_OBJECT_CLASS(klass)->finalize = stock_plugin_finalize;
+}
+
+static void
+stock_plugin_class_finalize (StockPluginClass *klass)
+{
+}
diff --git a/lib-stock-home-widget.h b/lib-stock-home-widget.h
new file mode 100644 (file)
index 0000000..ef8f87b
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Simple stock widget
+ * Jon Parr
+ */
+
+#ifndef STOCK_PLUGIN_H
+#define STOCK_PLUGIN_H
+
+/* Standard includes */
+#include <glib-object.h>
+#include <libhildondesktop/libhildondesktop.h>
+
+/* Custom includes */
+#include "stockgetter.h"
+#include "lib-stock-settings.h"
+
+G_BEGIN_DECLS
+
+typedef struct _StockPlugin StockPlugin;
+typedef struct _StockPluginClass StockPluginClass;
+
+#define STOCK_TYPE_HOME_PLUGIN (stock_home_plugin_get_type ())
+
+#define STOCK_HOME_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                        STOCK_TYPE_HOME_PLUGIN, StockPlugin))
+
+#define STOCK_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                        STOCK_TYPE_HOME_PLUGIN, StockPluginClass))
+
+#define STOCK_IS_HOME_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+                        STOCK_TYPE_HOME_PLUGIN))
+
+#define STOCK_IS_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+                        STOCK_TYPE_HOME_PLUGIN))
+
+#define STOCK_HOME_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+                        STOCK_TYPE_HOME_PLUGIN, StockPluginClass))
+
+typedef struct _StockPluginContext
+{
+       /* Used for updating when stock price changes */
+       HildonButton            *psButton;
+       GtkWidget                       *psLabel;
+       GtkWidget                       *psBox;
+
+       /* Stock Getter Handle */
+       SGHandle hSG;
+
+       /* Timer func id */
+       guint nTimerID;
+       
+       /* Settings for the plugin (saved and loaded into config file) */
+       StockPluginSettings *psSettings;
+       
+} StockPluginContext;
+
+struct _StockPlugin
+{
+    HDHomePluginItem    hitem;
+       StockPluginContext      *context;
+};
+
+struct _StockPluginClass
+{
+    HDHomePluginItemClass parent_class;
+};
+
+GType stock_home_plugin_get_type(void);
+G_END_DECLS
+
+#define DEBUG
+
+/* Debug print only */
+#if defined(DEBUG)
+#define DebugOut(X) {g_print("stockwidget: "); g_print X; g_print("\n");}
+#else
+#define DebugOut(X)
+#endif/*defined(DEBUG)*/
+
+#endif
diff --git a/lib-stock-settings.c b/lib-stock-settings.c
new file mode 100644 (file)
index 0000000..c220645
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Stock Widget: Resource settings
+ * Jon Parr
+ */
+#include "lib-stock-settings.h"
+
+void
+stock_free_settings(StockPluginSettings *psSettings)
+{
+       if(psSettings->ppszTickers)
+       {
+               g_strfreev(psSettings->ppszTickers);
+               psSettings->ppszTickers = NULL;
+       }
+       
+       psSettings->uiNumTickers = 0;
+       psSettings->uiUpdateTime = 0;
+}
+
+void
+stock_set_default_settings(StockPluginSettings *psSettings)
+{
+       /* Default to a single ticker ;) */
+       psSettings->uiNumTickers   = 1;
+       psSettings->ppszTickers    = g_malloc0(sizeof(char*)*2);
+       psSettings->ppszTickers[0] = g_strdup(STOCK_PLUGIN_DEFAULT_TICKER);
+
+       /* Default to no automatic update */
+       psSettings->uiUpdateTime = 0;
+}
+
+void
+stock_read_settings(StockPluginSettings *psSettings)
+{
+       GKeyFile        *psKeyFile;
+       gchar           *pszFileName;
+       gboolean         bExists;
+       gint             nItems = 0;
+
+       psKeyFile       = g_key_file_new();
+       pszFileName = g_strconcat(g_get_home_dir(), STOCK_PLUGIN_SETTINGS_FILE, NULL);
+       bExists         = g_key_file_load_from_file(psKeyFile, pszFileName, G_KEY_FILE_KEEP_COMMENTS, NULL);
+
+       if(bExists)
+       {
+               /*TODO: Check case if config file exists but no data, simply use defaults */
+               
+               if(g_key_file_has_key(psKeyFile,psSettings->iD,STOCK_PLUGIN_TICKERS,NULL))
+               {
+                       psSettings->ppszTickers =
+                               g_key_file_get_string_list(psKeyFile,
+                                                                                  psSettings->iD,
+                                                                                  STOCK_PLUGIN_TICKERS,
+                                                                                  &psSettings->uiNumTickers,
+                                                                                  NULL);
+               }
+               else
+               {
+                       bExists = FALSE;
+               }
+
+               if(g_key_file_has_key(psKeyFile,psSettings->iD,STOCK_PLUGIN_UPDATE_TIME,NULL))
+               {
+                       psSettings->uiUpdateTime =
+                               g_key_file_get_integer(psKeyFile,
+                                                                          psSettings->iD,
+                                                                          STOCK_PLUGIN_UPDATE_TIME,
+                                                                          NULL);
+                       nItems++;
+               }
+               else
+               {
+                       if(psSettings->ppszTickers)
+                       {
+                               g_strfreev(psSettings->ppszTickers);
+                               psSettings->ppszTickers = NULL;
+                       }
+                               
+                       bExists = FALSE;
+               }
+       }
+
+       /* If it doesn't exists or there's an error reading data, just set defaults */
+       if(!bExists)
+               stock_set_default_settings(psSettings);
+
+       g_key_file_free(psKeyFile);
+       g_free(pszFileName);
+}
+
+
+void
+stock_save_settings(StockPluginSettings *psSettings)
+{
+       GKeyFile *psKeyFile;
+       gchar *pszFileName;
+       gchar *pszFileData;
+       gsize nSize;
+
+       /* Create key file */
+       psKeyFile       = g_key_file_new();
+       pszFileName = g_strconcat(g_get_home_dir(), STOCK_PLUGIN_SETTINGS_FILE, NULL);
+       g_key_file_load_from_file(psKeyFile, pszFileName, G_KEY_FILE_KEEP_COMMENTS, NULL);
+
+       /* Set tickers */
+       g_key_file_set_string_list(psKeyFile,
+                                                          psSettings->iD,
+                                                          STOCK_PLUGIN_TICKERS,
+                                                          (const gchar **)psSettings->ppszTickers,
+                                                          psSettings->uiNumTickers);
+
+       /* Set update time */
+       g_key_file_set_integer(psKeyFile,
+                                                  psSettings->iD,
+                                                  STOCK_PLUGIN_UPDATE_TIME,
+                                                  psSettings->uiUpdateTime);
+
+       /* Save to disk */
+       pszFileData = g_key_file_to_data(psKeyFile,&nSize,NULL);
+       g_file_set_contents(pszFileName,pszFileData,nSize,NULL);
+       
+       /* Cleanup */
+       g_key_file_free(psKeyFile);
+       g_free(pszFileName);
+       g_free(pszFileData);
+}
diff --git a/lib-stock-settings.h b/lib-stock-settings.h
new file mode 100644 (file)
index 0000000..02683e9
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Stock Widget: Resource settings
+ * Jon Parr
+ */
+#ifndef STOCK_SETTINGS_H
+#define STOCK_SETTINGS_H
+
+#include <gtk/gtk.h>
+
+/* Configuration file entries */
+#define STOCK_PLUGIN_APPLET_ID                 "stockwidget"
+#define STOCK_PLUGIN_TICKERS                   "tickers"
+#define STOCK_PLUGIN_UPDATE_TIME               "updatetime"
+#define STOCK_PLUGIN_SETTINGS_FILE             "/stockwidget.config"
+#define STOCK_PLUGIN_DEFAULT_TICKER            "IMG.L"
+
+/*
+ * Struct used for holding settings stored out and loaded
+ * in from configuration file.
+ */
+typedef struct _StockPluginSettings
+{
+       /* Current number of stock tickers selectable in the settings dialog */
+       unsigned int uiNumTickers;
+       
+       /* Current stock ticker symbols selectable in the settings dialog.
+          The chosen one is always the first to make loading/saving chosen ticker easy as.
+        */
+       char **ppszTickers;
+
+       /* Number of minutes between an automatic update, if 0 no automatic updating */
+       unsigned int uiUpdateTime;
+
+       /* Applet ID used as key for config file */
+       gchar *iD;
+       
+} StockPluginSettings;
+
+void
+stock_free_settings(StockPluginSettings *psSettings);
+
+void
+stock_set_default_settings(StockPluginSettings *psSettings);
+
+void
+stock_read_settings(StockPluginSettings *psSettings);
+
+void
+stock_save_settings(StockPluginSettings *psSettings);
+
+#endif/*STOCK_SETTINGS_H*/
diff --git a/stock-home-widget.desktop b/stock-home-widget.desktop
new file mode 100644 (file)
index 0000000..fc99468
--- /dev/null
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Stock Widget
+Comment=Track your stocks
+Type=default
+X-Path=lib-stock-home-widget.so
diff --git a/stockgetter.c b/stockgetter.c
new file mode 100644 (file)
index 0000000..de13724
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Simple wrapper for curl and Yahoo finance queries
+ * to enable grabbing stock data.
+ * Jon Parr
+ */
+#include "stockgetter.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Current maximum query return size */
+#define SG_MAX_BUFFER 512
+
+/* Current query string, note the %s for the stock ticker name */
+#define SG_QUERY_STRING "http://finance.yahoo.com/d/quotes.csv?s=%s&f=l1c"
+
+static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+       StockBuffer *psContext = (StockBuffer*)userp;
+       int nBytes = size * nmemb;
+
+       /*TODO: Write extendable buffer */
+       if(nBytes > psContext->nCount - psContext->nCurr)
+       {
+               fprintf(stderr,"Ran out of buffer space! (Bytes %d, %d/%d)\n",nBytes,psContext->nCurr,psContext->nCount);
+               return 0;
+       }
+
+       memcpy((void*)(psContext->pszBuffer + psContext->nCurr),buffer,nBytes);
+       psContext->nCurr += nBytes;
+
+       return nmemb;
+}
+
+SGHandle
+InitStockGetter(void)
+{
+       SGHandle hSG;
+       CURL *curl_handle;
+
+       hSG = (SGHandle)malloc(sizeof(struct _SGHandle));
+
+       if(!hSG)
+               return NULL;
+
+       memset(hSG,0,sizeof(struct _SGHandle));
+
+       if(curl_global_init(CURL_GLOBAL_ALL) != 0)
+               goto err_cleanup;
+
+       curl_handle = curl_easy_init();
+
+       if(!curl_handle)
+               goto err_cleanup;
+       
+       if(curl_easy_setopt(curl_handle,CURLOPT_WRITEFUNCTION,write_data) != 0)
+               goto err_cleanup;
+       
+       if(curl_easy_setopt (curl_handle, CURLOPT_FOLLOWLOCATION, 1) != 0)
+               goto err_cleanup;
+
+       if(curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA,&hSG->sData) != 0)
+               goto err_cleanup;
+               
+       hSG->hCurlHandle = curl_handle;
+
+       return hSG;
+
+err_cleanup:
+       
+       if(hSG)
+               free(hSG);
+       
+       return NULL;
+}
+
+int
+RetrieveStockPrice(SGHandle             hSG,
+                                  char                 *szTicker,
+                                  float                *pfValue,
+                                  float                *pfChangeReal,
+                                  float                *pfChangePercent)
+{
+       const char      *pszQueryString = SG_QUERY_STRING;
+       char aszQuery[255]              = {0};
+       float           fValue          = 0.0f;
+       float           fChangeReal = 0.0f;
+       float           fChangePer      = 0.0f;
+
+       if(!hSG || !pfValue || !szTicker || strlen(szTicker) > 10)
+               return SG_INVALID_PARAMS;
+
+       /* On first lookup setup output buffer */
+       if(!hSG->sData.pszBuffer)
+       {
+               hSG->sData.pszBuffer = (char*)malloc(sizeof(char)*SG_MAX_BUFFER);
+
+               if(!hSG->sData.pszBuffer)
+                       return SG_BAD_ALLOC;
+
+               memset(hSG->sData.pszBuffer,0,SG_MAX_BUFFER);
+
+               hSG->sData.nCount = SG_MAX_BUFFER;
+       }
+       
+       sprintf(aszQuery,pszQueryString,szTicker);
+               
+       if(curl_easy_setopt(hSG->hCurlHandle, CURLOPT_URL,aszQuery) != 0)
+               return SG_LOOKUP_FAILURE;
+
+       if(curl_easy_perform(hSG->hCurlHandle) != 0)
+               return SG_LOOKUP_FAILURE;
+
+       hSG->sData.nCurr = 0;
+       
+       if(sscanf(hSG->sData.pszBuffer,"%f,\"%f - %f\"\n",&fValue,&fChangeReal,&fChangePer) != 3)
+               return SG_LOOKUP_FAILURE;
+
+       if(pfValue)
+               *pfValue = fValue;
+
+       if(pfChangeReal)
+               *pfChangeReal = fChangeReal;
+
+       if(pfChangePercent)
+               *pfChangePercent = fChangePer;
+
+       return SG_OK;
+}
+
+int
+FreeStockGetter(SGHandle hSG)
+{
+       if(!hSG)
+               return SG_INVALID_PARAMS;
+
+       curl_easy_cleanup(hSG->hCurlHandle);
+       curl_global_cleanup();
+
+       free(hSG);
+       
+       return SG_OK;
+}
+
diff --git a/stockgetter.h b/stockgetter.h
new file mode 100644 (file)
index 0000000..aacecc9
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef _STOCKGETTER_H_
+#define _STOCKGETTER_H_
+
+#include <curl/curl.h>
+
+#define SG_OK                                  +1
+#define SG_INVALID_PARAMS              -1
+#define SG_LOOKUP_FAILURE              -2
+#define SG_BAD_ALLOC                   -3
+
+typedef struct _StockBuffer
+{
+       int nCurr;
+       int nCount;
+       char *pszBuffer;
+} StockBuffer;
+
+typedef struct _SGHandle
+{
+       CURL *hCurlHandle;
+       StockBuffer sData;
+} *SGHandle;
+
+/* API */
+
+SGHandle
+InitStockGetter(void);
+
+int
+RetrieveStockPrice(SGHandle             hSG,
+                                  char                 *szTicker,
+                                  float                *pfValue,
+                                  float                *pfChangeReal,
+                                  float                *pfChangePercent);
+
+int
+FreeStockGetter(SGHandle hSG);
+
+#endif/*_STOCKGETTER_H_*/
diff --git a/welcome b/welcome
deleted file mode 100644 (file)
index e69de29..0000000