Updated UI to be transparent, added colour to output text.
[stockwidget] / lib-stock-home-widget.c
1 /*
2  * Simple stock widget
3  * Jon Parr
4  */
5
6 #include <gtk/gtk.h>
7 #include <gdk/gdk.h>
8 #include <string.h>
9 #include <hildon/hildon.h>
10 #include "lib-stock-home-widget.h"
11 #include "lib-stock-settings.h"
12
13 HD_DEFINE_PLUGIN_MODULE (StockPlugin, stock_plugin, HD_TYPE_HOME_PLUGIN_ITEM)
14
15 static void
16 OnClickRefresh(GtkWidget *widget, GdkEventButton *event, StockPluginContext *psContext)
17 {
18         DebugOut(("OnClickRefresh"));
19         
20         /* Disable button while getting latest stocks */
21         {
22                 float   fPrice;
23                 float   fChange;
24                 char    szBuffer[1024];         
25
26                 if(RetrieveStockPrice(psContext->hSG,psContext->psSettings->ppszTickers[0],&fPrice,NULL,&fChange) == SG_OK)
27                 {
28                         char             szTime[80];
29                         char            *pszColour = NULL;
30                         time_t           rawtime;
31                         struct tm       *timeinfo;
32
33                         time(&rawtime);
34                         timeinfo = localtime(&rawtime);
35                         strftime(szTime,80,"(%I:%M%p)",timeinfo);
36                         
37                         /* Highlight  the change in price */
38                         if(fChange > 0.0f)
39                                 pszColour = "green";
40                         else
41                                 pszColour = "red";
42
43                         /* Update to latest price */
44                         sprintf(szBuffer,
45                                         "%s %.2f <span foreground=\"%s\">%.2f%%</span> <span foreground=\"grey\">%s</span>",
46                                         psContext->psSettings->ppszTickers[0],fPrice,pszColour,fChange,szTime);
47
48                         gtk_label_set_markup(GTK_LABEL(psContext->psLabel),szBuffer);
49                         gtk_widget_queue_draw(GTK_WIDGET(psContext->psLabel));
50                 }
51                 /*
52                   If it fails, leave the old price as the user can see the timestamp
53                   and know it's not up-to-date. So just notify them of the failure.
54                  */
55                 else
56                 {
57                         hildon_banner_show_information(GTK_WIDGET(widget),NULL,"Failed to retrieve stock information, check ticker symbol and try again.");
58                 }
59         }
60 }
61
62 static gboolean
63 UpdateStockOnTimeout(gpointer data)
64 {
65         StockPluginContext *psContext = (StockPluginContext*)data;
66
67         DebugOut(("UpdateStockOnTimeout"));
68         
69         if(!psContext)
70                 return FALSE;
71
72         /* If no longer a timeout, don't do anything and stop */
73         if(!psContext->psSettings->uiUpdateTime)
74                 return FALSE;
75
76         /* Update stock price */
77         OnClickRefresh(GTK_WIDGET(psContext->psLabel),NULL,psContext);
78         return TRUE;
79 }
80
81 static void
82 stock_show_settings_dialog(GtkWidget* widget, gpointer data)
83 {
84         StockPluginContext      *psContext = (StockPluginContext*)data;
85         GtkWidget                       *dialog;
86         GtkWidget                       *content;
87         GtkSizeGroup            *group;
88         GtkWidget                       *addselector;
89         GtkWidget                       *addbutton;
90         GtkWidget                       *timeselector;
91         GtkWidget                       *timebutton;
92
93         DebugOut(("stock_show_settings_dialog"));
94         
95         /* Stop timer while settings are one */
96         if(psContext->nTimerID != -1)
97         {
98                 g_source_remove(psContext->nTimerID);
99                 psContext->nTimerID = -1;
100         }
101         
102         dialog  = gtk_dialog_new_with_buttons("Settings",NULL,0,"Save",GTK_RESPONSE_ACCEPT,NULL);
103         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
104         group   = GTK_SIZE_GROUP(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL));
105
106         /* Add new stock symbol */
107         {
108                 int i;
109                 
110                 addbutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
111                                                                                           HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
112
113                 addselector = hildon_touch_selector_entry_new_text();
114
115                 for(i=0; i < psContext->psSettings->uiNumTickers; i++)
116                 {
117                         hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(addselector),psContext->psSettings->ppszTickers[i]);
118                 }
119
120                 /* First ticker in ppszTickers is the chosen one */
121                 hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(addselector), 0, 0);
122                 
123                 hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (addbutton), HILDON_TOUCH_SELECTOR (addselector));
124                 
125                 hildon_button_set_title(HILDON_BUTTON(addbutton), "Select Ticker");
126         }
127
128         /* Set auto update time */
129         {
130                 int i;
131                 int sel = 0;
132
133                 char *aszUpdateTimes[] =
134                 {
135                         "No update",
136                         "1 minute",
137                         "15 minutes",
138                         "30 minutes",
139                         "1 hour"
140                 };
141                 
142                 timebutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
143                                                                                           HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
144
145                 timeselector = hildon_touch_selector_new_text();
146
147                 for(i=0; i < sizeof(aszUpdateTimes)/sizeof(aszUpdateTimes[0]); i++)
148                 {
149                         hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(timeselector),aszUpdateTimes[i]);
150                 }
151
152                 switch(psContext->psSettings->uiUpdateTime)
153                 {
154                         case 0:  sel = 0; break;
155                         case 1:  sel = 1; break;
156                         case 15: sel = 2; break;
157                         case 30: sel = 3; break;
158                         case 60: sel = 4; break;
159                 }
160                 
161                 hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(timeselector), 0, sel);
162                 hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (timebutton), HILDON_TOUCH_SELECTOR (timeselector));
163                 hildon_button_set_title(HILDON_BUTTON(timebutton), "Automatic Update Period");
164         }
165
166         gtk_container_add(GTK_CONTAINER(content),addbutton);
167         gtk_container_add(GTK_CONTAINER(content),timebutton);
168         gtk_widget_show_all(dialog);
169
170         if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) 
171         {
172                 const char *pszNewTicker;
173                 char aszOldTicker[255]={0};
174                 guint uiUpdateSelection = 0;
175
176                 /* Only perform ops if they actually changed the ticker */
177                 if(strcmp(aszOldTicker,psContext->psSettings->ppszTickers[0]))
178                 {
179                         /* Save off old ticker */
180                         strcpy(aszOldTicker,psContext->psSettings->ppszTickers[0]);
181                 
182                         /* Get new ticker */
183                         pszNewTicker = hildon_button_get_value(HILDON_BUTTON(addbutton));
184
185                         /* Check if ticker already exists */
186                         {
187                                 int i;
188                                 gboolean bFound = FALSE;
189                         
190                                 /* Check it doesn't exist already */
191                                 for(i=0; i < psContext->psSettings->uiNumTickers; i++)
192                                 {
193                                         if(!strcmp(pszNewTicker,psContext->psSettings->ppszTickers[i]))
194                                         {
195                                                 bFound = TRUE;
196                                                 break;
197                                         }
198                                 }
199
200                                 /* If they just chose another one, just swap the pointers so new one is at the top:
201                                  */
202                                 if(bFound)
203                                 {
204                                         /* Swapped ith and 0th element */
205                                         char *psTmp = psContext->psSettings->ppszTickers[i];
206                                         psContext->psSettings->ppszTickers[i] = psContext->psSettings->ppszTickers[0];
207                                         psContext->psSettings->ppszTickers[0] = psTmp;
208
209                                         DebugOut(("Swapped ticker %d (%s) with 0 (%s)",
210                                                           i,
211                                                           psContext->psSettings->ppszTickers[i],
212                                                           psContext->psSettings->ppszTickers[0]));
213                                 }
214                                 /* If it doesn't add it to the list, placing chosen one at the front */
215                                 else
216                                 {
217                                         gchar **ppszTickers = g_malloc0(sizeof(char*) * (psContext->psSettings->uiNumTickers+2));
218
219                                         /* Place the selected one first */
220                                         ppszTickers[0] = g_strdup(pszNewTicker);
221                                 
222                                         /* Copy across old tickers */
223                                         for(i=0; i < psContext->psSettings->uiNumTickers; i++)
224                                         {
225                                                 /* offset by one as the selected one is first */
226                                                 ppszTickers[i+1] = g_strdup(psContext->psSettings->ppszTickers[i]);
227                                         }
228
229                                         /* Free current array */
230                                         g_strfreev(psContext->psSettings->ppszTickers);
231
232                                         /* Assign new array */
233                                         psContext->psSettings->ppszTickers = ppszTickers;
234
235                                         /* Update count */
236                                         psContext->psSettings->uiNumTickers++;
237                                 }
238                         }
239
240                         /* Force a refresh as the ticker changed */
241                         OnClickRefresh(GTK_WIDGET(psContext->psLabel),NULL,psContext);
242                 }
243
244                 uiUpdateSelection = hildon_touch_selector_get_active(HILDON_TOUCH_SELECTOR(timeselector),0);
245                 
246                 switch(uiUpdateSelection)
247                 {
248                         /* No update */
249                         case 0: psContext->psSettings->uiUpdateTime =  0; break;
250                         case 1: psContext->psSettings->uiUpdateTime =  1; break;
251                         case 2: psContext->psSettings->uiUpdateTime = 15; break;
252                         case 3: psContext->psSettings->uiUpdateTime = 30; break;
253                         case 4: psContext->psSettings->uiUpdateTime = 60; break;
254                 }
255
256                 if(psContext->psSettings->uiUpdateTime != 0)
257                 {
258                         /* Remove previous timeout if there was one */
259                         if(psContext->nTimerID != -1)
260                         {
261                                 g_source_remove(psContext->nTimerID);
262                         }
263                         
264                         /* Add timer (in seconds) */
265                         psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60,
266                                                                                                                 UpdateStockOnTimeout,
267                                                                                                                 psContext);
268                 }
269
270                 /* Save Settings */
271                 stock_save_settings(psContext->psSettings);
272         }
273         
274         gtk_widget_destroy (dialog);
275 }
276
277 static GtkWidget *stock_create_gui(gchar                                *pszStockLabel,
278                                                                    StockPluginContext   *psContext)
279 {
280         GtkWidget *label;
281         GtkWidget *eventbox;
282
283         DebugOut(("stock_create_gui"));
284         
285         label    = gtk_label_new(pszStockLabel);
286         eventbox = gtk_event_box_new();
287
288         gtk_event_box_set_visible_window(GTK_EVENT_BOX(eventbox),FALSE);
289         gtk_container_set_border_width(GTK_CONTAINER(eventbox),3);
290
291         gtk_container_add(GTK_CONTAINER(eventbox),label);
292
293         g_signal_connect(GTK_CONTAINER(eventbox),"button-press-event",G_CALLBACK(OnClickRefresh),(gpointer)psContext);
294
295         /* Display */
296         gtk_widget_show_all(GTK_WIDGET(eventbox));
297
298         psContext->psLabel = label;
299         psContext->psEventBox = eventbox;
300         
301         return eventbox;
302 }
303
304 static void
305 stock_plugin_finalize (GObject *object)
306 {
307         StockPluginContext *psContext = ((StockPlugin*)object)->context;
308
309         DebugOut(("stock_plugin_finalize"));
310         
311         if(psContext)
312         {
313                 if(psContext->psSettings)
314                 {
315                         if(psContext->psSettings)
316                                 g_free(psContext->psSettings->iD);
317                         
318                         stock_free_settings(psContext->psSettings);
319                         g_free(psContext->psSettings);
320                 }
321
322                 if(psContext->nTimerID != -1)
323                 {
324                         g_source_remove(psContext->nTimerID);
325                         psContext->nTimerID = -1;
326                 }
327
328                 if(psContext->hSG)
329                         FreeStockGetter(psContext->hSG);
330                 
331                 g_free(psContext);
332         }
333 }
334
335 static void
336 stock_plugin_init (StockPlugin *desktop_plugin)
337 {
338         StockPluginContext *psContext;
339         SGHandle hSG;
340
341         DebugOut(("stock_plugin_init"));
342         
343         hSG = InitStockGetter();
344
345         if(!hSG)
346         {
347                 return;
348         }
349
350         /* Create plugin context */
351         psContext = g_new0(StockPluginContext,1);
352
353         /* Always create a settings */
354         psContext->psSettings     = g_new0(StockPluginSettings,1);
355         psContext->psSettings->iD = g_strdup(STOCK_PLUGIN_APPLET_ID);
356
357         /* Attempt to load settings (defaulted if not present) */
358         stock_read_settings(psContext->psSettings);
359
360         /* Create GUI widget interface (and sets links in context) */
361         stock_create_gui("Click to Update",psContext);
362
363         gtk_container_add(GTK_CONTAINER(desktop_plugin), psContext->psEventBox);
364         
365         psContext->hSG          = hSG;
366         psContext->nTimerID = -1;
367
368         /* Assign private data to plugin */
369         desktop_plugin->context = psContext;
370         
371         /* Remove previous timeout if there was one */
372         if(psContext->nTimerID != -1)
373         {
374                 g_source_remove(psContext->nTimerID);
375         }
376
377         /* Setup new one if required */
378         if(psContext->psSettings->uiUpdateTime > 0)
379         {
380                 psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60,
381                                                                                                         UpdateStockOnTimeout,
382                                                                                                         psContext);
383         }
384
385         /* Show a settings button */
386         hd_home_plugin_item_set_settings (HD_HOME_PLUGIN_ITEM (desktop_plugin), TRUE);
387
388         /* Connect to the settings button */
389         g_signal_connect(desktop_plugin, "show-settings", G_CALLBACK(stock_show_settings_dialog), psContext);
390 }
391
392 static void
393 stock_plugin_realize(GtkWidget* widget)
394 {
395         GdkScreen *screen = gtk_widget_get_screen(widget);
396         gtk_widget_set_colormap(widget, gdk_screen_get_rgba_colormap(screen));
397         gtk_widget_set_app_paintable(widget, TRUE);
398         GTK_WIDGET_CLASS(stock_plugin_parent_class)->realize(widget);
399 }
400
401 void
402 cairo_rounded_rectangle(cairo_t *cr,
403                                                 double x,
404                                                 double y,
405                                                 double width,
406                                                 double height,
407                                                 double corner_div)
408 {
409         double          aspect            = 1.0;
410         double          corner_radius = height / corner_div;
411         double          radius            = corner_radius / aspect;
412         double          degrees           = 3.14159265 / 180.0;
413
414         cairo_new_sub_path (cr);
415         cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
416         cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
417         cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
418         cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
419         cairo_close_path (cr);
420
421         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.25);
422         cairo_fill_preserve (cr);
423         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
424         cairo_set_line_width (cr, 1.0);
425         cairo_stroke (cr);
426 }
427
428 static gboolean
429 stock_plugin_expose(GtkWidget* widget, GdkEventExpose *event)
430 {
431         PangoLayout*                            layout;
432         PangoFontDescription*           desc;
433         cairo_t*                                        cr;
434         int                                                     width  = 0;
435         int                                                     height = 0;
436
437         GdkRectangle *psRect;
438
439         StockPluginContext *psContext = STOCK_HOME_PLUGIN(widget)->context;
440         
441         cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
442
443         psRect = &event->area;
444
445         width = psRect->width;
446         height = psRect->height;
447
448         DebugOut(("X: %d Y: %d Width: %d Height %d",psRect->x,psRect->y,psRect->width,psRect->height));
449         
450         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
451         cairo_rounded_rectangle(cr, 0, 0, width, height, 10.0);
452         cairo_fill(cr);
453         
454         cairo_destroy(cr);
455         return GTK_WIDGET_CLASS(stock_plugin_parent_class)->expose_event(widget, event);
456 }
457
458 static void
459 stock_plugin_class_init (StockPluginClass *klass)
460 {
461         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
462         #if 1
463         widget_class->realize                   = stock_plugin_realize;
464         widget_class->expose_event              = stock_plugin_expose;
465         #endif
466         G_OBJECT_CLASS(klass)->finalize = stock_plugin_finalize;
467 }
468
469 static void
470 stock_plugin_class_finalize (StockPluginClass *klass)
471 {
472 }