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

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2003, 2004 Colin Walters <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 #include <string.h>
 31 #include <stdlib.h>
 32 
 33 #include <glib/gi18n.h>
 34 #include <gtk/gtk.h>
 35 
 36 #include "rhythmdb.h"
 37 #include "rb-query-creator.h"
 38 #include "rb-query-creator-private.h"
 39 #include "rb-dialog.h"
 40 #include "rb-debug.h"
 41 #include "rb-builder-helpers.h"
 42 #include "rb-util.h"
 43 
 44 static void rb_query_creator_class_init (RBQueryCreatorClass *klass);
 45 static void rb_query_creator_constructed (GObject *object);
 46 static void rb_query_creator_dispose (GObject *object);
 47 static void rb_query_creator_set_property (GObject *object,
 48 					      guint prop_id,
 49 					      const GValue *value,
 50 					      GParamSpec *pspec);
 51 static void rb_query_creator_get_property (GObject *object,
 52 					      guint prop_id,
 53 					      GValue *value,
 54 					      GParamSpec *pspec);
 55 static void select_criteria_from_value (RBQueryCreator *creator,
 56 					GtkWidget *option_menu,
 57 					RhythmDBPropType prop,
 58 					RhythmDBQueryType qtype);
 59 static GtkWidget * create_property_option_menu (RBQueryCreator *creator,
 60 						const RBQueryCreatorPropertyOption *options,
 61 						int length);
 62 static GtkWidget * create_criteria_option_menu (const RBQueryCreatorCriteriaOption *options,
 63 						int length);
 64 static void setup_sort_option_menu (RBQueryCreator *creator,
 65 				    GtkWidget *option_menu,
 66 				    const RBQueryCreatorSortOption *options,
 67 				    int length);
 68 
 69 static GtkWidget * append_row (RBQueryCreator *creator);
 70 static void add_button_click_cb (GtkWidget *button, RBQueryCreator *creator);
 71 static void remove_button_click_cb (GtkWidget *button, RBQueryCreator *creator);
 72 static void limit_toggled_cb (GtkWidget *limit, RBQueryCreator *creator);
 73 
 74 static int get_property_index_from_proptype (const RBQueryCreatorPropertyOption *options,
 75 			    	  int length, RhythmDBPropType prop);
 76 static void sort_option_menu_changed (GtkComboBox *propmenu, RBQueryCreator *creator);
 77 
 78 typedef struct
 79 {
 80 	RhythmDB *db;
 81 
 82 	gboolean creating;
 83 
 84 	GtkSizeGroup *property_size_group;
 85 	GtkSizeGroup *criteria_size_group;
 86 	GtkSizeGroup *entry_size_group;
 87 	GtkSizeGroup *button_size_group;
 88 
 89 	GtkBox *vbox;
 90 	GList *rows;
 91 
 92 	GtkWidget *addbutton;
 93 	GtkWidget *disjunction_check;
 94 	GtkWidget *limit_check;
 95 	GtkWidget *limit_entry;
 96 	GtkWidget *limit_option;
 97 	GtkWidget *sort_label;
 98 	GtkWidget *sort_menu;
 99 	GtkWidget *sort_desc;
100 } RBQueryCreatorPrivate;
101 
102 G_DEFINE_TYPE (RBQueryCreator, rb_query_creator, GTK_TYPE_DIALOG)
103 #define QUERY_CREATOR_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), rb_query_creator_get_type(), RBQueryCreatorPrivate))
104 
105 /**
106  * SECTION:rb-query-creator
107  * @short_description: database query creator widget
108  *
109  * The query creator is used to create and edit automatic playlists.
110  * It is only capable of constructing queries that consist of a flat
111  * list of criteria.  It cannot nested criteria or represent full 
112  * boolean logic expressions.
113  *
114  * In addition to query criteria, the query creator also allows the user
115  * to specify limits on the size of the result set, in terms of the number
116  * of entries, the total duration, or the total file size; and also the
117  * order in which the results are to be sorted.
118  *
119  * The structure of the query creator is defined in the builder file
120  * create-playlist.ui.
121  */
122 
123 enum
124 {
125 	PROP_0,
126 	PROP_DB,
127 	PROP_CREATING,
128 };
129 
130 static void
131 rb_query_creator_class_init (RBQueryCreatorClass *klass)
132 {
133 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
134 
135 	object_class->dispose = rb_query_creator_dispose;
136 	object_class->constructed = rb_query_creator_constructed;
137 	object_class->set_property = rb_query_creator_set_property;
138 	object_class->get_property = rb_query_creator_get_property;
139 
140 	/**
141 	 * RBQueryCreator:db:
142 	 *
143 	 * The #RhythmDB instance
144 	 */
145 	g_object_class_install_property (object_class,
146 					 PROP_DB,
147 					 g_param_spec_object ("db",
148 							      "RhythmDB",
149 							      "RhythmDB database",
150 							      RHYTHMDB_TYPE,
151 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
152 
153 	/**
154 	 * RBQueryCreator:creating:
155 	 *
156 	 * TRUE if a new playlist is being created.
157 	 */
158 	g_object_class_install_property (object_class,
159 					 PROP_CREATING,
160 					 g_param_spec_boolean ("creating",
161 							       "creating",
162 							       "Whether or not we're creating a new playlist",
163 							       TRUE,
164 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
165 
166 	g_type_class_add_private (klass, sizeof (RBQueryCreatorPrivate));
167 }
168 
169 static void
170 rb_query_creator_init (RBQueryCreator *creator)
171 {
172 
173 }
174 
175 static void
176 rb_query_creator_constructed (GObject *object)
177 {
178 	RBQueryCreatorPrivate *priv;
179 	RBQueryCreator *creator;
180 	GtkWidget *mainbox;
181 	GtkWidget *content_area;
182 	GtkBuilder *builder;
183 
184 	RB_CHAIN_GOBJECT_METHOD (rb_query_creator_parent_class, constructed, object);
185 
186 	creator = RB_QUERY_CREATOR (object);
187 	priv = QUERY_CREATOR_GET_PRIVATE (creator);
188 
189 	if (priv->creating) {
190 		gtk_dialog_add_button (GTK_DIALOG (creator),
191 				       GTK_STOCK_CANCEL,
192 				       GTK_RESPONSE_CLOSE);
193 		gtk_dialog_add_button (GTK_DIALOG (creator),
194 				       GTK_STOCK_NEW,
195 				       GTK_RESPONSE_OK);
196 	} else {
197 		gtk_dialog_add_button (GTK_DIALOG (creator),
198 				       GTK_STOCK_CLOSE,
199 				       GTK_RESPONSE_CLOSE);
200 	}
201 	gtk_dialog_set_default_response (GTK_DIALOG (creator),
202 					 GTK_RESPONSE_CLOSE);
203 
204 	priv->property_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
205 	priv->criteria_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
206 	priv->entry_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
207 	priv->button_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
208 
209 	if (priv->creating)
210 		gtk_window_set_title (GTK_WINDOW (creator), _("Create Automatic Playlist"));
211 	else
212 		gtk_window_set_title (GTK_WINDOW (creator), _("Edit Automatic Playlist"));
213 
214 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (creator));
215 
216 	gtk_container_set_border_width (GTK_CONTAINER (creator), 5);
217 	gtk_box_set_spacing (GTK_BOX (content_area), 2);
218 
219 	builder = rb_builder_load ("create-playlist.ui", creator);
220 
221 	priv->disjunction_check = GTK_WIDGET (gtk_builder_get_object (builder, "disjunctionCheck"));
222 	priv->limit_check = GTK_WIDGET (gtk_builder_get_object (builder, "limitCheck"));
223 	priv->limit_entry = GTK_WIDGET (gtk_builder_get_object (builder, "limitEntry"));
224 	priv->limit_option = GTK_WIDGET (gtk_builder_get_object (builder, "limitOption"));
225 	priv->addbutton = GTK_WIDGET (gtk_builder_get_object (builder, "addButton"));
226 	priv->sort_label = GTK_WIDGET (gtk_builder_get_object (builder, "sortLabel"));
227 	priv->sort_menu = GTK_WIDGET (gtk_builder_get_object (builder, "sortMenu"));
228 	priv->sort_desc = GTK_WIDGET (gtk_builder_get_object (builder, "sortDesc"));
229 
230 	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->limit_option), 0);
231 
232 	g_signal_connect_object (G_OBJECT (priv->limit_check), "toggled", G_CALLBACK (limit_toggled_cb),
233 				 creator, 0);
234 	limit_toggled_cb (priv->limit_check, creator);
235 
236 	gtk_size_group_add_widget (priv->button_size_group, priv->addbutton);
237 	g_signal_connect_object (G_OBJECT (priv->addbutton), "clicked", G_CALLBACK (add_button_click_cb),
238 				 creator, 0);
239 
240 	setup_sort_option_menu (creator, priv->sort_menu, sort_options, num_sort_options);
241 
242 	priv->vbox = GTK_BOX (gtk_builder_get_object (builder, "sub_vbox"));
243 	if (priv->creating)
244 		append_row (creator);
245 
246 	mainbox = GTK_WIDGET (gtk_builder_get_object (builder, "complex-playlist-creator"));
247 	gtk_box_pack_start (GTK_BOX (content_area), mainbox, FALSE, FALSE, 0);
248 	gtk_widget_show_all (GTK_WIDGET (creator));
249 
250 	g_object_unref (builder);
251 }
252 
253 static void
254 rb_query_creator_dispose (GObject *object)
255 {
256 	RBQueryCreatorPrivate *priv;
257 
258 	g_return_if_fail (RB_IS_QUERY_CREATOR (object));
259 
260 	priv = QUERY_CREATOR_GET_PRIVATE (object);
261 	g_return_if_fail (priv != NULL);
262 
263 	if (priv->property_size_group != NULL) {
264 		g_object_unref (priv->property_size_group);
265 		priv->property_size_group = NULL;
266 	}
267 
268 	if (priv->criteria_size_group != NULL) {
269 		g_object_unref (priv->criteria_size_group);
270 		priv->criteria_size_group = NULL;
271 	}
272 	if (priv->entry_size_group != NULL) {
273 		g_object_unref (priv->entry_size_group);
274 		priv->entry_size_group = NULL;
275 	}
276 
277 	if (priv->button_size_group != NULL) {
278 		g_object_unref (priv->button_size_group);
279 		priv->button_size_group = NULL;
280 	}
281 
282 	if (priv->rows) {
283 		g_list_free (priv->rows);
284 		priv->rows = NULL;
285 	}
286 
287 	G_OBJECT_CLASS (rb_query_creator_parent_class)->dispose (object);
288 }
289 
290 static void
291 rb_query_creator_set_property (GObject *object,
292 			       guint prop_id,
293 			       const GValue *value,
294 			       GParamSpec *pspec)
295 {
296 	RBQueryCreator *creator = RB_QUERY_CREATOR (object);
297 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
298 
299 	switch (prop_id)
300 	{
301 	case PROP_DB:
302 		priv->db = g_value_get_object (value);
303 		break;
304 	case PROP_CREATING:
305 		priv->creating = g_value_get_boolean (value);
306 		break;
307 	default:
308 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309 		break;
310 	}
311 }
312 
313 static void
314 rb_query_creator_get_property (GObject *object,
315                                guint prop_id,
316                                GValue *value,
317                                GParamSpec *pspec)
318 {
319 	RBQueryCreator *creator = RB_QUERY_CREATOR (object);
320 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
321 
322 	switch (prop_id)
323 	{
324 	case PROP_DB:
325 		g_value_set_object (value, priv->db);
326 		break;
327 	case PROP_CREATING:
328 		g_value_set_boolean (value, priv->creating);
329 		break;
330 	default:
331 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
332 		break;
333 	}
334 }
335 
336 /**
337  * rb_query_creator_new:
338  * @db: the #RhythmDB instance
339  *
340  * Creates a new query creator widget.
341  *
342  * Return value: new query creator widget
343  */
344 GtkWidget *
345 rb_query_creator_new (RhythmDB *db)
346 {
347 	return g_object_new (RB_TYPE_QUERY_CREATOR, "db", db, NULL);
348 }
349 
350 static gboolean
351 rb_query_creator_load_query (RBQueryCreator *creator,
352                              GPtrArray *query,
353 			     RhythmDBQueryModelLimitType limit_type,
354                              GArray *limit_value)
355 {
356 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
357 	int i;
358 	GList *rows;
359 	gboolean disjunction = FALSE;
360 	RhythmDBQueryData *qdata;
361 	GPtrArray *subquery;
362 	guint64 limit;
363 
364 	g_return_val_if_fail (query->len == 2, FALSE);
365 
366 	qdata = g_ptr_array_index (query, 1);
367 	g_return_val_if_fail (qdata->type == RHYTHMDB_QUERY_SUBQUERY, FALSE);
368 
369 	subquery = qdata->subquery;
370 
371 	if (subquery->len > 0) {
372 		for (i = 0; i < subquery->len; i++) {
373 			RhythmDBQueryData *data = g_ptr_array_index (subquery, i);
374 			if (data->type != RHYTHMDB_QUERY_DISJUNCTION)
375 				append_row (creator);
376 		}
377 	}
378 
379 	rows = priv->rows;
380 
381 	for (i = 0; i < subquery->len; i++) {
382 		RhythmDBQueryData *data = g_ptr_array_index (subquery, i);
383 		GtkComboBox *propmenu;
384 		GtkWidget *criteria_menu;
385 		int index;
386 		const RBQueryCreatorPropertyType *property_type;
387 
388 		if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
389 			disjunction = TRUE;
390 			continue;
391 		}
392 
393 		propmenu = GTK_COMBO_BOX (get_box_widget_at_pos (GTK_BOX (rows->data), 0));
394 		index = get_property_index_from_proptype (property_options, num_property_options, data->propid);
395 		gtk_combo_box_set_active (propmenu, index);
396 
397 		criteria_menu = get_box_widget_at_pos (GTK_BOX (rows->data), 1);
398 		select_criteria_from_value (creator, criteria_menu, data->propid, data->type);
399 
400 		property_type = property_options[index].property_type;
401 		g_assert (property_type->criteria_set_widget_data != NULL);
402 		property_type->criteria_set_widget_data (get_box_widget_at_pos (GTK_BOX (rows->data), 2),
403 					      data->val);
404 
405 		rows = rows->next;
406 	}
407 
408 	/* setup the limits */
409 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->disjunction_check),
410 				      disjunction);
411 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->limit_check),
412 				      limit_type != RHYTHMDB_QUERY_MODEL_LIMIT_NONE);
413 
414 	switch (limit_type) {
415 	case RHYTHMDB_QUERY_MODEL_LIMIT_NONE:
416 		limit = 0;
417 		break;
418 
419 	case RHYTHMDB_QUERY_MODEL_LIMIT_COUNT:
420 		gtk_combo_box_set_active (GTK_COMBO_BOX (priv->limit_option), 0);
421 		limit = g_value_get_ulong (&g_array_index (limit_value, GValue, 0));
422 		break;
423 
424 	case RHYTHMDB_QUERY_MODEL_LIMIT_TIME:
425 		gtk_combo_box_set_active (GTK_COMBO_BOX (priv->limit_option), 3);
426 		/* convert to minutes */
427 		limit = g_value_get_ulong (&g_array_index (limit_value, GValue, 0)) / 60;
428 		break;
429 
430 	case RHYTHMDB_QUERY_MODEL_LIMIT_SIZE:
431 		limit = g_value_get_uint64 (&g_array_index (limit_value, GValue, 0));
432 
433 		if (limit % 1000 == 0) {
434 			gtk_combo_box_set_active (GTK_COMBO_BOX (priv->limit_option), 2);
435 			limit /= 1000;
436 		} else {
437 			gtk_combo_box_set_active (GTK_COMBO_BOX (priv->limit_option), 1);
438 		}
439 
440 		break;
441 	default:
442 		g_assert_not_reached ();
443 	}
444 
445 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->limit_entry), limit);
446 
447 	return TRUE;
448 }
449 
450 static gboolean
451 rb_query_creator_set_sorting (RBQueryCreator *creator,
452                               const char *sort_column,
453                               gint sort_direction)
454 {
455 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
456 	int i;
457 
458 	if (!sort_column || ! *sort_column) {
459 		g_warning("No playlist sorting order");
460 
461 		sort_column = sort_options[DEFAULT_SORTING_COLUMN].sort_key;
462 		sort_direction = DEFAULT_SORTING_ORDER;
463 	}
464 
465 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sort_desc), (sort_direction == GTK_SORT_DESCENDING));
466 
467 	for (i = 0; i < num_sort_options; i++)
468 		if (strcmp (sort_options[i].sort_key, sort_column) == 0)
469 			break;
470 
471 	/* check that it is a valid sort option */
472 	g_return_val_if_fail (i < num_property_options, FALSE);
473 
474 	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->sort_menu), i);
475 	sort_option_menu_changed (GTK_COMBO_BOX (priv->sort_menu), creator); /* force the checkbox to change label */
476 
477 	return TRUE;
478 }
479 
480 /**
481  * rb_query_creator_new_from_query:
482  * @db: the #RhythmDB instance
483  * @query: an existing query to start from
484  * @limit_type: the type of result set limit
485  * @limit_value: the result set limit value
486  * @sort_column: the column on which to sort query results
487  * @sort_direction: the direction in which to sort query results
488  *
489  * Constructs a new query creator with an existing query and limit and sort
490  * settings.
491  *
492  * Return value: new query creator widget
493  */
494 GtkWidget *
495 rb_query_creator_new_from_query (RhythmDB *db,
496                                  GPtrArray *query,
497 				 RhythmDBQueryModelLimitType limit_type,
498                                  GArray *limit_value,
499 				 const char *sort_column,
500                                  gint sort_direction)
501 {
502 	RBQueryCreator *creator = g_object_new (RB_TYPE_QUERY_CREATOR, "db", db,
503 						"creating", FALSE, NULL);
504 	if (!creator)
505 		return NULL;
506 
507 	if ( !rb_query_creator_load_query (creator, query, limit_type, limit_value)
508 	   | !rb_query_creator_set_sorting (creator, sort_column, sort_direction)) {
509 		gtk_widget_destroy (GTK_WIDGET (creator));
510 		return NULL;
511 	}
512 
513 	return GTK_WIDGET (creator);
514 }
515 
516 /**
517  * get_box_widget_at_pos:
518  * @box: #GtkBox to extract child from
519  * @pos: index of the child to retrieve
520  *
521  * Extracts a child widget from a #GtkBox.
522  *
523  * Return value: (transfer none): child widget from the box
524  */
525 GtkWidget *
526 get_box_widget_at_pos (GtkBox *box, guint pos)
527 {
528 	GtkWidget *ret = NULL;
529 	GList *children = gtk_container_get_children (GTK_CONTAINER (box));
530 	GList *tem;
531 	for (tem = children; tem; tem = tem->next) {
532 		GValue thispos = { 0, };
533 		g_value_init (&thispos, G_TYPE_INT);
534 		gtk_container_child_get_property (GTK_CONTAINER (box),
535 						  GTK_WIDGET (tem->data),
536 						  "position", &thispos);
537 		if (g_value_get_int (&thispos) == pos) {
538 			ret = tem->data;
539 			break;
540 		}
541 	}
542 	g_list_free (children);
543 	return GTK_WIDGET (ret);
544 }
545 
546 static GtkWidget *
547 get_entry_for_property (RBQueryCreator *creator,
548                         RhythmDBPropType prop,
549 			gboolean *constrain)
550 {
551 	const RBQueryCreatorPropertyType *property_type;
552 	int index = get_property_index_from_proptype (property_options, num_property_options, prop);
553 
554 	property_type = property_options[index].property_type;
555 	g_assert (property_type->criteria_create_widget != NULL);
556 
557 	*constrain = TRUE;
558 	return property_type->criteria_create_widget (constrain);
559 }
560 
561 /**
562  * rb_query_creator_get_query:
563  * @creator: #RBQueryCreator instance
564  *
565  * Constructs a database query that represents the criteria in the query creator.
566  *
567  * Return value: (transfer full): database query array
568  */
569 GPtrArray *
570 rb_query_creator_get_query (RBQueryCreator *creator)
571 {
572 	RBQueryCreatorPrivate *priv;
573 	GPtrArray *query;
574 	GPtrArray *sub_query;
575 	GList *rows, *row;
576 	gboolean disjunction;
577 
578 	g_return_val_if_fail (RB_IS_QUERY_CREATOR (creator), NULL);
579 
580 	priv = QUERY_CREATOR_GET_PRIVATE (creator);
581 
582 	disjunction = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->disjunction_check));
583 	sub_query = g_ptr_array_new ();
584 	rows = priv->rows;
585 
586 	for (row = rows; row; row = row->next) {
587 		GtkComboBox *propmenu = GTK_COMBO_BOX (get_box_widget_at_pos (GTK_BOX (row->data),
588 										  0));
589 		GtkComboBox *criteria_menu = GTK_COMBO_BOX (get_box_widget_at_pos (GTK_BOX (row->data),
590 										       1));
591 		guint prop_position = gtk_combo_box_get_active (propmenu);
592 		const RBQueryCreatorPropertyOption *prop_option = &property_options[prop_position];
593 		const RBQueryCreatorCriteriaOption *criteria_options = prop_option->property_type->criteria_options;
594 		const RBQueryCreatorCriteriaOption *criteria_option = &criteria_options[gtk_combo_box_get_active (criteria_menu)];
595 
596 		g_assert (prop_option->property_type->criteria_get_widget_data != NULL);
597 		{
598 			RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
599 			GValue *val = g_new0 (GValue, 1);
600 
601 			data->type = criteria_option->val;
602 			data->propid = criteria_option->strict ? prop_option->strict_val : prop_option->fuzzy_val;
603 
604 			prop_option->property_type->criteria_get_widget_data (get_box_widget_at_pos (GTK_BOX (row->data), 2), val);
605 			data->val = val;
606 
607 			g_ptr_array_add (sub_query, data);
608 			}
609 
610 			if (disjunction && row->next)
611 				rhythmdb_query_append (priv->db,
612 						       sub_query,
613 						       RHYTHMDB_QUERY_DISJUNCTION,
614 						       RHYTHMDB_QUERY_END);
615 		}
616 	query = rhythmdb_query_parse (priv->db,
617 				      /* type=songs */
618 				      RHYTHMDB_QUERY_PROP_EQUALS,
619 				      RHYTHMDB_PROP_TYPE,
620 				      RHYTHMDB_ENTRY_TYPE_SONG,
621 				      /* the constructed query */
622                                       RHYTHMDB_QUERY_SUBQUERY,
623                                       sub_query,
624                                       RHYTHMDB_QUERY_END);
625 	return query;
626 }
627 
628 /**
629  * rb_query_creator_get_limit:
630  * @creator: #RBQueryCreator instance
631  * @type: (out): used to return the limit type
632  * @limit: (out): used to return the limit value
633  *
634  * Retrieves the limit type and value from the query creator.
635  * The limit value is returned as the first element in a
636  * #GArray.
637  */
638 void
639 rb_query_creator_get_limit (RBQueryCreator *creator,
640 			    RhythmDBQueryModelLimitType *type,
641                             GArray **limit)
642 {
643 	RBQueryCreatorPrivate *priv;
644 
645 	g_return_if_fail (RB_IS_QUERY_CREATOR (creator));
646 
647 	priv = QUERY_CREATOR_GET_PRIVATE (creator);
648 
649 	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->limit_check))) {
650 		guint64 l;
651 
652 		l = gtk_spin_button_get_value(GTK_SPIN_BUTTON (priv->limit_entry));
653 		*limit = g_array_sized_new (FALSE, TRUE, sizeof (GValue), 0);
654 		g_array_set_clear_func (*limit, (GDestroyNotify) g_value_unset);
655 
656 		switch (gtk_combo_box_get_active (GTK_COMBO_BOX (priv->limit_option))) {
657 		case 0:
658 			*type = RHYTHMDB_QUERY_MODEL_LIMIT_COUNT;
659 			rb_value_array_append_data (*limit, G_TYPE_ULONG, (gulong)l);
660 			break;
661 		case 1:
662 			*type = RHYTHMDB_QUERY_MODEL_LIMIT_SIZE;
663 			rb_value_array_append_data (*limit, G_TYPE_UINT64, l);
664 			break;
665 
666 		case 2:
667 			*type = RHYTHMDB_QUERY_MODEL_LIMIT_SIZE;
668 			rb_value_array_append_data (*limit, G_TYPE_UINT64, l * 1000);
669 			break;
670 
671 		case 3:
672 			*type = RHYTHMDB_QUERY_MODEL_LIMIT_TIME;
673 			rb_value_array_append_data (*limit, G_TYPE_ULONG, (gulong)l * 60);
674 			break;
675 
676 		default:
677 			g_assert_not_reached ();
678 		}
679 	} else {
680 		*type = RHYTHMDB_QUERY_MODEL_LIMIT_NONE;
681 		*limit = NULL;
682 	}
683 }
684 
685 /**
686  * rb_query_creator_get_sort_order:
687  * @creator: #RBQueryCreator instance
688  * @sort_key: (out) (allow-none): returns the sort key name
689  * @sort_direction: (out) (allow-none): returns the sort direction
690  *
691  * Retrieves the sort settings from the query creator.
692  * The sort direction is returned as a #GtkSortType value.
693  */
694 void
695 rb_query_creator_get_sort_order (RBQueryCreator *creator,
696                                  const char **sort_key,
697                                  gint *sort_direction)
698 {
699 	RBQueryCreatorPrivate *priv;
700 
701 	g_return_if_fail (RB_IS_QUERY_CREATOR (creator));
702 
703 	priv = QUERY_CREATOR_GET_PRIVATE (creator);
704 
705 	if (sort_direction != NULL) {
706 		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->sort_desc)))
707 			*sort_direction = GTK_SORT_DESCENDING;
708 		else
709 			*sort_direction = GTK_SORT_ASCENDING;
710 	}
711 
712 	if (sort_key != NULL) {
713 		int i;
714 		i = gtk_combo_box_get_active (GTK_COMBO_BOX (priv->sort_menu));
715 		*sort_key = sort_options[i].sort_key;
716 	}
717 }
718 
719 static void
720 limit_toggled_cb (GtkWidget *limit,
721                   RBQueryCreator *creator)
722 {
723 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
724 	gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (limit));
725 
726 	gtk_widget_set_sensitive (priv->limit_entry, active);
727 	gtk_widget_set_sensitive (priv->limit_option, active);
728 	gtk_widget_set_sensitive (priv->sort_menu, active);
729 	gtk_widget_set_sensitive (priv->sort_label, active);
730 	gtk_widget_set_sensitive (priv->sort_desc, active);
731 }
732 
733 static GtkWidget *
734 lookup_row_by_widget (RBQueryCreator *creator,
735                       GtkWidget *widget)
736 {
737 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
738 	GList *rows = priv->rows;
739 	GList *row;
740 	GtkWidget *ret = NULL;
741 	guint i;
742 
743 	for (row = rows, i = 0; row; row = row->next, i++) {
744 		GList *columns = gtk_container_get_children (GTK_CONTAINER (row->data));
745 		gboolean found = g_list_find (columns, widget) != NULL;
746 		g_list_free (columns);
747 		if (found) {
748 			ret = row->data;
749 			break;
750 		}
751 	}
752 	return ret;
753 }
754 
755 static void
756 remove_button_click_cb (GtkWidget *button,
757                         RBQueryCreator *creator)
758 {
759 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
760 	GtkWidget *row;
761 
762 	row = lookup_row_by_widget (creator, button);
763 	g_assert (row);
764 	gtk_container_remove (GTK_CONTAINER (priv->vbox),
765 			      GTK_WIDGET (row));
766 	priv->rows = g_list_remove (priv->rows, row);
767 }
768 
769 static void
770 add_button_click_cb (GtkWidget *button,
771                      RBQueryCreator *creator)
772 {
773 	append_row (creator);
774 }
775 
776 static GtkWidget *
777 append_row (RBQueryCreator *creator)
778 {
779 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
780 	GtkWidget *option;
781 	GtkWidget *criteria;
782 	GtkWidget *entry;
783 	GtkWidget *remove_button;
784 	GtkBox *hbox;
785 	gboolean constrain;
786 
787 	hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5));
788 	gtk_box_pack_start (GTK_BOX (priv->vbox), GTK_WIDGET (hbox), TRUE, TRUE, 0);
789 	priv->rows = g_list_prepend (priv->rows, hbox);
790 	gtk_box_reorder_child (priv->vbox, GTK_WIDGET (hbox), -1);
791 
792 	/* This is the main (leftmost) GtkComboBox, for types. */
793 	option = create_property_option_menu (creator, property_options, num_property_options);
794 	gtk_size_group_add_widget (priv->property_size_group, option);
795 	gtk_box_pack_start (hbox, GTK_WIDGET (option), TRUE, TRUE, 0);
796 	gtk_combo_box_set_active (GTK_COMBO_BOX (option), 0);
797 	criteria = create_criteria_option_menu (property_options[0].property_type->criteria_options,
798 						property_options[0].property_type->num_criteria_options);
799 	gtk_size_group_add_widget (priv->criteria_size_group, criteria);
800 	gtk_box_pack_start (hbox, GTK_WIDGET (criteria), TRUE, TRUE, 0);
801 
802 	entry = get_entry_for_property (creator, property_options[0].strict_val, &constrain);
803 	if (constrain)
804 		gtk_size_group_add_widget (priv->entry_size_group, entry);
805 	gtk_box_pack_start (hbox, GTK_WIDGET (entry), TRUE, TRUE, 0);
806 
807 	remove_button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
808 	g_signal_connect_object (G_OBJECT (remove_button), "clicked", G_CALLBACK (remove_button_click_cb),
809 				 creator, 0);
810 	gtk_size_group_add_widget (priv->button_size_group, remove_button);
811 	gtk_box_pack_start (hbox, GTK_WIDGET (remove_button), TRUE, TRUE, 0);
812 
813 	gtk_widget_show_all (GTK_WIDGET (priv->vbox));
814 	return GTK_WIDGET (hbox);
815 }
816 
817 static int
818 get_property_index_from_proptype (const RBQueryCreatorPropertyOption *options,
819                                   int length,
820                                   RhythmDBPropType prop)
821 {
822 	int i;
823 
824 	for (i = 0; i < length; i++)
825 		if (prop == options[i].strict_val || prop == options[i].fuzzy_val)
826 			return i;
827 
828 	g_assert_not_reached ();
829 }
830 
831 static void
832 select_criteria_from_value (RBQueryCreator *creator,
833 			    GtkWidget *option_menu,
834 			    RhythmDBPropType prop,
835 			    RhythmDBQueryType qtype)
836 {
837 	int i;
838 	const RBQueryCreatorCriteriaOption *options;
839 	guint length;
840 
841 	i = get_property_index_from_proptype (property_options, num_property_options, prop);
842 	length = property_options[i].property_type->num_criteria_options;
843 	options =  property_options[i].property_type->criteria_options;
844 
845 	for (i = 0; i < length; i++) {
846 		if (qtype == options[i].val) {
847 			gtk_combo_box_set_active (GTK_COMBO_BOX (option_menu), i);
848 			return;
849 		}
850 	}
851 	g_assert_not_reached ();
852 }
853 
854 static void
855 property_option_menu_changed (GtkComboBox *propmenu,
856 			      RBQueryCreator *creator)
857 {
858 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
859 	GtkWidget *row;
860 	GtkWidget *criteria;
861 	GtkWidget *entry;
862 	const RBQueryCreatorPropertyOption *prop_option;
863 	const RBQueryCreatorCriteriaOption *criteria_options;
864 	guint length;
865 	guint old_value;
866 	gboolean constrain;
867 
868 	prop_option = &property_options[gtk_combo_box_get_active (propmenu)];
869 	old_value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (propmenu), "prop-menu old-value"));
870 
871 	/* don't recreate the criteria menu and entry if they will be the same*/
872 	if (prop_option->property_type == property_options[old_value].property_type)
873 		return;
874 
875 	g_object_set_data (G_OBJECT (propmenu), "prop-menu old-value",
876 			   GINT_TO_POINTER (gtk_combo_box_get_active (propmenu)));
877 
878 	row = lookup_row_by_widget (creator, GTK_WIDGET (propmenu));
879 
880 	criteria = get_box_widget_at_pos (GTK_BOX (row), 1);
881 	gtk_container_remove (GTK_CONTAINER (row), criteria);
882 
883 	criteria_options = prop_option->property_type->criteria_options;
884 	length = prop_option->property_type->num_criteria_options;
885 
886 	criteria = create_criteria_option_menu (criteria_options, length);
887 	gtk_widget_show (criteria);
888 	gtk_size_group_add_widget (priv->criteria_size_group, criteria);
889 	gtk_box_pack_start (GTK_BOX (row), GTK_WIDGET (criteria), TRUE, TRUE, 0);
890 	gtk_box_reorder_child (GTK_BOX (row), criteria, 1);
891 
892 	entry = get_box_widget_at_pos (GTK_BOX (row), 2);
893 	gtk_container_remove (GTK_CONTAINER (row), entry);
894 	entry = get_entry_for_property (creator, prop_option->strict_val,
895 					&constrain);
896 	gtk_widget_show (entry);
897 
898 	if (constrain)
899 		gtk_size_group_add_widget (priv->entry_size_group, entry);
900 	gtk_box_pack_start (GTK_BOX (row), GTK_WIDGET (entry), TRUE, TRUE, 0);
901 	gtk_box_reorder_child (GTK_BOX (row), entry, 2);
902 }
903 
904 static GtkWidget*
905 create_property_option_menu (RBQueryCreator *creator,
906                              const RBQueryCreatorPropertyOption *options,
907 			     int length)
908 {
909 	GtkWidget *combo;
910 	int i;
911 
912 	combo = gtk_combo_box_text_new ();
913 	for (i = 0; i < length; i++) {
914 		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), g_dpgettext2 (NULL, "query-criteria", options[i].name));
915 	}
916 
917 	gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
918 	
919 	g_object_set_data (G_OBJECT (combo), "prop-menu old value", GINT_TO_POINTER (0));
920 
921 	g_signal_connect_object (G_OBJECT (combo), "changed",
922 				 G_CALLBACK (property_option_menu_changed), creator, 0);
923 
924 	return combo;
925 }
926 
927 static GtkWidget*
928 create_criteria_option_menu (const RBQueryCreatorCriteriaOption *options,
929 			     int length)
930 {
931 	GtkWidget *combo;
932 	int i;
933 
934 	combo = gtk_combo_box_text_new ();
935 	for (i = 0; i < length; i++) {
936 		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _(options[i].name));
937 	}
938 	gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
939 
940 	return combo;
941 }
942 
943 static void
944 sort_option_menu_changed (GtkComboBox *propmenu,
945 			  RBQueryCreator *creator)
946 {
947 	RBQueryCreatorPrivate *priv = QUERY_CREATOR_GET_PRIVATE (creator);
948 	int index = gtk_combo_box_get_active (propmenu);
949 
950 	gtk_button_set_label (GTK_BUTTON (priv->sort_desc), _(sort_options[index].sort_descending_name));
951 	rb_debug("changing descending label to %s[%d]", sort_options[index].sort_descending_name, index);
952 }
953 
954 static void
955 setup_sort_option_menu (RBQueryCreator *creator,
956 			GtkWidget *option_menu,
957 			const RBQueryCreatorSortOption *options,
958 			int length)
959 {
960 	GtkListStore *store;
961 	int i;
962 
963 	store = gtk_list_store_new (1, G_TYPE_STRING);
964 
965 	for (i = 0; i < length; i++) {
966 		GtkTreeIter iter;
967 
968 		gtk_list_store_append (store, &iter);
969 		gtk_list_store_set (store, &iter, 0, g_dpgettext2 (NULL, "query-sort", options[i].name), -1);
970 	}
971 
972 	gtk_combo_box_set_model (GTK_COMBO_BOX (option_menu), GTK_TREE_MODEL (store));
973 	gtk_combo_box_set_active (GTK_COMBO_BOX (option_menu), 0);
974 
975 	g_signal_connect_object (G_OBJECT (option_menu), "changed",
976 				 G_CALLBACK (sort_option_menu_changed), creator, 0);
977 }