1 /**
2  * Copyright (c) 2012-2014 Piotr Sipika; see the AUTHORS file for more.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * See the COPYRIGHT file for more information.
19  */
20 
21 #include "location.h"
22 #include "forecast.h"
23 #include "yahooutil.h"
24 #include "weatherwidget.h"
25 #include "logutil.h"
26 
27 /* Using pthreads instead of glib's due to cancellability and API stability */
28 #include <pthread.h>
29 
30 #include <glib/gi18n.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
36 
37 #include "gtk-compat.h"
38 
39 /* Private structure, property and signal definitions. */
40 #define GTK_WEATHER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
41                                       GTK_WEATHER_TYPE, GtkWeatherPrivate))
42 
43 /* This will exit the app gracefully... */
44 #ifdef DEBUG
45 #define LOG_ERRNO(e, msg) \
46   do { errno = e; logUtil(LXW_ERROR, "%s: %s", msg, strerror(errno)); gtk_main_quit(); } while (0)
47 #else
48 #define LOG_ERRNO(e, msg) gtk_main_quit()
49 #endif
50 
51 #define GTK_WEATHER_NAME "GtkWeather"
52 #define GTK_WEATHER_NOT_AVAILABLE_LABEL _("[N/A]")
53 
54 typedef struct _GtkWeatherPrivate     GtkWeatherPrivate;
55 typedef struct _LocationThreadData    LocationThreadData;
56 typedef struct _ForecastThreadData    ForecastThreadData;
57 typedef struct _PopupMenuData         PopupMenuData;
58 typedef struct _PreferencesDialogData PreferencesDialogData;
59 
60 enum
61 {
62   CITY_COLUMN = 0,
63   STATE_COLUMN,
64   COUNTRY_COLUMN,
65   MAX_COLUMNS
66 };
67 
68 #ifdef USE_STANDALONE
69 struct _PopupMenuData
70 {
71   GtkWidget * menu;
72   GtkWidget * refresh_item;
73   GtkWidget * preferences_item;
74   GtkWidget * quit_item;
75 };
76 #endif
77 
78 struct _PreferencesDialogData
79 {
80   gboolean shown;
81   GtkWidget * dialog;
82   GtkWidget * location_label;
83   GtkWidget * location_button;
84   GtkWidget * alias_entry;
85   GtkWidget * c_button;
86   GtkWidget * f_button;
87   GtkWidget * manual_button;
88   GtkWidget * auto_button;
89   GtkWidget * auto_spin_button;
90 };
91 
92 struct _LocationThreadData
93 {
94   pthread_t * tid;
95   gchar     * location;
96   GtkProgressBar * progress_bar;
97   GtkWidget * progress_dialog;
98 };
99 
100 struct _ForecastThreadData
101 {
102   gint timerid;
103 };
104 
105 struct _GtkWeatherPrivate
106 {
107   /* Main Widget Box layout */
108   GtkWidget * hbox;
109   GtkWidget * image;
110   GtkWidget * label;
111 
112   /* Menus and dialogs */
113 #ifdef USE_STANDALONE
114   PopupMenuData menu_data;
115 #endif
116   PreferencesDialogData preferences_data;
117   GtkWidget * conditions_dialog;
118 
119   /* Internal data */
120   gpointer    previous_location;
121   gpointer    location;
122   gpointer    forecast;
123 
124   /* Data for location and forecast retrieval threads */
125   LocationThreadData location_data;
126   ForecastThreadData forecast_data;
127 };
128 
129 enum
130 {
131   LOCATION_CHANGED_SIGNAL,
132   FORECAST_CHANGED_SIGNAL,
133   LAST_SIGNAL
134 };
135 
136 enum
137 {
138   PROP_0,
139   PROP_LOCATION,
140   PROP_FORECAST
141 };
142 
143 static guint gtk_weather_signals[LAST_SIGNAL] = {0};
144 
145 /* Function declarations. */
146 static void gtk_weather_class_init    (GtkWeatherClass * klass);
147 static void gtk_weather_init          (GtkWeather * weather);
148 static void gtk_weather_render        (GtkWeather * weather);
149 static void gtk_weather_size_allocate (GtkWidget * widget, GtkAllocation * allocation);
150 
151 static void gtk_weather_destroy       (GObject * object);
152 
153 static void gtk_weather_set_property (GObject * object, guint prop_id,
154                                       const GValue * value, GParamSpec * param_spec);
155 static void gtk_weather_get_property (GObject * object, guint prop_id,
156                                       GValue * value, GParamSpec * param_spec);
157 
158 static void gtk_weather_set_location (GtkWeather * weather, gpointer location);
159 static void gtk_weather_set_forecast (GtkWeather * weather, gpointer forecast);
160 
161 static gboolean gtk_weather_button_pressed  (GtkWidget * widget, GdkEventButton * event);
162 static gboolean gtk_weather_key_pressed     (GtkWidget * widget, GdkEventKey * event, gpointer data);
163 static gboolean gtk_weather_change_location (GtkWidget * widget, GdkEventButton * event);
164 
165 static void gtk_weather_auto_update_toggled (GtkWidget * widget);
166 
167 #ifdef USE_STANDALONE
168 static void gtk_weather_create_popup_menu            (GtkWeather * weather);
169 #endif
170 static void gtk_weather_set_window_icon              (GtkWindow * window, gchar * icon_id);
171 static void gtk_weather_show_location_progress_bar   (GtkWeather * weather);
172 static void gtk_weather_show_location_list           (GtkWeather * weather, GList * list);
173 static void gtk_weather_update_preferences_dialog    (GtkWeather * weather);
174 
175 static void gtk_weather_get_forecast (GtkWidget * widget);
176 
177 static void gtk_weather_run_error_dialog (GtkWindow * parent, gchar * error_msg);
178 
179 static gboolean gtk_weather_update_location_progress_bar (gpointer data);
180 
181 static void * gtk_weather_get_location_threadfunc  (void * arg);
182 static gboolean gtk_weather_get_forecast_timerfunc (gpointer data);
183 
184 
185 /* Function definitions. */
186 
187 /**
188  * Provides the type definition for this widget.
189  *
190  * @return The type identifier for this widget.
191  */
192 GType
gtk_weather_get_type(void)193 gtk_weather_get_type(void)
194 {
195   /*
196    * Normally, the variable below is declared static and initialized to 0.
197    * However, when dealing with lxpanel, the type remains registered,
198    * while this widget class is removed from scope.
199    * This means that the variable below goes out of scope, BUT the type
200    * remains registered with GTK.
201    * Hence, g_type_from_name...
202    */
203   GType gtk_weather_type = g_type_from_name(GTK_WEATHER_NAME);
204 
205   LXW_LOG(LXW_DEBUG, "GtkWeather::get_type(): %lu", (gulong)gtk_weather_type);
206 
207   if (!gtk_weather_type)
208     {
209       static const GTypeInfo gtk_weather_info =
210         {
211           sizeof(GtkWeatherClass),
212           NULL,
213           NULL,
214           (GClassInitFunc)gtk_weather_class_init,
215           NULL,
216           NULL,
217           sizeof(GtkWeather),
218           0,
219           (GInstanceInitFunc)gtk_weather_init,
220           NULL
221         };
222 
223       gtk_weather_type = g_type_register_static(GTK_TYPE_EVENT_BOX,
224                                                 GTK_WEATHER_NAME,
225                                                 &gtk_weather_info,
226                                                 0);
227 
228     }
229 
230   return gtk_weather_type;
231 }
232 
233 /**
234  * Returns a new instance of this widget.
235  *
236  * @param standalone Whether or not this widget is being created from an
237  *                   application/plugin (FALSE) or if this widget IS the
238  *                   application (TRUE).
239  *
240  * @return A new instance of this widget type.
241  */
242 GtkWidget *
gtk_weather_new(void)243 gtk_weather_new(void)
244 {
245   GObject * object = g_object_new(gtk_weather_get_type(), NULL);
246 
247   return GTK_WIDGET(object);
248 }
249 
250 /**
251  * Initializes this widget's class internals.
252  *
253  * @param klass Pointer to this widget's class.
254  */
255 static void
gtk_weather_class_init(GtkWeatherClass * klass)256 gtk_weather_class_init(GtkWeatherClass * klass)
257 {
258   GObjectClass * gobject_class = (GObjectClass *)klass;
259   GtkWidgetClass * widget_class = (GtkWidgetClass *)klass;
260 
261   gobject_class->set_property = gtk_weather_set_property;
262   gobject_class->get_property = gtk_weather_get_property;
263   gobject_class->finalize     = gtk_weather_destroy;
264 
265   //widget_class->expose_event = gtk_weather_expose;
266   //widget_class->size_request = gtk_weather_size_request;
267   widget_class->size_allocate      = gtk_weather_size_allocate;
268   widget_class->button_press_event = gtk_weather_button_pressed;
269 
270   g_type_class_add_private(klass, sizeof(GtkWeatherPrivate));
271 
272   g_object_class_install_property(gobject_class, PROP_LOCATION,
273                                   g_param_spec_pointer("location",
274                                                        "Current Location",
275                                                        "Current Location",
276                                                        G_PARAM_READWRITE));
277 
278   g_object_class_install_property(gobject_class, PROP_FORECAST,
279                                   g_param_spec_pointer("forecast",
280                                                        "Current Conditions",
281                                                        "Current conditions and forecast",
282                                                        G_PARAM_READWRITE));
283 
284   gtk_weather_signals[LOCATION_CHANGED_SIGNAL] = g_signal_new("location-changed",
285                                                               G_TYPE_FROM_CLASS(klass),
286                                                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
287                                                               G_STRUCT_OFFSET(GtkWeatherClass, location_changed),
288                                                               NULL,
289                                                               NULL,
290                                                               g_cclosure_marshal_VOID__POINTER,
291                                                               G_TYPE_NONE,
292                                                               1,
293                                                               G_TYPE_POINTER);
294 
295   gtk_weather_signals[FORECAST_CHANGED_SIGNAL] = g_signal_new("forecast-changed",
296                                                               G_TYPE_FROM_CLASS(klass),
297                                                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
298                                                               G_STRUCT_OFFSET(GtkWeatherClass, forecast_changed),
299                                                               NULL,
300                                                               NULL,
301                                                               g_cclosure_marshal_VOID__POINTER,
302                                                               G_TYPE_NONE,
303                                                               1,
304                                                               G_TYPE_POINTER);
305 
306 }
307 
308 /**
309  * Initializes this widget's instance.
310  *
311  * @param weather Pointer to this widget's instance.
312  */
313 static void
gtk_weather_init(GtkWeather * weather)314 gtk_weather_init(GtkWeather * weather)
315 {
316   LXW_LOG(LXW_DEBUG, "GtkWeather::init()");
317 
318   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
319 
320   /* Box layout internals */
321   priv->hbox = gtk_hbox_new(FALSE, 1);
322 
323   priv->image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_BUTTON);
324 
325   priv->label = gtk_label_new(GTK_WEATHER_NOT_AVAILABLE_LABEL);
326 
327   gtk_box_pack_start(GTK_BOX(priv->hbox),
328                      priv->image,
329                      FALSE,
330                      FALSE,
331                      2);
332 
333   gtk_box_pack_start(GTK_BOX(priv->hbox),
334                      priv->label,
335                      FALSE,
336                      FALSE,
337                      0);
338 
339   gtk_container_add(GTK_CONTAINER(weather), priv->hbox);
340 
341   gtk_container_set_border_width(GTK_CONTAINER(weather), 2);
342 
343   /* Popup menu */
344 #ifdef USE_STANDALONE
345   gtk_weather_create_popup_menu(weather);
346 #endif
347 
348   priv->forecast_data.timerid = 0;
349 
350   /* Adjust size of label and icon inside */
351   gtk_weather_render(weather);
352 }
353 
354 /**
355  * Destroys the weather widget object
356  *
357  * @param object Pointer to this widget's instance cast as a GObject
358  */
359 static void
gtk_weather_destroy(GObject * object)360 gtk_weather_destroy(GObject * object)
361 {
362   LXW_LOG(LXW_DEBUG, "GtkWeather::destroy()");
363 
364   g_return_if_fail(object != NULL);
365   g_return_if_fail(IS_GTK_WEATHER(object));
366 
367   GtkWeather * weather = GTK_WEATHER(object);
368 
369   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
370 
371   if (priv->forecast_data.timerid > 0)
372     {
373       g_source_remove(priv->forecast_data.timerid);
374       priv->forecast_data.timerid = 0;
375     }
376 
377   /* Need to free location and forecast. */
378   freeLocation(priv->previous_location);
379   freeLocation(priv->location);
380   freeForecast(priv->forecast);
381 
382   priv->previous_location = NULL;
383   priv->location = NULL;
384   priv->forecast = NULL;
385 }
386 
387 /**
388  * Makes the requested allocation happen for this widget.
389  *
390  * @param widget     Pointer to the instance of this widget.
391  * @param allocation Pointer to the allocation being done.
392  */
393 static void
gtk_weather_size_allocate(GtkWidget * widget,GtkAllocation * allocation)394 gtk_weather_size_allocate(GtkWidget * widget, GtkAllocation * allocation)
395 {
396   /*  g_return_if_fail(widget != NULL || allocation != NULL);
397       g_return_if_fail(IS_GTK_WEATHER(widget));*/
398   if (!widget || !allocation || !IS_GTK_WEATHER(widget))
399     {
400       return;
401     }
402 
403   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
404 
405   LXW_LOG(LXW_DEBUG, "GtkWeather::size_allocate(%d): x: %d, y: %d, %dx%d (x: %d, y: %d, %dx%d)",
406           gtk_widget_get_has_window(widget),
407           allocation->x, allocation->y, allocation->width, allocation->height,
408           widget->allocation.x, widget->allocation.y,
409           widget->allocation.width, widget->allocation.height);
410 
411   /* check new allocation against previous one (height),
412      if they don't match, make a new icon...
413      this is done inside gtk_weather_render() function
414    */
415 
416   gtk_widget_set_allocation(widget, allocation);
417 
418   gboolean weather_has_window = gtk_widget_get_has_window(widget);
419 
420   if (gtk_widget_get_realized(widget) && weather_has_window)
421     {
422       gdk_window_move_resize(gtk_widget_get_window(widget),
423                              allocation->x,
424                              allocation->y,
425                              allocation->width,
426                              allocation->height);
427     }
428 
429   GtkAllocation box_allocation;
430 
431   /* we know the private hbox doesn't have a window */
432   box_allocation.x = 0;
433   box_allocation.y = 0;
434 
435   /* but in case we don't, either, let's make sure
436    * the box appears correctly...
437    */
438   if (!weather_has_window)
439     {
440       box_allocation.x = allocation->x;
441       box_allocation.y = allocation->y;
442     }
443 
444   box_allocation.height = allocation->height;
445   box_allocation.width = allocation->width;
446 
447   gtk_widget_size_allocate(GTK_WIDGET(priv->hbox), &box_allocation);
448 }
449 
450 /**
451  * Helper function to update the widget based on internal change.
452  *
453  * @param weather Pointer to the instance of this widget.
454  */
455 static void
gtk_weather_render(GtkWeather * weather)456 gtk_weather_render(GtkWeather * weather)
457 {
458   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
459 
460   LXW_LOG(LXW_DEBUG, "GtkWeather::render(): location: %p, forecast: %p",
461           priv->location, priv->forecast);
462 
463   if (priv->location && priv->forecast)
464     {
465       /*LocationInfo * location = (LocationInfo *)priv->location;*/
466       ForecastInfo * forecast = (ForecastInfo *)priv->forecast;
467 
468       GtkRequisition req;
469 
470       gtk_widget_size_request(GTK_WIDGET(priv->hbox), &req);
471 
472       /* req will hold valid data for painted widget, so disregard if we're
473        * running in a single app
474        */
475       if (req.height)
476         {
477           /* set this image to the one in the forecast at correct scale */
478           GdkPixbuf * forecast_pixbuf = gdk_pixbuf_scale_simple(forecast->pImage_,
479                                                                 req.height,
480                                                                 req.height,
481                                                                 GDK_INTERP_BILINEAR);
482 
483           gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image), forecast_pixbuf);
484 
485           if (G_IS_OBJECT(forecast_pixbuf))
486             {
487               g_object_unref(forecast_pixbuf);
488             }
489 
490         }
491 
492       /* update the label with proper temperature */
493       gchar * temperature = g_strdup_printf("%d \302\260%s",
494                                             forecast->iTemperature_,
495                                             forecast->units_.pcTemperature_);
496 
497       weather_set_label_text(GTK_WIDGET(weather), priv->label, temperature);
498 
499       //gtk_widget_show_all(priv->hbox);
500 
501       g_free(temperature);
502     }
503   else
504     {
505       /* N/A */
506       if (priv->location)
507         {
508           gtk_image_set_from_stock(GTK_IMAGE(priv->image),
509                                    GTK_STOCK_DIALOG_WARNING,
510                                    GTK_ICON_SIZE_BUTTON);
511         }
512       else
513         {
514           gtk_image_set_from_stock(GTK_IMAGE(priv->image),
515                                    GTK_STOCK_DIALOG_ERROR,
516                                    GTK_ICON_SIZE_BUTTON);
517         }
518 
519       weather_set_label_text(GTK_WIDGET(weather), priv->label,
520                              GTK_WEATHER_NOT_AVAILABLE_LABEL);
521     }
522 
523   /* update tooltip with proper data... */
524   gchar * tooltip_text = gtk_weather_get_tooltip_text(GTK_WIDGET(weather));
525 
526   gtk_widget_set_tooltip_text(GTK_WIDGET(weather), tooltip_text);
527 
528   g_free(tooltip_text);
529 }
530 
531 /* Property access functions */
532 /**
533  * Sets the specified property.
534  *
535  * @param object     Pointer to the GObject instance of this widget.
536  * @param prop_id    Property Id of the property to set.
537  * @param value      Pointer to the GValue containing actual value to use.
538  * @param param_spec Pointer to GParamSpec structure for this property.
539  */
540 static void
gtk_weather_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * param_spec)541 gtk_weather_set_property(GObject * object,
542                          guint prop_id,
543                          const GValue * value,
544                          GParamSpec * param_spec)
545 {
546   GtkWeather * weather = GTK_WEATHER(object);
547 
548   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
549 
550   LXW_LOG(LXW_DEBUG, "GtkWeather::set_property(%u - %s)", prop_id,
551           ((prop_id == PROP_LOCATION)?"location":
552            (prop_id == PROP_FORECAST)?"forecast":"???"));
553 
554   switch (prop_id)
555     {
556     case PROP_LOCATION:
557       gtk_weather_set_location(weather, g_value_get_pointer(value));
558 
559       /* Set previous location, to save it. */
560       copyLocation(&priv->previous_location, priv->location);
561 
562       /* The function starts timer if enabled, otherwise runs a single call. */
563       gtk_weather_get_forecast(GTK_WIDGET(weather));
564 
565       break;
566 
567     case PROP_FORECAST:
568       gtk_weather_set_forecast(weather, g_value_get_pointer(value));
569       break;
570 
571     default:
572       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, param_spec);
573       break;
574     }
575 
576 }
577 
578 /**
579  * Gets the specified property.
580  *
581  * @param object     Pointer to the GObject instance of this widget.
582  * @param prop_id    Property Id of the property to get.
583  * @param value      Pointer to the GValue to set with actual value.
584  * @param param_spec Pointer to GParamSpec structure for this property.
585  */
586 static void
gtk_weather_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * param_spec)587 gtk_weather_get_property(GObject * object,
588                          guint prop_id,
589                          GValue * value,
590                          GParamSpec * param_spec)
591 {
592   GtkWeather * weather = GTK_WEATHER(object);
593   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
594 
595   switch (prop_id)
596     {
597     case PROP_LOCATION:
598       g_value_set_pointer(value, priv->location);
599       break;
600 
601     case PROP_FORECAST:
602       g_value_set_pointer(value, priv->forecast);
603       break;
604 
605     default:
606       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, param_spec);
607       break;
608     }
609 
610 }
611 
612 /**
613  * Sets the location property pointer for this widget.
614  *
615  * @param weather  Pointer to the instance of this widget.
616  * @param location Location to use.
617  *
618  */
619 static void
gtk_weather_set_location(GtkWeather * weather,gpointer location)620 gtk_weather_set_location(GtkWeather * weather, gpointer location)
621 {
622   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
623 
624   LXW_LOG(LXW_DEBUG, "GtkWeather::set_location(): current: %p, new: %p",
625           priv->location, location);
626 
627 #ifdef DEBUG
628   printLocation(priv->location);
629   printLocation(location);
630 #endif
631 
632   if (location)
633     {
634       copyLocation(&priv->location, location);
635 
636       /* reset forecast */
637       gtk_weather_set_forecast(weather, NULL);
638 
639       /* weather is rendered inside */
640     }
641   else
642     {
643       freeLocation(priv->location);
644 
645       priv->location = NULL;
646 
647       gtk_weather_render(weather);
648     }
649 
650   /* Emit location-changed event */
651   g_signal_emit_by_name(weather, "location-changed", location);
652 }
653 
654 /**
655  * Sets the forecast property pointer for this widget.
656  *
657  * @param weather  Pointer to the instance of this widget.
658  * @param forecast Forecast to use.
659  *
660  */
661 static void
gtk_weather_set_forecast(GtkWeather * weather,gpointer forecast)662 gtk_weather_set_forecast(GtkWeather * weather, gpointer forecast)
663 {
664   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
665 
666   LXW_LOG(LXW_DEBUG, "GtkWeather::set_forecast(): current: %p, new: %p",
667           priv->forecast, forecast);
668 
669 #ifdef DEBUG
670   printForecast(priv->forecast);
671   printForecast(forecast);
672 #endif
673 
674   if (priv->forecast && priv->forecast != forecast)
675     {
676       freeForecast(priv->forecast);
677 
678       priv->forecast = forecast;
679     }
680 
681   gtk_weather_render(weather);
682 
683   /* Emit forecast-changed event */
684   g_signal_emit_by_name(weather, "forecast-changed", forecast);
685 }
686 
687 
688 /* Action callbacks (button/cursor/key) */
689 /**
690  * Handles the button-pressed event.
691  *
692  * @param widget Pointer to the instance on which the event occurred.
693  * @param event  Pointer to the event structure with details.
694  *
695  * @return TRUE if the event should not be propagated further, FALSE otherwise.
696  */
697 static gboolean
gtk_weather_button_pressed(GtkWidget * widget,GdkEventButton * event)698 gtk_weather_button_pressed(GtkWidget * widget, GdkEventButton * event)
699 {
700   LXW_LOG(LXW_DEBUG, "GtkWeather::button_pressed(): Button: %d, type: %d",
701           event->button, event->type);
702 
703   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
704 
705 #ifdef USE_STANDALONE
706   /* If right-clicked, show popup */
707   if (event->button == 3 && (event->type == GDK_BUTTON_PRESS))
708     {
709       gtk_weather_run_popup_menu(widget);
710 
711       return TRUE;
712     }
713 #endif
714   if (event->button == 1 && (event->type == GDK_BUTTON_PRESS))
715     {
716       if (priv->conditions_dialog)
717         gtk_dialog_response(GTK_DIALOG(priv->conditions_dialog), GTK_RESPONSE_ACCEPT);
718       else
719         gtk_weather_run_conditions_dialog(widget);
720 
721       return TRUE;
722     }
723 
724   return FALSE;
725 }
726 
727 /**
728  * Handles the toggled event for auto/manual radio buttons
729  *
730  * @param widget Poitner to the instance of this widget
731  */
732 static void
gtk_weather_auto_update_toggled(GtkWidget * widget)733 gtk_weather_auto_update_toggled(GtkWidget * widget)
734 {
735   LXW_LOG(LXW_DEBUG, "GtkWeather::auto_update_toggled()");
736 
737   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
738 
739   LocationInfo * location = (LocationInfo *)priv->location;
740 
741   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->preferences_data.auto_button)) &&
742       priv->location)
743     {
744       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.manual_button), FALSE);
745       gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), TRUE);
746       gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->preferences_data.auto_spin_button),
747                                 (gdouble)location->uiInterval_);
748     }
749   else
750     {
751       gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), FALSE);
752     }
753 
754 }
755 
756 /**
757  * Handles the button-pressed event for the location set/change button.
758  *
759  * @param widget Pointer to the instance of this widget.
760  * @param event  Pointer to the event structure with details.
761  *
762  * @return TRUE if the event should not be propagated further, FALSE otherwise.
763  */
764 static gboolean
gtk_weather_change_location(GtkWidget * widget,GdkEventButton * event)765 gtk_weather_change_location(GtkWidget * widget, GdkEventButton * event)
766 {
767   LXW_LOG(LXW_DEBUG, "GtkWeather::change_location");
768 
769   /* disable compilation warning */
770   (void)event;
771 
772   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
773 
774   GtkWidget * dialog = gtk_dialog_new_with_buttons(_("Enter New Location"),
775                                                    GTK_WINDOW(priv->preferences_data.dialog),
776                                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
777                                                    GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
778                                                    GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
779                                                    NULL);
780 
781   /* Set dialog window icon */
782   gtk_weather_set_window_icon(GTK_WINDOW(dialog), "gtk-properties");
783 
784   gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
785 
786   gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
787 
788   GtkWidget * location_label = gtk_label_new_with_mnemonic(_("_New Location:"));
789 
790   GtkWidget * location_entry = gtk_entry_new();
791 
792   g_signal_connect(G_OBJECT(location_entry),
793                    "key-press-event",
794                    G_CALLBACK(gtk_weather_key_pressed),
795                    (gpointer)dialog);
796 
797   GtkWidget * image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
798 
799   GtkWidget * description_label = gtk_label_new(_("Enter the:\n- city, or\n- city and state/country, or\n- postal code\nfor which to retrieve the weather forecast."));
800 
801   gtk_label_set_justify(GTK_LABEL(description_label), GTK_JUSTIFY_LEFT);
802 
803   GtkWidget * entry_hbox = gtk_hbox_new(FALSE, 10);
804 
805   gtk_box_pack_start(GTK_BOX(entry_hbox), location_label, FALSE, FALSE, 5);
806   gtk_box_pack_end(GTK_BOX(entry_hbox), location_entry, FALSE, FALSE, 5);
807 
808   GtkWidget * entry_vbox = gtk_vbox_new(FALSE, 10);
809 
810   gtk_box_pack_start(GTK_BOX(entry_vbox), description_label, FALSE, FALSE, 5);
811   gtk_box_pack_start(GTK_BOX(entry_vbox), entry_hbox, FALSE, FALSE, 5);
812 
813   GtkWidget * label_hbox = gtk_hbox_new(FALSE, 10);
814 
815   gtk_box_pack_start(GTK_BOX(label_hbox), image, FALSE, FALSE, 5);
816   gtk_box_pack_start(GTK_BOX(label_hbox), entry_vbox, FALSE, FALSE, 5);
817 
818   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), label_hbox, TRUE, FALSE, 10);
819 
820   gtk_widget_show_all(dialog);
821 
822   gint response = GTK_RESPONSE_NONE;
823 
824   do
825     {
826       response = gtk_dialog_run(GTK_DIALOG(dialog));
827 
828       /* handle ACCEPT/OK response to process new location */
829       switch(response)
830         {
831         case GTK_RESPONSE_ACCEPT:
832           /* location must be entered... */
833           if (gtk_entry_get_text_length(GTK_ENTRY(location_entry)) == 0)
834             {
835               gtk_weather_run_error_dialog(GTK_WINDOW(dialog),
836                                            _("You must specify a location."));
837 
838               break;
839             }
840 
841           gchar * new_location = g_strdup(gtk_entry_get_text(GTK_ENTRY(location_entry)));
842 
843           /* start thread here, let the progress bar do its own magic */
844           pthread_t tid;
845           pthread_attr_t tattr;
846 
847           int ret = pthread_attr_init(&tattr);
848 
849           if (ret != 0)
850             {
851               LOG_ERRNO(ret, "pthread_attr_init");
852             }
853 
854           ret = pthread_create(&tid, &tattr, &gtk_weather_get_location_threadfunc, new_location);
855 
856           if (ret != 0)
857             {
858               LOG_ERRNO(ret, "pthread_create");
859             }
860 
861           ret = pthread_attr_destroy(&tattr);
862 
863           if (ret != 0)
864             {
865               LOG_ERRNO(ret, "pthread_attr_destroy");
866             }
867 
868           priv->location_data.tid = &tid;
869           priv->location_data.location = new_location;
870 
871           /* show progress bar and lookup selected location */
872           gtk_weather_show_location_progress_bar(GTK_WEATHER(widget));
873 
874           void * result = NULL;
875 
876           ret = pthread_join(tid, &result);
877 
878           if (ret != 0)
879             {
880               LOG_ERRNO(ret, "pthread_join");
881             }
882 
883           gchar * error_msg = g_strdup_printf(_("Location '%s' not found!"), new_location);
884 
885           if (result && result != PTHREAD_CANCELED)
886             {
887               GList * list = (GList *)result;
888 
889               guint length = g_list_length(list);
890 
891               LXW_LOG(LXW_DEBUG, "Thread returned list of length %u", length);
892 
893               if (length > 0)
894                 {
895                   gtk_weather_show_location_list(GTK_WEATHER(widget), list);
896                 }
897               else
898                 {
899                   gtk_weather_run_error_dialog(GTK_WINDOW(dialog), error_msg);
900                 }
901 
902               /* Free list */
903               g_list_free_full(list, freeLocation);
904 
905               /* Repaint preferences dialog */
906               gtk_weather_update_preferences_dialog(GTK_WEATHER(widget));
907             }
908           else if (result == PTHREAD_CANCELED)
909             {
910               /* nothing, user canceled search... */
911             }
912           else
913             {
914               gtk_weather_run_error_dialog(GTK_WINDOW(dialog), error_msg);
915             }
916 
917           g_free(error_msg);
918 
919           g_free(new_location);
920 
921           break;
922 
923         default:
924           LXW_LOG(LXW_DEBUG, "\tdefault: %d", response);
925 
926           break;
927         }
928 
929     } while ( (response == GTK_RESPONSE_ACCEPT) &&
930               (gtk_entry_get_text_length(GTK_ENTRY(location_entry)) == 0) );
931 
932   if (GTK_IS_WIDGET(dialog))
933     {
934       gtk_widget_destroy(dialog);
935     }
936 
937   priv->location_data.tid = 0;
938   priv->location_data.location = NULL;
939 
940   dialog = NULL;
941 
942   return TRUE;
943 }
944 
945 /**
946  * Handles the key-pressed event.
947  *
948  * @param widget Pointer to the instance on which the event occurred.
949  * @param event  Pointer to the event structure with details.
950  * @param data   Pointer to user-data.
951  *
952  * @return TRUE if the event should not be propagated further, FALSE otherwise.
953  */
954 static gboolean
gtk_weather_key_pressed(GtkWidget * widget,GdkEventKey * event,gpointer data)955 gtk_weather_key_pressed(GtkWidget * widget, GdkEventKey * event, gpointer data)
956 {
957   LXW_LOG(LXW_DEBUG, "GtkWeather::key_pressed");
958 
959   if (GTK_IS_ENTRY(widget))
960     {
961       /* See if it's enter */
962       if (event->keyval == GDK_KEY_Return ||
963           event->keyval == GDK_KEY_KP_Enter)
964         {
965           /* Check length and act accordingly */
966           if (gtk_entry_get_text_length(GTK_ENTRY(widget)) == 0)
967             {
968               gtk_weather_run_error_dialog(GTK_WINDOW(data),
969                                            _("You must specify a location."));
970             }
971           else
972             {
973               gtk_dialog_response(GTK_DIALOG(data), GTK_RESPONSE_ACCEPT);
974             }
975 
976         }
977     }
978   else if (GTK_IS_BUTTON(widget))
979     {
980       if (event->keyval == GDK_KEY_Return ||
981           event->keyval == GDK_KEY_KP_Enter ||
982           event->keyval == GDK_KEY_space)
983         {
984           /* Don't care about the return value or the event pointer */
985           gtk_weather_change_location(GTK_WIDGET(data), NULL);
986         }
987 
988     }
989 
990   return FALSE;
991 }
992 
993 /* GTK helper functions */
994 /**
995  * Creates and shows an error dialog.
996  *
997  * @param parent Parent window pointer.
998  * @param error_msg Error message to display.
999  */
1000 static void
gtk_weather_run_error_dialog(GtkWindow * parent,gchar * error_msg)1001 gtk_weather_run_error_dialog(GtkWindow * parent, gchar * error_msg)
1002 {
1003   LXW_LOG(LXW_DEBUG, "GtkWeather::run_error_dialog(%s)", error_msg);
1004 
1005   static gboolean shown = FALSE;
1006 
1007   if (!shown)
1008     {
1009       GtkWidget * error_dialog = gtk_message_dialog_new(parent,
1010                                                         GTK_DIALOG_MODAL,
1011                                                         GTK_MESSAGE_ERROR,
1012                                                         GTK_BUTTONS_OK,
1013                                                         "%s", error_msg);
1014 
1015       gtk_weather_set_window_icon(GTK_WINDOW(error_dialog), "gtk-dialog-error");
1016 
1017       shown = TRUE;
1018 
1019       gtk_dialog_run(GTK_DIALOG(error_dialog));
1020 
1021       gtk_widget_destroy(error_dialog);
1022 
1023       shown = FALSE;
1024     }
1025 }
1026 
1027 #ifdef USE_STANDALONE
1028 /**
1029  * Creates a pop-up menu.
1030  *
1031  * @param weather Pointer to the instance of this widget.
1032  */
1033 static void
gtk_weather_create_popup_menu(GtkWeather * weather)1034 gtk_weather_create_popup_menu(GtkWeather * weather)
1035 {
1036   LXW_LOG(LXW_DEBUG, "GtkWeather::create_popup_menu()");
1037 
1038   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1039 
1040   priv->menu_data.menu = gtk_menu_new();
1041 
1042   priv->menu_data.preferences_item = gtk_image_menu_item_new_with_label(_("Preferences"));
1043 
1044   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(priv->menu_data.preferences_item),
1045                                 gtk_image_new_from_stock(GTK_STOCK_PREFERENCES,
1046                                                          GTK_ICON_SIZE_MENU));
1047 
1048   priv->menu_data.refresh_item = gtk_image_menu_item_new_with_label(_("Refresh"));
1049 
1050   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(priv->menu_data.refresh_item),
1051                                 gtk_image_new_from_stock(GTK_STOCK_REFRESH,
1052                                                          GTK_ICON_SIZE_MENU));
1053 
1054   priv->menu_data.quit_item = gtk_image_menu_item_new_with_label(_("Quit"));
1055 
1056   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(priv->menu_data.quit_item),
1057                                 gtk_image_new_from_stock(GTK_STOCK_QUIT,
1058                                                          GTK_ICON_SIZE_MENU));
1059 
1060   gtk_menu_shell_append(GTK_MENU_SHELL(priv->menu_data.menu), priv->menu_data.preferences_item);
1061 
1062   gtk_menu_shell_append(GTK_MENU_SHELL(priv->menu_data.menu), gtk_separator_menu_item_new());
1063 
1064   gtk_menu_shell_append(GTK_MENU_SHELL(priv->menu_data.menu), priv->menu_data.refresh_item);
1065 
1066   gtk_menu_shell_append(GTK_MENU_SHELL(priv->menu_data.menu), gtk_separator_menu_item_new());
1067 
1068   gtk_menu_shell_append(GTK_MENU_SHELL(priv->menu_data.menu), priv->menu_data.quit_item);
1069 
1070   /* connect signals appropriately */
1071   g_signal_connect_swapped(G_OBJECT(priv->menu_data.preferences_item),
1072                            "activate",
1073                            G_CALLBACK(gtk_weather_run_preferences_dialog),
1074                            GTK_WIDGET(weather));
1075 
1076   g_signal_connect_swapped(G_OBJECT(priv->menu_data.refresh_item),
1077                            "activate",
1078                            G_CALLBACK(gtk_weather_get_forecast),
1079                            GTK_WIDGET(weather));
1080 
1081   g_signal_connect_swapped(G_OBJECT(priv->menu_data.quit_item),
1082                            "activate",
1083                            G_CALLBACK(gtk_main_quit),
1084                            NULL);
1085 
1086   gtk_menu_attach_to_widget(GTK_MENU(priv->menu_data.menu), GTK_WIDGET(weather), NULL);
1087 
1088   gtk_widget_show_all(priv->menu_data.menu);
1089 }
1090 #endif
1091 
1092 /**
1093  * Callback for the preferences menu response.
1094  *
1095  * @param dialog Pointer to the preferences dialog.
1096  * @param response ID of the response action.
1097  * @param data   Pointer to user data (weather widget instance).
1098  */
1099 void
gtk_weather_preferences_dialog_response(GtkDialog * dialog,gint response,gpointer data)1100 gtk_weather_preferences_dialog_response(GtkDialog *dialog, gint response, gpointer data)
1101 {
1102   LXW_LOG(LXW_DEBUG, "GtkWeather::popup_menu(%d)", response);
1103 
1104   GtkWeather * weather = GTK_WEATHER(data);
1105 
1106   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1107 
1108   switch(response)
1109     {
1110     case GTK_RESPONSE_ACCEPT:
1111       if (priv->location)
1112         {
1113           LocationInfo * location = (LocationInfo *)priv->location;
1114 
1115           setLocationAlias(priv->location,
1116                            (gpointer)gtk_entry_get_text(GTK_ENTRY(priv->preferences_data.alias_entry)));
1117 
1118           location->bEnabled_ = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->preferences_data.auto_button));
1119 
1120           if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON((priv->preferences_data.c_button))))
1121             {
1122               location->cUnits_ = 'c';
1123             }
1124           else
1125             {
1126               location->cUnits_ = 'f';
1127             }
1128 
1129           location->uiInterval_ = (guint)gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(priv->preferences_data.auto_spin_button));
1130 
1131           /* Set this location as the valid one */
1132           copyLocation(&priv->previous_location, priv->location);
1133 
1134           /* get forecast */
1135           gtk_weather_get_forecast(GTK_WIDGET(weather));
1136 
1137           gtk_weather_render(weather);
1138 
1139           weather_save_configuration(GTK_WIDGET(weather), location);
1140         }
1141 
1142       break;
1143 
1144     case GTK_RESPONSE_REJECT:
1145       gtk_weather_set_location(weather, priv->previous_location);
1146 
1147       gtk_weather_get_forecast(GTK_WIDGET(weather));
1148 
1149       break;
1150     default:
1151       /* Leave everything as-is*/
1152       break;
1153     }
1154 
1155   priv->preferences_data.dialog = NULL;
1156 
1157   priv->preferences_data.shown = FALSE;
1158 }
1159 
1160 #ifdef USE_STANDALONE
1161 /**
1162  * Shows the popup menu used for configuration.
1163  *
1164  * @param widget Pointer to the current instance of the weather widget.
1165  */
1166 void
gtk_weather_run_popup_menu(GtkWidget * widget)1167 gtk_weather_run_popup_menu(GtkWidget * widget)
1168 {
1169   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
1170 
1171   LXW_LOG(LXW_DEBUG, "GtkWeather::popup_menu()");
1172 
1173   gtk_widget_show(GTK_WIDGET(priv->menu_data.quit_item));
1174 
1175   /* grey-out refresh, if no location is set */
1176   if (!priv->location)
1177     {
1178       gtk_widget_set_sensitive(priv->menu_data.refresh_item, FALSE);
1179     }
1180   else
1181     {
1182       gtk_widget_set_sensitive(priv->menu_data.refresh_item, TRUE);
1183     }
1184 
1185   gtk_menu_popup(GTK_MENU(priv->menu_data.menu),
1186                  NULL, NULL, NULL, NULL,
1187                  3, // right-click
1188                  gtk_get_current_event_time());
1189 
1190 }
1191 #endif
1192 
1193 /**
1194  * Creates the preferences dialog.
1195  *
1196  * @param widget Pointer to the current instance of the weather object.
1197  *
1198  * @return pointer to the preferences dialog, or NULL on failure.
1199  */
1200 GtkWidget *
gtk_weather_create_preferences_dialog(GtkWidget * widget)1201 gtk_weather_create_preferences_dialog(GtkWidget * widget)
1202 {
1203   GtkWeather * weather = GTK_WEATHER(widget);
1204 
1205   /* @NOTE: watch for parent window when dealing with the plugin */
1206   /* @TODO: connect the response signal to the proper function */
1207   LXW_LOG(LXW_DEBUG, "GtkWeather::create_preferences_dialog()");
1208 
1209   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1210 
1211   priv->preferences_data.dialog = gtk_dialog_new_with_buttons(_("Weather Preferences"),
1212                                                               NULL,
1213                                                               GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1214                                                               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1215                                                               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1216                                                               NULL);
1217 
1218   /* Set dialog window icon */
1219   gtk_weather_set_window_icon(GTK_WINDOW(priv->preferences_data.dialog), "gtk-preferences");
1220 
1221   gtk_window_set_resizable(GTK_WINDOW(priv->preferences_data.dialog), FALSE);
1222 
1223   gtk_dialog_set_default_response(GTK_DIALOG(priv->preferences_data.dialog), GTK_RESPONSE_ACCEPT);
1224 
1225   GtkWidget * location_frame = gtk_frame_new(_("Current Location"));
1226 
1227   GtkWidget * location_hbox = gtk_hbox_new(FALSE, 1);
1228 
1229   priv->preferences_data.location_label = gtk_label_new(_("None configured"));
1230 
1231   priv->preferences_data.location_button = gtk_button_new_with_mnemonic(_("_Set"));
1232 
1233   g_signal_connect(G_OBJECT(priv->preferences_data.location_button),
1234                    "key-press-event",
1235                    G_CALLBACK(gtk_weather_key_pressed),
1236                    (gpointer)widget);
1237 
1238   g_signal_connect_swapped(G_OBJECT(priv->preferences_data.location_button),
1239                            "button-press-event",
1240                            G_CALLBACK(gtk_weather_change_location),
1241                            GTK_WIDGET(weather));
1242 
1243   gtk_box_pack_start(GTK_BOX(location_hbox),
1244                      priv->preferences_data.location_label,
1245                      TRUE, FALSE, 1);
1246 
1247   gtk_box_pack_end(GTK_BOX(location_hbox),
1248                    priv->preferences_data.location_button, FALSE, FALSE, 10);
1249 
1250   gtk_container_add(GTK_CONTAINER(location_frame), location_hbox);
1251 
1252   GtkWidget * display_frame = gtk_frame_new(_("Display"));
1253 
1254   GtkWidget * display_table = gtk_table_new(2, 2, FALSE);
1255 
1256   GtkWidget * alias_label = gtk_label_new(_("Name:"));
1257 
1258   priv->preferences_data.alias_entry = gtk_entry_new();
1259 
1260   GtkWidget * button_label = gtk_label_new(_("Units:"));
1261 
1262   GtkWidget * button_hbox = gtk_hbox_new(TRUE, 10);
1263 
1264   priv->preferences_data.c_button = gtk_radio_button_new_with_mnemonic(NULL, _("_Metric (\302\260C)"));
1265 
1266   priv->preferences_data.f_button = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(priv->preferences_data.c_button), _("_English (\302\260F)"));
1267 
1268   gtk_box_pack_end(GTK_BOX(button_hbox), priv->preferences_data.c_button, FALSE, FALSE, 1);
1269   gtk_box_pack_end(GTK_BOX(button_hbox), priv->preferences_data.f_button, FALSE, FALSE, 1);
1270 
1271   gtk_table_attach(GTK_TABLE(display_table),
1272                    alias_label,
1273                    0,1,0,1,
1274                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1275                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1276                    10,5);
1277 
1278   gtk_table_attach(GTK_TABLE(display_table),
1279                    priv->preferences_data.alias_entry,
1280                    1,2,0,1,
1281                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1282                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1283                    10,5);
1284 
1285   gtk_table_attach(GTK_TABLE(display_table),
1286                    button_label,
1287                    0,1,1,2,
1288                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1289                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1290                    10,5);
1291 
1292   gtk_table_attach(GTK_TABLE(display_table),
1293                    button_hbox,
1294                    1,2,1,2,
1295                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1296                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1297                    10,5);
1298 
1299   gtk_container_add(GTK_CONTAINER(display_frame), display_table);
1300 
1301   GtkWidget * forecast_frame = gtk_frame_new(_("Forecast"));
1302 
1303   GtkWidget * forecast_table = gtk_table_new(2, 2, FALSE);
1304 
1305   GtkWidget * update_label = gtk_label_new(_("Updates:"));
1306 
1307   GtkWidget * update_vbox = gtk_vbox_new(TRUE, 10);
1308 
1309   priv->preferences_data.manual_button = gtk_radio_button_new_with_mnemonic(NULL, _("Ma_nual"));
1310 
1311   priv->preferences_data.auto_button =
1312     gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(priv->preferences_data.manual_button),
1313                                                    _("_Automatic, every"));
1314 
1315   g_signal_connect_swapped(G_OBJECT(priv->preferences_data.manual_button),
1316                            "toggled",
1317                            G_CALLBACK(gtk_weather_auto_update_toggled),
1318                            widget);
1319 
1320   g_signal_connect(G_OBJECT(priv->preferences_data.dialog),
1321                    "response",
1322                    G_CALLBACK(gtk_weather_preferences_dialog_response),
1323                    widget);
1324 
1325   /*  g_signal_connect_swapped(G_OBJECT(priv->preferences_data.auto_button),
1326                            "toggled",
1327                            G_CALLBACK(gtk_weather_auto_update_toggled),
1328                            widget);*/
1329 
1330   GtkWidget * auto_hbox = gtk_hbox_new(FALSE, 2);
1331 
1332   priv->preferences_data.auto_spin_button = gtk_spin_button_new_with_range(1, 60, 1);
1333 
1334   GtkWidget * auto_min_label = gtk_label_new(_("minutes"));
1335 
1336   gtk_box_pack_start(GTK_BOX(auto_hbox), priv->preferences_data.auto_button, FALSE, FALSE, 1);
1337   gtk_box_pack_start(GTK_BOX(auto_hbox), priv->preferences_data.auto_spin_button, FALSE, FALSE, 1);
1338   gtk_box_pack_start(GTK_BOX(auto_hbox), auto_min_label, FALSE, FALSE, 1);
1339 
1340   gtk_box_pack_start(GTK_BOX(update_vbox), priv->preferences_data.manual_button, TRUE, TRUE, 0);
1341   gtk_box_pack_start(GTK_BOX(update_vbox), auto_hbox, TRUE, TRUE, 0);
1342 
1343   GtkWidget * source_label = gtk_label_new(_("Source:"));
1344 
1345   GtkWidget * yahoo_button = gtk_radio_button_new_with_mnemonic(NULL, "_Yahoo! Weather");
1346 
1347   gtk_widget_set_sensitive(yahoo_button, FALSE);
1348 
1349   gtk_table_attach(GTK_TABLE(forecast_table),
1350                    update_label,
1351                    0,1,0,1,
1352                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1353                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1354                    10,5);
1355 
1356   gtk_table_attach(GTK_TABLE(forecast_table),
1357                    update_vbox,
1358                    1,2,0,1,
1359                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1360                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1361                    10,5);
1362 
1363   gtk_table_attach(GTK_TABLE(forecast_table),
1364                    source_label,
1365                    0,1,1,2,
1366                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1367                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1368                    10,5);
1369 
1370   gtk_table_attach(GTK_TABLE(forecast_table),
1371                    yahoo_button,
1372                    1,2,1,2,
1373                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1374                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1375                    10,5);
1376 
1377   gtk_container_add(GTK_CONTAINER(forecast_frame), forecast_table);
1378 
1379   /* VBox packing starts here */
1380   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(priv->preferences_data.dialog))),
1381                      location_frame, TRUE, TRUE, 0);
1382 
1383   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(priv->preferences_data.dialog))),
1384                      display_frame, TRUE, TRUE, 0);
1385 
1386   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(priv->preferences_data.dialog))),
1387                      forecast_frame, TRUE, TRUE, 0);
1388 
1389   gtk_weather_update_preferences_dialog(weather);
1390 
1391   gtk_widget_show_all(priv->preferences_data.dialog);
1392 
1393   return priv->preferences_data.dialog;
1394 }
1395 
1396 #ifdef USE_STANDALONE
1397 /**
1398  * Creates and shows the preferences dialog.
1399  *
1400  * @param widget Pointer to the current instance of the weather object.
1401  */
1402 void
gtk_weather_run_preferences_dialog(GtkWidget * widget)1403 gtk_weather_run_preferences_dialog(GtkWidget * widget)
1404 {
1405   GtkWeather * weather = GTK_WEATHER(widget);
1406 
1407   /* @NOTE: watch for parent window when dealing with the plugin */
1408   LXW_LOG(LXW_DEBUG, "GtkWeather::run_preferences_dialog()");
1409 
1410   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1411 
1412   if (priv->preferences_data.shown)
1413     {
1414       return;
1415     }
1416 
1417   /* this dialog is the same one as priv->preferences_data.dialog */
1418   GtkWidget * dialog = gtk_weather_create_preferences_dialog(widget);
1419 
1420   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
1421 
1422   priv->preferences_data.shown = TRUE;
1423 }
1424 #endif
1425 
1426 /**
1427  * Creates and shows the preferences dialog window
1428  *
1429  * @param weather Pointer to the instance of this widget.
1430  */
1431 static void
gtk_weather_update_preferences_dialog(GtkWeather * weather)1432 gtk_weather_update_preferences_dialog(GtkWeather * weather)
1433 {
1434   // @NOTE: watch for parent window when dealing with the plugin.
1435   // @TODO: possibly set the position of dialog window right in the middle of the screen.
1436   LXW_LOG(LXW_DEBUG, "GtkWeather::update_preferences_dialog()");
1437 
1438   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1439 
1440   if (!priv->preferences_data.dialog)
1441     {
1442       return;
1443     }
1444 
1445   if (priv->location)
1446     {
1447       LocationInfo * location = (LocationInfo *)priv->location;
1448 
1449       /* populate location_label */
1450       gchar * loc = g_strconcat((location->pcCity_)?location->pcCity_:"",
1451                                 (location->pcCity_)?", ":"",
1452                                 (location->pcState_)?location->pcState_:"",
1453                                 (location->pcState_)?", ":"",
1454                                 (location->pcCountry_)?location->pcCountry_:"",
1455                                 NULL);
1456 
1457       gtk_label_set_text(GTK_LABEL(priv->preferences_data.location_label), loc);
1458 
1459       gtk_button_set_label(GTK_BUTTON(priv->preferences_data.location_button), _("C_hange"));
1460 
1461       /* populate the alias entry with pcAlias_ */
1462       gtk_widget_set_sensitive(priv->preferences_data.alias_entry, TRUE);
1463       gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), location->pcAlias_);
1464 
1465       gtk_widget_set_sensitive(priv->preferences_data.c_button, TRUE);
1466       gtk_widget_set_sensitive(priv->preferences_data.f_button, TRUE);
1467 
1468       gtk_widget_set_sensitive(priv->preferences_data.manual_button, TRUE);
1469       gtk_widget_set_sensitive(priv->preferences_data.auto_button, TRUE);
1470 
1471       /* populate/activate proper c/f button */
1472       if (location->cUnits_ == 'c')
1473         {
1474           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.c_button), TRUE);
1475           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.f_button), FALSE);
1476         }
1477       else
1478         {
1479           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.c_button), FALSE);
1480           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.f_button), TRUE);
1481         }
1482 
1483       /* populate/activate auto/manual button with auto-spin, if configured */
1484       if (location->bEnabled_)
1485         {
1486           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.auto_button), TRUE);
1487           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.manual_button), FALSE);
1488           gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), TRUE);
1489           gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->preferences_data.auto_spin_button),
1490                                     (gdouble)location->uiInterval_);
1491         }
1492       else
1493         {
1494           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.auto_button), FALSE);
1495           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.manual_button), TRUE);
1496           gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), FALSE);
1497         }
1498 
1499       g_free(loc);
1500     }
1501   else
1502     {
1503       gtk_button_set_label(GTK_BUTTON(priv->preferences_data.location_button), _("_Set"));
1504 
1505       gtk_label_set_text(GTK_LABEL(priv->preferences_data.location_label),
1506                          _("None configured"));
1507 
1508       gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), "");
1509 
1510       gtk_widget_set_sensitive(priv->preferences_data.alias_entry, FALSE);
1511 
1512       gtk_widget_set_sensitive(priv->preferences_data.c_button, FALSE);
1513       gtk_widget_set_sensitive(priv->preferences_data.f_button, FALSE);
1514 
1515       gtk_widget_set_sensitive(priv->preferences_data.auto_button, FALSE);
1516       gtk_widget_set_sensitive(priv->preferences_data.manual_button, FALSE);
1517       gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), FALSE);
1518     }
1519 
1520 }
1521 
1522 /**
1523  * Creates and shows the current conditions dialog.
1524  *
1525  * @param widget Pointer to the current instance of the weather object.
1526  */
1527 void
gtk_weather_run_conditions_dialog(GtkWidget * widget)1528 gtk_weather_run_conditions_dialog(GtkWidget * widget)
1529 {
1530   GtkWeather * weather = GTK_WEATHER(widget);
1531 
1532   LXW_LOG(LXW_DEBUG, "GtkWeather::run_conditions_dialog()");
1533 
1534   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1535 
1536   LocationInfo * location = (LocationInfo *)priv->location;
1537   ForecastInfo * forecast = (ForecastInfo *)priv->forecast;
1538 
1539   if (location && forecast)
1540     {
1541       if (priv->conditions_dialog)
1542         {
1543           return;
1544         }
1545 
1546       /* Both are available */
1547       gchar * dialog_title = g_strdup_printf(_("Current Conditions for %s"),
1548                                              (location)?location->pcAlias_:"");
1549 
1550       GtkWidget * dialog = gtk_dialog_new_with_buttons(dialog_title,
1551                                                        NULL,
1552                                                        GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1553                                                        GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
1554                                                        GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1555                                                        NULL);
1556 
1557       GtkWidget * everything_hbox = gtk_hbox_new(FALSE, 5);
1558 
1559       /* This vbox gets filled-in when the table is populated */
1560       GtkWidget * icon_vbox = gtk_vbox_new(FALSE, 1);
1561 
1562       GtkWidget * forecast_table = gtk_table_new(9, 2, FALSE);
1563 
1564       gchar * location_label_text = g_strconcat((location->pcCity_)?location->pcCity_:"",
1565                                                 (location->pcCity_)?", ":"",
1566                                                 (location->pcState_)?location->pcState_:"",
1567                                                 (location->pcState_)?", ":"",
1568                                                 (location->pcCountry_)?location->pcCountry_:"",
1569                                                 NULL);
1570 
1571       GtkWidget * location_name_label = gtk_label_new(_("Location:"));
1572       GtkWidget * location_name_text  = gtk_label_new(location_label_text);
1573 
1574       GtkWidget * label_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1575       GtkWidget * text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1576 
1577       gtk_container_add(GTK_CONTAINER(label_alignment), location_name_label);
1578       gtk_container_add(GTK_CONTAINER(text_alignment), location_name_text);
1579 
1580       gtk_table_attach(GTK_TABLE(forecast_table),
1581                        label_alignment,
1582                        0,1,0,1,
1583                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1584                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1585                        2,2);
1586 
1587       gtk_table_attach(GTK_TABLE(forecast_table),
1588                        text_alignment,
1589                        1,2,0,1,
1590                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1591                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1592                        2,2);
1593 
1594       GtkWidget * updated_label = gtk_label_new(_("Last updated:"));
1595       GtkWidget * updated_text = gtk_label_new(forecast->pcTime_);
1596 
1597       GtkWidget * updated_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1598       GtkWidget * updated_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1599 
1600       gtk_container_add(GTK_CONTAINER(updated_alignment), updated_label);
1601       gtk_container_add(GTK_CONTAINER(updated_text_alignment), updated_text);
1602 
1603       gtk_table_attach(GTK_TABLE(forecast_table),
1604                        updated_alignment,
1605                        0,1,1,2,
1606                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1607                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1608                        2,2);
1609 
1610       gtk_table_attach(GTK_TABLE(forecast_table),
1611                        updated_text_alignment,
1612                        1,2,1,2,
1613                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1614                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1615                        2,2);
1616 
1617       gchar * feels = g_strdup_printf("%d \302\260%s",
1618                           /* Yahoo reports chill always in Fahreheit degrees */
1619                                       (location->cUnits_ == 'c') ?
1620                                       (forecast->iWindChill_ - 32) * 5 / 9 :
1621                                       forecast->iWindChill_,
1622                                       forecast->units_.pcTemperature_);
1623 
1624       GtkWidget * feels_label = gtk_label_new(_("Feels like:"));
1625       GtkWidget * feels_text = gtk_label_new(feels);
1626 
1627       GtkWidget * feels_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1628       gtk_container_add(GTK_CONTAINER(feels_alignment), feels_label);
1629 
1630       GtkWidget * feels_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1631       gtk_container_add(GTK_CONTAINER(feels_text_alignment), feels_text);
1632 
1633       gtk_table_attach(GTK_TABLE(forecast_table),
1634                        feels_alignment,
1635                        0,1,2,3,
1636                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1637                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1638                        2,2);
1639 
1640       gtk_table_attach(GTK_TABLE(forecast_table),
1641                        feels_text_alignment,
1642                        1,2,2,3,
1643                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1644                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1645                        2,2);
1646 
1647       gchar * humidity = g_strdup_printf("%d%%", forecast->iHumidity_);
1648 
1649       GtkWidget * humidity_label = gtk_label_new(_("Humidity:"));
1650       GtkWidget * humidity_text = gtk_label_new(humidity);
1651 
1652       GtkWidget * humidity_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1653       gtk_container_add(GTK_CONTAINER(humidity_alignment), humidity_label);
1654 
1655       GtkWidget * humidity_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1656       gtk_container_add(GTK_CONTAINER(humidity_text_alignment), humidity_text);
1657 
1658       gtk_table_attach(GTK_TABLE(forecast_table),
1659                        humidity_alignment,
1660                        0,1,3,4,
1661                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1662                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1663                        2,2);
1664 
1665       gtk_table_attach(GTK_TABLE(forecast_table),
1666                        humidity_text_alignment,
1667                        1,2,3,4,
1668                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1669                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1670                        2,2);
1671 
1672       gchar * pressure = g_strdup_printf("%4.2f %s",
1673                                          forecast->dPressure_,
1674                                          forecast->units_.pcPressure_);
1675 
1676       GtkWidget * pressure_label = gtk_label_new(_("Pressure:"));
1677       GtkWidget * pressure_text = gtk_label_new(pressure);
1678 
1679       GtkWidget * pressure_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1680       gtk_container_add(GTK_CONTAINER(pressure_alignment), pressure_label);
1681 
1682       GtkWidget * pressure_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1683       gtk_container_add(GTK_CONTAINER(pressure_text_alignment), pressure_text);
1684 
1685       gtk_table_attach(GTK_TABLE(forecast_table),
1686                        pressure_alignment,
1687                        0,1,4,5,
1688                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1689                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1690                        2,2);
1691 
1692       gtk_table_attach(GTK_TABLE(forecast_table),
1693                        pressure_text_alignment,
1694                        1,2,4,5,
1695                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1696                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1697                        2,2);
1698 
1699       gchar * visibility = g_strdup_printf("%4.2f %s",
1700                                          forecast->dVisibility_,
1701                                          forecast->units_.pcDistance_);
1702 
1703       GtkWidget * visibility_label = gtk_label_new(_("Visibility:"));
1704       GtkWidget * visibility_text = gtk_label_new(visibility);
1705 
1706       GtkWidget * visibility_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1707       gtk_container_add(GTK_CONTAINER(visibility_alignment), visibility_label);
1708 
1709       GtkWidget * visibility_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1710       gtk_container_add(GTK_CONTAINER(visibility_text_alignment), visibility_text);
1711 
1712       gtk_table_attach(GTK_TABLE(forecast_table),
1713                        visibility_alignment,
1714                        0,1,5,6,
1715                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1716                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1717                        2,2);
1718 
1719       gtk_table_attach(GTK_TABLE(forecast_table),
1720                        visibility_text_alignment,
1721                        1,2,5,6,
1722                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1723                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1724                        2,2);
1725 
1726       gchar * wind = g_strdup_printf("%s %d %s",
1727                                      forecast->pcWindDirection_,
1728                                      forecast->iWindSpeed_,
1729                                      forecast->units_.pcSpeed_);
1730 
1731       GtkWidget * wind_label = gtk_label_new(_("Wind:"));
1732       GtkWidget * wind_text = gtk_label_new(wind);
1733 
1734       GtkWidget * wind_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1735       gtk_container_add(GTK_CONTAINER(wind_alignment), wind_label);
1736 
1737       GtkWidget * wind_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1738       gtk_container_add(GTK_CONTAINER(wind_text_alignment), wind_text);
1739 
1740       gtk_table_attach(GTK_TABLE(forecast_table),
1741                        wind_alignment,
1742                        0,1,6,7,
1743                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1744                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1745                        2,2);
1746 
1747       gtk_table_attach(GTK_TABLE(forecast_table),
1748                        wind_text_alignment,
1749                        1,2,6,7,
1750                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1751                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1752                        2,2);
1753 
1754       GtkWidget * sunrise_label = gtk_label_new(_("Sunrise:"));
1755       GtkWidget * sunrise_text = gtk_label_new(forecast->pcSunrise_);
1756 
1757       GtkWidget * sunrise_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1758       gtk_container_add(GTK_CONTAINER(sunrise_alignment), sunrise_label);
1759 
1760       GtkWidget * sunrise_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1761       gtk_container_add(GTK_CONTAINER(sunrise_text_alignment), sunrise_text);
1762 
1763       gtk_table_attach(GTK_TABLE(forecast_table),
1764                        sunrise_alignment,
1765                        0,1,7,8,
1766                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1767                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1768                        2,2);
1769 
1770       gtk_table_attach(GTK_TABLE(forecast_table),
1771                        sunrise_text_alignment,
1772                        1,2,7,8,
1773                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1774                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1775                        2,2);
1776 
1777       GtkWidget * sunset_label = gtk_label_new(_("Sunset:"));
1778       GtkWidget * sunset_text = gtk_label_new(forecast->pcSunset_);
1779 
1780       GtkWidget * sunset_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1781       gtk_container_add(GTK_CONTAINER(sunset_alignment), sunset_label);
1782 
1783       GtkWidget * sunset_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
1784       gtk_container_add(GTK_CONTAINER(sunset_text_alignment), sunset_text);
1785 
1786       gtk_table_attach(GTK_TABLE(forecast_table),
1787                        sunset_alignment,
1788                        0,1,8,9,
1789                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1790                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1791                        2,2);
1792 
1793       gtk_table_attach(GTK_TABLE(forecast_table),
1794                        sunset_text_alignment,
1795                        1,2,8,9,
1796                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1797                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
1798                        2,2);
1799 
1800       /* Image and conditions label. Image is filled after dialog is shown
1801        * to nicely scale the image pixbuf.
1802        */
1803       GtkWidget * icon_image = gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE,
1804                                                         GTK_ICON_SIZE_MENU);
1805 
1806       gchar * conditions_label_text = g_strdup_printf("<b>%d \302\260%s %s</b>",
1807                                                       forecast->iTemperature_,
1808                                                       forecast->units_.pcTemperature_,
1809                                                       _(forecast->pcConditions_));
1810 
1811       GtkWidget * conditions_label = gtk_label_new(NULL);
1812       gtk_label_set_markup(GTK_LABEL(conditions_label), conditions_label_text);
1813 
1814       /* Pack boxes */
1815       gtk_box_pack_start(GTK_BOX(icon_vbox), icon_image, FALSE, FALSE, 1);
1816       gtk_box_pack_start(GTK_BOX(icon_vbox), conditions_label, FALSE, FALSE, 1);
1817 
1818       gtk_box_pack_start(GTK_BOX(everything_hbox), icon_vbox, TRUE, TRUE, 35);
1819       gtk_box_pack_start(GTK_BOX(everything_hbox), forecast_table, FALSE, FALSE, 5);
1820 
1821       /* Free everything */
1822       g_free(conditions_label_text);
1823       g_free(wind);
1824       g_free(visibility);
1825       g_free(pressure);
1826       g_free(feels);
1827       g_free(humidity);
1828       g_free(location_label_text);
1829       g_free(dialog_title);
1830 
1831       gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), everything_hbox, FALSE, FALSE, 5);
1832 
1833 
1834       /* Set dialog window icon */
1835       gtk_weather_set_window_icon(GTK_WINDOW(dialog), "gtk-about");
1836 
1837       gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1838 
1839       gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1840 
1841       priv->conditions_dialog = dialog;
1842 
1843       gtk_widget_show_all(dialog);
1844 
1845       /* Get dimensions to create proper icon... */
1846       GtkRequisition req;
1847 
1848       gtk_widget_size_request(dialog, &req);
1849 
1850       /* Need the minimum */
1851       gint dim = (req.width < req.height) ? req.width/2 : req.height/2;
1852 
1853       GdkPixbuf * icon_buf = gdk_pixbuf_scale_simple(forecast->pImage_,
1854                                                      dim, dim,
1855                                                      GDK_INTERP_BILINEAR);
1856 
1857       gtk_image_set_from_pixbuf(GTK_IMAGE(icon_image), icon_buf);
1858 
1859       g_object_unref(icon_buf);
1860 
1861       gint response = GTK_RESPONSE_NONE;
1862 
1863       do
1864         {
1865           response = gtk_dialog_run(GTK_DIALOG(dialog));
1866 
1867           if (response == GTK_RESPONSE_APPLY)
1868             {
1869               gtk_weather_get_forecast(widget);
1870             }
1871 
1872         }  while (response != GTK_RESPONSE_ACCEPT);
1873 
1874       if (GTK_IS_WIDGET(dialog))
1875         {
1876           gtk_widget_destroy(dialog);
1877         }
1878 
1879       priv->conditions_dialog = NULL;
1880     }
1881   else if (!forecast && location)
1882     {
1883       gchar * error_msg = g_strdup_printf(_("Forecast for %s unavailable."),
1884                                           location->pcAlias_);
1885 
1886       gtk_weather_run_error_dialog(NULL, error_msg);
1887 
1888       g_free(error_msg);
1889     }
1890   else
1891     {
1892       gtk_weather_run_error_dialog(NULL, _("Location not set."));
1893     }
1894 
1895 }
1896 
1897 /**
1898  * Creates and shows the location retrieval progress bar.
1899  *
1900  * @param weather Pointer to the instance of this widget.
1901  */
1902 static void
gtk_weather_show_location_progress_bar(GtkWeather * weather)1903 gtk_weather_show_location_progress_bar(GtkWeather * weather)
1904 {
1905   LXW_LOG(LXW_DEBUG, "GtkWeather::show_location_progress_bar()");
1906 
1907   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
1908 
1909   gchar * progress_str = g_strdup_printf(_("Searching for '%s'..."), priv->location_data.location);
1910 
1911   GtkWidget * dialog = gtk_dialog_new_with_buttons(progress_str,
1912                                                    GTK_WINDOW(priv->preferences_data.dialog),
1913                                                    GTK_DIALOG_DESTROY_WITH_PARENT,
1914                                                    GTK_STOCK_CANCEL,
1915                                                    GTK_RESPONSE_CANCEL,
1916                                                    NULL);
1917 
1918   //  gtk_window_set_decorated(GTK_WINDOW(dialog), FALSE);
1919 
1920   GtkWidget * alignment = gtk_alignment_new(0.5, 0.5, 0.5, 0.5);
1921 
1922   GtkWidget * progress_bar = gtk_progress_bar_new();
1923 
1924   priv->location_data.progress_bar = GTK_PROGRESS_BAR(progress_bar);
1925 
1926   priv->location_data.progress_dialog = dialog;
1927 
1928   gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar), progress_str);
1929 
1930   gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), 0.5);
1931 
1932   gtk_container_add(GTK_CONTAINER(alignment), progress_bar);
1933 
1934   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), alignment, TRUE, TRUE, 0);
1935 
1936   int timer = g_timeout_add(500, gtk_weather_update_location_progress_bar, &priv->location_data);
1937 
1938   gtk_widget_show_all(dialog);
1939 
1940   gint response = gtk_dialog_run(GTK_DIALOG(dialog));
1941 
1942   switch(response)
1943     {
1944     case GTK_RESPONSE_ACCEPT:
1945       break;
1946 
1947     case GTK_RESPONSE_CANCEL:
1948       if (pthread_kill(*(priv->location_data.tid), 0) != ESRCH)
1949         {
1950           int ret = pthread_cancel(*(priv->location_data.tid));
1951 
1952           if (ret != 0)
1953             {
1954               LOG_ERRNO(ret, "pthread_cancel");
1955             }
1956         }
1957 
1958       break;
1959 
1960     default:
1961       break;
1962     }
1963 
1964   if (GTK_IS_WIDGET(dialog))
1965     {
1966       gtk_widget_destroy(dialog);
1967     }
1968 
1969   g_source_remove(timer);
1970 
1971   g_free(progress_str);
1972 
1973 }
1974 
1975 /**
1976  * Updates the location progress bar at regular intervals.
1977  *
1978  * @param data Pointer to the location thread data
1979  */
1980 static gboolean
gtk_weather_update_location_progress_bar(gpointer data)1981 gtk_weather_update_location_progress_bar(gpointer data)
1982 {
1983   LocationThreadData * location_data = (LocationThreadData *)data;
1984 
1985   LXW_LOG(LXW_DEBUG, "GtkWeather::update_location_progress_bar(): %d percent complete.",
1986           (location_data)?(int)(gtk_progress_bar_get_fraction(location_data->progress_bar) * 100):-1);
1987 
1988   if (!location_data)
1989     {
1990       return FALSE;
1991     }
1992 
1993   gboolean ret = TRUE;
1994 
1995   /* Get the percentage */
1996 
1997   /* If it's less than 100, check the thread.
1998    * If the thread is still running, increment percentage.
1999    * Otherwise, cancel thread - something's wrong.
2000    */
2001   gint percentage = gtk_progress_bar_get_fraction(location_data->progress_bar) * 100;
2002 
2003   if ( (percentage >= 100) ||
2004        (pthread_kill(*(location_data->tid), 0) == ESRCH) )
2005     {
2006       gtk_widget_destroy(location_data->progress_dialog);
2007 
2008       ret = FALSE;
2009     }
2010   else
2011     {
2012       percentage += 10;
2013 
2014       gtk_progress_bar_set_fraction(location_data->progress_bar, (gdouble)percentage/100);
2015 
2016       ret = TRUE;
2017     }
2018 
2019   return ret;
2020 }
2021 
2022 /**
2023  * Creates and shows the location list selection dialog.
2024  *
2025  * @param weather Pointer to the instance of this widget.
2026  * @param list   Pointer to the list of retrieved locations.
2027  */
2028 static void
gtk_weather_show_location_list(GtkWeather * weather,GList * list)2029 gtk_weather_show_location_list(GtkWeather * weather, GList * list)
2030 {
2031   LXW_LOG(LXW_DEBUG, "GtkWeather::show_location_list(%d)", g_list_length(list));
2032 
2033   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
2034 
2035   gchar * dialog_str = g_strdup_printf(_("Location matches for '%s'"),
2036                                        priv->location_data.location);
2037 
2038   GtkWidget * dialog = gtk_dialog_new_with_buttons(dialog_str,
2039                                                    GTK_WINDOW(priv->preferences_data.dialog),
2040                                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
2041                                                    GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2042                                                    GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2043                                                    NULL);
2044 
2045   gtk_widget_set_size_request(dialog, 300, 250);
2046 
2047   /* Set dialog window icon */
2048   gtk_weather_set_window_icon(GTK_WINDOW(dialog), "gtk-properties");
2049 
2050   /* TreeView */
2051   GtkWidget * treeview = gtk_tree_view_new();
2052 
2053   /* city */
2054   GtkCellRenderer * cell_renderer = gtk_cell_renderer_text_new();
2055   GtkTreeViewColumn * treeview_column = gtk_tree_view_column_new_with_attributes(_("City"),
2056                                                                                  cell_renderer,
2057                                                                                  "text",
2058                                                                                  CITY_COLUMN,
2059                                                                                  NULL);
2060 
2061   gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), treeview_column);
2062 
2063   /* state */
2064   cell_renderer = gtk_cell_renderer_text_new();
2065   treeview_column = gtk_tree_view_column_new_with_attributes(_("State"),
2066                                                              cell_renderer,
2067                                                              "text",
2068                                                              STATE_COLUMN,
2069                                                              NULL);
2070 
2071   gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), treeview_column);
2072 
2073   /* country */
2074   cell_renderer = gtk_cell_renderer_text_new();
2075   treeview_column = gtk_tree_view_column_new_with_attributes(_("Country"),
2076                                                              cell_renderer,
2077                                                              "text",
2078                                                              COUNTRY_COLUMN,
2079                                                              NULL);
2080 
2081   gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), treeview_column);
2082 
2083   /* TreeView items */
2084   GtkListStore * list_store = gtk_list_store_new(MAX_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
2085 
2086   GtkTreeIter iterator;
2087 
2088   guint length = g_list_length(list);
2089 
2090   guint index = 0;
2091 
2092   for (; index < length; ++index)
2093     {
2094       gtk_list_store_append(list_store, &iterator);
2095 
2096       LocationInfo * location = (LocationInfo *)g_list_nth_data(list, index);
2097 
2098       gtk_list_store_set(list_store, &iterator,
2099                          CITY_COLUMN, location->pcCity_,
2100                          STATE_COLUMN, location->pcState_,
2101                          COUNTRY_COLUMN, location->pcCountry_, -1);
2102     }
2103 
2104   /* Set the model behind the tree view, and forget about it */
2105   gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(list_store));
2106   g_object_unref(list_store);
2107 
2108   GtkTreeSelection * selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
2109 
2110   gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
2111 
2112   /* Internals of the dialog window */
2113   GtkWidget * scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2114   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2115                                  GTK_POLICY_AUTOMATIC,
2116                                  GTK_POLICY_AUTOMATIC);
2117 
2118   gtk_container_add(GTK_CONTAINER(scrolled_window), treeview);
2119 
2120   gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), scrolled_window, TRUE, TRUE, 0);
2121 
2122   gtk_widget_show_all(dialog);
2123 
2124   gint response = gtk_dialog_run(GTK_DIALOG(dialog));
2125 
2126   GtkTreeModel * model;
2127 
2128   /* handle selection */
2129   switch(response)
2130     {
2131     case GTK_RESPONSE_ACCEPT:
2132       model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2133 
2134       if (gtk_tree_selection_get_selected(selection, &model, &iterator))
2135         {
2136           /* Save the current location, if set... */
2137           if (priv->location)
2138             {
2139               copyLocation(&priv->previous_location, priv->location);
2140             }
2141 
2142           gchar * path = gtk_tree_model_get_string_from_iter(model, &iterator);
2143 
2144           gint index = (gint)g_ascii_strtoull(path, NULL, 10);
2145 
2146           LocationInfo * location = g_list_nth_data(list, index);
2147 
2148           gtk_weather_set_location(weather, (gpointer)location);
2149           /* list of locations is released by the caller */
2150 
2151           /* preferences dialog is also repainted by caller */
2152           g_free(path);
2153         }
2154 
2155       break;
2156 
2157     default:
2158       break;
2159     }
2160 
2161   if (GTK_IS_WIDGET(dialog))
2162     {
2163       gtk_widget_destroy(dialog);
2164     }
2165 
2166   g_free(dialog_str);
2167 }
2168 
2169 /**
2170  * Generates the text for the tooltip based on current location and forecast.
2171  *
2172  * @param widget Pointer to the current instance of the weather widget.
2173  *
2174  * @return Text to be shown as part of the tooltip. The caller must release
2175  *         the memory using g_free.
2176  */
2177 gchar *
gtk_weather_get_tooltip_text(GtkWidget * widget)2178 gtk_weather_get_tooltip_text(GtkWidget * widget)
2179 {
2180   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
2181 
2182   LXW_LOG(LXW_DEBUG, "GtkWeather::get_tooltip_text()");
2183 
2184   gchar * tooltip_text = NULL;
2185 
2186   if (priv->location && priv->forecast)
2187     {
2188       LocationInfo * location = priv->location;
2189       ForecastInfo * forecast = priv->forecast;
2190 
2191       gchar * temperature = g_strdup_printf("%d \302\260%s\n",
2192                                             forecast->iTemperature_,
2193                                             forecast->units_.pcTemperature_);
2194 
2195       gchar * today = g_strdup_printf("%s %d\302\260 / %d\302\260",
2196                                       _(forecast->today_.pcConditions_),
2197                                       forecast->today_.iLow_,
2198                                       forecast->today_.iHigh_);
2199 
2200       gchar * tomorrow = g_strdup_printf("%s %d\302\260 / %d\302\260",
2201                                          _(forecast->tomorrow_.pcConditions_),
2202                                          forecast->tomorrow_.iLow_,
2203                                          forecast->tomorrow_.iHigh_);
2204 
2205       /* make it nice and pretty */
2206       tooltip_text = g_strconcat(_("Currently in "),location->pcAlias_, ": ",
2207                                  _(forecast->pcConditions_), " ", temperature, "",
2208                                  _("Today: "), today, "\n",
2209                                  _("Tomorrow: "), tomorrow,
2210                                  NULL);
2211 
2212       g_free(temperature);
2213       g_free(today);
2214       g_free(tomorrow);
2215 
2216     }
2217   else if (priv->location)
2218     {
2219       tooltip_text = g_strdup_printf(_("Forecast for %s unavailable."),
2220                                      ((LocationInfo *)priv->location)->pcAlias_);
2221     }
2222   else
2223     {
2224       tooltip_text = g_strdup_printf(_("Location not set."));
2225     }
2226 
2227   LXW_LOG(LXW_DEBUG, "\tReturning: %s", tooltip_text);
2228 
2229   return tooltip_text;
2230 }
2231 
2232 /**
2233  * Sets the icon on the specified window, if the icon id is found.
2234  *
2235  * @param window  Pointer to the GtkWindow to decorate.
2236  * @param icon_id The id of the icon to find.
2237  */
2238 static void
gtk_weather_set_window_icon(GtkWindow * window,gchar * icon_id)2239 gtk_weather_set_window_icon(GtkWindow * window, gchar * icon_id)
2240 {
2241   LXW_LOG(LXW_DEBUG, "GtkWeather::set_window_icon(%s)", icon_id);
2242 
2243 	if(gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), icon_id))
2244     {
2245       GdkPixbuf* window_icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
2246                                                         icon_id,
2247                                                         24,
2248                                                         0,
2249                                                         NULL);
2250 
2251       gtk_window_set_icon(window, window_icon);
2252     }
2253 
2254 }
2255 
2256 /**
2257  * Retrieves the forecast. Starts the forecast timer, if enabled in
2258  * the particular location.
2259  *
2260  * @param widget Pointer to the current instance of the weather widget
2261  */
2262 static void
gtk_weather_get_forecast(GtkWidget * widget)2263 gtk_weather_get_forecast(GtkWidget * widget)
2264 {
2265   LXW_LOG(LXW_DEBUG, "GtkWeather::get_forecast()");
2266 
2267   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
2268 
2269   LocationInfo * location = (LocationInfo *)priv->location;
2270 
2271   if (location && location->bEnabled_)
2272     {
2273       /* just to be sure... */
2274       guint interval_in_seconds = 60 * ((location->uiInterval_) ? location->uiInterval_ : 1);
2275 
2276       if (priv->forecast_data.timerid > 0)
2277         {
2278           g_source_remove(priv->forecast_data.timerid);
2279         }
2280 
2281       /* start forecast thread here */
2282       priv->forecast_data.timerid = g_timeout_add_seconds(interval_in_seconds,
2283                                                           gtk_weather_get_forecast_timerfunc,
2284                                                           (gpointer)widget);
2285 
2286     }
2287   else
2288     {
2289       if (priv->forecast_data.timerid > 0)
2290         {
2291           g_source_remove(priv->forecast_data.timerid);
2292 
2293           priv->forecast_data.timerid = 0;
2294         }
2295     }
2296 
2297   /* One, single call just to get the latest forecast */
2298   if (location)
2299     {
2300       gtk_weather_get_forecast_timerfunc((gpointer)widget);
2301     }
2302 }
2303 
2304 /**
2305  * The location retrieval thread function.
2306  *
2307  * @param arg Pointer to argument data.
2308  *
2309  * @return Data based on thread completion.
2310  */
2311 static void *
gtk_weather_get_location_threadfunc(void * arg)2312 gtk_weather_get_location_threadfunc(void * arg)
2313 {
2314   gchar * location = (gchar *)arg;
2315 
2316   GList * list = getLocationInfo(location);
2317 
2318   g_list_foreach(list, setLocationAlias, (gpointer)location);
2319 
2320   return list;
2321 }
2322 
2323 /**
2324  * The forecast retrieval timer function.
2325  *
2326  * @param data Pointer to user-data (instance of this widget).
2327  *
2328  * @return TRUE if the timer should be restarted, FALSE otherwise.
2329  */
2330 static gboolean
gtk_weather_get_forecast_timerfunc(gpointer data)2331 gtk_weather_get_forecast_timerfunc(gpointer data)
2332 {
2333   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(data));
2334 
2335   LXW_LOG(LXW_DEBUG, "GtkWeather::get_forecast_timerfunc(%d %d)",
2336           (priv->location)?((LocationInfo*)priv->location)->bEnabled_:0,
2337           (priv->location)?((LocationInfo*)priv->location)->uiInterval_ * 60:0);
2338 
2339   if (!priv->location)
2340     {
2341       return FALSE;
2342     }
2343 
2344   LocationInfo * location = (LocationInfo *)priv->location;
2345 
2346   getForecastInfo(location->pcWOEID_, location->cUnits_, &priv->forecast);
2347 
2348   gtk_weather_set_forecast(GTK_WEATHER(data), priv->forecast);
2349 
2350   return location->bEnabled_;
2351 }
2352