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