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