hythmbox-2.98/widgets/rb-query-creator-properties.c

No issues found

  1 /*
  2  *  Copyright (C) 2003, 2004 Colin Walters <walters@gnome.org>
  3  *  Copyright (C) 2005 James Livingston <walters@gnome.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include "config.h"
 30 
 31 #include <glib/gi18n.h>
 32 #include <glib.h>
 33 #include <gtk/gtk.h>
 34 
 35 #include "rhythmdb.h"
 36 #include "rb-query-creator-private.h"
 37 #include "rb-rating.h"
 38 
 39 const RBQueryCreatorPropertyType string_property_type;
 40 const RBQueryCreatorPropertyType escaped_string_property_type;
 41 const RBQueryCreatorPropertyType rating_property_type;
 42 const RBQueryCreatorPropertyType double_property_type;
 43 const RBQueryCreatorPropertyType integer_property_type;
 44 const RBQueryCreatorPropertyType year_property_type;
 45 const RBQueryCreatorPropertyType duration_property_type;
 46 const RBQueryCreatorPropertyType relative_time_property_type;
 47 
 48 static GtkWidget * stringCriteriaCreateWidget (gboolean *constrain);
 49 static void stringCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 50 static void stringCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 51 static void escapedStringCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 52 static void escapedStringCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 53 static GtkWidget * ratingCriteriaCreateWidget (gboolean *constrain);
 54 static void ratingCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 55 static void ratingCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 56 static GtkWidget * doubleCriteriaCreateWidget (gboolean *constrain);
 57 static void doubleCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 58 static void doubleCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 59 static GtkWidget * integerCriteriaCreateWidget (gboolean *constrain);
 60 static void integerCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 61 static void integerCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 62 static GtkWidget * yearCriteriaCreateWidget (gboolean *constrain);
 63 static void yearCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 64 static void yearCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 65 static GtkWidget * durationCriteriaCreateWidget (gboolean *constrain);
 66 static void durationCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 67 static void durationCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 68 static GtkWidget * relativeTimeCriteriaCreateWidget (gboolean *constrain);
 69 static void relativeTimeCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 70 static void relativeTimeCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 71 
 72 /*
 73  * This table is the list of properties that are displayed in the query-creator
 74  */
 75 const RBQueryCreatorPropertyOption property_options[] =
 76 {
 77 	{ NC_("query-criteria", "Title"), RHYTHMDB_PROP_TITLE, RHYTHMDB_PROP_TITLE_FOLDED, &string_property_type },
 78 	{ NC_("query-criteria", "Artist"), RHYTHMDB_PROP_ARTIST, RHYTHMDB_PROP_ARTIST_FOLDED, &string_property_type },
 79 	{ NC_("query-criteria", "Album"), RHYTHMDB_PROP_ALBUM, RHYTHMDB_PROP_ALBUM_FOLDED, &string_property_type },
 80 	{ NC_("query-criteria", "Album Artist"), RHYTHMDB_PROP_ALBUM_ARTIST, RHYTHMDB_PROP_ALBUM_ARTIST_FOLDED, &string_property_type },
 81 	{ NC_("query-criteria", "Genre"), RHYTHMDB_PROP_GENRE, RHYTHMDB_PROP_GENRE_FOLDED, &string_property_type },
 82 	{ NC_("query-criteria", "Year"), RHYTHMDB_PROP_DATE, RHYTHMDB_PROP_DATE, &year_property_type },
 83 	{ NC_("query-criteria", "Rating"), RHYTHMDB_PROP_RATING, RHYTHMDB_PROP_RATING, &rating_property_type },
 84 	{ NC_("query-criteria", "Path"), RHYTHMDB_PROP_LOCATION, RHYTHMDB_PROP_LOCATION, &escaped_string_property_type },
 85 	{ NC_("query-criteria", "Comment"), RHYTHMDB_PROP_COMMENT, RHYTHMDB_PROP_COMMENT, &string_property_type },
 86 
 87 	{ NC_("query-criteria", "Play Count"), RHYTHMDB_PROP_PLAY_COUNT, RHYTHMDB_PROP_PLAY_COUNT, &integer_property_type },
 88 	{ NC_("query-criteria", "Track Number"), RHYTHMDB_PROP_TRACK_NUMBER, RHYTHMDB_PROP_TRACK_NUMBER, &integer_property_type },
 89 	{ NC_("query-criteria", "Disc Number"), RHYTHMDB_PROP_DISC_NUMBER, RHYTHMDB_PROP_DISC_NUMBER, &integer_property_type },
 90 	{ NC_("query-criteria", "Bitrate"), RHYTHMDB_PROP_BITRATE, RHYTHMDB_PROP_BITRATE, &integer_property_type },
 91 
 92 	{ NC_("query-criteria", "Duration"), RHYTHMDB_PROP_DURATION, RHYTHMDB_PROP_DURATION, &duration_property_type },
 93 	{ NC_("query-criteria", "Beats Per Minute"), RHYTHMDB_PROP_BPM, RHYTHMDB_PROP_BPM, &double_property_type },
 94 	{ NC_("query-criteria", "Time of Last Play"), RHYTHMDB_PROP_LAST_PLAYED, RHYTHMDB_PROP_LAST_PLAYED, &relative_time_property_type },
 95 	{ NC_("query-criteria", "Time Added to Library"), RHYTHMDB_PROP_FIRST_SEEN, RHYTHMDB_PROP_FIRST_SEEN, &relative_time_property_type },
 96 };
 97 
 98 const int num_property_options = G_N_ELEMENTS (property_options);
 99 
100 /*
101  * This table describes which properties can be used for sorting a playlist
102  * All entries MUST have column keys column keys listed in rb-entry-view.c
103  */
104 const RBQueryCreatorSortOption sort_options[] =
105 {
106 	{ NC_("query-sort", "Artist"), "Artist", N_("_In reverse alphabetical order") },
107 	{ NC_("query-sort", "Album"), "Album", N_("_In reverse alphabetical order") },
108 	{ NC_("query-sort", "Album Artist"), "AlbumArtist", N_("_In reverse alphabetical order") },
109 	{ NC_("query-sort", "Genre"), "Genre", N_("_In reverse alphabetical order") },
110 	{ NC_("query-sort", "Title"), "Title", N_("_In reverse alphabetical order") },
111 	{ NC_("query-sort", "Rating"), "Rating", N_("W_ith more highly rated tracks first") },
112 	{ NC_("query-sort", "Play Count"), "PlayCount", N_("W_ith more often played songs first") },
113 	{ NC_("query-sort", "Year"), "Year", N_("W_ith newer tracks first") },
114 	{ NC_("query-sort", "Duration"), "Time", N_("W_ith longer tracks first") },
115 	{ NC_("query-sort", "Track Number"), "Track", N_("_In decreasing order")},
116 	{ NC_("query-sort", "Last Played"), "LastPlayed", N_("W_ith more recently played tracks first") },
117 	{ NC_("query-sort", "Date Added"), "FirstSeen", N_("W_ith more recently added tracks first") },
118 	{ NC_("query-sort", "Comment"), "Comment", N_("_In reverse alphabetical order") },
119 	{ NC_("query-sort", "Beats Per Minute"), "BPM", N_("W_ith faster tempo tracks first") },
120 };
121 
122 const int num_sort_options = G_N_ELEMENTS (sort_options);
123 const int DEFAULT_SORTING_COLUMN = 0;
124 const gint DEFAULT_SORTING_ORDER = GTK_SORT_ASCENDING;
125 
126 /*
127  * This is the property type for string properties
128  */
129 
130 const RBQueryCreatorCriteriaOption string_criteria_options[] =
131 {
132 	{ N_("contains"), 0, RHYTHMDB_QUERY_PROP_LIKE },
133 	{ N_("does not contain"), 0, RHYTHMDB_QUERY_PROP_NOT_LIKE },
134 	{ N_("equals"), 1, RHYTHMDB_QUERY_PROP_EQUALS },
135 	{ N_("not equal to"), 1, RHYTHMDB_QUERY_PROP_NOT_EQUAL },
136 	{ N_("starts with"), 0, RHYTHMDB_QUERY_PROP_PREFIX },
137 	{ N_("ends with"), 0, RHYTHMDB_QUERY_PROP_SUFFIX },
138 };
139 
140 const RBQueryCreatorPropertyType string_property_type =
141 {
142 	G_N_ELEMENTS (string_criteria_options),
143 	string_criteria_options,
144 	stringCriteriaCreateWidget,
145 	stringCriteriaSetWidgetData,
146 	stringCriteriaGetWidgetData
147 };
148 
149 const RBQueryCreatorPropertyType escaped_string_property_type =
150 {
151 	G_N_ELEMENTS (string_criteria_options),
152 	string_criteria_options,
153 	stringCriteriaCreateWidget,
154 	escapedStringCriteriaSetWidgetData,
155 	escapedStringCriteriaGetWidgetData
156 };
157 
158 /*
159  * This are the property types for numeric quantities, such as rating and playcounts
160  */
161 
162 const RBQueryCreatorCriteriaOption numeric_criteria_options[] =
163 {
164 	{ N_("equals"), 1, RHYTHMDB_QUERY_PROP_EQUALS },
165 	{ N_("not equal to"), 1, RHYTHMDB_QUERY_PROP_NOT_EQUAL },
166 	{ N_("at least"), 1, RHYTHMDB_QUERY_PROP_GREATER },	/* matches if A >= B */
167 	{ N_("at most"), 1, RHYTHMDB_QUERY_PROP_LESS }		/* matches if A <= B */
168 };
169 
170 /*
171  * Property type for date quantities
172  */
173 
174 const RBQueryCreatorCriteriaOption year_criteria_options[] =
175 {
176 	/* Translators: this matches songs within 1-Jan-YEAR to 31-Dec-YEAR */
177 	{ N_("in"), 1, RHYTHMDB_QUERY_PROP_YEAR_EQUALS },
178 	/* Translators: this matches songs before 1-Jan-YEAR or after 31-Dec-YEAR */
179 	{ N_("not in"), 1, RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL },
180 	/* Translators: this matches songs after 31-Dec-YEAR */
181 	{ N_("after"), 1, RHYTHMDB_QUERY_PROP_YEAR_GREATER },
182 	/* Translators: this matches songs before 1-Jan-YEAR */
183 	{ N_("before"), 1, RHYTHMDB_QUERY_PROP_YEAR_LESS }
184 };
185 
186 const RBQueryCreatorPropertyType rating_property_type =
187 {
188 	G_N_ELEMENTS (numeric_criteria_options),
189 	numeric_criteria_options,
190 	ratingCriteriaCreateWidget,
191 	ratingCriteriaSetWidgetData,
192 	ratingCriteriaGetWidgetData
193 };
194 
195 const RBQueryCreatorPropertyType double_property_type =
196 {
197 	G_N_ELEMENTS (numeric_criteria_options),
198 	numeric_criteria_options,
199 	doubleCriteriaCreateWidget,
200 	doubleCriteriaSetWidgetData,
201 	doubleCriteriaGetWidgetData
202 };
203 
204 const RBQueryCreatorPropertyType integer_property_type =
205 {
206 	G_N_ELEMENTS (numeric_criteria_options),
207 	numeric_criteria_options,
208 	integerCriteriaCreateWidget,
209 	integerCriteriaSetWidgetData,
210 	integerCriteriaGetWidgetData
211 };
212 
213 const RBQueryCreatorPropertyType year_property_type =
214 {
215 	G_N_ELEMENTS (year_criteria_options),
216 	year_criteria_options,
217 	yearCriteriaCreateWidget,
218 	yearCriteriaSetWidgetData,
219 	yearCriteriaGetWidgetData
220 };
221 
222 const RBQueryCreatorPropertyType duration_property_type =
223 {
224 	G_N_ELEMENTS (numeric_criteria_options),
225 	numeric_criteria_options,
226 	durationCriteriaCreateWidget,
227 	durationCriteriaSetWidgetData,
228 	durationCriteriaGetWidgetData
229 };
230 
231 /*
232  * This is the property type for relative time properties, such as last played and first seen
233  */
234 
235 typedef struct
236 {
237 	const char *name;
238 	gulong timeMultiplier;
239 } RBQueryCreatorTimeUnitOption;
240 
241 const RBQueryCreatorCriteriaOption relative_time_criteria_options[] =
242 {
243 	/*
244 	 * Translators: this will match when within <value> of the current time
245 	 * e.g. "in the last" "7 days" will match if within 7 days of the current time
246 	 */
247 	{ N_("in the last"), 1, RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN },
248 
249 	/*
250 	 * Translators: this is the opposite of the above, and will match if not
251 	 * within <value> of the current time
252 	 */
253 	{ N_("not in the last"), 1, RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN }
254 };
255 
256 const RBQueryCreatorPropertyType relative_time_property_type =
257 {
258 	G_N_ELEMENTS (relative_time_criteria_options),
259 	relative_time_criteria_options,
260 	relativeTimeCriteriaCreateWidget,
261 	relativeTimeCriteriaSetWidgetData,
262 	relativeTimeCriteriaGetWidgetData
263 };
264 
265 const RBQueryCreatorTimeUnitOption time_unit_options[] =
266 {
267 	{ N_("seconds"), 1 },
268 	{ N_("minutes"), 60 },
269 	{ N_("hours"), 60 * 60 },
270 	{ N_("days"), 60 * 60 * 24 },
271 	{ N_("weeks"), 60 * 60 * 24 * 7 }
272 };
273 
274 const int time_unit_options_default = 4; /* days */
275 
276 /*
277  * Implementation for the string properties, using a single GtkEntry.
278  */
279 
280 static GtkWidget *
281 stringCriteriaCreateWidget (gboolean *constrain)
282 {
283 	return gtk_entry_new ();
284 }
285 
286 static void
287 stringCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
288 {
289 	gtk_entry_set_text (GTK_ENTRY (widget), g_value_get_string (val));
290 }
291 
292 static void
293 stringCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
294 {
295 	const char* text = gtk_entry_get_text (GTK_ENTRY (widget));
296 
297 	g_value_init (val, G_TYPE_STRING);
298 	g_value_set_string (val, text);
299 }
300 
301 /* escaped string operations, for use with URIs, etc */
302 
303 static void
304 escapedStringCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
305 {
306 	char *text = g_uri_unescape_string (g_value_get_string (val), NULL);
307 	gtk_entry_set_text (GTK_ENTRY (widget), text);
308 	g_free (text);
309 }
310 
311 static void
312 escapedStringCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
313 {
314 	char *text = g_uri_escape_string (gtk_entry_get_text (GTK_ENTRY (widget)), G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
315 
316 	g_value_init (val, G_TYPE_STRING);
317 	g_value_set_string (val, text);
318 }
319 
320 /*
321  * Implementation for the ratings property, using the RbRating widget
322  */
323 
324 static void
325 set_rating_score (RBRating *rating, gdouble score)
326 {
327 	g_object_set (G_OBJECT (rating), "rating", score, NULL);
328 }
329 
330 static GtkWidget *
331 ratingCriteriaCreateWidget (gboolean *constrain)
332 {
333 	RBRating *rating = rb_rating_new ();
334 	g_signal_connect_object (G_OBJECT (rating), "rated",
335 				 G_CALLBACK (set_rating_score), NULL, 0);
336 	*constrain = FALSE;
337 	return GTK_WIDGET (rating);
338 }
339 
340 static void
341 ratingCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
342 {
343 	g_object_set (G_OBJECT (widget), "rating", g_value_get_double (val), NULL);
344 }
345 
346 static void
347 ratingCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
348 {
349 	double rating;
350 	g_object_get (G_OBJECT (widget), "rating", &rating, NULL);
351 
352 	g_value_init (val, G_TYPE_DOUBLE);
353 	g_value_set_double (val, rating);
354 }
355 
356 /*
357  * Implementation for the double properties, using a single GtkSpinButton.
358  */
359 
360 static GtkWidget *
361 doubleCriteriaCreateWidget (gboolean *constrain)
362 {
363 	GtkWidget *spin;
364 	spin = gtk_spin_button_new_with_range (0.0, G_MAXDOUBLE, 1.0);
365 	gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), 2);
366 	return spin;
367 }
368 
369 static void
370 doubleCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
371 {
372 	gdouble num = g_value_get_double (val);
373 	g_assert (num <= G_MAXDOUBLE);
374 
375 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), num );
376 }
377 
378 static void
379 doubleCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
380 {
381 	gdouble num = gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
382 	g_assert (num >= 0);
383 
384 	g_value_init (val, G_TYPE_DOUBLE);
385 	g_value_set_double (val, num);
386 }
387 /*
388  * Implementation for the integer properties, using a single GtkSpinButton.
389  */
390 
391 static GtkWidget *
392 integerCriteriaCreateWidget (gboolean *constrain)
393 {
394 	return gtk_spin_button_new_with_range (0.0, (double)G_MAXINT, 1.0);
395 }
396 
397 static void
398 integerCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
399 {
400 	gulong num = g_value_get_ulong (val);
401 	g_assert (num <= G_MAXINT);
402 
403 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), (gint)num );
404 }
405 
406 static void
407 integerCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
408 {
409 	gint num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
410 	g_assert (num >= 0);
411 
412 	g_value_init (val, G_TYPE_ULONG);
413 	g_value_set_ulong (val, (gulong)num);
414 }
415 
416 /* Implementation for Year properties, using a single GtkSpinButton. */
417 
418 static GtkWidget *
419 yearCriteriaCreateWidget (gboolean *constrain)
420 {
421 	GtkWidget *spin;
422 	GDate date = {0, };
423 	spin = gtk_spin_button_new_with_range (0.0, (double)G_MAXINT, 1.0);
424 
425 	/* set it to the current year */
426 	g_date_set_time_t (&date, time (NULL));
427 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), g_date_get_year (&date));
428 	return spin;
429 }
430 
431 static void
432 yearCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
433 {
434 	GDate *date = NULL;
435 	gulong num = g_value_get_ulong (val);
436 	gint display_year;
437 	g_assert (num <= G_MAXINT);
438 
439 	if (num != 0) {
440 	  /* Create a date structure to get year from */
441 	  date = g_date_new();
442 	  g_date_set_julian (date, num);
443 	  display_year = (gint)g_date_get_year(date);
444 	  g_date_free(date);
445 	} else {
446 	  display_year = 0;
447 	}
448 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), display_year);
449 }
450 
451 static void
452 yearCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
453 {
454 	GDate *date = NULL;
455 	gint num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
456 	guint32 display_date;
457 	g_assert (num >=  0);
458 
459 	g_value_init (val, G_TYPE_ULONG);
460 
461 	if (num != 0) {
462 	  /* New date structure, use year set in widget */
463 	  date = g_date_new_dmy (1, G_DATE_JANUARY, num);
464 	  display_date = g_date_get_julian (date);
465 	  g_date_free(date);
466 	} else {
467 	  display_date = 0;
468 	}
469 	g_value_set_ulong (val, (gulong)display_date);
470 }
471 
472 /*
473  * Implementation for the duration property, using two single GtkSpinButtons.
474  */
475 
476 static GtkWidget *
477 durationCriteriaCreateWidget (gboolean *constrain)
478 {
479 	GtkBox *box;
480 	GtkWidget *minutesSpin;
481 	GtkWidget *minutesLabel;
482 	GtkWidget *secondsSpin;
483 
484 	/* the widget for Duration is set out like the following [ 2] : [30] */
485 	box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3));
486 
487 	minutesSpin = gtk_spin_button_new_with_range (0.0, (double)((G_MAXINT - 59) / 60), 1.0);
488 	gtk_box_pack_start (box, minutesSpin, FALSE, FALSE, 0);
489 
490 	minutesLabel = gtk_label_new (":");
491 	gtk_box_pack_start (box, minutesLabel, FALSE, FALSE, 0);
492 
493 	secondsSpin = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
494 	gtk_box_pack_start (box, secondsSpin, FALSE, FALSE, 0);
495 
496 	gtk_widget_show_all (GTK_WIDGET (box));
497 	return GTK_WIDGET (box);
498 }
499 
500 static void
501 durationCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
502 {
503 	GtkSpinButton *minutesSpinner = GTK_SPIN_BUTTON (get_box_widget_at_pos (GTK_BOX (widget), 0));
504 	GtkSpinButton *secondsSpinner = GTK_SPIN_BUTTON (get_box_widget_at_pos (GTK_BOX (widget), 2));
505 
506 	gtk_spin_button_set_value (minutesSpinner, (gdouble) (g_value_get_ulong (val) / 60));
507 	gtk_spin_button_set_value (secondsSpinner, (gdouble) (g_value_get_ulong (val) % 60));
508 }
509 
510 static void
511 durationCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
512 {
513 
514 	GtkSpinButton *minutesSpinner = GTK_SPIN_BUTTON (get_box_widget_at_pos (GTK_BOX (widget), 0));
515 	GtkSpinButton *secondsSpinner = GTK_SPIN_BUTTON (get_box_widget_at_pos (GTK_BOX (widget), 2));
516 
517 	gint value = gtk_spin_button_get_value_as_int (minutesSpinner) * 60
518 		   + gtk_spin_button_get_value_as_int (secondsSpinner);
519 	g_assert (value >= 0);
520 
521 	g_value_init (val, G_TYPE_ULONG);
522 	g_value_set_ulong (val, (gulong) value);
523 }
524 
525 /*
526  * Implementation for the relative time properties, using a spin button and a menu.
527  */
528 
529 static void
530 update_time_unit_limits (GtkComboBox *menu, GtkWidget *spin_button)
531 {
532 	/* set the range on the spin button so it can't overflow when
533 	 * converted to seconds when we're constructing the query
534 	 */
535 	gulong mult = time_unit_options [gtk_combo_box_get_active (menu)].timeMultiplier;
536 	gtk_spin_button_set_range (GTK_SPIN_BUTTON (spin_button), 1, G_MAXINT / mult);
537 }
538 
539 static GtkWidget*
540 create_time_unit_option_menu (const RBQueryCreatorTimeUnitOption *options,
541 			     int length)
542 {
543 	GtkWidget *combo;
544 	int i;
545 
546 	combo = gtk_combo_box_text_new ();
547 	for (i = 0; i < length; i++) {
548 		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _(options[i].name));
549 	}
550 	gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
551 
552 	return combo;
553 }
554 
555 static GtkWidget *
556 relativeTimeCriteriaCreateWidget (gboolean *constrain)
557 {
558 	GtkBox *box;
559 
560 	GtkWidget *timeSpin;
561 	GtkWidget *timeOption;
562 
563 	box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6));
564 
565 	timeSpin = gtk_spin_button_new_with_range (1.0, G_MAXINT, 1.0);
566 	gtk_box_pack_start (box, timeSpin, TRUE, TRUE, 0);
567 
568 	timeOption = create_time_unit_option_menu (time_unit_options, G_N_ELEMENTS (time_unit_options));
569 	gtk_combo_box_set_active (GTK_COMBO_BOX (timeOption), time_unit_options_default);
570 	gtk_box_pack_start (box, timeOption, TRUE, TRUE, 0);
571 
572 	g_signal_connect_object (timeOption, "changed",
573 				 G_CALLBACK (update_time_unit_limits),
574 				 timeSpin, 0);
575 
576 	gtk_widget_show_all (GTK_WIDGET (box));
577 	return GTK_WIDGET (box);
578 }
579 
580 static void
581 relativeTimeCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
582 {
583 	GtkBox *box = GTK_BOX (widget);
584 
585 	GtkSpinButton *timeSpin = GTK_SPIN_BUTTON (get_box_widget_at_pos (box, 0));
586 	GtkComboBox *unitMenu = GTK_COMBO_BOX (get_box_widget_at_pos (box, 1));
587 
588 	gulong time = g_value_get_ulong (val);
589 	gulong unit = 0;
590 	int i;
591 
592 	/* determine the best units to use for the given value */
593 	for (i = 0; i < G_N_ELEMENTS(time_unit_options); i++) {
594 		/* find out if the time is an even multiple of the unit */
595 		if (time % time_unit_options[i].timeMultiplier == 0)
596 			unit = i;
597 	}
598 
599 	time = time / time_unit_options[unit].timeMultiplier;
600 	g_assert (time < G_MAXINT);
601 	/* set the time value and unit*/
602 	gtk_combo_box_set_active (unitMenu, unit);
603 	gtk_spin_button_set_value (timeSpin, time);
604 }
605 
606 static void
607 relativeTimeCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
608 {
609 	GtkSpinButton *timeSpin = GTK_SPIN_BUTTON (get_box_widget_at_pos (GTK_BOX (widget), 0));
610 	GtkComboBox *unitMenu = GTK_COMBO_BOX (get_box_widget_at_pos (GTK_BOX (widget), 1));
611 
612 	gulong timeMultiplier = time_unit_options [gtk_combo_box_get_active (unitMenu)].timeMultiplier;
613 	gint value = gtk_spin_button_get_value_as_int (timeSpin) * timeMultiplier;
614 	g_assert (value >= 0);
615 
616 	g_value_init (val, G_TYPE_ULONG);
617 	g_value_set_ulong (val, (gulong) value);
618 }