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 }