Update copyright notices.
[gmpc-autoplaylist.git] / src / main.c
blob5f1342532181fdf984b3077579dae5f7fb0a1064
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.
20 #include <stdio.h>
21 #include <string.h>
22 #include <glade/glade.h>
23 #include <libmpd/libmpd.h>
24 #include <libmpd/debug_printf.h>
25 #include <gmpc/plugin.h>
26 #include <config.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);
35 /** in gmpc */
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;
42 gmpcPlugin plugin = {
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
51 int apl_get_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)
72 int matched = 0;
73 gchar *key = NULL;
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;
86 if(key == NULL)
88 return FALSE;
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;
93 /* return value */
94 return matched;
98 static MpdData * apl_data_filter(MpdData *data, int field, int rule, char *match)
100 while(data)
102 if(apl_data_matched(data, field, rule, match) == 0)
105 data = mpd_data_delete_item(data);
107 else {
108 if(!mpd_data_is_last(data))
110 data = mpd_data_get_next(data);
112 else
114 return mpd_data_get_first(data);
118 return NULL;
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));
125 while(*total_list)
127 if(apl_data_matched(*total_list, field, rule, match))
129 GtkTreeIter iter;
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);
138 else
140 if(!mpd_data_is_last(*total_list))
142 *total_list = mpd_data_get_next(*total_list);
144 else
146 *total_list = mpd_data_get_first(*total_list);
147 return;
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);
166 if(data != NULL)
168 GtkTreeIter iter;
169 if(gtk_tree_model_get_iter_first(model, &iter))
172 gint field, rule;
173 gchar *cfield, *crule, *match;
174 gtk_tree_model_get(model, &iter,
175 0, &cfield,
176 1, &crule,
177 2, &match,
178 -1);
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;
183 else rule = 2;
184 if(match_type)
186 apl_data_filter_itemwise(&data, field, rule, match);
188 else
190 data = apl_data_filter(data, field, rule, match);
192 g_free(cfield);
193 g_free(crule);
194 g_free(match);
195 }while(gtk_tree_model_iter_next(model, &iter));
197 if(match_type)
199 mpd_data_free(data);
200 data = NULL;
202 for(;data; data = mpd_data_get_next(data))
204 GtkTreeIter iter;
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);
217 else{
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)
226 GtkTreeIter iter;
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))
238 char *path;
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,
253 const gchar *path,
254 const gchar *value,
255 GtkListStore *list)
257 GtkTreeIter iter;
258 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list), &iter, path))
260 gtk_list_store_set(list, &iter,
261 0,value,
262 -1);
266 static void rule_combo_edited_cb (GtkCellRendererText *cell,
267 const gchar *path,
268 const gchar *value,
269 GtkListStore *list)
271 GtkTreeIter iter;
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,
278 const gchar *path,
279 const gchar *value,
280 GtkListStore *list)
282 GtkTreeIter iter;
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)
290 GtkTreeIter iter;
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,
295 0, "Artist",
296 1,"Contains",
297 2,"<Value>",
298 3, TRUE,
299 -1);
302 static void apl_remove_row(GtkWidget *button, GladeXML *apl_xml)
304 GtkTreeIter iter;
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()
316 int i=0, max = 3;
317 GtkCellRenderer *renderer;
318 GtkTreeIter iter;
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);
324 g_free(path);
325 if(apl_xml == NULL)
327 debug_printf(DEBUG_ERROR, "apl_start: Cannot find: %s/apl.glade", DATA_DIR);
328 return;
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);
334 /* need 3 columns */
335 /* combo box */
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;
348 for(i=0;i< max;i++)
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);
363 /* combo box */
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);
384 /* text */
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,
396 0, "Artist",
397 1,"Contains",
398 2,"<Value>",
399 3, TRUE,
400 -1);
403 /** Result */
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,
409 "text", 0,NULL);
410 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),-1,"Artist", renderer,
411 "text", 1,NULL);
412 renderer = gtk_cell_renderer_text_new();
413 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),-1,"Album", renderer,
414 "text", 2,NULL);
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)) {
436 return 0;
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))
441 GtkWidget *item;
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);
448 return 1;
450 return 0;
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)
484 gunichar *nuni;
485 gunichar unival;
486 gint nlen;
487 const guchar *o, *p;
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));
496 nlen = 0;
497 for (p = e_util_unicode_get_utf8 (needle, &unival);
498 p && 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 */
504 if (!p){
506 return FALSE;
510 o = (const guchar *)haystack;
511 for (p = e_util_unicode_get_utf8 ((const gchar *)o, &unival);
512 p && unival;
513 p = e_util_unicode_get_utf8 ((const gchar *)p, &unival))
515 gint sc;
516 sc = g_unichar_tolower (unival);
517 /* We have valid stripped char */
518 if (sc == nuni[0]) {
519 const guchar *q = p;
520 gint npos = 1;
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;
526 npos++;
528 if (npos == nlen) {
529 return TRUE;
532 o = p;
534 return FALSE;