1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
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.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * $ If disconnected, don't destroy all tag browsers, just clear the first one, if connected refill the first one
26 * Released under the GPL.
32 #include <gdk/gdkkeysyms.h>
35 #include "gmpc-mpddata-model.h"
36 #include <libmpd/libmpd-internal.h>
37 #include "playlist3.h"
38 #include "playlist3-playlist-editor.h"
39 #include "gmpc-extras.h"
41 #define LOG_DOMAIN "TagBrowser"
43 * dirty hack to workaround single parameter for now
47 static void tag2_init(void);
48 static void tag2_destroy(void);
49 static void tag2_set_enabled(int enabled
);
50 static int tag2_get_enabled(void);
52 static void tag2_save_myself(void);
53 static void tag2_browser_add(GtkWidget
* cat_tree
);
55 /* connection changed signal handling */
56 static void tag2_connection_changed(MpdObj
* mi
, int connect
, gpointer data
);
57 static void tag2_status_changed(MpdObj
* mi
, ChangedStatusType what
, gpointer data
);
58 /* intergration in gmpc */
59 static void tag2_browser_selected(GtkWidget
* container
);
60 static void tag2_browser_unselected(GtkWidget
* container
);
61 static int tag2_browser_add_go_menu(GtkWidget
* menu
);
62 static void tag2_pref_combo_changed(GtkComboBox
* box
, GtkTreeModel
* model2
);
63 static int tag2_browser_right_mouse_menu(GtkWidget
* menu
, int type
, GtkWidget
* tree
, GdkEventButton
* event
);
64 static int tag2_browser_key_press_event(GtkWidget
* mw
, GdkEventKey
* event
, int type
);
65 /* One treemodel to store all possible tags, use this multiple times. */
66 static GtkTreeModel
*tags_store
= NULL
;
71 static void tag2_pref_construct(GtkWidget
* container
);
72 static void tag2_pref_destroy(GtkWidget
* container
);
73 static GtkWidget
*pref_vbox
= NULL
;
74 static GtkWidget
*pref_entry
= NULL
;
75 static GtkWidget
*pref_combo
= NULL
;
80 * Preferences structure
82 gmpcPrefPlugin tag2_prefs
= {
83 .construct
= tag2_pref_construct
,
84 .destroy
= tag2_pref_destroy
89 * Consists of add, selected, unselected.
91 gmpcPlBrowserPlugin tag2_browser_plugin
= {
92 .add
= tag2_browser_add
,
93 .selected
= tag2_browser_selected
,
94 .unselected
= tag2_browser_unselected
,
95 .add_go_menu
= tag2_browser_add_go_menu
,
96 .cat_right_mouse_menu
= tag2_browser_right_mouse_menu
,
97 .key_press_event
= tag2_browser_key_press_event
100 gmpcPlugin tag2_plug
= {
101 .name
= N_("Tag based browser"),
102 .version
= {0, 15, 0},
103 .plugin_type
= GMPC_PLUGIN_PL_BROWSER
| GMPC_INTERNALL
,
105 .destroy
= tag2_destroy
,
106 .save_yourself
= tag2_save_myself
,
107 .get_enabled
= tag2_get_enabled
,
108 .set_enabled
= tag2_set_enabled
,
109 .browser
= &tag2_browser_plugin
,
111 .mpd_connection_changed
= tag2_connection_changed
,
112 .mpd_status_changed
= tag2_status_changed
,
115 /** Little hack to work around gmpc's limitations */
116 static GList
*tag2_ht
= NULL
;
117 /** This stucture contains all the needed data for a browser
119 typedef struct _tag_browser
122 /* The key used as ID in gmpc, and as config id and name */
124 /* The paned window that is packed into gmpc */
125 GtkWidget
*tag2_vbox
;
126 /* The hbox that is used to pack the tag browsers in to the browser window */
128 /* The GmpcMpdDataTreeView that shows the songs */
129 GtkTreeView
*tag_songlist
;
130 /* List with tag_element's (the tag browsers that are in tag_hbox) */
132 /* GtkTreeRowReference */
133 GtkTreeRowReference
*ref_iter
;
138 static void tag2_save_browser(tag_browser
* browser
);
140 /* The current visible browser, this is needed to workaround gmpc's limitation */
141 static GtkWidget
*tag2_current
= NULL
;
142 static void tag2_destroy_browser(tag_browser
* browser
, gpointer user_data
);
143 static void tag2_connection_changed_foreach(tag_browser
* browser
, gpointer data
);
144 static void tag2_init_browser(tag_browser
* browser
);
147 * Adds a browser to gmpc's category browser
149 static void tag2_browser_add_browser(GtkWidget
* cat_tree
, char *key
)
154 gchar
*group
= g_strdup_printf("tag2-plugin:%s", key
);
155 gchar
*name
= cfg_get_single_value_as_string_with_default(config
, group
, "name", "default");
156 GtkListStore
*tree
= (GtkListStore
*) gtk_tree_view_get_model(GTK_TREE_VIEW(cat_tree
));
157 gint pos
= cfg_get_single_value_as_int_with_default(config
, group
, "position", 50 + g_list_length(tag2_ht
));
159 playlist3_insert_browser(&iter
, PL3_CAT_BROWSER_LIBRARY
+pos
%PL3_CAT_BROWSER_LIBRARY
);
160 gtk_list_store_set(tree
, &iter
,
161 PL3_CAT_TYPE
, tag2_plug
.id
,
162 PL3_CAT_TITLE
, name
, PL3_CAT_INT_ID
, key
, PL3_CAT_ICON_ID
, "tag-browser", -1);
163 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(tree
), &iter
);
165 tb
= g_malloc0(sizeof(*tb
));
167 tb
->key
= g_strdup(key
);
168 /* get a reference to the key */
169 tb
->ref_iter
= gtk_tree_row_reference_new(GTK_TREE_MODEL(tree
), path
);
170 tag2_ht
= g_list_append(tag2_ht
, tb
);
171 // tag2_init_browser(tb);
172 gtk_tree_path_free(path
);
173 pl3_update_go_menu();
177 static void tag2_browser_add(GtkWidget
* cat_tree
)
179 conf_mult_obj
*cmo
, *iter
;
180 if (!tag2_get_enabled())
183 * Init hash table if it does not extists
185 cmo
= cfg_get_key_list(config
, "tag2-browsers");
186 for (iter
= cmo
; iter
; iter
= iter
->next
)
188 tag2_browser_add_browser(cat_tree
, iter
->key
);
191 cfg_free_multiple(cmo
);
195 typedef struct _tag_element
198 GmpcMpdDataTreeviewTooltip
*tool
;
202 GtkCellRenderer
*image_renderer
;
203 GtkTreeViewColumn
*column
;
204 GtkWidget
*column_label
;
209 tag_browser
*browser
;
213 unsigned int signal_id
;
216 static void tag2_changed(GtkTreeSelection
* sel2
, tag_element
* te
);
218 static gboolean
tag2_changed_delayed(gpointer data
);
222 int tag2_get_enabled(void)
224 return cfg_get_single_value_as_int_with_default(config
, "tag2-plugin", "enable", TRUE
);
227 static void tag2_set_enabled(int enabled
)
229 cfg_set_single_value_as_int(config
, "tag2-plugin", "enable", enabled
);
230 if (enabled
&& !tag2_ht
)
232 GtkTreeView
*tree
= playlist3_get_category_tree_view();
233 tag2_browser_add((GtkWidget
*) tree
);
234 } else if (!enabled
&& tag2_ht
)
241 * Destroy the browser
243 static void tag2_destroy(void)
245 /* clear all the browsers */
246 g_list_foreach(tag2_ht
, (GFunc
) tag2_destroy_browser
, NULL
);
247 g_list_free(tag2_ht
);
251 static void tag2_destroy_tag(tag_element
* te
)
253 /* Part of inconsistent update hack */
254 g_idle_remove_by_data(te
);
256 g_source_remove(te
->timeout
);
257 gtk_widget_destroy(te
->vbox
);
258 gtk_widget_destroy(GTK_WIDGET(te
->tool
));
261 g_object_unref(te
->model
);
266 * Handles right mouse press event
267 * this function fixes the behauviour of gtk to something more logic,
268 * the actually handling of the press happens in the release event
270 static gboolean
tag2_song_list_button_press_event(GtkWidget
* but
, GdkEventButton
* event
,
273 GtkTreePath
*path
= NULL
;
274 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(but
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
))
276 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(but
));
277 if (gtk_tree_selection_path_is_selected(sel
, path
))
279 if (event
->button
== 3)
281 gtk_tree_path_free(path
);
285 gtk_tree_selection_unselect_all(sel
);
286 gtk_tree_path_free(path
);
293 gtk_tree_path_free(path
);
299 * Add songs from the selected browser entry of te
301 static void tag2_browser_add_selected(GtkWidget
* item
, tag_element
* te
)
304 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
));
305 if (gtk_tree_selection_get_selected(sel
, &(te
->model
), &iter
))
309 /* now get the song content, this needs all the fields of the previous
311 mpd_database_search_start(connection
, TRUE
);
312 /* add constraints based on previous browsers */
313 list
= g_list_first(te
->browser
->tag_lists
);
316 tag_element
*te3
= list
->data
; //g_list_nth_data(te->browser->tag_lists, i);
317 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te3
->tree
));
318 if (gtk_tree_selection_get_selected(sel
, &(te3
->model
), &iter
))
321 gtk_tree_model_get(te3
->model
, &iter
, MPDDATA_MODEL_COL_SONG_TITLE
, &value
, -1);
322 mpd_database_search_add_constraint(connection
, te3
->type
, (value
) ? value
: "");
327 data
= mpd_database_search_commit(connection
);
328 /* if there is a result queue them and add them */
331 data
= misc_sort_mpddata_by_album_disc_track(data
);
332 for (; data
; data
= mpd_data_get_next(data
))
334 mpd_playlist_queue_add(connection
, data
->song
->file
);
336 mpd_playlist_queue_commit(connection
);
342 * Redirect to metadata browser
345 extern GmpcBrowsersMetadata
*browsers_metadata
;
346 static void tag2_browser_header_information(GtkWidget
* item
, tag_element
* te
)
349 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
));
350 if (gtk_tree_selection_get_selected(sel
, &(te
->model
), &iter
))
352 if (gtk_tree_selection_get_selected(sel
, &(te
->model
), &iter
))
355 gtk_tree_model_get(te
->model
, &iter
, MPDDATA_MODEL_COL_SONG_TITLE
, &value
, -1);
356 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
)
359 gmpc_browsers_metadata_set_artist(browsers_metadata
, value
);
361 } else if (te
->type
== MPD_TAG_ITEM_ALBUM
)
363 const gchar
*artist
= gmpc_mpddata_model_get_request_artist(GMPC_MPDDATA_MODEL(te
->model
));
367 gmpc_browsers_metadata_set_album(browsers_metadata
, artist
, value
);
370 info2_fill_album_view(artist,value);
382 * Replace playlist with content of selected browser
384 static void tag2_browser_replace_selected(GtkWidget
* item
, tag_element
* te
)
386 mpd_playlist_clear(connection
);
387 if (mpd_check_connected(connection
))
389 tag2_browser_add_selected(item
, te
);
390 mpd_player_play(connection
);
395 * Handles right mouse release on song list
397 static gboolean
tag2_browser_button_release_event(GtkTreeView
* tree
, GdkEventButton
* event
, tag_element
* te
)
399 /* only on right mouse click */
400 if (event
->button
== 3)
402 GtkWidget
*menu
, *item
;
403 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(tree
);
404 int count
= gtk_tree_selection_count_selected_rows(selection
);
405 /* if nothing is selected return */
409 /* create menu to popup */
410 menu
= gtk_menu_new();
411 /* add the add widget */
412 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD
, NULL
);
413 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
414 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_browser_add_selected
), te
);
415 /* add the replace widget */
416 item
= gtk_image_menu_item_new_with_label(_("Replace"));
417 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
418 gtk_image_new_from_stock(GTK_STOCK_REDO
, GTK_ICON_SIZE_MENU
));
419 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
420 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_browser_replace_selected
), te
);
422 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
423 || (te
->type
== MPD_TAG_ITEM_ALBUM
424 && gmpc_mpddata_model_get_request_artist(GMPC_MPDDATA_MODEL(te
->model
)) != NULL
))
426 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_INFO
, NULL
);
427 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
428 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_browser_header_information
), te
);
432 gtk_widget_show_all(GTK_WIDGET(menu
));
433 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 0, event
->time
);
440 static gboolean
tag2_key_entry_key_press_event(GtkWidget
* entry
, GdkEventKey
* event
, tag_element
* te
)
442 const gchar
*text
= gtk_entry_get_text(GTK_ENTRY(entry
));
444 if (strlen(text
) == 0)
446 if (event
->keyval
== GDK_KEY_BackSpace
|| event
->keyval
== GDK_KEY_Escape
)
448 gtk_widget_hide(te
->sentry
);
449 gtk_widget_grab_focus(te
->tree
);
456 static int tag2_key_release_event(GtkTreeView
* tree
, GdkEventKey
* event
, tag_element
* te
)
459 if ((event
->state
& GDK_CONTROL_MASK
) != 0 && event
->keyval
== GDK_KEY_f
)
461 gtk_widget_grab_focus(te
->sentry
);
462 gtk_widget_show(te
->sentry
);
465 else if(event->keyval == GDK_KEY_Escape){
466 gtk_entry_set_text(GTK_ENTRY(te->sentry),"");
467 gtk_widget_hide(te->sentry);
470 else if ((event
->state
& (GDK_CONTROL_MASK
| GDK_MOD1_MASK
)) == 0 &&
471 /* ((event->keyval >= GDK_space && event->keyval <= GDK_z)))*/
472 (uc
= gdk_keyval_to_unicode(event
->keyval
)))
475 data
[0] = (char)gdk_keyval_to_unicode(event
->keyval
);
477 gtk_widget_grab_focus(te
->sentry
);
478 gtk_entry_set_text(GTK_ENTRY(te
->sentry
), data
);
479 gtk_editable_set_position(GTK_EDITABLE(te
->sentry
), 1);
488 * This is a hack here, for one purpose.
489 * When mpd database changes, it could hapening that when updating a sel change on column A
490 * that during the update, the selection of column B changes, what causes another change in column A
491 * So you get recursive updates.
492 * To avoid this, we "delay" the update callbacks to after the update on column A is completely handled.
493 * TODO: This needs to be handled nicer.
495 static gboolean
tag2_changed_delayed(gpointer data
)
497 tag2_changed(NULL
, (tag_element
*) data
);
501 void tag2_changed(GtkTreeSelection
* sel2
, tag_element
* te
)
503 int nfilter
= cfg_get_single_value_as_int_with_default(config
, "tag2-plugin", "don't filter", 0);
505 MpdData
*data
= NULL
;
506 tag_browser
*browser
= te
->browser
;
507 int not_to_update
= te
->index
;
508 GList
*tel
= g_list_first(browser
->tag_lists
);
509 static int working
= 0;
512 // Only call the idle thing once.
514 g_idle_add(tag2_changed_delayed
, te
);
519 /* Clear songs list */
520 tel
= g_list_first(browser
->tag_lists
);
521 /* check if the user selected a row, if not do nothing */
523 /* Set on all tags the request artist, if available. */
525 gchar
*artist
= NULL
;
526 GList
*first_te
= NULL
;
527 for (first_te
= tel
; first_te
&& !artist
; first_te
= first_te
->next
)
529 tag_element
*te3
= first_te
->data
;
531 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te3
->tree
));
532 if (gtk_tree_selection_get_selected(sel
, &(te3
->model
), &iter
))
534 if (mpd_server_tag_supported(connection
, te3
->type
))
537 gtk_tree_model_get(te3
->model
, &iter
, MPDDATA_MODEL_COL_SONG_TITLE
, &value
, -1);
538 if (te3
->type
== MPD_TAG_ITEM_ARTIST
|| te3
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
)
540 artist
= g_strdup(value
);
547 for (first_te
= tel
; first_te
; first_te
= first_te
->next
)
549 tag_element
*te3
= first_te
->data
;
550 if (te3
->index
!= not_to_update
)
552 gmpc_mpddata_model_set_request_artist(GMPC_MPDDATA_MODEL(te3
->model
), artist
);
553 if (te3
->tool
->request_artist
)
555 g_free(te3
->tool
->request_artist
);
556 te3
->tool
->request_artist
= NULL
;
558 te3
->tool
->request_artist
= g_strdup(artist
);
566 tag_element
*te_i
= tel
->data
;
567 if (te_i
->index
!= not_to_update
&& mpd_server_tag_supported(connection
, te_i
->type
))
569 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te_i
->tree
));
571 GList
*first
= g_list_first(browser
->tag_lists
);
572 /* only do the following query if it isn't the last one */
574 /* Search for the fields of the next tag, this needs the value/type of all the previous,
575 * Parsed from left to right
577 mpd_database_search_field_start(connection
, te_i
->type
);
578 /* fil in the next */
581 tag_element
*te3
= first
->data
;
582 if (te3
->index
!= te_i
->index
)
584 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te3
->tree
));
585 if (gtk_tree_selection_get_selected(sel
, &(te3
->model
), &iter
))
587 if (mpd_server_tag_supported(connection
, te3
->type
))
590 gtk_tree_model_get(te3
->model
, &iter
, MPDDATA_MODEL_COL_SONG_TITLE
, &value
, -1);
591 if (!nfilter
|| te3
->index
< te_i
->index
)
592 mpd_database_search_add_constraint(connection
, te3
->type
, (value
) ? value
: "");
599 data
= mpd_database_search_commit(connection
);
600 /* Delete items that match */
601 if (strlen(gtk_entry_get_text(GTK_ENTRY(te_i
->sentry
))) > 0)
603 MpdData_real
*d
= (MpdData_real
*) data
;
604 /* Get the entry from the text view */
605 const char *str
= gtk_entry_get_text(GTK_ENTRY(te_i
->sentry
));
607 gchar
*sb1
= g_utf8_casefold(str
, -1);
608 /* Normalize it, this to ensure they use the same way of representing the character */
609 gchar
*sb
= g_utf8_normalize(sb1
, -1, G_NORMALIZE_ALL_COMPOSE
);
614 gchar
*sa1
= g_utf8_casefold(d
->tag
, -1);
615 /* Normalize it, this to ensure they use the same way of representing the character */
616 gchar
*sa
= g_utf8_normalize(sa1
, -1, G_NORMALIZE_ALL_COMPOSE
);
618 /* compare the utf-8 strings, this should be a fairly good compare
619 * because I made sure the utf-8 is represented in a equal way
621 if (strstr(sa
, sb
) == NULL
)
623 /* If it does not match, remove the item from the list */
624 data
= mpd_data_delete_item((MpdData
*) d
);
625 d
= (MpdData_real
*) data
;
627 /* if it does match, we go to the next and check that */
633 /* if there are items in the lest, make sure we get the first one */
635 data
= mpd_data_get_first(data
);
639 * Update the TreeModel using the special incremental replace function.
640 * This will make sure, selected rows that are matched, don't get de-selected
642 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te_i
->tree
));
645 /* get the selected row, if any */
646 if (gtk_tree_selection_get_selected(sel
, NULL
, NULL
))
648 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te_i
->model
), data
);
649 /* this make sure the selected row is centered in the middle of the treeview.
650 * Otherwise the user could have the tedious job of finding it again
653 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
655 /* get the path to the selected row */
656 GtkTreePath
*path
= gtk_tree_model_get_path(te_i
->model
, &iter
);
657 /* scroll to the path, and center it in the middle of the treeview, at the left of the column */
658 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(te_i
->tree
), path
, NULL
, TRUE
, 0.5, 0);
660 gtk_tree_path_free(path
);
664 data
= misc_sort_mpddata(data
,
665 (GCompareDataFunc
)gmpc_mpddata_model_test_sort_func
,te_i
->model
);
666 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te_i
->model
), data
);
672 tel
= g_list_first(browser
->tag_lists
);
676 GtkTreeSelection
*sel
;
679 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
));
680 if (gtk_tree_selection_get_selected(sel
, &(te
->model
), &iter
))
685 mpd_database_search_start(connection
, TRUE
);
688 gtk_tree_model_get(te
->model
, &iter
, MPDDATA_MODEL_COL_SONG_TITLE
, &value
, -1);
689 mpd_database_search_add_constraint(connection
, te
->type
, (value
) ? value
: "");
695 data
= mpd_database_search_commit(connection
);
696 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(gtk_tree_view_get_model(browser
->tag_songlist
)), data
);
700 static void tag2_destroy_browser(tag_browser
* browser
, gpointer user_data
)
710 /* remove it from the left hand view */
711 model
= gtk_tree_row_reference_get_model(browser
->ref_iter
);
712 path
= gtk_tree_row_reference_get_path(browser
->ref_iter
);
715 if (gtk_tree_model_get_iter(model
, &iter
, path
))
717 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
719 gtk_tree_path_free(path
);
721 gtk_tree_row_reference_free(browser
->ref_iter
);
724 g_free(browser
->name
);
725 g_free(browser
->key
);
726 /* free the tag browsers */
727 g_list_foreach(browser
->tag_lists
, (GFunc
) tag2_destroy_tag
, NULL
);
728 g_list_free(browser
->tag_lists
);
729 /* destroy the container */
730 if (browser
->tag2_vbox
)
732 g_object_unref(browser
->tag2_vbox
);
734 /* clear structure */
738 static void tag2_column_header_menu_item_search_clicked(GtkCheckMenuItem
* item
, tag_element
* te
)
740 gtk_widget_grab_focus(te
->sentry
);
741 gtk_widget_show(te
->sentry
);
743 static void tag2_column_header_menu_item_clicked(GtkCheckMenuItem
* item
, tag_element
* te
)
746 gpointer userdata
= g_object_get_data(G_OBJECT(item
), "tag-id");
747 te
->type
= GPOINTER_TO_INT(userdata
);
748 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
)
750 te
->tool
->mtype
= META_ARTIST_ART
;
751 } else if (te
->type
== MPD_TAG_ITEM_ALBUM
)
753 te
->tool
->mtype
= META_ALBUM_ART
;
757 // gtk_tree_view_column_set_title(te->column, _(mpdTagItemKeys[te->type]));
758 gtk_label_set_text(GTK_LABEL(te
->column_label
), _(mpdTagItemKeys
[te
->type
]));
759 /* if the first is changed, refill the first.
760 * if any other is changed, make the edited refill by triggering changed signal on the previous.
762 /* clear the list, makes changing the tree layout better. */
763 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
764 /* clear all settings */
765 if (te
->image_renderer
)
767 /* Disable fixed height mode, otherwise GTK won't propperly resize the
768 * height of the row */
769 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te
->tree
), FALSE
);
770 gtk_cell_layout_clear_attributes(GTK_CELL_LAYOUT(te
->column
), te
->image_renderer
);
771 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM
)
773 int size
= cfg_get_single_value_as_int_with_default(config
, "gmpc-mpddata-model", "icon-size", 32);
774 gtk_tree_view_column_add_attribute(te
->column
, te
->image_renderer
, "pixbuf", MPDDATA_MODEL_META_DATA
);
775 gtk_cell_renderer_set_fixed_size(te
->image_renderer
, size
, size
);
779 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU
, &width
, &height
);
780 gtk_cell_renderer_set_fixed_size(te
->image_renderer
, width
, height
);
781 gtk_tree_view_column_add_attribute(te
->column
, te
->image_renderer
, "icon-name", MPDDATA_MODEL_COL_ICON_ID
);
783 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te
->tree
), TRUE
);
786 /* index starts at 1 */
789 list
= g_list_nth(te
->browser
->tag_lists
, te
->index
- 2);
792 tag_element
*te2
= list
->data
;
793 /* update the content */
794 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te2
->tree
)), te2
);
799 list
= g_list_first(te
->browser
->tag_lists
);
802 tag_element
*te2
= list
->next
->data
;
803 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te2
->tree
)), te2
);
806 if (mpd_server_tag_supported(connection
, te
->type
))
808 mpd_database_search_field_start(connection
, te
->type
);
809 data
= mpd_database_search_commit(connection
);
810 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te
->model
), data
);
813 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
818 tag2_save_browser(te
->browser
);
819 /* Hack to make pref window update */
823 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(pref_combo
), &iter
))
825 gtk_combo_box_set_active(GTK_COMBO_BOX(pref_combo
), -1);
826 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(pref_combo
), &iter
);
831 static void tag2_column_header_clicked(GtkTreeViewColumn
* column
, tag_element
* te
)
834 GtkWidget
*menu
= gtk_menu_new();
837 GtkWidget
*item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_FIND
, NULL
);
838 g_signal_connect(G_OBJECT(item
),"activate", G_CALLBACK(tag2_column_header_menu_item_search_clicked
), te
);
839 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), GTK_WIDGET(item
));
841 for (i
= 0; i
< MPD_TAG_ITEM_ANY
; i
++)
843 if (mpd_server_tag_supported(connection
, i
))
845 GtkWidget
*item
= gtk_check_menu_item_new_with_label(_(mpdTagItemKeys
[i
]));
846 g_object_set_data(G_OBJECT(item
), "tag-id", GINT_TO_POINTER(i
));
849 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
851 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_column_header_menu_item_clicked
), te
);
852 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), GTK_WIDGET(item
));
855 gtk_widget_show_all(GTK_WIDGET(menu
));
856 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 0, gtk_get_current_event_time());
860 static gboolean
tag2_sentry_changed_real(tag_element
* te
)
862 tag_element
*te2
= g_malloc0(sizeof(*te2
));
864 te2
->browser
= te
->browser
;
865 tag2_changed(NULL
, te2
);
870 if (strlen(gtk_entry_get_text(GTK_ENTRY(te->sentry))) == 0)
872 gtk_widget_hide(te->sentry);
878 static void tag2_sentry_changed(GtkWidget
* entry
, tag_element
* te
)
880 gtk_widget_show(te
->sentry
);
882 g_source_remove(te
->timeout
);
883 te
->timeout
= g_timeout_add(250, (GSourceFunc
) tag2_sentry_changed_real
, te
);
884 // tag2_sentry_changed_real(te);
887 static void playtime_changed(GmpcMpdDataModel
* model
, gulong playtime
)
891 static void tag2_songlist_clear_selection(GtkWidget
* button
, tag_browser
* browser
)
893 GList
*iter
= g_list_first(browser
->tag_lists
);
894 for (; iter
; iter
= g_list_next(iter
))
896 tag_element
*te
= iter
->data
;
897 g_signal_handlers_block_by_func(G_OBJECT(te
->sentry
), tag2_sentry_changed
, te
);
898 g_signal_handlers_block_by_func(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
))), tag2_changed
,
900 gtk_entry_set_text(GTK_ENTRY(te
->sentry
), "");
901 gtk_widget_hide(te
->sentry
);
903 for (iter
= g_list_first(browser
->tag_lists
); iter
; iter
= g_list_next(iter
))
906 tag_element
*te
= iter
->data
;
908 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
909 if (mpd_server_tag_supported(connection
, te
->type
))
911 mpd_database_search_field_start(connection
, te
->type
);
913 data
= mpd_database_search_commit(connection
);
914 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te
->model
), data
);
916 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
917 gmpc_mpddata_model_set_request_artist(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
918 if (te
->tool
->request_artist
)
920 g_free(te
->tool
->request_artist
);
921 te
->tool
->request_artist
= NULL
;
925 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(gtk_tree_view_get_model(browser
->tag_songlist
)), NULL
);
927 for (iter
= g_list_first(browser
->tag_lists
); iter
; iter
= g_list_next(iter
))
929 tag_element
*te
= iter
->data
;
930 g_signal_handlers_unblock_by_func(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
))), tag2_changed
,
932 g_signal_handlers_unblock_by_func(G_OBJECT(te
->sentry
), tag2_sentry_changed
, te
);
935 static void tag_browser_clear_search_entry(GtkEntry
* entry
, GtkEntryIconPosition icon_pos
, GdkEvent
* event
,
938 if (icon_pos
== GTK_ENTRY_ICON_SECONDARY
)
940 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry
))) == 0)
942 gtk_widget_hide(GTK_WIDGET(entry
));
946 gtk_entry_set_text(GTK_ENTRY(entry
), "");
952 static void tag2_songlist_add_tag(tag_browser
* browser
, const gchar
* name
, int type
)
954 GtkCellRenderer
*renderer
;
955 GtkTreeViewColumn
*column
;
957 tag_element
*te
= g_malloc0(sizeof(*te
));
959 browser
->tag_lists
= g_list_append(browser
->tag_lists
, te
);
962 te
->index
= g_list_length(browser
->tag_lists
);
963 te
->model
= (GtkTreeModel
*) gmpc_mpddata_model_new();
965 te
->sentry
= gtk_entry_new();
966 gtk_entry_set_icon_from_stock(GTK_ENTRY(te
->sentry
), GTK_ENTRY_ICON_SECONDARY
, GTK_STOCK_CLEAR
);
967 g_signal_connect(GTK_ENTRY(te
->sentry
), "icon-press", G_CALLBACK(tag_browser_clear_search_entry
), NULL
);
969 gtk_entry_set_icon_from_stock(GTK_ENTRY(te
->sentry
), GTK_ENTRY_ICON_PRIMARY
, GTK_STOCK_FIND
);
970 gtk_entry_set_icon_activatable(GTK_ENTRY(te
->sentry
), GTK_ENTRY_ICON_PRIMARY
, FALSE
);
971 g_signal_connect_swapped(G_OBJECT(te
->sentry
), "activate", G_CALLBACK(tag2_sentry_changed_real
), te
);
972 te
->sw
= gtk_scrolled_window_new(NULL
, NULL
);
973 te
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
974 te
->tree
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(te
->model
));
975 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
)),
976 GTK_SELECTION_SINGLE
);
977 te
->tool
= gmpc_mpd_data_treeview_tooltip_new(GTK_TREE_VIEW(te
->tree
), 0);
978 te
->browser
= browser
;
980 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(te
->tree
), TRUE
);
981 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
)
983 te
->tool
->mtype
= META_ARTIST_ART
;
984 } else if (te
->type
== MPD_TAG_ITEM_ALBUM
)
986 te
->tool
->mtype
= META_ALBUM_ART
;
991 gtk_widget_set_no_show_all(te
->sentry
, TRUE
);
992 gtk_widget_hide(te
->sentry
);
995 g_signal_connect(G_OBJECT(te
->sentry
), "changed", G_CALLBACK(tag2_sentry_changed
), te
);
996 g_signal_connect(G_OBJECT(te
->sentry
), "key-press-event", G_CALLBACK(tag2_key_entry_key_press_event
), te
);
998 gtk_box_pack_start(GTK_BOX(te
->vbox
), te
->sentry
, FALSE
, TRUE
, 0);
1000 g_signal_connect(G_OBJECT(te
->tree
), "key-press-event", G_CALLBACK(tag2_key_release_event
), te
);
1002 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(te
->sw
), GTK_SHADOW_ETCHED_IN
);
1003 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(te
->sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1005 gtk_container_add(GTK_CONTAINER(te
->sw
), te
->tree
);
1006 gtk_box_pack_start(GTK_BOX(te
->vbox
), te
->sw
, TRUE
, TRUE
, 0);
1007 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(te
->tree
), FALSE
);
1009 /* Add the column, and set it up */
1010 te
->column
= column
= gtk_tree_view_column_new();
1013 /* Set search icon next to the label */
1014 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1015 gtk_box_pack_start(GTK_BOX(hbox
),
1016 gtk_image_new_from_stock(GTK_STOCK_FIND
, GTK_ICON_SIZE_MENU
),
1019 te
->column_label
= gtk_label_new(name
);
1020 gtk_box_pack_start(GTK_BOX(hbox
),
1023 gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(te
->column
), hbox
);
1024 gtk_widget_show_all(hbox
);
1026 g_signal_connect(G_OBJECT(te
->column
), "clicked", G_CALLBACK(tag2_column_header_clicked
), te
);
1027 gtk_tree_view_column_set_clickable(te
->column
, TRUE
);
1028 te
->image_renderer
= NULL
;
1029 if (cfg_get_single_value_as_int_with_default(config
, "tag2-plugin", "show-image-column", 1))
1031 te
->image_renderer
= renderer
= gtk_cell_renderer_pixbuf_new();
1033 gtk_tree_view_column_pack_start(column
, renderer
, FALSE
);
1034 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM
)
1036 int size
= cfg_get_single_value_as_int_with_default(config
, "gmpc-mpddata-model", "icon-size", 32);
1038 gtk_tree_view_column_add_attribute(column
, renderer
, "pixbuf", MPDDATA_MODEL_META_DATA
);
1039 gtk_cell_renderer_set_fixed_size(renderer
, size
, size
);
1043 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU
, &width
, &height
);
1044 gtk_cell_renderer_set_fixed_size(te
->image_renderer
, width
, height
);
1045 gtk_tree_view_column_add_attribute(column
, renderer
, "icon-name", MPDDATA_MODEL_COL_ICON_ID
);
1049 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1050 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te
->tree
), TRUE
);
1051 renderer
= gtk_cell_renderer_text_new();
1052 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
1053 gtk_tree_view_column_add_attribute(column
, renderer
, "text", MPDDATA_MODEL_COL_SONG_TITLE
);
1054 gtk_tree_view_insert_column(GTK_TREE_VIEW(te
->tree
), column
, -1);
1055 /* set the search column */
1056 gtk_tree_view_set_search_column(GTK_TREE_VIEW(te
->tree
), MPDDATA_MODEL_COL_SONG_TITLE
);
1058 gtk_box_pack_start(GTK_BOX(browser
->tag_hbox
), te
->vbox
, TRUE
, TRUE
, 0);
1059 g_signal_connect(G_OBJECT(te
->tree
), "button-press-event", G_CALLBACK(tag2_song_list_button_press_event
), te
);
1060 g_signal_connect(G_OBJECT(te
->tree
), "button-release-event", G_CALLBACK(tag2_browser_button_release_event
), te
);
1063 te
->signal_id
= g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
))),
1064 "changed", G_CALLBACK(tag2_changed
), te
);
1068 static void tag2_create_tags(tag_browser
* browser
)
1071 cfg_get_single_value_as_string_with_default(config
, "tag2-browsers", browser
->key
, "genre|artist|album");
1074 char **strv
= g_strsplit(str
, "|", 0);
1076 for (i
= 0; strv
&& strv
[i
]; i
++)
1078 int tag
= mpd_misc_get_tag_by_name(strv
[i
]);
1081 /* This needs to be done based on config */
1082 tag2_songlist_add_tag(browser
, strv
[i
], tag
);
1088 gtk_widget_show_all(browser
->tag2_vbox
);
1091 static void tag2_init(void)
1095 * if first time used, add 2 example browsers
1097 if (cfg_get_single_value_as_int_with_default(config
, "tag2-plugin", "first-use", 1))
1099 cfg_set_single_value_as_int(config
, "tag2-plugin", "first-use", 0);
1101 cfg_set_single_value_as_string(config
, "tag2-browsers", "Artist Browser", "artist|album|disc");
1102 cfg_set_single_value_as_string(config
, "tag2-plugin:Artist Browser", "name", "Artist Browser");
1103 cfg_set_single_value_as_string(config
, "tag2-browsers", "Genre Browser", "genre|artist|album|disc");
1104 cfg_set_single_value_as_string(config
, "tag2-plugin:Genre Browser", "name", "Genre Browser");
1107 tags_store
= (GtkTreeModel
*) gtk_list_store_new(3, G_TYPE_STRING
, G_TYPE_INT
, G_TYPE_STRING
);
1108 for (i
= 0; i
< MPD_TAG_ITEM_ANY
; i
++)
1111 gtk_list_store_append(GTK_LIST_STORE(tags_store
), &titer
);
1112 gtk_list_store_set(GTK_LIST_STORE(tags_store
), &titer
, 0, mpdTagItemKeys
[i
], 1, i
, 2, _(mpdTagItemKeys
[i
]), -1);
1116 static void tag2_set_orientation(tag_browser
*browser
)
1118 if(cfg_get_single_value_as_int_with_default(config
,"tag2-plugin", "orientation", 0)) {
1119 gtk_orientable_set_orientation(GTK_ORIENTABLE(browser
->tag2_vbox
),
1120 GTK_ORIENTATION_VERTICAL
);
1121 gtk_orientable_set_orientation(GTK_ORIENTABLE(browser
->tag_hbox
),
1122 GTK_ORIENTATION_HORIZONTAL
);
1124 gtk_orientable_set_orientation(GTK_ORIENTABLE(browser
->tag2_vbox
),
1125 GTK_ORIENTATION_HORIZONTAL
);
1126 gtk_orientable_set_orientation(GTK_ORIENTABLE(browser
->tag_hbox
),
1127 GTK_ORIENTATION_VERTICAL
);
1130 static void tag2_init_browser(tag_browser
* browser
)
1134 GmpcMpdDataModel
*model
= NULL
;
1135 /* create the pane that separates the song list from the browsers */
1136 key
= g_strdup_printf("tag2-plugin:%s", browser
->key
);
1137 browser
->tag2_vbox
= gtk_hpaned_new();
1138 gmpc_paned_size_group_add_paned(GMPC_PANED_SIZE_GROUP(paned_size_group
), GTK_PANED(browser
->tag2_vbox
));
1140 /* box with tag treeviews (browsers) */
1141 browser
->tag_hbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
1143 /* Add this to the 1st pane */
1144 gtk_paned_add1(GTK_PANED(browser
->tag2_vbox
), browser
->tag_hbox
);
1146 /** Create Songs list view */
1147 /* create the treeview model, this is a GmpcMpdData model */
1148 model
= gmpc_mpddata_model_new();
1149 gmpc_mpddata_model_disable_image(GMPC_MPDDATA_MODEL(model
));
1150 g_signal_connect(G_OBJECT(model
), "playtime_changed", G_CALLBACK(playtime_changed
), NULL
);
1151 /* create scrolled window to make the treeview scrollable */
1152 sw
= gtk_scrolled_window_new(NULL
, NULL
);
1153 /* setup the scrolled window */
1154 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_ETCHED_IN
);
1155 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1156 /* create the actual treeview, use the GmpcTreeview type, so all the handling is done automatic */
1158 browser
->tag_songlist
= (GtkTreeView
*) gmpc_data_view_new(key
, FALSE
);
1159 gtk_tree_view_set_model(GTK_TREE_VIEW(browser
->tag_songlist
), model
);
1161 /* add the treeview to the scrolled window */
1162 gtk_container_add(GTK_CONTAINER(sw
), GTK_WIDGET(browser
->tag_songlist
));
1163 /* add the scrolled window to the 2nd pane */
1164 gtk_paned_add2(GTK_PANED(browser
->tag2_vbox
), sw
);
1165 /* Create an extra reference to the paned window containing everything, this way
1166 * I can add/remove it from gmpc's container withouth it being destroyed by gtk
1168 g_object_ref_sink(browser
->tag2_vbox
);
1169 /* show everything */
1170 gtk_widget_show_all(browser
->tag2_vbox
);
1173 tag2_set_orientation(browser
);
1175 tag2_connection_changed_foreach(browser
, NULL
);
1178 static gboolean
tag2_custom_find(tag_browser
* a
, gchar
* key
)
1180 return strcmp(a
->key
, key
);
1183 static void tag2_browser_selected(GtkWidget
* container
)
1185 GtkTreeView
*tree
= playlist3_get_category_tree_view();
1186 GtkTreeModel
*model
= gtk_tree_view_get_model(tree
);
1187 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(tree
);
1189 if (gtk_tree_selection_get_selected(sel
, &model
, &iter
))
1192 gtk_tree_model_get(model
, &iter
, PL3_CAT_INT_ID
, &key
, -1);
1195 GList
*node
= g_list_find_custom(tag2_ht
, key
, (GCompareFunc
) tag2_custom_find
);
1198 tag_browser
*tb
= node
->data
;
1202 if (tb
->tag2_vbox
== NULL
)
1203 tag2_init_browser(tb
);
1204 gtk_container_add(GTK_CONTAINER(container
), tb
->tag2_vbox
);
1205 gtk_widget_show_all(container
);
1206 tag2_current
= tb
->tag2_vbox
;
1215 static void tag2_browser_unselected(GtkWidget
* container
)
1218 gtk_container_remove(GTK_CONTAINER(container
), tag2_current
);
1219 tag2_current
= NULL
;
1222 static void tag2_save_browser(tag_browser
* browser
)
1224 GString
*str
= g_string_new("");
1225 GList
*list
= browser
->tag_lists
;
1226 for (; list
; list
= g_list_next(list
))
1228 tag_element
*te
= list
->data
;
1229 str
= g_string_append(str
, mpdTagItemKeys
[te
->type
]);
1231 str
= g_string_append(str
, "|");
1234 cfg_set_single_value_as_string(config
, "tag2-browsers", browser
->key
, str
->str
);
1235 g_string_free(str
, TRUE
);
1238 static void tag2_connection_changed_foreach(tag_browser
* browser
, gpointer userdata
)
1240 if (browser
->tag2_vbox
)
1242 tag_element
*te
= NULL
;
1243 if (browser
->tag_lists
== NULL
)
1244 tag2_create_tags(browser
);
1245 te
= g_list_nth_data(browser
->tag_lists
, 0);
1246 if (te
!= NULL
&& mpd_check_connected(connection
))
1249 tag_element
*te2
= NULL
;
1250 if (mpd_server_tag_supported(connection
, te
->type
))
1252 mpd_database_search_field_start(connection
, te
->type
);
1253 data
= mpd_database_search_commit(connection
);
1254 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te
->model
), data
);
1257 gmpc_mpddata_model_set_mpd_data_slow(GMPC_MPDDATA_MODEL(te
->model
), NULL
);
1260 te2
= g_malloc0(sizeof(*te2
));
1262 te2
->browser
= te
->browser
;
1263 tag2_changed(NULL
, te2
);
1267 tag2_changed(gtk_tree_view_get_selection(GTK_TREE_VIEW(te
->tree
)), te
);
1272 static void tag2_connection_changed(MpdObj
* mi
, int connect
, gpointer data
)
1274 if (connect
&& tag2_ht
)
1277 g_list_foreach(tag2_ht
, (GFunc
) tag2_connection_changed_foreach
, NULL
);
1281 static void tag2_status_changed(MpdObj
* mi
, ChangedStatusType what
, gpointer data
)
1283 if (what
& MPD_CST_DATABASE
)
1287 g_list_foreach(tag2_ht
, (GFunc
) tag2_connection_changed_foreach
, NULL
);
1288 /* GList *list = g_list_first(tag2_ht);
1289 for(;list;list = g_list_next(list))
1291 tag2_songlist_clear_selection(NULL, list->data);
1299 * Preferences window
1301 void tag2_pref_destroy(GtkWidget
* container
)
1303 gtk_container_remove(GTK_CONTAINER(container
), pref_vbox
);
1310 * Browser selector changed
1312 static void tag2_pref_combo_changed(GtkComboBox
* box
, GtkTreeModel
* model2
)
1316 /* clear old data */
1317 gtk_list_store_clear(GTK_LIST_STORE(model2
));
1319 if (gtk_combo_box_get_active_iter(box
, &iter
))
1321 GtkTreeModel
*model
= gtk_combo_box_get_model(box
);
1325 /* get the active tag browser */
1326 gtk_tree_model_get(model
, &iter
, 2, &tb
, -1);
1327 if (tb
->tag2_vbox
== NULL
)
1328 tag2_init_browser(tb
);
1329 /* iterate over all the tag elements and add them to the list */
1332 GList
*liter
= g_list_first(tb
->tag_lists
);
1333 for (; liter
; liter
= g_list_next(liter
))
1336 tag_element
*te
= liter
->data
;
1337 gtk_list_store_append(GTK_LIST_STORE(model2
), &titer
);
1338 gtk_list_store_set(GTK_LIST_STORE(model2
), &titer
, 0, mpdTagItemKeys
[te
->type
], 1, te
->type
, 2, te
, -1);
1342 /* Get browser name and set entry */
1343 group
= g_strdup_printf("tag2-plugin:%s", tb
->key
);
1344 str
= cfg_get_single_value_as_string_with_default(config
, group
, "name", "default");
1345 gtk_entry_set_text(GTK_ENTRY(pref_entry
), str
);
1350 /* only clear the text when nothing is selected, else the currently selected is cleared..
1351 * yeah, longlive signals
1353 gtk_entry_set_text(GTK_ENTRY(pref_entry
), "");
1357 static void tag2_pref_add_browser_clicked(GtkWidget
* but
, GtkComboBox
* combo
)
1359 GtkTreeView
*tree
= playlist3_get_category_tree_view();
1362 GtkListStore
*model
= (GtkListStore
*) gtk_combo_box_get_model(combo
);
1363 gchar
*name
= g_strdup_printf("%u", g_random_int());
1364 cfg_set_single_value_as_string(config
, "tag2-browsers", name
, "");
1366 tag2_browser_add_browser(GTK_WIDGET(tree
), name
);
1368 node
= g_list_find_custom(tag2_ht
, name
, (GCompareFunc
) tag2_custom_find
);
1371 tag_browser
*tb
= node
->data
;
1372 gtk_list_store_append(model
, &titer
);
1373 gtk_list_store_set(model
, &titer
, 0, name
, 1, "default", 2, tb
, -1);
1374 gtk_combo_box_set_active_iter(combo
, &titer
);
1375 /* change this to store TB in list store) */
1382 * Update the title of the browser
1384 static void tag2_pref_entry_changed(GtkWidget
* entry
, GtkComboBox
* combo
)
1387 GtkListStore
*model
= (GtkListStore
*) gtk_combo_box_get_model(combo
);
1388 if (gtk_combo_box_get_active_iter(combo
, &iter
))
1393 const gchar
*name
= gtk_entry_get_text(GTK_ENTRY(pref_entry
));
1394 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, 0, &key
, 2, &tb
, -1);
1395 gtk_list_store_set(model
, &iter
, 1, name
, -1);
1398 tb
->name
= g_strdup(name
);
1399 if ((path
= gtk_tree_row_reference_get_path(tb
->ref_iter
)))
1402 GtkTreeModel
*model2
= gtk_tree_row_reference_get_model(tb
->ref_iter
);
1403 gtk_tree_model_get_iter(model2
, &iter2
, path
);
1404 gtk_list_store_set(GTK_LIST_STORE(model2
), &iter2
, PL3_CAT_TITLE
, name
, -1);
1405 gtk_tree_path_free(path
);
1408 group
= g_strdup_printf("tag2-plugin:%s", key
);
1409 cfg_set_single_value_as_string(config
, group
, "name", (char *)name
);
1415 * Handles editing of the columns type
1417 static void tag2_pref_column_type_edited(GtkCellRendererText
* text
, gchar
* path
, char *new_data
, GtkTreeModel
* model
)
1420 if (gtk_tree_model_get_iter_from_string(model
, &iter
, path
))
1422 int tag
= mpd_misc_get_tag_by_name(new_data
);
1424 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, 0, new_data
, 1, tag
, -1);
1425 gtk_tree_model_get(model
, &iter
, 2, &te
, -1);
1428 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
)
1430 te
->tool
->mtype
= META_ARTIST_ART
;
1431 } else if (te
->type
== MPD_TAG_ITEM_ALBUM
)
1433 te
->tool
->mtype
= META_ALBUM_ART
;
1435 te
->tool
->mtype
= 0;
1436 /* clear all settings */
1437 if (te
->image_renderer
)
1439 /* Disable fixed height mode, otherwise GTK won't propperly resize the
1440 * height of the row */
1441 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te
->tree
), FALSE
);
1442 gtk_cell_layout_clear_attributes(GTK_CELL_LAYOUT(te
->column
), te
->image_renderer
);
1443 if (te
->type
== MPD_TAG_ITEM_ARTIST
|| te
->type
== MPD_TAG_ITEM_ALBUM_ARTIST
1444 || te
->type
== MPD_TAG_ITEM_ALBUM
)
1446 int size
= cfg_get_single_value_as_int_with_default(config
, "gmpc-mpddata-model", "icon-size", 32);
1447 gtk_tree_view_column_add_attribute(te
->column
, te
->image_renderer
, "pixbuf", MPDDATA_MODEL_META_DATA
);
1448 gtk_cell_renderer_set_fixed_size(te
->image_renderer
, size
, size
);
1452 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU
, &width
, &height
);
1453 gtk_cell_renderer_set_fixed_size(te
->image_renderer
, width
, height
);
1454 gtk_tree_view_column_add_attribute(te
->column
, te
->image_renderer
, "icon-name",
1455 MPDDATA_MODEL_COL_ICON_ID
);
1457 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(te
->tree
), TRUE
);
1460 gtk_label_set_text(GTK_LABEL(te
->column_label
), _(mpdTagItemKeys
[te
->type
]));
1462 //gtk_tree_view_column_set_title(te->column, _(mpdTagItemKeys[te->type]));
1463 tag2_songlist_clear_selection(NULL
, te
->browser
);
1464 tag2_save_browser(te
->browser
);
1469 * Add's a column to the selected tag browser
1471 static void tag2_pref_browser_remove(GtkWidget
* but
, GtkComboBox
* box
)
1474 if (gtk_combo_box_get_active_iter(box
, &iter
))
1476 GtkTreeModel
*model
= gtk_combo_box_get_model(box
);
1480 gtk_tree_model_get(model
, &iter
, 2, &tb
, -1);
1481 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
1483 key
= g_strdup(tb
->key
);
1484 /* remove from browser list */
1485 tag2_ht
= g_list_remove(tag2_ht
, tb
);
1486 /* destroy remaining */
1487 tag2_destroy_browser(tb
, NULL
);
1490 cfg_del_single_value(config
, "tag2-browsers", key
);
1492 name
= g_strdup_printf("tag2-plugin:%s", key
);
1493 cfg_remove_class(config
, name
);
1495 name
= g_strdup_printf("tag2-plugin:%s-colsize", key
);
1496 cfg_remove_class(config
, name
);
1498 name
= g_strdup_printf("tag2-plugin:%s-colpos", key
);
1499 cfg_remove_class(config
, name
);
1501 name
= g_strdup_printf("tag2-plugin:%s-colshow", key
);
1502 cfg_remove_class(config
, name
);
1505 pl3_update_go_menu();
1510 * Add's a column to the selected tag browser
1512 static void tag2_pref_column_add(GtkWidget
* but
, GtkComboBox
* box
)
1515 GtkTreeModel
*model2
= g_object_get_data(G_OBJECT(but
), "model");
1516 if (gtk_combo_box_get_active_iter(box
, &iter
))
1518 GtkTreeModel
*model
= gtk_combo_box_get_model(box
);
1520 /* TODO: Change to the browser tag editing stuff */
1521 gtk_tree_model_get(model
, &iter
, 2, &tb
, -1);
1522 tag2_songlist_add_tag(tb
, mpdTagItemKeys
[MPD_TAG_ITEM_ARTIST
], MPD_TAG_ITEM_ARTIST
);
1523 tag2_pref_combo_changed(box
, model2
);
1524 gtk_widget_show_all(tb
->tag_hbox
);
1525 tag2_save_browser(tb
);
1526 /* just reset the whole thing */
1527 tag2_songlist_clear_selection(NULL
, tb
);
1532 * Removes the selected column from the tag browser
1534 static void tag2_pref_column_remove(GtkWidget
* but
, GtkComboBox
* box
)
1537 GtkTreeView
*tree
= g_object_get_data(G_OBJECT(but
), "tree");
1538 if (gtk_combo_box_get_active_iter(box
, &iter
))
1540 GtkTreeModel
*model
= gtk_combo_box_get_model(box
);
1541 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(tree
);
1543 /* get the selected tag browser */
1544 gtk_tree_model_get(model
, &iter
, 2, &tb
, -1);
1545 model
= gtk_tree_view_get_model(tree
);
1547 /* Get the selected column */
1548 if (gtk_tree_selection_get_selected(sel
, &model
, &iter
))
1553 /* get tag element */
1554 gtk_tree_model_get(model
, &iter
, 2, &te
, -1);
1555 /* remove from pref editor */
1556 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
1557 /* remove from browsers list */
1558 tb
->tag_lists
= g_list_remove(tb
->tag_lists
, te
);
1561 * renumber, this is needed to keep the filling consistent
1562 * Beware, the numbering starts at 1. not 0. (yes shoot me for this)
1565 for (giter
= g_list_first(tb
->tag_lists
); giter
; giter
= g_list_next(giter
))
1567 tag_element
*te2
= giter
->data
;
1572 /* destroy from interface */
1573 tag2_destroy_tag(te
);
1575 * Refill the complete browser.
1576 * TODO: Only refill the part that is needed
1579 giter = g_list_first(te->browser->tag_lists);
1583 tag_element *te2 = giter->data;
1585 /* update the content *//*
1586 mpd_database_search_field_start(connection, te2->type);
1587 data = mpd_database_search_commit(connection);
1588 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(te2->model), data);
1592 tag2_connection_changed_foreach(te
->browser
, NULL
);
1594 /* save the browsers content */
1595 tag2_save_browser(tb
);
1600 void tag2_browser_update_orientation()
1602 g_list_foreach(tag2_ht
, (GFunc
)tag2_set_orientation
, NULL
);
1605 void tag2_pref_construct(GtkWidget
* container
)
1607 GtkWidget
*sw
, *tree
, *but
;
1608 GtkCellRenderer
*renderer
;
1609 GtkTreeModel
*model
;
1610 GtkWidget
*hbox
, *label
, *vbox
;
1611 GtkWidget
*combo
= NULL
;
1612 conf_mult_obj
*cmo
, *iter
;
1614 * Create the parent widget where the preferences window is packed in
1616 pref_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
1618 /* select browser to edit */
1619 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1620 model
= (GtkTreeModel
*) gtk_list_store_new(3, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
);
1621 pref_combo
= combo
= gtk_combo_box_new_with_model(model
);
1622 renderer
= gtk_cell_renderer_text_new();
1623 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), renderer
, TRUE
);
1624 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo
), renderer
, "text", 1, NULL
);
1626 gtk_box_pack_start(GTK_BOX(hbox
), combo
, TRUE
, TRUE
, 0);
1628 but
= gtk_button_new_from_stock(GTK_STOCK_ADD
);
1629 gtk_box_pack_start(GTK_BOX(hbox
), but
, FALSE
, TRUE
, 0);
1630 g_signal_connect(G_OBJECT(but
), "clicked", G_CALLBACK(tag2_pref_add_browser_clicked
), combo
);
1631 but
= gtk_button_new_from_stock(GTK_STOCK_REMOVE
);
1632 g_signal_connect(G_OBJECT(but
), "clicked", G_CALLBACK(tag2_pref_browser_remove
), combo
);
1633 gtk_box_pack_start(GTK_BOX(hbox
), but
, FALSE
, TRUE
, 0);
1634 gtk_box_pack_start(GTK_BOX(pref_vbox
), hbox
, FALSE
, TRUE
, 0);
1636 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1637 label
= gtk_label_new("Name:");
1638 gtk_misc_set_alignment(GTK_MISC(label
), 1, 0.5);
1639 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, TRUE
, 0);
1641 pref_entry
= gtk_entry_new();
1642 g_signal_connect(GTK_ENTRY(pref_entry
), "changed", G_CALLBACK(tag2_pref_entry_changed
), combo
);
1643 gtk_box_pack_start(GTK_BOX(hbox
), pref_entry
, TRUE
, TRUE
, 0);
1645 gtk_box_pack_start(GTK_BOX(pref_vbox
), hbox
, FALSE
, TRUE
, 0);
1647 /* change this to store TB in list store) */
1648 cmo
= cfg_get_key_list(config
, "tag2-browsers");
1649 for (iter
= cmo
; iter
; iter
= iter
->next
)
1652 gchar
*group
= g_strdup_printf("tag2-plugin:%s", iter
->key
);
1653 gchar
*name
= cfg_get_single_value_as_string_with_default(config
, group
, "name", "default");
1654 GList
*node
= g_list_find_custom(tag2_ht
, iter
->key
, (GCompareFunc
) tag2_custom_find
);
1657 tag_browser
*tb
= node
->data
;
1659 gtk_list_store_append(GTK_LIST_STORE(model
), &titer
);
1660 gtk_list_store_set(GTK_LIST_STORE(model
), &titer
, 0, iter
->key
, 1, name
, 2, tb
, -1);
1668 cfg_free_multiple(cmo
);
1670 /* scrolled window used to pack the treeview */
1672 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1673 sw
= gtk_scrolled_window_new(NULL
, NULL
);
1674 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1675 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_ETCHED_IN
);
1677 tree
= gtk_tree_view_new();
1678 /* the model for the treeview */
1679 model
= (GtkTreeModel
*) gtk_list_store_new(3, G_TYPE_STRING
, G_TYPE_INT
, G_TYPE_POINTER
);
1680 gtk_tree_view_set_model(GTK_TREE_VIEW(tree
), model
);
1682 renderer
= gtk_cell_renderer_combo_new();
1683 g_object_set(G_OBJECT(renderer
), "editable", TRUE
, NULL
);
1684 g_object_set(G_OBJECT(renderer
), "model", tags_store
, "text-column", 0, "has-entry", FALSE
, NULL
);
1685 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree
), -1, "text", renderer
, "text", 0, NULL
);
1686 g_signal_connect(G_OBJECT(renderer
), "edited", G_CALLBACK(tag2_pref_column_type_edited
), model
);
1688 g_signal_connect(G_OBJECT(combo
), "changed", G_CALLBACK(tag2_pref_combo_changed
), model
);
1689 /* tree to scrolled window */
1690 gtk_container_add(GTK_CONTAINER(sw
), tree
);
1693 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
1695 but
= gtk_button_new_from_stock(GTK_STOCK_ADD
);
1696 gtk_box_pack_start(GTK_BOX(vbox
), but
, FALSE
, TRUE
, 0);
1697 g_object_set_data(G_OBJECT(but
), "model", model
);
1698 g_signal_connect(G_OBJECT(but
), "clicked", G_CALLBACK(tag2_pref_column_add
), combo
);
1699 but
= gtk_button_new_from_stock(GTK_STOCK_REMOVE
);
1700 g_object_set_data(G_OBJECT(but
), "tree", tree
);
1701 g_signal_connect(G_OBJECT(but
), "clicked", G_CALLBACK(tag2_pref_column_remove
), combo
);
1703 gtk_box_pack_start(GTK_BOX(vbox
), but
, FALSE
, TRUE
, 0);
1705 gtk_box_pack_start(GTK_BOX(hbox
), sw
, TRUE
, TRUE
, 0);
1706 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, FALSE
, TRUE
, 0);
1707 gtk_box_pack_start(GTK_BOX(pref_vbox
), hbox
, TRUE
, TRUE
, 0);
1712 gtk_container_add(GTK_CONTAINER(container
), pref_vbox
);
1713 gtk_widget_show_all(container
);
1716 static void tag2_browser_activate(GtkWidget
* item
, tag_browser
* browser
)
1718 GtkTreeSelection
*selec
= gtk_tree_view_get_selection((GtkTreeView
*) playlist3_get_category_tree_view());
1720 GtkTreePath
*path
= gtk_tree_row_reference_get_path(browser
->ref_iter
);
1723 gtk_tree_selection_select_path(selec
, path
);
1724 gtk_tree_path_free(path
);
1729 static void tag2_browser_add_go_menu_foreach(tag_browser
* browser
, GtkWidget
* menu
)
1731 GtkWidget
*item
= gtk_image_menu_item_new_with_label(browser
->name
);
1732 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
1733 gtk_image_new_from_icon_name("media-tag", GTK_ICON_SIZE_MENU
));
1734 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1736 gtk_widget_add_accelerator(GTK_WIDGET(item
), "activate", gtk_menu_get_accel_group(GTK_MENU(menu
)), GDK_KEY_F1
+ counter
,
1737 GDK_SHIFT_MASK
, GTK_ACCEL_VISIBLE
);
1739 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_browser_activate
), browser
);
1742 static int tag2_browser_add_go_menu(GtkWidget
* menu
)
1744 if (tag2_get_enabled() == FALSE
)
1749 g_list_foreach(tag2_ht
, (GFunc
) tag2_browser_add_go_menu_foreach
, menu
);
1756 static void tag2_save_myself(void)
1760 GList
*iter
= g_list_first(tag2_ht
);
1763 tag_browser
*tb
= iter
->data
;
1764 GtkTreePath
*path
= gtk_tree_row_reference_get_path(tb
->ref_iter
);
1767 gint
*indices
= gtk_tree_path_get_indices(path
);
1768 gchar
*group
= g_strdup_printf("tag2-plugin:%s", tb
->key
);
1769 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Saving myself to position: %i\n", indices
[0]);
1770 cfg_set_single_value_as_int(config
, group
, "position", indices
[0]);
1771 gtk_tree_path_free(path
);
1774 iter
= g_list_next(iter
);
1779 static int tag2_browser_right_mouse_menu(GtkWidget
* menu
, int type
, GtkWidget
* tree
, GdkEventButton
* event
)
1782 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
1783 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tree
));
1785 if (type
!= tag2_plug
.id
)
1787 if (gtk_tree_selection_get_selected(sel
, &model
, &iter
))
1791 gtk_tree_model_get(model
, &iter
, PL3_CAT_INT_ID
, &key
, -1);
1794 GList
*node
= g_list_find_custom(tag2_ht
, key
, (GCompareFunc
) tag2_custom_find
);
1797 tag_browser
*tb
= node
->data
;
1800 GtkWidget
*item
= gtk_image_menu_item_new_with_label(_("Reset browser"));
1801 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
1802 gtk_image_new_from_stock(GTK_STOCK_CLEAR
, GTK_ICON_SIZE_MENU
));
1803 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1804 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(tag2_songlist_clear_selection
), tb
);
1815 static int tag2_browser_key_press_event(GtkWidget
* mw
, GdkEventKey
* event
, int type
)
1817 if (type
!= tag2_plug
.id
)
1819 if (event
->keyval
== GDK_KEY_r
&& event
->state
& GDK_MOD1_MASK
)
1821 GtkTreeView
*tree
= playlist3_get_category_tree_view();
1822 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
1823 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tree
));
1825 if (type
!= tag2_plug
.id
)
1827 if (gtk_tree_selection_get_selected(sel
, &model
, &iter
))
1831 gtk_tree_model_get(model
, &iter
, PL3_CAT_INT_ID
, &key
, -1);
1834 GList
*node
= g_list_find_custom(tag2_ht
, key
, (GCompareFunc
) tag2_custom_find
);
1837 tag_browser
*tb
= node
->data
;
1840 tag2_songlist_clear_selection(NULL
, tb
);
1853 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120*/