Tag browser:
[gmpc.git] / src / browsers / playlist3-tag2-browser.c
blob3eca1244a34a39e3f71ed06289613b7294772927
1 /**
2 * TODO:
3 * $ If disconnected, don't destroy all tag browsers, just clear the first one, if connected refill the first one
4 */
6 /**
7 * Released under the GPL.
9 */
10 #include <stdio.h>
11 #include <string.h>
12 #include <gtk/gtk.h>
13 #include <gdk/gdkkeysyms.h>
14 #include <libmpd/debug_printf.h>
15 #include "plugin.h"
16 #include "main.h"
17 #include "misc.h"
18 #include "gmpc-mpddata-treeview.h"
19 #include "gmpc-mpddata-model.h"
20 #include "sexy-icon-entry.h"
21 #include <libmpd/libmpd-internal.h>
23 /**
24 * dirty hack to workaround single parameter for now
26 static int counter;
28 static void tag2_init(void);
29 static void tag2_destroy(void);
30 static void tag2_set_enabled(int enabled);
31 static int tag2_get_enabled(void);
33 static void tag2_save_myself(void);
34 static void tag2_browser_add(GtkWidget *cat_tree);
36 /* connection changed signal handling */
37 static void tag2_connection_changed(MpdObj *mi, int connect, gpointer data);
38 static void tag2_status_changed(MpdObj *mi, ChangedStatusType what, gpointer data);
39 /* intergration in gmpc */
40 static void tag2_browser_selected(GtkWidget *container);
41 static void tag2_browser_unselected(GtkWidget *container);
42 static int tag2_browser_add_go_menu(GtkWidget *menu);
44 /* One treemodel to store all possible tags, use this multiple times. */
45 static GtkTreeModel *tags_store = NULL;
47 /**
48 * preferences
50 static void tag2_pref_construct(GtkWidget *container);
51 static void tag2_pref_destroy(GtkWidget *container);
52 static GtkWidget *pref_vbox = NULL;
53 static GtkWidget *pref_entry = NULL;
55 /**
56 * Preferences structure
58 gmpcPrefPlugin tag2_prefs = {
59 .construct = tag2_pref_construct,
60 .destroy = tag2_pref_destroy
64 /**
65 * Browser plugin
66 * Consists of add, selected, unselected.
68 gmpcPlBrowserPlugin tag2_browser_plugin ={
69 .add = tag2_browser_add,
70 .selected = tag2_browser_selected,
71 .unselected = tag2_browser_unselected,
72 .add_go_menu = tag2_browser_add_go_menu
75 gmpcPlugin tag2_plug = {
76 .name = N_("Tag based browser"),
77 .version = {0,15,0},
78 .plugin_type = GMPC_PLUGIN_PL_BROWSER,
79 .init = tag2_init,
80 .destroy = tag2_destroy,
81 .save_yourself = tag2_save_myself,
82 .get_enabled = tag2_get_enabled,
83 .set_enabled = tag2_set_enabled,
84 .browser = &tag2_browser_plugin,
85 .pref = &tag2_prefs,
86 .mpd_connection_changed = tag2_connection_changed,
87 .mpd_status_changed = tag2_status_changed,
89 /** Little hack to work around gmpc's limitations */
90 static GList *tag2_ht = NULL;
91 /** This stucture contains all the needed data for a browser
93 typedef struct _tag_browser {
94 gchar *name;
95 /* The key used as ID in gmpc, and as config id and name */
96 gchar *key;
97 /* The paned window that is packed into gmpc */
98 GtkWidget *tag2_vbox;
99 /* The hbox that is used to pack the tag browsers in to the browser window */
100 GtkWidget *tag_hbox;
101 /* The GmpcMpdDataTreeView that shows the songs */
102 GtkTreeView *tag_songlist;
103 /* List with tag_element's (the tag browsers that are in tag_hbox) */
104 GList *tag_lists;
105 /* GtkTreeRowReference */
106 GtkTreeRowReference *ref_iter;
107 } tag_browser;
108 static void tag2_save_browser(tag_browser *browser);
110 /* The current visible browser, this is needed to workaround gmpc's limitation */
111 static GtkWidget *tag2_current = NULL;
112 static void tag2_destroy_browser(tag_browser *browser, gpointer user_data);
113 static void tag2_connection_changed_foreach(tag_browser *browser, gpointer data);
114 static void tag2_init_browser(tag_browser *browser);
117 * Adds a browser to gmpc's category browser
119 static void tag2_browser_add_browser(GtkWidget *cat_tree, char *key)
121 GtkTreePath *path;
122 GtkTreeIter iter;
123 tag_browser *tb;
124 gchar *group = g_strdup_printf("tag2-plugin:%s",key);
125 gchar *name = cfg_get_single_value_as_string_with_default(config, group, "name", "default");
126 GtkListStore *tree = (GtkListStore *)gtk_tree_view_get_model(GTK_TREE_VIEW(cat_tree));
127 gint pos = cfg_get_single_value_as_int_with_default(config, group,"position",50+g_list_length(tag2_ht));
128 g_free(group);
129 playlist3_insert_browser(&iter, pos);
130 gtk_list_store_set(tree, &iter,
131 PL3_CAT_TYPE, tag2_plug.id,
132 PL3_CAT_TITLE, name,
133 PL3_CAT_INT_ID,key,
134 PL3_CAT_ICON_ID, "tag-browser",
135 PL3_CAT_PROC, TRUE,
136 PL3_CAT_ICON_SIZE,GTK_ICON_SIZE_DND,-1);
137 path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree), &iter);
139 tb = g_malloc0(sizeof(*tb));
140 tb->name = name;
141 tb->key = g_strdup(key);
142 /* get a reference to the key */
143 tb->ref_iter = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree),path);
144 tag2_ht = g_list_append(tag2_ht,tb);
145 tag2_init_browser(tb);
146 gtk_tree_path_free(path);
147 pl3_update_go_menu();
150 static void tag2_browser_add(GtkWidget *cat_tree)
152 conf_mult_obj *cmo,*iter;
153 if(!tag2_get_enabled())
154 return;
156 * Init hash table if it does not extists
158 cmo = cfg_get_key_list(config, "tag2-browsers");
159 for(iter=cmo;iter;iter = iter->next)
161 tag2_browser_add_browser(cat_tree, iter->key);
163 if(cmo)
164 cfg_free_multiple(cmo);
169 typedef struct _tag_element{
170 GtkWidget *tree;
171 GtkTreeModel *model;
172 GtkWidget *sw;
173 GtkWidget *combo;
174 GtkWidget *vbox;
175 GtkCellRenderer *image_renderer;
176 GtkTreeViewColumn *column;
178 GtkWidget *sentry;
179 int type;
180 int index;
181 tag_browser *browser;
182 guint timeout;
183 }tag_element;
187 * Get/Set enabled
189 int tag2_get_enabled()
191 return cfg_get_single_value_as_int_with_default(config, "tag2-plugin", "enable", TRUE);
193 static void tag2_set_enabled(int enabled)
195 cfg_set_single_value_as_int(config, "tag2-plugin", "enable", enabled);
196 if(enabled && !tag2_ht)
198 GtkTreeView *tree = playlist3_get_category_tree_view();
199 tag2_browser_add((GtkWidget *)tree);
200 } else if (!enabled && tag2_ht ) {
201 tag2_destroy();
205 * Destroy the browser
207 static void tag2_destroy()
209 /* clear all the browsers */
210 g_list_foreach(tag2_ht, (GFunc)tag2_destroy_browser, NULL);
211 g_list_free(tag2_ht);
212 tag2_ht = NULL;
215 static void tag2_destroy_tag(tag_element *te)
217 if(te->timeout)
218 g_source_remove(te->timeout);
219 gtk_widget_destroy(te->vbox);
221 if(te->model)
222 g_object_unref(te->model);
223 g_free(te);
231 * Handles right mouse press event
232 * this function fixes the behauviour of gtk to something more logic,
233 * the actually handling of the press happens in the release event
235 static gboolean tag2_song_list_button_press_event(GtkWidget *but, GdkEventButton *event)
237 GtkTreePath *path = NULL;
238 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(but), event->x, event->y,&path,NULL,NULL,NULL))
240 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(but));
241 if(gtk_tree_selection_path_is_selected(sel, path))
243 if(event->button == 3)
245 gtk_tree_path_free(path);
246 return TRUE;
247 }else{
248 gtk_tree_selection_unselect_path(sel, path);
249 return TRUE;
255 if(path) {
256 gtk_tree_path_free(path);
258 return FALSE;
262 * Add songs from the selected browser entry of te
264 static void tag2_browser_add_selected(GtkWidget *item, tag_element *te)
266 GtkTreeIter iter;
267 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree));
268 if(gtk_tree_selection_get_selected(sel, &(te->model), &iter))
270 MpdData *data;
271 GList *list = NULL;
272 /* now get the song content, this needs all the fields of the previous
274 mpd_database_search_start(connection, TRUE);
275 /* add constraints based on previous browsers */
276 list = g_list_first(te->browser->tag_lists);
277 while(list)
279 tag_element *te3 = list->data;//g_list_nth_data(te->browser->tag_lists, i);
280 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te3->tree));
281 if(gtk_tree_selection_get_selected(sel, &(te3->model), &iter))
283 gchar *value;
284 gtk_tree_model_get(te3->model, &iter, MPDDATA_MODEL_COL_SONG_TITLE, &value, -1);
285 mpd_database_search_add_constraint(connection, te3->type, value);
286 g_free(value);
288 list = list->next;
290 data = mpd_database_search_commit(connection);
291 /* if there is a result queue them and add them */
292 if(data)
294 data = misc_sort_mpddata_by_album_disc_track(data);
295 for(;data;data = mpd_data_get_next(data))
297 mpd_playlist_queue_add(connection,data->song->file);
299 mpd_playlist_queue_commit(connection);
304 * Redirect to metadata browser
306 static void tag2_browser_header_information(GtkWidget *item, tag_element *te)
308 GtkTreeIter iter;
309 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree));
310 if(gtk_tree_selection_get_selected(sel, &(te->model), &iter))
312 MpdData *data;
313 GList *list = NULL;
316 if(gtk_tree_selection_get_selected(sel, &(te->model), &iter))
318 gchar *value = NULL;
319 gtk_tree_model_get(te->model, &iter, MPDDATA_MODEL_COL_SONG_TITLE, &value, -1);
320 if(te->type == MPD_TAG_ITEM_ARTIST)
322 info2_activate();
323 info2_fill_artist_view(value);
325 }else if (te->type == MPD_TAG_ITEM_ALBUM){
326 gchar *artist = gmpc_mpddata_model_get_request_artist(GMPC_MPDDATA_MODEL(te->model));
327 if(artist) {
328 info2_activate();
329 info2_fill_album_view(artist,value);
333 g_free(value);
338 * Replace playlist with content of selected browser
340 static void tag2_browser_replace_selected(GtkWidget *item, tag_element *te)
342 mpd_playlist_clear(connection);
343 if(mpd_check_connected(connection))
345 tag2_browser_add_selected(item, te);
346 mpd_player_play(connection);
351 * Handles right mouse release on song list
353 static gboolean tag2_browser_button_release_event(GtkTreeView *tree, GdkEventButton *event, tag_element *te)
355 /* only on right mouse click */
356 if(event->button == 3)
358 GtkWidget *menu, *item;
359 GtkTreeSelection *selection = gtk_tree_view_get_selection(tree);
360 int count = gtk_tree_selection_count_selected_rows(selection);
361 /* if nothing is selected return */
362 if(count == 0)
363 return FALSE;
365 /* create menu to popup */
366 menu = gtk_menu_new();
367 /* add the add widget */
368 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD,NULL);
369 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
370 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(tag2_browser_add_selected), te);
371 /* add the replace widget */
372 item = gtk_image_menu_item_new_with_label("Replace");
373 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
374 gtk_image_new_from_stock(GTK_STOCK_REDO, GTK_ICON_SIZE_MENU));
375 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
376 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(tag2_browser_replace_selected), te);
378 if(te->type == MPD_TAG_ITEM_ARTIST || (te->type == MPD_TAG_ITEM_ALBUM && gmpc_mpddata_model_get_request_artist(GMPC_MPDDATA_MODEL(te->model)) != NULL))
380 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_INFO,NULL);
381 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
382 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(tag2_browser_header_information), te);
386 /* popup */
387 gtk_widget_show_all(GTK_WIDGET(menu));
388 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,NULL, NULL, 0, event->time);
389 return TRUE;
392 return FALSE;
395 static int tag2_key_release_event(GtkTreeView *tree, GdkEventKey *event,tag_element *te)
397 if((event->state&(GDK_CONTROL_MASK|GDK_MOD1_MASK)) == 0 &&
398 ((event->keyval >= GDK_space && event->keyval <= GDK_z)))
400 char data[2];
401 data[0] = (char)gdk_keyval_to_unicode(event->keyval);
402 data[1] = '\0';
403 gtk_widget_grab_focus(te->sentry);
404 gtk_entry_set_text(GTK_ENTRY(te->sentry),data);
405 gtk_editable_set_position(GTK_EDITABLE(te->sentry),1);
407 return TRUE;
409 return FALSE;
413 static void tag2_changed(GtkTreeSelection *sel2, tag_element *te)
415 int found = 0;
416 MpdData *data = NULL;
417 tag_browser *browser = te->browser;
418 int not_to_update = te->index;
419 int artist_set = FALSE;
420 GList *tel = g_list_first(browser->tag_lists);
421 /* Clear songs list */
422 /* clear the depending browsers */
423 while(tel)
425 tag_element *te2 =tel->data;
426 if(te2)
428 gmpc_mpddata_model_set_request_artist(GMPC_MPDDATA_MODEL(te2->model), NULL);
430 tel = tel->next;
432 tel = g_list_first(browser->tag_lists);
433 /* check if the user selected a row, if not do nothing */
434 while(tel)
436 te = tel->data;
437 if(te->index != not_to_update)
439 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree));
440 GtkTreeIter iter;
441 gchar *artist = NULL;
442 GList *first = g_list_first(browser->tag_lists);
443 /* only do the following query if it isn't the last one */
445 int artist_seen = 0;
446 /* Search for the fields of the next tag, this needs the value/type of all the previous,
447 * Parsed from left to right
449 mpd_database_search_field_start(connection, te->type);
450 /* fil in the next */
451 while(first){
452 tag_element *te3 = first->data;
453 if(te3->index != te->index)
455 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te3->tree));
456 if(gtk_tree_selection_get_selected(sel, &(te3->model), &iter))
458 gchar *value;
459 gtk_tree_model_get(te3->model, &iter, MPDDATA_MODEL_COL_SONG_TITLE, &value, -1);
460 mpd_database_search_add_constraint(connection, te3->type, value);
461 if(te3->index < (te->index) && !artist_seen)
463 if(te3->type == MPD_TAG_ITEM_ARTIST)
465 artist = g_strdup(value);
466 artist_seen = 1;
470 g_free(value);
473 first =first->next;
476 /* Set on all tags the request artist, if available. */
477 /* TODO: This sets it many, uneeded times, fix that.. */
478 if(artist){
479 if(!artist_set) {
480 GList *first = g_list_first(browser->tag_lists);
481 for(;first;first = first->next) {
482 tag_element *te3 = first->data;
483 gmpc_mpddata_model_set_request_artist(GMPC_MPDDATA_MODEL(te3->model), artist);
485 artist_set = TRUE;
487 q_free(artist);
490 data = mpd_database_search_commit(connection);
491 /* Delete items that match */
492 if(strlen(gtk_entry_get_text(GTK_ENTRY(te->sentry))) > 0)
494 MpdData_real *d = (MpdData_real *)data;
495 /* Get the entry from the text view */
496 const char *str = gtk_entry_get_text(GTK_ENTRY(te->sentry));
497 /* Lowercase it. */
498 gchar *sb1 = g_utf8_casefold(str, -1);
499 /* Normalize it, this to ensure they use the same way of representing the character */
500 gchar *sb = g_utf8_normalize(sb1,-1,G_NORMALIZE_ALL_COMPOSE);
501 g_free(sb1);
502 while(d)
504 /* Lowercase it. */
505 gchar *sa1 = g_utf8_casefold(d->tag, -1);
506 /* Normalize it, this to ensure they use the same way of representing the character */
507 gchar *sa = g_utf8_normalize(sa1,-1,G_NORMALIZE_ALL_COMPOSE);
508 g_free(sa1);
509 /* compare the utf-8 strings, this should be a fairly good compare
510 * because I made sure the utf-8 is represented in a equal way
512 if(strstr(sa,sb) == NULL)
514 /* If it does not match, remove the item from the list */
515 data= mpd_data_delete_item((MpdData *)d);
516 d= (MpdData_real *)data;
518 else
519 /* if it does match, we go to the next and check that */
520 d = d->next;
521 /* cleanup */
522 g_free(sa);
525 /* if there are items in the lest, make sure we get the first one */
526 if (data)
527 data = mpd_data_get_first(data);
528 g_free(sb);
531 * Update the TreeModel using the special incremental replace function.
532 * This will make sure, selected rows that are matched, don't get de-selected
534 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree));
535 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te->model), data);
536 /* this make sure the selected row is centered in the middle of the treeview.
537 * Otherwise the user could have the tedious job of finding it again
539 /* get the selected row, if any */
540 if(gtk_tree_selection_get_selected(sel, &(te->model), &iter))
542 /* get the path to the selected row */
543 GtkTreePath *path = gtk_tree_model_get_path(te->model, &iter);
544 /* scroll to the path, and center it in the middle of the treeview, at the left of the column */
545 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(te->tree), path, NULL, TRUE, 0.5,0);
546 /* free the path */
547 gtk_tree_path_free(path);
550 tel = tel->next;
553 tel = g_list_first(browser->tag_lists);
554 data = NULL;
555 while(tel)
557 GtkTreeSelection *sel;
558 GtkTreeIter iter;
559 te = tel->data;
560 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree));
561 if(gtk_tree_selection_get_selected(sel, &(te->model), &iter))
563 gchar *value;
564 if(!found)
566 mpd_database_search_start(connection, TRUE);
567 found=1;
569 gtk_tree_model_get(te->model, &iter, MPDDATA_MODEL_COL_SONG_TITLE, &value, -1);
570 mpd_database_search_add_constraint(connection, te->type, value);
571 g_free(value);
573 tel = tel->next;
575 if(found)
576 data = mpd_database_search_commit(connection);
577 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(gtk_tree_view_get_model(browser->tag_songlist)), data);
581 static void tag2_destroy_browser(tag_browser *browser, gpointer user_data)
583 GtkTreeIter iter;
584 GtkTreeModel *model;
585 GtkTreePath *path;
586 gchar *d;
587 if(!browser)
589 return;
592 d = g_strdup_printf("tag2-plugin:%s", browser->key);
593 cfg_set_single_value_as_int(config, d, "pane-pos", gtk_paned_get_position(GTK_PANED(browser->tag2_vbox)));
594 g_free(d);
596 /* remove it from the left hand view */
597 model = gtk_tree_row_reference_get_model(browser->ref_iter);
598 path = gtk_tree_row_reference_get_path(browser->ref_iter);
599 if(path)
601 if(gtk_tree_model_get_iter(model,&iter, path))
603 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
605 gtk_tree_path_free(path);
607 gtk_tree_row_reference_free(browser->ref_iter);
609 /* free browser */
610 g_free(browser->name);
611 g_free(browser->key);
612 /* free the tag browsers */
613 g_list_foreach(browser->tag_lists, (GFunc)tag2_destroy_tag, NULL);
614 g_list_free(browser->tag_lists);
615 /* destroy the container */
616 if(browser->tag2_vbox) {
617 g_object_unref(browser->tag2_vbox);
619 /* clear structure */
620 g_free(browser);
622 static void tag2_songlist_combo_box_changed(GtkComboBox *box, tag_element *te)
624 GList *list;
625 te->type = gtk_combo_box_get_active(box);
626 /* if the first is changed, refill the first.
627 * if any other is changed, make the edited refill by triggering changed signal on the previous.
629 /* clear the list, makes changing the tree layout better.*/
630 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te->model), NULL);
631 /* clear all settings */
632 gtk_cell_layout_clear_attributes(GTK_CELL_LAYOUT(te->column), te->image_renderer);
633 if(te->type == MPD_TAG_ITEM_ARTIST ||te->type == MPD_TAG_ITEM_ALBUM)
635 int size = cfg_get_single_value_as_int_with_default(config, "gmpc-mpddata-model", "icon-size", 32);
636 gtk_tree_view_column_add_attribute(te->column,te->image_renderer, "pixbuf", MPDDATA_MODEL_META_DATA);
637 gtk_cell_renderer_set_fixed_size(te->image_renderer, size,size);
639 else
641 int width, height;
642 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
643 gtk_cell_renderer_set_fixed_size(te->image_renderer, width,height);
644 gtk_tree_view_column_add_attribute(te->column,te->image_renderer, "icon-name", MPDDATA_MODEL_COL_ICON_ID);
647 /* index starts at 1 */
648 if(te->index != 1)
650 list = g_list_nth(te->browser->tag_lists,te->index -2);
651 if(list)
653 tag_element *te2 = list->data;
654 /* update the content */
655 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te2->tree)),te2);
658 else
660 MpdData *data;
661 list = g_list_first(te->browser->tag_lists);
662 if(list->next)
664 tag_element *te2 = list->next->data;
665 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te2->tree)),te2);
667 else
669 mpd_database_search_field_start(connection, te->type);
670 data = mpd_database_search_commit(connection);
671 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te->model), data);
675 tag2_save_browser(te->browser);
678 static gboolean tag2_sentry_changed_real(tag_element *te)
680 tag_element *te2 = g_malloc0(sizeof(*te2));
681 te2->index = -1;
682 te2->browser = te->browser;
683 tag2_changed(NULL, te2);
685 g_free(te2);
686 te->timeout = 0;
687 if(strlen(gtk_entry_get_text(GTK_ENTRY(te->sentry)))==0)
689 gtk_widget_hide(te->sentry);
691 return FALSE;
694 static void tag2_sentry_changed(SexyIconEntry *entry, tag_element *te)
696 gtk_widget_show(te->sentry);
697 if(te->timeout)
698 g_source_remove(te->timeout);
699 te->timeout = g_timeout_add_seconds(1, (GSourceFunc)tag2_sentry_changed_real, te);
703 static void playtime_changed(GmpcMpdDataModel *model, gulong playtime)
705 if(pl3_cat_get_selected_browser() == tag2_plug.id)
707 playlist3_show_playtime(playtime);
711 static void tag2_songlist_add_tag(tag_browser *browser,const gchar *name, int type)
713 GtkCellRenderer *renderer;
714 GtkTreeViewColumn *column;
715 tag_element *te = g_malloc0(sizeof(*te));
717 browser->tag_lists = g_list_append(browser->tag_lists, te);
718 /* Tag Element */
719 te->type = type;
720 te->index = g_list_length(browser->tag_lists);
721 te->model = (GtkTreeModel *) gmpc_mpddata_model_new();
722 te->combo = gtk_combo_box_new_with_model(tags_store);
723 te->sentry = sexy_icon_entry_new();
724 te->sw = gtk_scrolled_window_new(NULL,NULL);
725 te->vbox = gtk_vbox_new(FALSE, 6);
726 te->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(te->model));
727 te->browser = browser;
730 /* setup combo box */
731 renderer = gtk_cell_renderer_text_new();
732 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(te->combo), renderer, TRUE);
733 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(te->combo), renderer, "text", 2,NULL);
734 gtk_box_pack_start(GTK_BOX(te->vbox), te->combo, FALSE, TRUE, 0);
735 gtk_combo_box_set_active(GTK_COMBO_BOX(te->combo), te->type);
736 g_signal_connect(G_OBJECT(te->combo), "changed", G_CALLBACK(tag2_songlist_combo_box_changed), te);
738 /* entry */
739 gtk_widget_set_no_show_all(te->sentry, TRUE);
740 gtk_widget_hide(te->sentry);
741 sexy_icon_entry_add_clear_button(SEXY_ICON_ENTRY(te->sentry));
742 g_signal_connect(G_OBJECT(te->sentry), "changed", G_CALLBACK(tag2_sentry_changed), te);
743 gtk_box_pack_start(GTK_BOX(te->vbox), te->sentry,FALSE, TRUE, 0);
745 g_signal_connect(G_OBJECT(te->tree), "key-press-event", G_CALLBACK(tag2_key_release_event), te);
746 /* setup sw */
747 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(te->sw), GTK_SHADOW_ETCHED_IN);
748 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(te->sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
749 /* add tree */
750 gtk_container_add(GTK_CONTAINER(te->sw), te->tree);
751 gtk_box_pack_start(GTK_BOX(te->vbox), te->sw, TRUE, TRUE, 0);
752 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(te->tree), FALSE);
753 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(te->tree), FALSE);
757 /* Add the column, and set it up */
758 te->column = column = gtk_tree_view_column_new();
760 gtk_tree_view_column_set_title(column, name);
762 te->image_renderer = renderer = gtk_cell_renderer_pixbuf_new();
764 gtk_tree_view_column_pack_start(column, renderer, FALSE);
765 if(te->type == MPD_TAG_ITEM_ARTIST ||te->type == MPD_TAG_ITEM_ALBUM)
767 int size = cfg_get_single_value_as_int_with_default(config, "gmpc-mpddata-model", "icon-size", 32);
769 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", MPDDATA_MODEL_META_DATA);
770 gtk_cell_renderer_set_fixed_size(renderer, size,size);
771 gtk_tree_view_column_set_sizing(column , GTK_TREE_VIEW_COLUMN_FIXED);
773 else
775 int width, height;
776 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
777 gtk_cell_renderer_set_fixed_size(te->image_renderer, width,height);
778 gtk_tree_view_column_add_attribute(column, renderer, "icon-name", MPDDATA_MODEL_COL_ICON_ID);
779 gtk_tree_view_column_set_sizing(column , GTK_TREE_VIEW_COLUMN_FIXED);
781 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te->tree), TRUE);
782 renderer = gtk_cell_renderer_text_new();
783 gtk_tree_view_column_pack_start(column, renderer, TRUE);
784 gtk_tree_view_column_add_attribute(column, renderer, "text", MPDDATA_MODEL_COL_SONG_TITLE);
785 gtk_tree_view_insert_column(GTK_TREE_VIEW(te->tree),column, -1);
786 /* set the search column */
787 gtk_tree_view_set_search_column(GTK_TREE_VIEW(te->tree), MPDDATA_MODEL_COL_SONG_TITLE);
789 gtk_box_pack_start(GTK_BOX(browser->tag_hbox), te->vbox, TRUE, TRUE, 0);
790 g_signal_connect(G_OBJECT(te->tree), "button-press-event", G_CALLBACK(tag2_song_list_button_press_event), te);
791 g_signal_connect(G_OBJECT(te->tree), "button-release-event", G_CALLBACK(tag2_browser_button_release_event), te);
795 /* Signal */
796 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree))), "changed", G_CALLBACK(tag2_changed), te);
800 static void tag2_create_tags(tag_browser *browser)
802 gchar *str = cfg_get_single_value_as_string_with_default(config, "tag2-browsers",browser->key, "genre|artist|album");
803 if(str)
805 char **strv = g_strsplit(str, "|",0);
806 int i;
807 for(i=0;strv && strv[i];i++)
809 int tag = mpd_misc_get_tag_by_name(strv[i]);
810 if(tag>=0)
812 /* This needs to be done based on config */
813 tag2_songlist_add_tag(browser,strv[i],tag);
816 g_strfreev(strv);
817 g_free(str);
819 gtk_widget_show_all(browser->tag2_vbox);
822 * Add all selected songs
825 static void tag2_songlist_add_selected_songs(GtkWidget *bug, tag_browser *browser)
827 GtkTreeSelection *sel = gtk_tree_view_get_selection(browser->tag_songlist);
828 GtkTreeModel *model = gtk_tree_view_get_model(browser->tag_songlist);
829 /* get a list of selected paths */
830 GList *list = gtk_tree_selection_get_selected_rows(sel, &model);
831 if(list)
833 GtkTreeIter piter;
834 GList *iter = g_list_first(list);
835 /* iterate over all the selected rows */
836 for(;iter;iter = g_list_next(iter))
838 /* get iter from path */
839 if(gtk_tree_model_get_iter(model, &piter, iter->data))
841 gchar *path;
842 gtk_tree_model_get(model, &piter, MPDDATA_MODEL_COL_PATH, &path, -1);
843 mpd_playlist_queue_add(connection, path);
844 g_free(path);
847 mpd_playlist_queue_commit(connection);
848 /* cleanup */
849 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
850 g_list_free(list);
855 * Replace with selected songs
857 static void tag2_songlist_replace_selected_songs(GtkWidget *bug, tag_browser *browser)
859 GtkTreeSelection *sel = gtk_tree_view_get_selection(browser->tag_songlist);
860 /* if no song is selected don't do anything */
861 if(gtk_tree_selection_count_selected_rows(sel) > 0)
863 /* clear the current playlist */
864 mpd_playlist_clear(connection);
865 /* Add selected songs */
866 tag2_songlist_add_selected_songs(bug, browser);
867 /* start playing, as is standard in gmpc */
868 mpd_player_play(connection);
873 * Calls the column editing function of the GmpcTreeview
875 static void tag2_song_list_edit_columns(GtkMenuItem *item, tag_browser *browser)
877 gmpc_mpddata_treeview_edit_columns(GMPC_MPDDATA_TREEVIEW(browser->tag_songlist));
882 * Handles right mouse release on song list
884 static gboolean tag2_song_list_button_release_event(GtkTreeView *tree,
885 GdkEventButton *event,
886 tag_browser *browser)
888 /* only on right mouse click */
889 if(event->button == 3)
891 GtkWidget *menu, *item;
892 GtkTreeSelection *selection = gtk_tree_view_get_selection(tree);
893 int count = gtk_tree_selection_count_selected_rows(selection);
895 /* create menu to popup */
896 menu = gtk_menu_new();
897 /* only show when soething is selected */
898 if(count >0)
900 /* add the add widget */
901 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD,NULL);
902 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
903 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(tag2_songlist_add_selected_songs), browser);
904 /* add the replace widget */
905 item = gtk_image_menu_item_new_with_label("Replace");
906 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
907 gtk_image_new_from_stock(GTK_STOCK_REDO, GTK_ICON_SIZE_MENU));
908 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
909 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(tag2_songlist_replace_selected_songs), browser);
911 /* Separator */
912 item = gtk_separator_menu_item_new();
913 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
915 /* Edit columns */
916 item = gtk_image_menu_item_new_with_label(("Edit Columns"));
917 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
918 gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU));
919 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
920 g_signal_connect(G_OBJECT(item), "activate",
921 G_CALLBACK(tag2_song_list_edit_columns), browser);
924 /* popup */
925 gtk_widget_show_all(GTK_WIDGET(menu));
926 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,NULL, NULL, 0, event->time);
927 return TRUE;
930 return FALSE;
933 * Handles a double click/activate on a row of the song list
935 static void tag2_row_activate(GtkTreeView *tree,
936 GtkTreePath *path,
937 GtkTreeViewColumn *column,
938 tag_browser *browser)
940 GtkTreeIter iter;
941 if(gtk_tree_model_get_iter(gtk_tree_view_get_model(tree), &iter, path))
943 gchar *song_path;
944 gtk_tree_model_get(gtk_tree_view_get_model(tree), &iter, MPDDATA_MODEL_COL_PATH, &song_path, -1);
945 if(song_path)
947 /* gcc complains that this function is declared implicit.
948 * This is correct, but it exists in the gmpc program space
950 play_path(song_path);
951 /* free */
952 g_free(song_path);
957 static void tag2_init(void)
959 int i;
961 * if first time used, add 2 example browsers
963 if(cfg_get_single_value_as_int_with_default(config, "tag2-plugin", "first-use", 1))
965 cfg_set_single_value_as_int(config,"tag2-plugin", "first-use",0);
967 cfg_set_single_value_as_string(config, "tag2-browsers", "Artist Browser", "artist|album|disc");
968 cfg_set_single_value_as_string(config, "tag2-plugin:Artist Browser", "name", "Artist Browser");
969 cfg_set_single_value_as_string(config, "tag2-browsers", "Genre Browser", "genre|artist|album|disc");
970 cfg_set_single_value_as_string(config, "tag2-plugin:Genre Browser", "name", "Genre Browser");
973 tags_store = (GtkTreeModel *)gtk_list_store_new(3, G_TYPE_STRING,G_TYPE_INT,G_TYPE_STRING);
974 for(i=0;i<MPD_TAG_ITEM_ANY;i++)
976 GtkTreeIter titer;
977 gtk_list_store_append(GTK_LIST_STORE(tags_store), &titer);
978 gtk_list_store_set(GTK_LIST_STORE(tags_store), &titer, 0, mpdTagItemKeys[i],1, i,2,_(mpdTagItemKeys[i]),-1);
983 static void tag2_init_browser(tag_browser *browser) {
984 gchar *key;
985 GtkWidget *sw;
986 GmpcMpdDataModel *model = NULL;
987 int pp;
989 /* create the pane that separates the song list from the browsers */
990 key = g_strdup_printf("tag2-plugin:%s", browser->key);
991 pp = cfg_get_single_value_as_int_with_default(config, key, "pane-pos",150);
992 browser->tag2_vbox = gtk_vpaned_new();
993 /* set the previous pane position */
994 gtk_paned_set_position(GTK_PANED(browser->tag2_vbox), pp);
997 /* box with tag treeviews (browsers) */
998 browser->tag_hbox = gtk_hbox_new(TRUE, 6);
1000 /* Add this to the 1st pane*/
1001 gtk_paned_add1(GTK_PANED(browser->tag2_vbox), browser->tag_hbox);
1004 /** Create Songs list view */
1005 /* create the treeview model, this is a GmpcMpdData model */
1006 model = gmpc_mpddata_model_new();
1007 g_signal_connect(G_OBJECT(model), "playtime_changed", G_CALLBACK(playtime_changed), NULL);
1008 /* create scrolled window to make the treeview scrollable */
1009 sw = gtk_scrolled_window_new(NULL,NULL);
1010 /* setup the scrolled window */
1011 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1012 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1013 /* create the actual treeview, use the GmpcTreeview type, so all the handling is done automatic */
1015 browser->tag_songlist = (GtkTreeView *)gmpc_mpddata_treeview_new(key,TRUE,GTK_TREE_MODEL(model));
1016 g_free(key);
1017 /* add the treeview to the scrolled window */
1018 gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(browser->tag_songlist));
1019 /* add the scrolled window to the 2nd pane */
1020 gtk_paned_add2(GTK_PANED(browser->tag2_vbox),sw);
1021 /* connect some of the signals */
1022 g_signal_connect(G_OBJECT(browser->tag_songlist), "row-activated", G_CALLBACK(tag2_row_activate), browser);
1023 // g_signal_connect(G_OBJECT(browser->tag_songlist), "button-press-event", G_CALLBACK(tag2_song_list_button_press_event), browser);
1024 g_signal_connect(G_OBJECT(browser->tag_songlist), "button-release-event", G_CALLBACK(tag2_song_list_button_release_event), browser);
1025 /* Create an extra reference to the paned window containing everything, this way
1026 * I can add/remove it from gmpc's container withouth it being destroyed by gtk
1028 g_object_ref(browser->tag2_vbox);
1029 /* show everything */
1030 gtk_widget_show_all(browser->tag2_vbox);
1032 tag2_connection_changed_foreach(browser, NULL);
1034 static gboolean tag2_custom_find(tag_browser *a ,gchar *key)
1036 return strcmp(a->key, key);
1040 static void tag2_browser_selected(GtkWidget *container)
1042 GtkTreeView *tree = playlist3_get_category_tree_view();
1043 GtkTreeModel *model = gtk_tree_view_get_model(tree);
1044 GtkTreeSelection *sel = gtk_tree_view_get_selection(tree);
1045 GtkTreeIter iter;
1046 if(gtk_tree_selection_get_selected(sel, &model, &iter))
1048 gchar *key;
1049 gtk_tree_model_get(model, &iter, PL3_CAT_INT_ID, &key, -1);
1050 if(key)
1052 GList *node = g_list_find_custom(tag2_ht, key, (GCompareFunc)tag2_custom_find);
1053 if(node)
1055 tag_browser *tb = node->data;
1056 if(tb)
1058 /* GList *list;*/
1059 if(tb->tag2_vbox ==NULL)
1060 tag2_init_browser(tb);
1061 gtk_container_add(GTK_CONTAINER(container), tb->tag2_vbox);
1062 gtk_widget_show_all(container);
1063 tag2_current = tb->tag2_vbox;
1065 list = g_list_first(tb->tag_lists);
1066 if(list)
1068 tag_element *te= list->data;
1069 gtk_widget_grab_focus(te->tree);
1073 playlist3_show_playtime(gmpc_mpddata_model_get_playtime(GMPC_MPDDATA_MODEL(
1074 gtk_tree_view_get_model(tb->tag_songlist))));
1076 else{
1079 g_free(key);
1085 static void tag2_browser_unselected(GtkWidget *container)
1087 if(tag2_current)
1088 gtk_container_remove(GTK_CONTAINER(container), tag2_current);
1089 tag2_current = NULL;
1092 static void tag2_clear(tag_browser *browser)
1094 if(browser->tag2_vbox)
1096 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(browser->tag_songlist));
1097 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(model), NULL);
1098 g_list_foreach(browser->tag_lists, (GFunc)tag2_destroy_tag, NULL);
1099 g_list_free(browser->tag_lists);
1100 browser->tag_lists = NULL;
1103 static void tag2_save_browser(tag_browser *browser)
1105 GString *str = g_string_new("");
1106 GList *list = browser->tag_lists;
1107 for(;list;list = g_list_next(list))
1109 tag_element *te = list->data;
1110 str = g_string_append(str, mpdTagItemKeys[te->type]);
1111 if(list->next)
1112 str = g_string_append(str, "|");
1115 cfg_set_single_value_as_string(config, "tag2-browsers",browser->key,str->str);
1116 g_string_free(str, TRUE);
1118 static void tag2_connection_changed_foreach(tag_browser *browser, gpointer userdata)
1120 if(browser->tag2_vbox)
1122 tag_element *te = NULL;
1123 if(browser->tag_lists == NULL)
1124 tag2_create_tags(browser);
1125 te = g_list_nth_data(browser->tag_lists, 0);
1126 if(te != NULL && mpd_check_connected(connection) )
1128 MpdData *data;
1130 while(te){
1131 te = g_list_next(te);
1136 mpd_database_search_field_start(connection, te->type);
1137 data = mpd_database_search_commit(connection);
1138 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te->model), data);
1140 tag_element *te2 = g_malloc0(sizeof(*te2));
1141 te2->index = -1;
1142 te2->browser = te->browser;
1143 tag2_changed(NULL, te2);
1145 g_free(te2);
1147 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te->tree)),te);
1153 static void tag2_connection_changed(MpdObj *mi, int connect, gpointer data)
1155 // if(tag2_ht)
1156 // g_list_foreach(tag2_ht,(GFunc)tag2_clean, NULL);
1157 /*tag2_clear();*/
1158 if(connect && tag2_ht)
1160 /* create tags */
1161 g_list_foreach(tag2_ht,(GFunc)tag2_connection_changed_foreach, NULL);
1167 static void tag2_status_changed(MpdObj *mi, ChangedStatusType what, gpointer data)
1169 if(what&MPD_CST_DATABASE)
1171 if(tag2_ht)
1173 /* g_list_foreach(tag2_ht,(GFunc)tag2_clear, NULL);
1174 g_list_foreach(tag2_ht,(GFunc)tag2_connection_changed_foreach, NULL);
1175 */ }
1180 * Preferences window
1182 void tag2_pref_destroy(GtkWidget *container)
1184 gtk_container_remove(GTK_CONTAINER(container), pref_vbox);
1187 * Browser selector changed
1189 static void tag2_pref_combo_changed(GtkComboBox *box, GtkTreeModel *model2)
1191 GtkTreeIter iter;
1194 /* clear old data */
1195 gtk_list_store_clear(GTK_LIST_STORE(model2));
1197 if(gtk_combo_box_get_active_iter(box, &iter))
1199 GtkTreeModel *model = gtk_combo_box_get_model(box);
1200 gchar *group;
1201 gchar *str;
1202 tag_browser *tb;
1203 /* get the active tag browser */
1204 gtk_tree_model_get(model, &iter, 2,&tb,-1);
1205 /* iterate over all the tag elements and add them to the list */
1206 if(tb->tag_lists)
1208 GList *liter = g_list_first(tb->tag_lists);
1209 for(;liter;liter = g_list_next(liter))
1211 GtkTreeIter titer;
1212 tag_element *te = liter->data;
1213 gtk_list_store_append(GTK_LIST_STORE(model2),&titer);
1214 gtk_list_store_set(GTK_LIST_STORE(model2), &titer, 0,mpdTagItemKeys[te->type],1,te->type,2,te,-1);
1218 /* Get browser name and set entry */
1219 group = g_strdup_printf("tag2-plugin:%s", tb->key);
1220 str = cfg_get_single_value_as_string_with_default(config,group,"name", "default");
1221 gtk_entry_set_text(GTK_ENTRY(pref_entry), str);
1222 g_free(str);
1223 g_free(group);
1225 else
1227 /* only clear the text when nothing is selected, else the currently selected is cleared..
1228 * yeah, longlive signals
1230 gtk_entry_set_text(GTK_ENTRY(pref_entry), "");
1234 static void tag2_pref_add_browser_clicked(GtkWidget *but, GtkComboBox *combo)
1236 GtkTreeView *tree = playlist3_get_category_tree_view();
1237 GtkTreeIter titer;
1238 GList *node;
1239 GtkListStore *model = (GtkListStore *)gtk_combo_box_get_model(combo);
1240 gchar *name = g_strdup_printf("%u", g_random_int());
1241 cfg_set_single_value_as_string(config, "tag2-browsers",name, "");
1245 tag2_browser_add_browser(GTK_WIDGET(tree),name);
1247 node = g_list_find_custom(tag2_ht, name, (GCompareFunc)tag2_custom_find);
1248 if(node)
1250 tag_browser *tb = node->data;
1251 gtk_list_store_append(model, &titer);
1252 gtk_list_store_set(model, &titer, 0,name, 1,"default",2,tb,-1);
1253 gtk_combo_box_set_active_iter(combo, &titer);
1254 /* change this to store TB in list store) */
1256 g_free(name);
1260 * Update the title of the browser
1262 static void tag2_pref_entry_changed(GtkWidget *entry, GtkComboBox *combo)
1264 GtkTreeIter iter;
1265 GtkListStore *model = (GtkListStore *)gtk_combo_box_get_model(combo);
1266 if(gtk_combo_box_get_active_iter(combo, &iter))
1268 gchar *group,*key;
1269 GtkTreePath *path;
1270 tag_browser *tb;
1271 const gchar *name = gtk_entry_get_text(GTK_ENTRY(pref_entry));
1272 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,0,&key, 2, &tb, -1);
1273 gtk_list_store_set(model, &iter,1,name,-1);
1275 g_free(tb->name);
1276 tb->name = g_strdup(name);
1277 if((path = gtk_tree_row_reference_get_path(tb->ref_iter)))
1279 GtkTreeIter iter2;
1280 GtkTreeModel *model2 = gtk_tree_row_reference_get_model(tb->ref_iter);
1281 gtk_tree_model_get_iter(model2, &iter2, path);
1282 gtk_list_store_set(GTK_LIST_STORE(model2), &iter2, PL3_CAT_TITLE, name, -1);
1283 gtk_tree_path_free(path);
1286 group = g_strdup_printf("tag2-plugin:%s", key);
1287 cfg_set_single_value_as_string(config, group, "name", (char *)name);
1288 g_free(group);
1292 * Handles editing of the columns type
1294 static void tag2_pref_column_type_edited(GtkCellRendererText *text, gchar *path, char *new_data, GtkTreeModel *model)
1296 GtkTreeIter iter;
1297 if(gtk_tree_model_get_iter_from_string(model, &iter, path))
1299 int tag = mpd_misc_get_tag_by_name(new_data);
1300 tag_element *te;
1301 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, new_data,1,tag, -1);
1302 gtk_tree_model_get(model, &iter, 2, &te, -1);
1303 gtk_combo_box_set_active(GTK_COMBO_BOX(te->combo), tag);
1307 * Add's a column to the selected tag browser
1309 static void tag2_pref_browser_remove(GtkWidget *but, GtkComboBox *box)
1311 GtkTreeIter iter;
1312 if(gtk_combo_box_get_active_iter(box, &iter))
1314 GtkTreeModel *model = gtk_combo_box_get_model(box);
1315 gchar *name;
1316 gchar *key;
1317 tag_browser *tb;
1318 gtk_tree_model_get(model, &iter, 2,&tb,-1);
1319 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1321 key = g_strdup(tb->key);
1322 /* remove from browser list */
1323 tag2_ht = g_list_remove(tag2_ht, tb);
1324 /* destroy remaining */
1325 tag2_destroy_browser(tb,NULL);
1328 /* TODO delete */
1329 cfg_del_single_value(config, "tag2-browsers", key);
1331 name = g_strdup_printf("tag2-plugin:%s", key);
1332 cfg_remove_class(config, name);
1333 g_free(name);
1334 name = g_strdup_printf("tag2-plugin:%s-colsize", key);
1335 cfg_remove_class(config, name);
1336 g_free(name);
1337 name = g_strdup_printf("tag2-plugin:%s-colpos", key);
1338 cfg_remove_class(config, name);
1339 g_free(name);
1340 name = g_strdup_printf("tag2-plugin:%s-colshow", key);
1341 cfg_remove_class(config, name);
1342 g_free(name);
1343 g_free(key);
1344 pl3_update_go_menu();
1348 * Add's a column to the selected tag browser
1350 static void tag2_pref_column_add(GtkWidget *but, GtkComboBox *box)
1352 GtkTreeIter iter;
1353 GtkTreeModel *model2 = g_object_get_data(G_OBJECT(but),"model");
1354 if(gtk_combo_box_get_active_iter(box, &iter))
1356 GtkTreeModel *model = gtk_combo_box_get_model(box);
1357 tag_browser *tb;
1358 /* TODO: Change to the browser tag editing stuff */
1359 gtk_tree_model_get(model, &iter, 2,&tb,-1);
1360 tag2_songlist_add_tag(tb,mpdTagItemKeys[MPD_TAG_ITEM_ARTIST], MPD_TAG_ITEM_ARTIST);
1361 tag2_pref_combo_changed(box, model2);
1362 gtk_widget_show_all(tb->tag_hbox);
1363 tag2_save_browser(tb);
1364 /* if it's the first, update */
1365 if(g_list_length(tb->tag_lists) == 1)
1367 GList *giter = g_list_first(tb->tag_lists);
1368 if(giter)
1370 MpdData *data;
1371 tag_element *te2 = giter->data;
1372 /* update the content */
1373 mpd_database_search_field_start(connection, te2->type);
1374 data = mpd_database_search_commit(connection);
1375 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te2->model), data);
1381 * Removes the selected column from the tag browser
1383 static void tag2_pref_column_remove(GtkWidget *but, GtkComboBox *box)
1385 GtkTreeIter iter;
1386 GtkTreeView *tree= g_object_get_data(G_OBJECT(but),"tree");
1387 if(gtk_combo_box_get_active_iter(box, &iter))
1389 GtkTreeModel *model = gtk_combo_box_get_model(box);
1390 GtkTreeSelection *sel = gtk_tree_view_get_selection(tree);
1391 tag_browser *tb;
1392 /* get the selected tag browser */
1393 gtk_tree_model_get(model, &iter, 2,&tb,-1);
1394 model = gtk_tree_view_get_model(tree);
1396 /* Get the selected column */
1397 if(gtk_tree_selection_get_selected(sel, &model,&iter))
1399 tag_element *te;
1400 GList *giter;
1401 int i;
1402 /* get tag element */
1403 gtk_tree_model_get(model, &iter,2, &te, -1);
1404 /* remove from pref editor */
1405 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1406 /* remove from browsers list */
1407 tb->tag_lists = g_list_remove(tb->tag_lists, te);
1410 * renumber, this is needed to keep the filling consistent
1411 * Beware, the numbering starts at 1. not 0. (yes shoot me for this)
1413 i=1;
1414 for(giter = g_list_first(tb->tag_lists);giter;giter = g_list_next(giter))
1416 tag_element *te2 = giter->data;
1417 te2->index = i;
1418 i++;
1421 /* destroy from interface */
1422 tag2_destroy_tag(te);
1424 * Refill the complete browser.
1425 * TODO: Only refill the part that is needed
1427 giter = g_list_first(te->browser->tag_lists);
1428 if(giter)
1430 MpdData *data;
1431 tag_element *te2 = giter->data;
1432 /* update the content */
1433 mpd_database_search_field_start(connection, te2->type);
1434 data = mpd_database_search_commit(connection);
1435 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te2->model), data);
1440 /* save the browsers content */
1441 tag2_save_browser(tb);
1447 void tag2_pref_construct(GtkWidget *container)
1449 GtkWidget *sw, *tree, *but;
1450 GtkCellRenderer *renderer;
1451 GtkTreeModel *model;
1452 GtkWidget *hbox,*label,*vbox;
1453 GtkWidget *combo = NULL;
1454 conf_mult_obj *cmo,*iter;
1456 * Create the parent widget where the preferences window is packed in
1458 pref_vbox = gtk_vbox_new(FALSE,6);
1460 /* select browser to edit */
1461 hbox = gtk_hbox_new(FALSE, 6);
1462 model = (GtkTreeModel *)gtk_list_store_new(3, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_POINTER);
1463 combo = gtk_combo_box_new_with_model(model);
1464 renderer = gtk_cell_renderer_text_new();
1465 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1466 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", 1,NULL);
1468 gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
1470 but = gtk_button_new_from_stock(GTK_STOCK_ADD);
1471 gtk_box_pack_start(GTK_BOX(hbox),but, FALSE, TRUE, 0);
1472 g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(tag2_pref_add_browser_clicked), combo);
1473 but = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
1474 g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(tag2_pref_browser_remove), combo);
1475 gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, TRUE, 0);
1476 gtk_box_pack_start(GTK_BOX(pref_vbox), hbox, FALSE, TRUE, 0);
1479 hbox = gtk_hbox_new(FALSE, 6);
1480 label = gtk_label_new("Name:");
1481 gtk_misc_set_alignment(GTK_MISC(label), 1,0.5);
1482 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
1484 pref_entry = gtk_entry_new();
1485 g_signal_connect(GTK_ENTRY(pref_entry),"changed", G_CALLBACK(tag2_pref_entry_changed), combo);
1486 gtk_box_pack_start(GTK_BOX(hbox), pref_entry,TRUE, TRUE, 0);
1488 gtk_box_pack_start(GTK_BOX(pref_vbox), hbox, FALSE, TRUE, 0);
1493 /* change this to store TB in list store) */
1494 cmo = cfg_get_key_list(config, "tag2-browsers");
1495 for(iter=cmo;iter;iter = iter->next)
1497 GtkTreeIter titer;
1498 gchar *group = g_strdup_printf("tag2-plugin:%s", iter->key);
1499 gchar *name = cfg_get_single_value_as_string_with_default(config, group, "name", "default");
1500 GList *node = g_list_find_custom(tag2_ht, iter->key, (GCompareFunc)tag2_custom_find);
1501 if(node)
1503 tag_browser *tb = node->data;
1504 /* add to list */
1505 gtk_list_store_append(GTK_LIST_STORE(model), &titer);
1506 gtk_list_store_set(GTK_LIST_STORE(model), &titer, 0, iter->key,1,name,2,tb,-1);
1508 /* cleanup */
1509 g_free(group);
1511 g_free(name);
1513 if(cmo)
1514 cfg_free_multiple(cmo);
1518 /* scrolled window used to pack the treeview */
1520 hbox = gtk_hbox_new(FALSE, 6);
1521 sw = gtk_scrolled_window_new(NULL, NULL);
1522 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1523 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),GTK_SHADOW_ETCHED_IN);
1524 /* the treeview */
1525 tree = gtk_tree_view_new();
1526 /* the model for the treeview */
1527 model = (GtkTreeModel *) gtk_list_store_new(3, G_TYPE_STRING,G_TYPE_INT, G_TYPE_POINTER);
1528 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), model);
1530 renderer = gtk_cell_renderer_combo_new();
1531 g_object_set(G_OBJECT(renderer), "editable", TRUE,NULL);
1532 g_object_set(G_OBJECT(renderer), "model", tags_store,"text-column", 0,"has-entry", FALSE,NULL);
1533 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree), -1, "text",renderer,"text",0,NULL);
1534 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(tag2_pref_column_type_edited),model);
1536 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(tag2_pref_combo_changed), model);
1537 /* tree to scrolled window */
1538 gtk_container_add(GTK_CONTAINER(sw), tree);
1540 /* vbox */
1541 vbox = gtk_vbox_new(FALSE,6);
1543 but = gtk_button_new_from_stock(GTK_STOCK_ADD);
1544 gtk_box_pack_start(GTK_BOX(vbox), but, FALSE, TRUE, 0);
1545 g_object_set_data(G_OBJECT(but), "model", model);
1546 g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(tag2_pref_column_add), combo);
1547 but = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
1548 g_object_set_data(G_OBJECT(but), "tree", tree);
1549 g_signal_connect(G_OBJECT(but), "clicked", G_CALLBACK(tag2_pref_column_remove), combo);
1552 gtk_box_pack_start(GTK_BOX(vbox), but, FALSE, TRUE, 0);
1554 gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
1555 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, TRUE, 0);
1556 gtk_box_pack_start(GTK_BOX(pref_vbox), hbox, TRUE, TRUE, 0);
1558 gtk_container_add(GTK_CONTAINER(container), pref_vbox);
1559 gtk_widget_show_all(container);
1561 static void tag2_browser_activate(GtkWidget *item, tag_browser *browser)
1563 GtkTreeSelection *selec = gtk_tree_view_get_selection((GtkTreeView *)
1564 playlist3_get_category_tree_view());
1566 GtkTreePath *path = gtk_tree_row_reference_get_path(browser->ref_iter);
1567 if(path)
1569 gtk_tree_selection_select_path(selec, path);
1570 gtk_tree_path_free(path);
1576 static void tag2_browser_add_go_menu_foreach(tag_browser *browser, GtkWidget *menu)
1578 GtkWidget *item = gtk_image_menu_item_new_with_label(browser->name);
1579 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
1580 gtk_image_new_from_icon_name("media-tag", GTK_ICON_SIZE_MENU));
1581 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1583 gtk_widget_add_accelerator(GTK_WIDGET(item), "activate", gtk_menu_get_accel_group(GTK_MENU(menu)), GDK_F1 +counter,GDK_SHIFT_MASK , GTK_ACCEL_VISIBLE);
1584 counter++;
1585 g_signal_connect(G_OBJECT(item), "activate",
1586 G_CALLBACK(tag2_browser_activate), browser);
1589 static int tag2_browser_add_go_menu(GtkWidget *menu)
1591 if(tag2_get_enabled() == FALSE)
1592 return 0;
1593 if(tag2_ht)
1595 counter = 0;
1596 g_list_foreach(tag2_ht,(GFunc)tag2_browser_add_go_menu_foreach, menu);
1597 return 1;
1600 return 0;
1603 static void tag2_save_myself(void)
1605 if(tag2_ht)
1607 GList *iter = g_list_first(tag2_ht);
1608 while(iter){
1609 tag_browser *tb = iter->data;
1610 GtkTreePath *path = gtk_tree_row_reference_get_path(tb->ref_iter);
1611 if(path)
1613 gint *indices = gtk_tree_path_get_indices(path);
1614 gchar *group = g_strdup_printf("tag2-plugin:%s",tb->key);
1615 debug_printf(DEBUG_INFO,"Saving myself to position: %i\n", indices[0]);
1616 cfg_set_single_value_as_int(config, group,"position",indices[0]);
1617 gtk_tree_path_free(path);
1618 g_free(group);
1620 iter = g_list_next(iter);