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