1 | /* Lists/Weather |
2 | * |
3 | * This demo shows a few of the rarer features of GtkListView and |
4 | * how they can be used to display weather information. |
5 | * |
6 | * The hourly weather info uses a horizontal listview. This is easy |
7 | * to achieve because GtkListView implements the GtkOrientable interface. |
8 | * To make the items in the list stand out more, the listview uses |
9 | * separators. |
10 | * |
11 | * A GtkNoSelectionModel is used to make sure no item in the list can be |
12 | * selected. All other interactions with the items is still possible. |
13 | * |
14 | * The dataset used here has 70 000 items. |
15 | */ |
16 | |
17 | #include <gtk/gtk.h> |
18 | |
19 | #define GTK_TYPE_WEATHER_INFO (gtk_weather_info_get_type ()) |
20 | |
21 | G_DECLARE_FINAL_TYPE (GtkWeatherInfo, gtk_weather_info, GTK, WEATHER_INFO, GObject) |
22 | |
23 | typedef enum { |
24 | GTK_WEATHER_CLEAR, |
25 | GTK_WEATHER_FEW_CLOUDS, |
26 | GTK_WEATHER_FOG, |
27 | GTK_WEATHER_OVERCAST, |
28 | GTK_WEATHER_SCATTERED_SHOWERS, |
29 | GTK_WEATHER_SHOWERS, |
30 | GTK_WEATHER_SNOW, |
31 | GTK_WEATHER_STORM |
32 | } GtkWeatherType; |
33 | |
34 | struct _GtkWeatherInfo |
35 | { |
36 | GObject parent_instance; |
37 | |
38 | gint64 timestamp; |
39 | int temperature; |
40 | GtkWeatherType weather_type; |
41 | }; |
42 | |
43 | struct _GtkWeatherInfoClass |
44 | { |
45 | GObjectClass parent_class; |
46 | }; |
47 | |
48 | G_DEFINE_TYPE (GtkWeatherInfo, gtk_weather_info, G_TYPE_OBJECT) |
49 | |
50 | static void |
51 | gtk_weather_info_class_init (GtkWeatherInfoClass *klass) |
52 | { |
53 | } |
54 | |
55 | static void |
56 | gtk_weather_info_init (GtkWeatherInfo *self) |
57 | { |
58 | } |
59 | |
60 | static GtkWeatherInfo * |
61 | gtk_weather_info_new (GDateTime *timestamp, |
62 | GtkWeatherInfo *copy_from) |
63 | { |
64 | GtkWeatherInfo *result; |
65 | |
66 | result = g_object_new (GTK_TYPE_WEATHER_INFO, NULL); |
67 | |
68 | result->timestamp = g_date_time_to_unix (datetime: timestamp); |
69 | if (copy_from) |
70 | { |
71 | result->temperature = copy_from->temperature; |
72 | result->weather_type = copy_from->weather_type; |
73 | } |
74 | |
75 | return result; |
76 | } |
77 | |
78 | static GDateTime * |
79 | parse_timestamp (const char *string, |
80 | GTimeZone *_tz) |
81 | { |
82 | char *with_seconds; |
83 | GDateTime *result; |
84 | |
85 | with_seconds = g_strconcat (string1: string, ":00" , NULL); |
86 | result = g_date_time_new_from_iso8601 (text: with_seconds, default_tz: _tz); |
87 | g_free (mem: with_seconds); |
88 | |
89 | return result; |
90 | } |
91 | |
92 | static GtkWeatherType |
93 | parse_weather_type (const char *clouds, |
94 | const char *precip, |
95 | GtkWeatherType fallback) |
96 | { |
97 | if (strstr (haystack: precip, needle: "SN" )) |
98 | return GTK_WEATHER_SNOW; |
99 | |
100 | if (strstr (haystack: precip, needle: "TS" )) |
101 | return GTK_WEATHER_STORM; |
102 | |
103 | if (strstr (haystack: precip, needle: "DZ" )) |
104 | return GTK_WEATHER_SCATTERED_SHOWERS; |
105 | |
106 | if (strstr (haystack: precip, needle: "SH" ) || strstr (haystack: precip, needle: "RA" )) |
107 | return GTK_WEATHER_SHOWERS; |
108 | |
109 | if (strstr (haystack: precip, needle: "FG" )) |
110 | return GTK_WEATHER_FOG; |
111 | |
112 | if (g_str_equal (v1: clouds, v2: "M" ) || |
113 | g_str_equal (v1: clouds, v2: "" )) |
114 | return fallback; |
115 | |
116 | if (strstr (haystack: clouds, needle: "OVC" ) || |
117 | strstr (haystack: clouds, needle: "BKN" )) |
118 | return GTK_WEATHER_OVERCAST; |
119 | |
120 | if (strstr (haystack: clouds, needle: "BKN" ) || |
121 | strstr (haystack: clouds, needle: "SCT" )) |
122 | return GTK_WEATHER_FEW_CLOUDS; |
123 | |
124 | if (strstr (haystack: clouds, needle: "VV" )) |
125 | return GTK_WEATHER_FOG; |
126 | |
127 | return GTK_WEATHER_CLEAR; |
128 | } |
129 | |
130 | static double |
131 | parse_temperature (const char *s, |
132 | double fallback) |
133 | { |
134 | char *endptr; |
135 | double d; |
136 | |
137 | d = g_ascii_strtod (nptr: s, endptr: &endptr); |
138 | if (*endptr != '\0') |
139 | return fallback; |
140 | |
141 | return d; |
142 | } |
143 | |
144 | static GListModel * |
145 | create_weather_model (void) |
146 | { |
147 | GListStore *store; |
148 | GTimeZone *utc; |
149 | GDateTime *timestamp; |
150 | GtkWeatherInfo *info; |
151 | GBytes *data; |
152 | char **lines; |
153 | guint i; |
154 | |
155 | store = g_list_store_new (GTK_TYPE_WEATHER_INFO); |
156 | data = g_resources_lookup_data (path: "/listview_weather/listview_weather.txt" , lookup_flags: 0, NULL); |
157 | lines = g_strsplit (string: g_bytes_get_data (bytes: data, NULL), delimiter: "\n" , max_tokens: 0); |
158 | |
159 | utc = g_time_zone_new_utc (); |
160 | timestamp = g_date_time_new (tz: utc, year: 2011, month: 1, day: 1, hour: 0, minute: 0, seconds: 0); |
161 | info = gtk_weather_info_new (timestamp, NULL); |
162 | g_list_store_append (store, item: info); |
163 | g_object_unref (object: info); |
164 | |
165 | for (i = 0; lines[i] != NULL && *lines[i]; i++) |
166 | { |
167 | char **fields; |
168 | GDateTime *date; |
169 | |
170 | fields = g_strsplit (string: lines[i], delimiter: "," , max_tokens: 0); |
171 | date = parse_timestamp (string: fields[0], tz: utc); |
172 | while (g_date_time_difference (end: date, begin: timestamp) > 30 * G_TIME_SPAN_MINUTE) |
173 | { |
174 | GDateTime *new_timestamp = g_date_time_add_hours (datetime: timestamp, hours: 1); |
175 | g_date_time_unref (datetime: timestamp); |
176 | timestamp = new_timestamp; |
177 | info = gtk_weather_info_new (timestamp, copy_from: info); |
178 | g_list_store_append (store, item: info); |
179 | g_object_unref (object: info); |
180 | } |
181 | |
182 | info->temperature = parse_temperature (s: fields[1], fallback: info->temperature); |
183 | info->weather_type = parse_weather_type (clouds: fields[2], precip: fields[3], fallback: info->weather_type); |
184 | g_date_time_unref (datetime: date); |
185 | g_strfreev (str_array: fields); |
186 | } |
187 | |
188 | g_date_time_unref (datetime: timestamp); |
189 | g_strfreev (str_array: lines); |
190 | g_bytes_unref (bytes: data); |
191 | g_time_zone_unref (tz: utc); |
192 | |
193 | return G_LIST_MODEL (ptr: store); |
194 | } |
195 | |
196 | static void |
197 | setup_widget (GtkSignalListItemFactory *factory, |
198 | GtkListItem *list_item) |
199 | { |
200 | GtkWidget *box, *child; |
201 | |
202 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
203 | gtk_list_item_set_child (self: list_item, child: box); |
204 | |
205 | child = gtk_label_new (NULL); |
206 | gtk_label_set_width_chars (GTK_LABEL (child), n_chars: 5); |
207 | gtk_box_append (GTK_BOX (box), child); |
208 | |
209 | child = gtk_image_new (); |
210 | gtk_image_set_icon_size (GTK_IMAGE (child), icon_size: GTK_ICON_SIZE_LARGE); |
211 | gtk_box_append (GTK_BOX (box), child); |
212 | |
213 | child = gtk_label_new (NULL); |
214 | gtk_widget_set_vexpand (widget: child, TRUE); |
215 | gtk_widget_set_valign (widget: child, align: GTK_ALIGN_END); |
216 | gtk_label_set_width_chars (GTK_LABEL (child), n_chars: 4); |
217 | gtk_box_append (GTK_BOX (box), child); |
218 | } |
219 | |
220 | static void |
221 | bind_widget (GtkSignalListItemFactory *factory, |
222 | GtkListItem *list_item) |
223 | { |
224 | GtkWidget *box, *child; |
225 | GtkWeatherInfo *info; |
226 | GDateTime *timestamp; |
227 | char *s; |
228 | |
229 | box = gtk_list_item_get_child (self: list_item); |
230 | info = gtk_list_item_get_item (self: list_item); |
231 | |
232 | child = gtk_widget_get_first_child (widget: box); |
233 | timestamp = g_date_time_new_from_unix_utc (t: info->timestamp); |
234 | s = g_date_time_format (datetime: timestamp, format: "%R" ); |
235 | gtk_label_set_text (GTK_LABEL (child), str: s); |
236 | g_free (mem: s); |
237 | g_date_time_unref (datetime: timestamp); |
238 | |
239 | child = gtk_widget_get_next_sibling (widget: child); |
240 | switch (info->weather_type) |
241 | { |
242 | case GTK_WEATHER_CLEAR: |
243 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-clear-symbolic" ); |
244 | break; |
245 | case GTK_WEATHER_FEW_CLOUDS: |
246 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-few-clouds-symbolic" ); |
247 | break; |
248 | case GTK_WEATHER_FOG: |
249 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-fog-symbolic" ); |
250 | break; |
251 | case GTK_WEATHER_OVERCAST: |
252 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-overcast-symbolic" ); |
253 | break; |
254 | case GTK_WEATHER_SCATTERED_SHOWERS: |
255 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-showers-scattered-symbolic" ); |
256 | break; |
257 | case GTK_WEATHER_SHOWERS: |
258 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-showers-symbolic" ); |
259 | break; |
260 | case GTK_WEATHER_SNOW: |
261 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-snow-symbolic" ); |
262 | break; |
263 | case GTK_WEATHER_STORM: |
264 | gtk_image_set_from_icon_name (GTK_IMAGE (child), icon_name: "weather-storm-symbolic" ); |
265 | break; |
266 | default: |
267 | gtk_image_clear (GTK_IMAGE (child)); |
268 | break; |
269 | } |
270 | |
271 | |
272 | child = gtk_widget_get_next_sibling (widget: child); |
273 | s = g_strdup_printf (format: "%d°" , info->temperature); |
274 | gtk_label_set_text (GTK_LABEL (child), str: s); |
275 | g_free (mem: s); |
276 | } |
277 | |
278 | static GtkWidget *window = NULL; |
279 | |
280 | GtkWidget * |
281 | create_weather_view (void) |
282 | { |
283 | GtkWidget *listview; |
284 | GtkSelectionModel *model; |
285 | GtkListItemFactory *factory; |
286 | |
287 | factory = gtk_signal_list_item_factory_new (); |
288 | g_signal_connect (factory, "setup" , G_CALLBACK (setup_widget), NULL); |
289 | g_signal_connect (factory, "bind" , G_CALLBACK (bind_widget), NULL); |
290 | model = GTK_SELECTION_MODEL (ptr: gtk_no_selection_new (model: create_weather_model ())); |
291 | listview = gtk_list_view_new (model, factory); |
292 | gtk_orientable_set_orientation (GTK_ORIENTABLE (listview), orientation: GTK_ORIENTATION_HORIZONTAL); |
293 | gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE); |
294 | |
295 | return listview; |
296 | } |
297 | |
298 | GtkWidget * |
299 | do_listview_weather (GtkWidget *do_widget) |
300 | { |
301 | if (window == NULL) |
302 | { |
303 | GtkWidget *listview, *sw; |
304 | |
305 | window = gtk_window_new (); |
306 | gtk_window_set_default_size (GTK_WINDOW (window), width: 600, height: 400); |
307 | gtk_window_set_title (GTK_WINDOW (window), title: "Weather" ); |
308 | gtk_window_set_display (GTK_WINDOW (window), |
309 | display: gtk_widget_get_display (widget: do_widget)); |
310 | gtk_window_set_title (GTK_WINDOW (window), title: "Weather" ); |
311 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *) &window); |
312 | |
313 | sw = gtk_scrolled_window_new (); |
314 | gtk_window_set_child (GTK_WINDOW (window), child: sw); |
315 | listview = create_weather_view (); |
316 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: listview); |
317 | } |
318 | |
319 | if (!gtk_widget_get_visible (widget: window)) |
320 | gtk_widget_show (widget: window); |
321 | else |
322 | gtk_window_destroy (GTK_WINDOW (window)); |
323 | |
324 | return window; |
325 | } |
326 | |