Remove the one row to many, now correctly
[gmpc-albumview.git] / src / plugin.c
blob1acb357eea5ef58f56423a67b9edd7f705b96b19
1 #include <gtk/gtk.h>
2 #include <gmpc/plugin.h>
3 #include <gmpc/playlist3-messages.h>
4 #include <gmpc/gmpc-metaimage.h>
5 #include <gmpc/misc.h>
6 #include <libmpd/libmpd-internal.h>
7 #include <config.h>
8 #include <math.h>
9 #include "exo-wrap-table.h"
10 #include "plugin.h"
12 static gchar * albumview_format_time(unsigned long seconds);
13 static void albumview_browser_save_myself(void);
14 /* Allow gmpc to check the version the plugin is compiled against */
15 int plugin_api_version = PLUGIN_API_VERSION;
16 GtkTreeModel *albumview_model = NULL;
18 void update_view(void);
19 /**
20 * Browser extention
22 gmpcPlBrowserPlugin albumview_gbp = {
23 /** add */
24 .add = albumview_add,
25 /** selected */
26 .selected = albumview_selected,
27 /** unselected */
28 .unselected = albumview_unselected,
32 /**
33 * Define the plugin structure
35 gmpcPlugin plugin = {
36 /* name */
37 .name = "Album View",
38 /* version */
39 .version = {PLUGIN_MAJOR_VERSION,PLUGIN_MINOR_VERSION,PLUGIN_MICRO_VERSION},
40 /* type */
41 .plugin_type = GMPC_PLUGIN_PL_BROWSER,
42 /* init function */
43 .init = albumview_plugin_init,
44 /** playlist extention struct */
45 .browser = &albumview_gbp,
46 /** Connection changed */
47 .mpd_connection_changed = albumview_connection_changed,
48 /** enable/disable */
49 .get_enabled = albumview_get_enabled,
50 .set_enabled = albumview_set_enabled,
51 /* Safe myself */
52 .save_yourself = albumview_browser_save_myself
55 static GtkTreeRowReference *albumview_ref = NULL;
56 static GtkWidget *albumview_vbox= NULL, *albumview_tree = NULL,*albumview_combo = NULL;
57 static gboolean cancel_query = FALSE;
58 /**
59 * Get/Set enable
62 int albumview_get_enabled(void)
64 return cfg_get_single_value_as_int_with_default(config, "albumview", "enable", TRUE);
67 void albumview_set_enabled(int enabled)
69 cfg_set_single_value_as_int(config, "albumview", "enable", enabled);
70 if(enabled)
72 if(albumview_ref == NULL)
74 albumview_add(GTK_WIDGET(playlist3_get_category_tree_view()));
77 else
79 GtkTreePath *path = gtk_tree_row_reference_get_path(albumview_ref);
80 GtkTreeModel *model = gtk_tree_row_reference_get_model(albumview_ref);
81 if (path){
82 GtkTreeIter iter;
83 if (gtk_tree_model_get_iter(model, &iter, path)){
84 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
86 gtk_tree_path_free(path);
87 gtk_tree_row_reference_free(albumview_ref);
88 albumview_ref = NULL;
93 /**
94 * Playlist browser functions
96 static void albumview_add(GtkWidget *category_tree)
98 GtkTreePath *path;
99 GtkTreeModel *model = GTK_TREE_MODEL(playlist3_get_category_tree_store());
100 GtkTreeIter iter;
101 gint pos;
103 * don't do anything if we are disabled
105 if(!cfg_get_single_value_as_int_with_default(config, "albumview", "enable", TRUE)) return;
106 /**
107 * Add ourslef to the list
109 pos = cfg_get_single_value_as_int_with_default(config, "albumview","position",2);
110 playlist3_insert_browser(&iter, pos);
111 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
112 PL3_CAT_TYPE, plugin.id,
113 PL3_CAT_TITLE,"Album View",
114 PL3_CAT_ICON_ID, "gtk-open",
115 -1);
116 /**
117 * remove odl reference if exists
119 if (albumview_ref) {
120 gtk_tree_row_reference_free(albumview_ref);
121 albumview_ref = NULL;
124 * create reference to ourself in the list
126 path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter);
127 if (path) {
128 albumview_ref = gtk_tree_row_reference_new(model, path);
129 gtk_tree_path_free(path);
132 static void albumview_browser_save_myself(void)
134 if(albumview_ref)
136 GtkTreePath *path = gtk_tree_row_reference_get_path(albumview_ref);
137 if(path)
139 gint *indices = gtk_tree_path_get_indices(path);
140 debug_printf(DEBUG_INFO,"Saving myself to position: %i\n", indices[0]);
141 cfg_set_single_value_as_int(config, "albumview","position",indices[0]);
142 gtk_tree_path_free(path);
147 GtkWidget *entry = NULL;
148 int album_size = 200;
149 int supported_columns = 1;
150 void size_changed(GtkWidget *widget, GtkAllocation *alloc)
152 int columns = (alloc->width-20)/(album_size +25);
153 if(columns != supported_columns)
155 supported_columns = columns;
156 printf("update columns: %i %i %i\n", alloc->width-20,columns, album_size);
157 if(entry && GTK_WIDGET_IS_SENSITIVE(entry))
159 update_view();
163 void album_size_changed(GtkRange *spin)
165 int new_size = ((int)gtk_range_get_value(spin))*20;
166 if(new_size != album_size) {
167 album_size = new_size;
168 size_changed(albumview_vbox, &(albumview_vbox->allocation));
170 if(entry && GTK_WIDGET_IS_SENSITIVE(entry))
172 update_view();
176 cfg_set_single_value_as_int(config, "albumview", "zoom-level", (int)gtk_range_get_value(spin));
178 static gboolean expose_event(GtkWidget *wid, GdkEventExpose *event, gpointer data)
180 /* TODO improve this, somehow applying the clipmask fail. */
181 int offset = ((event->area.y)%(album_size+40));
182 int step = (album_size +40)- offset;
183 int start = event->area.y;
184 int flip = ((int)(event->area.y+step-5)/(album_size+40))&1;
185 int stop = event->area.y+event->area.height;
186 cairo_t *cc = gdk_cairo_create(GTK_WIDGET(wid)->window);
188 if(flip)
189 cairo_set_source_rgba(cc, 1.0,1.0,1.0,1.0);
190 else
191 cairo_set_source_rgba(cc, 0.8,0.8,0.8,1.0);
192 cairo_rectangle(cc, 0, start, wid->allocation.width,step );
193 cairo_fill(cc);
194 start += step;
195 step = album_size+40;
196 flip = (flip == 1)?0:1;
197 }while(start<stop);
198 cairo_destroy(cc);
199 return FALSE;
201 static void albumview_init()
203 /** Get an allready exposed widgets to grab theme colors from. */
204 GtkWidget *colw = (GtkWidget *)playlist3_get_category_tree_view();
205 GtkWidget *label = NULL;
206 GtkWidget *event = gtk_event_box_new();
207 int i = 0;
208 albumview_vbox = gtk_vbox_new(FALSE, 0);
210 album_size = 20*cfg_get_single_value_as_int_with_default(config, "albumview", "zoom-level", 10);
211 g_signal_connect(G_OBJECT(albumview_vbox), "size-allocate", G_CALLBACK(size_changed), NULL);
213 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
214 GtkWidget *iv = albumview_tree = gtk_vbox_new(FALSE, 6);
217 entry = gtk_entry_new();
218 gtk_box_pack_start(GTK_BOX(albumview_vbox), entry, FALSE, FALSE, 0);
219 g_signal_connect(G_OBJECT(entry),"changed", G_CALLBACK(update_view), NULL);
221 GtkWidget *spin= gtk_hscale_new_with_range(1, 12, 1);
222 gtk_scale_set_draw_value(GTK_SCALE(spin), FALSE);
223 gtk_range_set_value(GTK_RANGE(spin), album_size);
224 gtk_box_pack_start(GTK_BOX(albumview_vbox), spin, FALSE, FALSE, 0);
226 #if GTK_CHECK_VERSION(2,16,0)
227 for(i=1; i<13;i++){
228 gtk_scale_add_mark(GTK_SCALE(spin), (gdouble)i,GTK_POS_TOP, NULL);
230 #endif
232 gtk_range_set_value(GTK_RANGE(spin), cfg_get_single_value_as_int_with_default(config, "albumview", "zoom-level", 10));
233 g_signal_connect(G_OBJECT(spin), "value-changed", G_CALLBACK(album_size_changed), NULL);
236 gtk_box_pack_start(GTK_BOX(albumview_vbox),sw, TRUE, TRUE, 0);
237 // gtk_widget_modify_bg(event, GTK_STATE_NORMAL,&(albumview_vbox->style->white));
238 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), TRUE);
239 gtk_widget_set_app_paintable(GTK_WIDGET(event),TRUE);
240 g_signal_connect(G_OBJECT(event), "expose-event", G_CALLBACK(expose_event), NULL);
242 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), event);
243 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
244 gtk_container_add(GTK_CONTAINER(event), iv);
246 gtk_widget_show_all(albumview_vbox);
247 /* maintain my own reference to the widget, so it won't get destroyed removing
248 * from view
250 g_object_ref(albumview_vbox);
254 static void albumview_selected(GtkWidget *container)
256 if(albumview_vbox== NULL) {
257 albumview_init();
258 albumview_connection_changed(connection,1,NULL);
260 gtk_container_add(GTK_CONTAINER(container), albumview_vbox);
261 gtk_widget_show(albumview_vbox);
264 static void albumview_unselected(GtkWidget *container)
266 gtk_container_remove(GTK_CONTAINER(container), albumview_vbox);
271 void albumview_plugin_init(void)
273 gchar *path = gmpc_plugin_get_data_path(&plugin);
274 gchar *url = g_build_path(G_DIR_SEPARATOR_S,path, "albumview", NULL);
275 debug_printf(DEBUG_WARNING,"Found url: %s\n", url);
278 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default (),url);
280 g_free(path);
281 g_free(url);
283 #define TIMER_SUB(start,stop,diff) diff.tv_usec = stop.tv_usec - start.tv_usec;\
284 diff.tv_sec = stop.tv_sec - start.tv_sec;\
285 if(diff.tv_usec < 0) {\
286 diff.tv_sec -= 1; \
287 diff.tv_usec += G_USEC_PER_SEC; \
289 static gint __add_sort(gpointer aa, gpointer bb, gpointer data)
291 MpdData_real *a = *(MpdData_real **)aa;
292 MpdData_real *b = *(MpdData_real **)bb;
293 if(!a || !b) return 0;
294 if(a->type == MPD_DATA_TYPE_SONG && b->type == MPD_DATA_TYPE_SONG)
296 if(a->song->artist && b->song->artist)
298 int val;
300 gchar *sa,*sb;
301 sa = g_utf8_strdown(a->song->artist, -1);
302 sb = g_utf8_strdown(b->song->artist, -1);
303 val = g_utf8_collate(sa,sb);
304 g_free(sa);
305 g_free(sb);
306 }/* else {
307 val = (a == NULL)?((b==NULL)?0:-1):1;
309 return val;
312 return -1;
314 MpdData *complete_list = NULL;
315 GtkWidget *pb;
316 int max = 0, current = 0;
317 static gboolean load_list_itterate(MpdData *data)
319 MpdData *data2 = NULL;
320 current++;
321 if(max>0){
322 gchar *temp = g_strdup_printf("%i of %i albums loaded", current, max);
323 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pb), current/(double)max);
324 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pb), temp);
325 g_free(temp);
328 if(data)
330 mpd_database_search_field_start(connection, MPD_TAG_ITEM_ARTIST);
331 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, (data)->tag);
332 data2 = mpd_database_search_commit(connection);
333 if(data2)
335 mpd_Song *song = mpd_newSong();
336 song->album = g_strdup((data)->tag);
337 song->artist = g_strdup(data2->tag);
338 if(!mpd_data_is_last(data2))
340 /* test if server supports album artist */
341 if(mpd_server_tag_supported(connection, MPD_TAG_ITEM_ALBUM_ARTIST))
343 mpd_database_search_field_start(connection, MPD_TAG_ITEM_ALBUM_ARTIST);
344 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, (data)->tag);
345 MpdData *data3 = mpd_database_search_commit(connection);
346 if(mpd_data_is_last(data3)){
347 song->albumartist = g_strdup(data3->tag);
349 else{
350 mpd_freeSong(song);
351 song = NULL;
353 mpd_data_free(data3);
355 else {
356 mpd_freeSong(song);
357 song = NULL;
360 mpd_data_free(data2);
361 if(song){
362 complete_list = mpd_new_data_struct_append(complete_list);
363 complete_list->song = song;
364 complete_list->type = MPD_DATA_TYPE_SONG;
365 song = NULL;
369 (data) = mpd_data_get_next((data));
371 if(data == NULL){
372 complete_list = (MpdData *)misc_sort_mpddata(mpd_data_get_first(complete_list), (GCompareDataFunc)__add_sort, NULL);
373 printf("update view\n");
374 gtk_widget_destroy(pb);
375 update_view();
377 gtk_widget_set_sensitive(entry, TRUE);
378 return FALSE;
381 g_idle_add((GSourceFunc)load_list_itterate, data);
382 return FALSE;
385 static GtkWidget *table = NULL;
386 static void load_list(void)
388 mpd_data_free(complete_list);
389 complete_list = NULL;
391 pb = gtk_progress_bar_new();
392 gtk_box_pack_start(GTK_BOX(albumview_tree), pb, FALSE, FALSE, 0);
393 gtk_widget_show_all(albumview_tree);
394 mpd_database_search_field_start(connection, MPD_TAG_ITEM_ALBUM);
395 MpdData *iter,*data = mpd_database_search_commit(connection);
396 max = 0;
397 current = 0;
398 gtk_widget_set_sensitive(entry, FALSE);
399 for(iter = data; iter; iter = mpd_data_get_next_real(iter, FALSE)) max++;
400 g_idle_add((GSourceFunc)load_list_itterate, data);
402 void albumview_connection_changed(MpdObj *mi, int connect,void *usedata)
405 if(connect && albumview_vbox)
407 load_list();
409 else if(albumview_vbox){
410 mpd_data_free(complete_list);
411 complete_list = NULL;
414 static void album_add(GtkWidget *button, mpd_Song *song)
416 mpd_database_search_start(connection,TRUE);
418 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, song->album);
419 if(song->albumartist){
420 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM_ARTIST, song->albumartist);
421 }else{
422 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, song->artist);
424 MpdData *data = mpd_database_search_commit(connection);
425 for(;data;data = mpd_data_get_next(data)){
426 mpd_playlist_queue_add(connection, data->song->file);
428 mpd_playlist_queue_commit(connection);
430 static void album_view(GtkWidget *button ,mpd_Song *song)
432 if (song && song->artist && song->album) {
433 info2_activate();
434 info2_fill_album_view(song->artist, song->album);
438 static void album_replace(GtkWidget *button, mpd_Song *song)
440 mpd_playlist_clear(connection);
441 album_add(button, song);
442 mpd_player_play(connection);
444 static gboolean album_button_press(GtkWidget *item, GdkEventButton *event, mpd_Song *song)
446 printf("button press event %i\n", event->button);
447 if(event->button == 1 && event->type == GDK_BUTTON_PRESS)
449 GtkWidget *menu = gtk_menu_new();
450 GtkWidget *item;
452 item = gtk_image_menu_item_new_with_label("Album information");
453 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
454 gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_MENU));
455 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
456 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(album_view), song);
458 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD,NULL);
459 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
460 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(album_add), song);
462 /* replace the replace widget */
463 item = gtk_image_menu_item_new_with_label("Replace");
464 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
465 gtk_image_new_from_stock(GTK_STOCK_REDO, GTK_ICON_SIZE_MENU));
466 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
467 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(album_replace), song);
468 gtk_widget_show_all(menu);
469 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, event->time);
471 else if (event->button == 2){
472 album_add(NULL, song);
474 return TRUE;
477 static guint update_timeout = 0;
479 static gboolean update_view_real(void)
481 MpdData *complete_list_iter;
482 const char *search_query = gtk_entry_get_text(GTK_ENTRY(entry));
483 int i=0;
484 GTimeVal start, stop,diff;
485 int j=0;
486 gchar *artist= NULL;
487 int items =0;
488 MpdData *data = NULL;
489 printf("search query: %s\n", search_query);
490 GList *entries = NULL;
491 GList *list = (table)?gtk_container_get_children(GTK_CONTAINER(table)):NULL;
492 GList *iter;
493 if(table)
494 gtk_widget_hide(table);
495 for(iter = g_list_first(list); iter; iter = iter->next){
496 GtkWidget *widget = iter->data;
497 gtk_container_remove(GTK_CONTAINER(table), widget);
499 g_list_free(list);
500 list = NULL;
501 g_get_current_time(&start);
503 gtk_widget_show(albumview_tree);
506 * Create a filtered linked list
508 for(complete_list_iter = mpd_data_get_first(complete_list);
509 complete_list_iter;
510 complete_list_iter = mpd_data_get_next_real(complete_list_iter, FALSE))
512 if(search_query[0] == '\0' || strstr(complete_list_iter->song->artist, search_query)){
513 items++;
514 list = g_list_append(list, complete_list_iter);
518 g_get_current_time(&stop);
519 TIMER_SUB(start, stop, diff);
520 printf("Removing old stuff time elapsed: %lu s %lus us\n", diff.tv_sec, diff.tv_usec);
521 int rows = ceil(items/(double)supported_columns);
523 * Create holding table if it does not exist
525 if(!table){
526 GtkWidget *ali = gtk_alignment_new(0.0, 0.5, 0,0);
527 table = exo_wrap_table_new(TRUE);//gtk_table_new(rows, supported_columns, TRUE);
528 gtk_container_add(GTK_CONTAINER(ali), table);
529 gtk_box_pack_start(GTK_BOX(albumview_tree), ali, FALSE, FALSE, 0);
531 // else gtk_table_resize(GTK_TABLE(table), rows, supported_columns);
533 /* I know how large it is going to be.. so lets set the size */
534 gtk_widget_set_size_request(table, supported_columns*(album_size+20+6), (rows)*(album_size+40));
536 * Add albums
538 if((iter = g_list_first(list)))
541 complete_list_iter = iter->data;
542 if(complete_list_iter->song && (complete_list_iter->song->artist)[0] != '\0')
544 GtkWidget *vbox = complete_list_iter->userdata;
545 GtkWidget *item;
546 int a,b;
547 if(vbox == NULL){
548 GtkWidget *label = NULL;
549 gchar *temp = NULL;
550 /* Wrap it in a vbox */
551 vbox = gtk_vbox_new(FALSE, 3);
552 gtk_widget_set_size_request(vbox, album_size+20,album_size+40);
554 item = gmpc_metaimage_new_size(META_ALBUM_ART,album_size);
555 gtk_widget_set_has_tooltip(GTK_WIDGET(item), FALSE);
556 gmpc_metaimage_set_squared(GMPC_METAIMAGE(item), TRUE);
558 gmpc_metaimage_update_cover_from_song_delayed(GMPC_METAIMAGE(item), complete_list_iter->song);
560 gtk_box_pack_start(GTK_BOX(vbox), item, TRUE, TRUE, 0);
561 /* Set artist name */
562 if(complete_list_iter->song->albumartist){
563 GtkWidget *label = gtk_label_new(complete_list_iter->song->albumartist);
564 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
565 gtk_box_pack_end(GTK_BOX(vbox), label, FALSE, FALSE, 0);
566 }else{
567 GtkWidget *label = gtk_label_new(complete_list_iter->song->artist);
568 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
569 gtk_box_pack_end(GTK_BOX(vbox), label, FALSE, FALSE, 0);
572 /* Set album name */
573 label = gtk_label_new("");
574 temp = g_markup_printf_escaped("<b>%s</b>", complete_list_iter->song->album);
575 gtk_label_set_markup(GTK_LABEL(label), temp);
576 g_free(temp);
577 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
578 gtk_box_pack_end(GTK_BOX(vbox), label, FALSE, FALSE, 0);
581 /* Attach it to the song */
582 complete_list_iter->userdata = g_object_ref_sink(vbox);
583 complete_list_iter->freefunc = (void *)gtk_widget_destroy;
584 g_object_set_data(G_OBJECT(vbox), "item", item);
585 g_signal_connect(item, "button-press-event",
586 G_CALLBACK(album_button_press), complete_list_iter->song);
589 else{
590 item = g_object_get_data(G_OBJECT(vbox), "item");
591 /* Resize if needed */
592 if(album_size != gmpc_metaimage_get_size(GMPC_METAIMAGE(item))){
593 gtk_widget_set_size_request(vbox, album_size+20,album_size+40);
594 gmpc_metaimage_set_size(GMPC_METAIMAGE(item), album_size);
595 gmpc_metaimage_reload_image(GMPC_METAIMAGE(item));
599 entries = g_list_prepend(entries, vbox);
600 j++;
602 }while((iter = g_list_next(iter)));
603 /* remove list */
604 if(list) g_list_free(list);
605 list = NULL;
607 g_get_current_time(&stop);
608 TIMER_SUB(start, stop, diff);
609 printf("Creating/Updating albums time elapsed: %lu s %lus us\n", diff.tv_sec, diff.tv_usec);
610 for(iter = entries = g_list_reverse(entries); iter; iter = g_list_next(iter)){
611 gtk_container_add(GTK_CONTAINER(table), iter->data);
613 if(entries) g_list_free(entries);
614 TIMER_SUB(start, stop, diff);
615 printf("adding items to view: %lu s %lus us\n", diff.tv_sec, diff.tv_usec);
617 gtk_widget_show_all(albumview_tree);
618 g_get_current_time(&stop);
619 TIMER_SUB(start, stop, diff);
620 printf("Showing widget time elapsed: %lu s %lus us\n", diff.tv_sec, diff.tv_usec);
622 * Remove the timeout
624 if(update_timeout)
625 g_source_remove(update_timeout);
626 update_timeout = 0;
627 return FALSE;
631 void update_view()
633 if(update_timeout != 0) {
634 g_source_remove(update_timeout);
636 update_timeout = g_timeout_add(500, (GSourceFunc)update_view_real,NULL);