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