update versions
[gmpc-autoplaylist.git] / src / main.c
blob6fca662afc1bc7910c15e0b578fa20bcf33f0a0d
1 #include <stdio.h>
2 #include <string.h>
3 #include <glade/glade.h>
4 #include <libmpd/libmpd.h>
5 #include <libmpd/debug_printf.h>
6 #include <gmpc/plugin.h>
7 #include <config.h>
8 GladeXML *apl_xml = NULL;
9 int e_util_utf8_strstrcase (const gchar *haystack, const gchar *needle);
10 int apl_right_mouse_menu(GtkWidget *menu, int type, GtkWidget *tree, GdkEventButton *event);
13 int apl_get_enabled();
14 void apl_set_enabled(int enabled);
16 /** in gmpc */
17 void pl3_option_menu_activate();
19 gmpcPlBrowserPlugin apl_gpb = {
20 NULL, /**Add */
21 NULL, /** Selected */
22 NULL, /** Unselected */
23 NULL, /** slection changed */
24 NULL, /** expanded */
25 apl_right_mouse_menu, /* right mouse menu */
26 NULL, /** cat key press */
27 NULL, /** add go menu */
28 NULL /** key press event */
30 int plugin_api_version = PLUGIN_API_VERSION;
31 gmpcPlugin plugin = {
32 "Automatic Playlist Creation",
33 {0,0,3},
34 GMPC_PLUGIN_PL_BROWSER,
35 0, /* plugin id */
36 NULL, /* path to plugin */
37 NULL, /* initialization */
38 NULL, /* Destroy */
39 &apl_gpb, /* browser intergration */
40 NULL, /* status changed */
41 NULL, /* connection changed */
42 NULL, /* preferences */
43 NULL, /* Metadata */
44 apl_get_enabled,
45 apl_set_enabled
48 int apl_get_enabled()
50 return cfg_get_single_value_as_int_with_default(config, "autoplaylist", "enable", TRUE);
52 void apl_set_enabled(int enabled)
54 cfg_set_single_value_as_int(config, "autoplaylist", "enable", enabled);
55 pl3_option_menu_activate();
58 static void apl_close(GtkWidget *dialog, GladeXML *apl_xml)
60 GtkWidget *tree = glade_xml_get_widget(apl_xml, "result_tree");
61 GtkListStore *model = (GtkListStore *)gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
62 gtk_widget_destroy(GTK_WIDGET(dialog));
63 g_object_unref(model);
64 g_object_unref(apl_xml);
67 static int apl_data_matched(MpdData *data, int field, int rule, char *match)
69 int matched = 0;
70 gchar *key = NULL;
71 /* get the correct field */
72 if(field == MPD_TAG_ITEM_ARTIST && data->song->artist) key = data->song->artist;
73 else if(field == MPD_TAG_ITEM_ALBUM && data->song->album) key = data->song->album;
74 else if(field == MPD_TAG_ITEM_TITLE && data->song->title) key = data->song->title;
75 else if(field == MPD_TAG_ITEM_TRACK && data->song->track) key = data->song->track;
76 else if(field == MPD_TAG_ITEM_NAME && data->song->name) key = data->song->name;
77 else if(field == MPD_TAG_ITEM_GENRE && data->song->genre) key = data->song->genre;
78 else if(field == MPD_TAG_ITEM_DATE && data->song->date) key = data->song->date;
79 else if(field == MPD_TAG_ITEM_COMPOSER && data->song->composer) key = data->song->composer;
80 else key = data->song->file;
81 if(key == NULL)
83 return FALSE;
85 if(rule == 0) matched = e_util_utf8_strstrcase (key,match);
86 else if(rule == 1) matched = !e_util_utf8_strstrcase (key, match);
87 else matched = g_utf8_collate(match,key)?FALSE:TRUE;
88 /* return value */
89 return matched;
93 static MpdData * apl_data_filter(MpdData *data, int field, int rule, char *match)
95 while(data)
97 if(apl_data_matched(data, field, rule, match) == 0)
100 data = mpd_data_delete_item(data);
102 else {
103 if(!mpd_data_is_last(data))
105 data = mpd_data_get_next(data);
107 else
109 return mpd_data_get_first(data);
113 return NULL;
116 static void apl_data_filter_itemwise(MpdData **total_list, int field, int rule, char *match)
118 GtkWidget *tree = glade_xml_get_widget(apl_xml, "result_tree");
119 GtkListStore *model =(GtkListStore *) gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
120 while(*total_list)
122 if(apl_data_matched(*total_list, field, rule, match))
124 GtkTreeIter iter;
125 // mpd_playlist_queue_add(connection, (*total_list)->song->file);
126 gtk_list_store_append(model, &iter);
127 gtk_list_store_set(model, &iter,
128 0, (*total_list)->song->title,
129 1,(*total_list)->song->artist,
130 2,(*total_list)->song->album,
131 3,(*total_list)->song->file,-1);
132 *total_list = mpd_data_delete_item(*total_list);
134 else
136 if(!mpd_data_is_last(*total_list))
138 *total_list = mpd_data_get_next(*total_list);
140 else
142 *total_list = mpd_data_get_first(*total_list);
143 return;
147 *total_list = mpd_data_get_first(*total_list);
150 static void apl_search()
152 GtkWidget *tree = glade_xml_get_widget(apl_xml, "result_tree");
153 GtkListStore *rs_store =(GtkListStore *) gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
155 GtkTreeModel *model = gtk_tree_view_get_model
156 (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")));
157 int match_type = gtk_toggle_button_get_active
158 (GTK_TOGGLE_BUTTON(glade_xml_get_widget(apl_xml, "ck_any_item")));
159 MpdData *data = mpd_database_get_complete(connection);
160 /** clear previous results */
161 gtk_list_store_clear(rs_store);
162 /* if(!gtk_toggle_button_get_active
163 (GTK_TOGGLE_BUTTON(glade_xml_get_widget(apl_xml, "ck_append_playlist"))))
165 mpd_playlist_clear(connection);
167 */ if(data != NULL)
169 GtkTreeIter iter;
170 if(gtk_tree_model_get_iter_first(model, &iter))
173 gint field, rule;
174 gchar *cfield, *crule, *match;
175 gtk_tree_model_get(model, &iter,
176 0, &cfield,
177 1, &crule,
178 2, &match,
179 -1);
181 field = mpd_misc_get_tag_by_name (cfield);
182 if(!strcmp(crule, "Contains")) rule = 0;
183 else if (!strcmp(crule, "Does not contain")) rule = 1;
184 else rule = 2;
185 if(match_type)
187 apl_data_filter_itemwise(&data, field, rule, match);
189 else
191 data = apl_data_filter(data, field, rule, match);
193 g_free(cfield);
194 g_free(crule);
195 g_free(match);
196 }while(gtk_tree_model_iter_next(model, &iter));
198 if(match_type)
200 mpd_data_free(data);
201 data = NULL;
203 for(;data; data = mpd_data_get_next(data))
205 // mpd_playlist_queue_add(connection, data->song->file);
206 GtkTreeIter iter;
207 gtk_list_store_append(rs_store,&iter);
208 gtk_list_store_set(rs_store, &iter,
209 0, data->song->title,
210 1, data->song->artist,
211 2, data->song->album,
212 3, data->song->file,-1);
215 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(rs_store), NULL) >0)
217 gtk_widget_set_sensitive(glade_xml_get_widget(apl_xml, "okbutton1"), TRUE);
219 else{
220 gtk_widget_set_sensitive(glade_xml_get_widget(apl_xml, "okbutton1"), FALSE);
224 static void apl_response(GtkWidget *dialog, gint response, GladeXML *apl_xml)
226 if(response == GTK_RESPONSE_OK)
228 GtkTreeIter iter;
229 GtkWidget *tree = glade_xml_get_widget(apl_xml, "result_tree");
230 GtkListStore *rs_store =(GtkListStore *)gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
232 if(!gtk_toggle_button_get_active
233 (GTK_TOGGLE_BUTTON(glade_xml_get_widget(apl_xml, "ck_append_playlist"))))
235 mpd_playlist_clear(connection);
237 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(rs_store), &iter))
240 char *path;
241 gtk_tree_model_get(GTK_TREE_MODEL(rs_store), &iter, 3, &path, -1);
242 mpd_playlist_queue_add(connection, path);
244 }while(gtk_tree_model_iter_next(GTK_TREE_MODEL(rs_store), &iter));
245 mpd_playlist_queue_commit(connection);
250 apl_close(dialog, apl_xml);
254 static void field_combo_edited_cb (GtkCellRendererText *cell,
255 const gchar *path,
256 const gchar *value,
257 GtkListStore *list)
259 GtkTreeIter iter;
260 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list), &iter, path))
262 gtk_list_store_set(list, &iter,
263 0,value,
264 -1);
268 static void rule_combo_edited_cb (GtkCellRendererText *cell,
269 const gchar *path,
270 const gchar *value,
271 GtkListStore *list)
273 GtkTreeIter iter;
274 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list), &iter, path))
276 gtk_list_store_set(list, &iter, 1,value, -1);
279 static void text_edited_cb (GtkCellRendererText *cell,
280 const gchar *path,
281 const gchar *value,
282 GtkListStore *list)
284 GtkTreeIter iter;
285 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list), &iter, path))
287 gtk_list_store_set(list, &iter, 2,value, -1);
290 static void apl_add_row(GtkWidget *button, GladeXML *apl_xml)
292 GtkTreeIter iter;
293 GtkListStore *lstore = (GtkListStore *)gtk_tree_view_get_model(
294 GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")));
295 gtk_list_store_append(lstore, &iter);
296 gtk_list_store_set(lstore, &iter,
297 0, "Artist",
298 1,"Contains",
299 2,"<Value>",
300 3, TRUE,
301 -1);
304 static void apl_remove_row(GtkWidget *button, GladeXML *apl_xml)
306 GtkTreeIter iter;
307 GtkTreeView *tree = GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree"));
308 GtkTreeModel *lstore = gtk_tree_view_get_model(tree);
310 if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(tree),&lstore,&iter))
312 gtk_list_store_remove(GTK_LIST_STORE(lstore), &iter);
315 static void apl_start()
318 int i=0, max = 3;
319 GtkCellRenderer *renderer;
320 GtkTreeIter iter;
321 GtkTreeViewColumn *column;
322 GtkWidget *tree = NULL;
323 GtkListStore *lstore = NULL, *tmp_store = NULL;
324 gchar *path = g_strdup_printf("%s/apl/apl.glade", plugin.path);
325 apl_xml = glade_xml_new(path, "apl_win", NULL);
326 g_free(path);
327 if(apl_xml == NULL)
329 debug_printf(DEBUG_ERROR, "apl_start: Cannot find: %s/apl/apl.glade", plugin.path);
330 return;
332 debug_printf(DEBUG_INFO, "apl_start: Starting");
333 lstore = gtk_list_store_new(4, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
334 gtk_tree_view_set_model(GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")), GTK_TREE_MODEL(lstore));
335 g_object_unref(lstore);
336 /* need 3 columns */
337 /* combo box */
338 renderer = gtk_cell_renderer_combo_new();
339 column = gtk_tree_view_column_new();
340 gtk_tree_view_column_pack_start(column, renderer, TRUE);
341 gtk_tree_view_column_set_attributes(column, renderer, "text", 0,NULL);
342 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")), column);
343 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
344 tmp_store = gtk_list_store_new(1, G_TYPE_STRING);
346 if(mpd_server_check_version(connection,0,12,0))
348 max = MPD_TAG_NUM_OF_ITEM_TYPES;
350 for(i=0;i< max;i++)
352 if(i != MPD_TAG_ITEM_COMMENT) /* I Don't want COMMENT */
354 gtk_list_store_append(tmp_store, &iter);
355 gtk_list_store_set(tmp_store,&iter, 0, mpdTagItemKeys[i],-1);
358 g_object_set(G_OBJECT(renderer), "model", tmp_store, NULL);
359 g_object_set(G_OBJECT(renderer), "text-column", 0, NULL);
360 g_object_set(G_OBJECT(renderer), "has-entry", FALSE, NULL);
361 g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
362 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(field_combo_edited_cb), lstore);
365 /* combo box */
366 renderer = gtk_cell_renderer_combo_new();
367 column = gtk_tree_view_column_new();
368 gtk_tree_view_column_pack_start(column, renderer, TRUE);
369 gtk_tree_view_column_set_attributes(column, renderer, "text", 1,NULL);
370 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")), column);
371 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
372 tmp_store = gtk_list_store_new(1, G_TYPE_STRING);
373 gtk_list_store_append(tmp_store, &iter);
374 gtk_list_store_set(tmp_store, &iter, 0, "Contains",-1);
375 gtk_list_store_append(tmp_store, &iter);
376 gtk_list_store_set(tmp_store, &iter, 0, "Does not contain",-1);
377 gtk_list_store_append(tmp_store, &iter);
378 gtk_list_store_set(tmp_store, &iter, 0, "Equals",-1);
379 g_object_set(G_OBJECT(renderer), "model", tmp_store, NULL);
380 g_object_set(G_OBJECT(renderer), "text-column", 0, NULL);
381 g_object_set(G_OBJECT(renderer), "has-entry", FALSE, NULL);
382 g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
383 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(rule_combo_edited_cb), lstore);
386 /* text */
387 renderer = gtk_cell_renderer_text_new();
388 column = gtk_tree_view_column_new();
389 gtk_tree_view_column_pack_start(column, renderer, TRUE);
390 gtk_tree_view_column_set_attributes(column, renderer, "text", 2,"editable", 3,NULL);
391 gtk_tree_view_append_column (GTK_TREE_VIEW (glade_xml_get_widget(apl_xml, "apl_tree")), column);
392 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
393 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(text_edited_cb), lstore);
396 gtk_list_store_append(lstore, &iter);
397 gtk_list_store_set(lstore, &iter,
398 0, "Artist",
399 1,"Contains",
400 2,"<Value>",
401 3, TRUE,
402 -1);
405 /** Result */
406 tree = glade_xml_get_widget(apl_xml, "result_tree");
407 lstore = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
408 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(lstore));
409 renderer = gtk_cell_renderer_text_new();
410 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),-1,"Title", renderer,
411 "text", 0,NULL);
412 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),-1,"Artist", renderer,
413 "text", 1,NULL);
414 renderer = gtk_cell_renderer_text_new();
415 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),-1,"Album", renderer,
416 "text", 2,NULL);
427 glade_xml_signal_connect_data (apl_xml, "apl_search",
428 G_CALLBACK(apl_search), apl_xml);
431 glade_xml_signal_connect_data (apl_xml, "on_apl_win_close",
432 G_CALLBACK(apl_close), apl_xml);
433 glade_xml_signal_connect_data (apl_xml, "on_apl_win_response",
434 G_CALLBACK(apl_response), apl_xml);
435 glade_xml_signal_connect_data (apl_xml, "on_apl_add_clicked",
436 G_CALLBACK(apl_add_row), apl_xml);
437 glade_xml_signal_connect_data (apl_xml, "on_apl_remove_clicked",
438 G_CALLBACK(apl_remove_row), apl_xml);
443 int apl_right_mouse_menu(GtkWidget *menu, int type, GtkWidget *tree, GdkEventButton *event)
445 gmpcPlugin *plug = plugin_get_from_id(type);
446 if(!cfg_get_single_value_as_int_with_default(config, "autoplaylist", "enable", TRUE)) {
447 return 0;
449 debug_printf(DEBUG_INFO,"Automatic playlist right mouse clicked");
450 if(!strcmp(plug->name, "Current Playlist Browser") && mpd_server_check_version(connection, 0,12,0))
452 GtkWidget *item;
453 debug_printf(DEBUG_INFO,"Automatic playlist right mouse for me");
454 item = gtk_image_menu_item_new_with_label("Generate Playlist");
455 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
456 gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
457 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
458 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(apl_start), NULL);
459 return 1;
461 return 0;
465 * e_util_unicode_get_utf8:
466 * @text: The string to take the UTF-8 character from.
467 * @out: The location to store the UTF-8 character in.
469 * Get a UTF-8 character from the beginning of @text.
471 * Returns: A pointer to the next character in @text after @out.
473 static guchar * e_util_unicode_get_utf8 (const gchar *text, gunichar *out)
475 *out = g_utf8_get_char (text);
476 return (guchar *)((*out == (gunichar)-1) ? NULL : g_utf8_next_char (text));
481 * e_util_utf8_strstrcase:
482 * @haystack: The string to search in.
483 * @needle: The string to search for.
485 * Find the first instance of @needle in @haystack, ignoring case. (No
486 * proper case folding or decomposing is done.) Both @needle and
487 * @haystack are UTF-8 strings.
489 * Returns: A pointer to the first instance of @needle in @haystack, or
490 * %NULL if no match is found, or if either of the strings are
491 * not legal UTF-8 strings.
493 int e_util_utf8_strstrcase (const gchar *haystack, const gchar *needle)
495 gunichar *nuni;
496 gunichar unival;
497 gint nlen;
498 const guchar *o, *p;
500 if (haystack == NULL) return FALSE;
501 if (needle == NULL) return FALSE;
502 if (strlen (needle) == 0) return FALSE;
503 if (strlen (haystack) == 0) return FALSE;
505 nuni = g_alloca (sizeof (gunichar) * strlen (needle));
507 nlen = 0;
508 for (p = e_util_unicode_get_utf8 (needle, &unival);
509 p && unival;
510 p = e_util_unicode_get_utf8 ((const gchar *)p, &unival))
512 nuni[nlen++] = g_unichar_tolower (unival);
514 /* NULL means there was illegal utf-8 sequence */
515 if (!p){
517 return FALSE;
521 o = (const guchar *)haystack;
522 for (p = e_util_unicode_get_utf8 ((const gchar *)o, &unival);
523 p && unival;
524 p = e_util_unicode_get_utf8 ((const gchar *)p, &unival))
526 gint sc;
527 sc = g_unichar_tolower (unival);
528 /* We have valid stripped char */
529 if (sc == nuni[0]) {
530 const guchar *q = p;
531 gint npos = 1;
532 while (npos < nlen) {
533 q = e_util_unicode_get_utf8 ((const gchar *)q, &unival);
534 if (!q || !unival) return FALSE;
535 sc = g_unichar_tolower (unival);
536 if (sc != nuni[npos]) break;
537 npos++;
539 if (npos == nlen) {
540 return TRUE;
543 o = p;
545 return FALSE;