1 /* gmpc-autoplaylist (GMPC plugin)
2 * Copyright (C) 2006-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://gmpcwiki.sarine.nl/
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 #include <glade/glade.h>
23 #include <libmpd/libmpd.h>
24 #include <libmpd/debug_printf.h>
25 #include <gmpc/plugin.h>
27 GladeXML
*apl_xml
= NULL
;
28 int e_util_utf8_strstrcase (const gchar
*haystack
, const gchar
*needle
);
29 int apl_right_mouse_menu(GtkWidget
*menu
, int type
, GtkWidget
*tree
, GdkEventButton
*event
);
32 int apl_get_enabled();
33 void apl_set_enabled(int enabled
);
36 void pl3_option_menu_activate();
38 gmpcPlBrowserPlugin apl_gpb
= {
39 .cat_right_mouse_menu
= apl_right_mouse_menu
41 int plugin_api_version
= PLUGIN_API_VERSION
;
43 .name
= "Automatic Playlist Creation",
44 .version
= {PLUGIN_MAJOR_VERSION
,PLUGIN_MINOR_VERSION
,PLUGIN_MICRO_VERSION
},
45 .plugin_type
= GMPC_PLUGIN_PL_BROWSER
,
46 .browser
= &apl_gpb
, /* browser intergration */
47 .get_enabled
= apl_get_enabled
,
48 .set_enabled
= apl_set_enabled
53 return cfg_get_single_value_as_int_with_default(config
, "autoplaylist", "enable", TRUE
);
55 void apl_set_enabled(int enabled
)
57 cfg_set_single_value_as_int(config
, "autoplaylist", "enable", enabled
);
58 pl3_option_menu_activate();
61 static void apl_close(GtkWidget
*dialog
, GladeXML
*apl_xml
)
63 GtkWidget
*tree
= glade_xml_get_widget(apl_xml
, "result_tree");
64 GtkListStore
*model
= (GtkListStore
*)gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
65 gtk_widget_destroy(GTK_WIDGET(dialog
));
66 g_object_unref(model
);
67 g_object_unref(apl_xml
);
70 static int apl_data_matched(MpdData
*data
, int field
, int rule
, char *match
)
74 /* get the correct field */
75 if(field
== MPD_TAG_ITEM_ARTIST
&& data
->song
->artist
) key
= data
->song
->artist
;
76 else if(field
== MPD_TAG_ITEM_ALBUM
&& data
->song
->album
) key
= data
->song
->album
;
77 else if(field
== MPD_TAG_ITEM_TITLE
&& data
->song
->title
) key
= data
->song
->title
;
78 else if(field
== MPD_TAG_ITEM_TRACK
&& data
->song
->track
) key
= data
->song
->track
;
79 else if(field
== MPD_TAG_ITEM_NAME
&& data
->song
->name
) key
= data
->song
->name
;
80 else if(field
== MPD_TAG_ITEM_GENRE
&& data
->song
->genre
) key
= data
->song
->genre
;
81 else if(field
== MPD_TAG_ITEM_DATE
&& data
->song
->date
) key
= data
->song
->date
;
82 else if(field
== MPD_TAG_ITEM_COMPOSER
&& data
->song
->composer
) key
= data
->song
->composer
;
83 else if(field
== MPD_TAG_ITEM_PERFORMER
&& data
->song
->performer
) key
= data
->song
->performer
;
84 else if(field
== MPD_TAG_ITEM_DISC
&& data
->song
->disc
) key
= data
->song
->disc
;
85 else key
= data
->song
->file
;
90 if(rule
== 0) matched
= e_util_utf8_strstrcase (key
,match
);
91 else if(rule
== 1) matched
= !e_util_utf8_strstrcase (key
, match
);
92 else matched
= g_utf8_collate(match
,key
)?FALSE
:TRUE
;
98 static MpdData
* apl_data_filter(MpdData
*data
, int field
, int rule
, char *match
)
102 if(apl_data_matched(data
, field
, rule
, match
) == 0)
105 data
= mpd_data_delete_item(data
);
108 if(!mpd_data_is_last(data
))
110 data
= mpd_data_get_next(data
);
114 return mpd_data_get_first(data
);
121 static void apl_data_filter_itemwise(MpdData
**total_list
, int field
, int rule
, char *match
)
123 GtkWidget
*tree
= glade_xml_get_widget(apl_xml
, "result_tree");
124 GtkListStore
*model
=(GtkListStore
*) gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
127 if(apl_data_matched(*total_list
, field
, rule
, match
))
130 gtk_list_store_append(model
, &iter
);
131 gtk_list_store_set(model
, &iter
,
132 0, (*total_list
)->song
->title
,
133 1,(*total_list
)->song
->artist
,
134 2,(*total_list
)->song
->album
,
135 3,(*total_list
)->song
->file
,-1);
136 *total_list
= mpd_data_delete_item(*total_list
);
140 if(!mpd_data_is_last(*total_list
))
142 *total_list
= mpd_data_get_next(*total_list
);
146 *total_list
= mpd_data_get_first(*total_list
);
151 *total_list
= mpd_data_get_first(*total_list
);
154 static void apl_search()
156 GtkWidget
*tree
= glade_xml_get_widget(apl_xml
, "result_tree");
157 GtkListStore
*rs_store
=(GtkListStore
*) gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
159 GtkTreeModel
*model
= gtk_tree_view_get_model
160 (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")));
161 int match_type
= gtk_toggle_button_get_active
162 (GTK_TOGGLE_BUTTON(glade_xml_get_widget(apl_xml
, "ck_any_item")));
163 MpdData
*data
= mpd_database_get_complete(connection
);
164 /** clear previous results */
165 gtk_list_store_clear(rs_store
);
169 if(gtk_tree_model_get_iter_first(model
, &iter
))
173 gchar
*cfield
, *crule
, *match
;
174 gtk_tree_model_get(model
, &iter
,
180 field
= mpd_misc_get_tag_by_name (cfield
);
181 if(!strcmp(crule
, "Contains")) rule
= 0;
182 else if (!strcmp(crule
, "Does not contain")) rule
= 1;
186 apl_data_filter_itemwise(&data
, field
, rule
, match
);
190 data
= apl_data_filter(data
, field
, rule
, match
);
195 }while(gtk_tree_model_iter_next(model
, &iter
));
202 for(;data
; data
= mpd_data_get_next(data
))
205 gtk_list_store_append(rs_store
,&iter
);
206 gtk_list_store_set(rs_store
, &iter
,
207 0, data
->song
->title
,
208 1, data
->song
->artist
,
209 2, data
->song
->album
,
210 3, data
->song
->file
,-1);
213 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(rs_store
), NULL
) >0)
215 gtk_widget_set_sensitive(glade_xml_get_widget(apl_xml
, "okbutton1"), TRUE
);
218 gtk_widget_set_sensitive(glade_xml_get_widget(apl_xml
, "okbutton1"), FALSE
);
222 static void apl_response(GtkWidget
*dialog
, gint response
, GladeXML
*apl_xml
)
224 if(response
== GTK_RESPONSE_OK
)
227 GtkWidget
*tree
= glade_xml_get_widget(apl_xml
, "result_tree");
228 GtkListStore
*rs_store
=(GtkListStore
*)gtk_tree_view_get_model(GTK_TREE_VIEW(tree
));
230 if(!gtk_toggle_button_get_active
231 (GTK_TOGGLE_BUTTON(glade_xml_get_widget(apl_xml
, "ck_append_playlist"))))
233 mpd_playlist_clear(connection
);
235 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(rs_store
), &iter
))
239 gtk_tree_model_get(GTK_TREE_MODEL(rs_store
), &iter
, 3, &path
, -1);
240 mpd_playlist_queue_add(connection
, path
);
242 }while(gtk_tree_model_iter_next(GTK_TREE_MODEL(rs_store
), &iter
));
243 mpd_playlist_queue_commit(connection
);
248 apl_close(dialog
, apl_xml
);
252 static void field_combo_edited_cb (GtkCellRendererText
*cell
,
258 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list
), &iter
, path
))
260 gtk_list_store_set(list
, &iter
,
266 static void rule_combo_edited_cb (GtkCellRendererText
*cell
,
272 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list
), &iter
, path
))
274 gtk_list_store_set(list
, &iter
, 1,value
, -1);
277 static void text_edited_cb (GtkCellRendererText
*cell
,
283 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list
), &iter
, path
))
285 gtk_list_store_set(list
, &iter
, 2,value
, -1);
288 static void apl_add_row(GtkWidget
*button
, GladeXML
*apl_xml
)
291 GtkListStore
*lstore
= (GtkListStore
*)gtk_tree_view_get_model(
292 GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")));
293 gtk_list_store_append(lstore
, &iter
);
294 gtk_list_store_set(lstore
, &iter
,
302 static void apl_remove_row(GtkWidget
*button
, GladeXML
*apl_xml
)
305 GtkTreeView
*tree
= GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree"));
306 GtkTreeModel
*lstore
= gtk_tree_view_get_model(tree
);
308 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(tree
),&lstore
,&iter
))
310 gtk_list_store_remove(GTK_LIST_STORE(lstore
), &iter
);
313 static void apl_start()
317 GtkCellRenderer
*renderer
;
319 GtkTreeViewColumn
*column
;
320 GtkWidget
*tree
= NULL
;
321 GtkListStore
*lstore
= NULL
, *tmp_store
= NULL
;
322 gchar
*path
= g_strdup_printf("%s/apl.glade",DATA_DIR
);
323 apl_xml
= glade_xml_new(path
, "apl_win", NULL
);
327 debug_printf(DEBUG_ERROR
, "apl_start: Cannot find: %s/apl.glade", DATA_DIR
);
330 debug_printf(DEBUG_INFO
, "apl_start: Starting");
331 lstore
= gtk_list_store_new(4, G_TYPE_STRING
,G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
332 gtk_tree_view_set_model(GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")), GTK_TREE_MODEL(lstore
));
333 g_object_unref(lstore
);
336 renderer
= gtk_cell_renderer_combo_new();
337 column
= gtk_tree_view_column_new();
338 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
339 gtk_tree_view_column_set_attributes(column
, renderer
, "text", 0,NULL
);
340 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")), column
);
341 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
342 tmp_store
= gtk_list_store_new(1, G_TYPE_STRING
);
344 if(mpd_server_check_version(connection
,0,12,0))
346 max
= MPD_TAG_NUM_OF_ITEM_TYPES
;
350 if(i
!= MPD_TAG_ITEM_COMMENT
) /* I Don't want COMMENT */
352 gtk_list_store_append(tmp_store
, &iter
);
353 gtk_list_store_set(tmp_store
,&iter
, 0, mpdTagItemKeys
[i
],-1);
356 g_object_set(G_OBJECT(renderer
), "model", tmp_store
, NULL
);
357 g_object_set(G_OBJECT(renderer
), "text-column", 0, NULL
);
358 g_object_set(G_OBJECT(renderer
), "has-entry", FALSE
, NULL
);
359 g_object_set(G_OBJECT(renderer
), "editable", TRUE
, NULL
);
360 g_signal_connect(G_OBJECT(renderer
), "edited", G_CALLBACK(field_combo_edited_cb
), lstore
);
364 renderer
= gtk_cell_renderer_combo_new();
365 column
= gtk_tree_view_column_new();
366 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
367 gtk_tree_view_column_set_attributes(column
, renderer
, "text", 1,NULL
);
368 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")), column
);
369 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
370 tmp_store
= gtk_list_store_new(1, G_TYPE_STRING
);
371 gtk_list_store_append(tmp_store
, &iter
);
372 gtk_list_store_set(tmp_store
, &iter
, 0, "Contains",-1);
373 gtk_list_store_append(tmp_store
, &iter
);
374 gtk_list_store_set(tmp_store
, &iter
, 0, "Does not contain",-1);
375 gtk_list_store_append(tmp_store
, &iter
);
376 gtk_list_store_set(tmp_store
, &iter
, 0, "Equals",-1);
377 g_object_set(G_OBJECT(renderer
), "model", tmp_store
, NULL
);
378 g_object_set(G_OBJECT(renderer
), "text-column", 0, NULL
);
379 g_object_set(G_OBJECT(renderer
), "has-entry", FALSE
, NULL
);
380 g_object_set(G_OBJECT(renderer
), "editable", TRUE
, NULL
);
381 g_signal_connect(G_OBJECT(renderer
), "edited", G_CALLBACK(rule_combo_edited_cb
), lstore
);
385 renderer
= gtk_cell_renderer_text_new();
386 column
= gtk_tree_view_column_new();
387 gtk_tree_view_column_pack_start(column
, renderer
, TRUE
);
388 gtk_tree_view_column_set_attributes(column
, renderer
, "text", 2,"editable", 3,NULL
);
389 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml
, "apl_tree")), column
);
390 gtk_tree_view_column_set_sizing(column
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
391 g_signal_connect(G_OBJECT(renderer
), "edited", G_CALLBACK(text_edited_cb
), lstore
);
394 gtk_list_store_append(lstore
, &iter
);
395 gtk_list_store_set(lstore
, &iter
,
404 tree
= glade_xml_get_widget(apl_xml
, "result_tree");
405 lstore
= gtk_list_store_new(4, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
406 gtk_tree_view_set_model(GTK_TREE_VIEW(tree
), GTK_TREE_MODEL(lstore
));
407 renderer
= gtk_cell_renderer_text_new();
408 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree
),-1,"Title", renderer
,
410 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree
),-1,"Artist", renderer
,
412 renderer
= gtk_cell_renderer_text_new();
413 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree
),-1,"Album", renderer
,
416 glade_xml_signal_connect_data (apl_xml
, "apl_search",
417 G_CALLBACK(apl_search
), apl_xml
);
420 glade_xml_signal_connect_data (apl_xml
, "on_apl_win_close",
421 G_CALLBACK(apl_close
), apl_xml
);
422 glade_xml_signal_connect_data (apl_xml
, "on_apl_win_response",
423 G_CALLBACK(apl_response
), apl_xml
);
424 glade_xml_signal_connect_data (apl_xml
, "on_apl_add_clicked",
425 G_CALLBACK(apl_add_row
), apl_xml
);
426 glade_xml_signal_connect_data (apl_xml
, "on_apl_remove_clicked",
427 G_CALLBACK(apl_remove_row
), apl_xml
);
432 int apl_right_mouse_menu(GtkWidget
*menu
, int type
, GtkWidget
*tree
, GdkEventButton
*event
)
434 gmpcPlugin
*plug
= plugin_get_from_id(type
);
435 if(!cfg_get_single_value_as_int_with_default(config
, "autoplaylist", "enable", TRUE
)) {
438 debug_printf(DEBUG_INFO
,"Automatic playlist right mouse clicked");
439 if(!strcmp(plug
->name
, "Current Playlist Browser") && mpd_server_check_version(connection
, 0,12,0))
442 debug_printf(DEBUG_INFO
,"Automatic playlist right mouse for me");
443 item
= gtk_image_menu_item_new_with_label("Generate Playlist");
444 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
445 gtk_image_new_from_stock(GTK_STOCK_ADD
, GTK_ICON_SIZE_MENU
));
446 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
447 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(apl_start
), NULL
);
454 * e_util_unicode_get_utf8:
455 * @text: The string to take the UTF-8 character from.
456 * @out: The location to store the UTF-8 character in.
458 * Get a UTF-8 character from the beginning of @text.
460 * Returns: A pointer to the next character in @text after @out.
462 static guchar
* e_util_unicode_get_utf8 (const gchar
*text
, gunichar
*out
)
464 *out
= g_utf8_get_char (text
);
465 return (guchar
*)((*out
== (gunichar
)-1) ? NULL
: g_utf8_next_char (text
));
470 * e_util_utf8_strstrcase:
471 * @haystack: The string to search in.
472 * @needle: The string to search for.
474 * Find the first instance of @needle in @haystack, ignoring case. (No
475 * proper case folding or decomposing is done.) Both @needle and
476 * @haystack are UTF-8 strings.
478 * Returns: A pointer to the first instance of @needle in @haystack, or
479 * %NULL if no match is found, or if either of the strings are
480 * not legal UTF-8 strings.
482 int e_util_utf8_strstrcase (const gchar
*haystack
, const gchar
*needle
)
489 if (haystack
== NULL
) return FALSE
;
490 if (needle
== NULL
) return FALSE
;
491 if (strlen (needle
) == 0) return FALSE
;
492 if (strlen (haystack
) == 0) return FALSE
;
494 nuni
= g_alloca (sizeof (gunichar
) * strlen (needle
));
497 for (p
= e_util_unicode_get_utf8 (needle
, &unival
);
499 p
= e_util_unicode_get_utf8 ((const gchar
*)p
, &unival
))
501 nuni
[nlen
++] = g_unichar_tolower (unival
);
503 /* NULL means there was illegal utf-8 sequence */
510 o
= (const guchar
*)haystack
;
511 for (p
= e_util_unicode_get_utf8 ((const gchar
*)o
, &unival
);
513 p
= e_util_unicode_get_utf8 ((const gchar
*)p
, &unival
))
516 sc
= g_unichar_tolower (unival
);
517 /* We have valid stripped char */
521 while (npos
< nlen
) {
522 q
= e_util_unicode_get_utf8 ((const gchar
*)q
, &unival
);
523 if (!q
|| !unival
) return FALSE
;
524 sc
= g_unichar_tolower (unival
);
525 if (sc
!= nuni
[npos
]) break;