/* vim: set ai et ts=4 sw=4: */ #ifdef HAVE_CONFIG_H #include #endif #include #include "liteamp.h" #include "playlist.h" #include "dnd.h" #include "callbacks.h" #include "tag.h" #include "util.h" #include "decoder.h" /*-----------------------------------------------------------------*/ // // treeview column definitions and ... // // column title static const gchar* column_title[] = { "", "", N_("No."), N_("Title"), N_("Artist"), N_("Album"), N_("Track"), N_("Year"), N_("Genre"), N_("Time"), N_("Quality"), N_("Rating"), N_("Comment"), N_("Filename"), N_("Bytes"), N_("Seconds"), NULL }; // values to pass playlist_set_current_icon() static const gchar* ICON_START = "start.png"; static const gchar* ICON_PAUSE = "pause.png"; static const gchar* ICON_BLANK = NULL; /*-----------------------------------------------------------------*/ // // clipboard supports // TODO: support generic clipboard with GtkClipboard // // just a local clipboard static GPtrArray* clipboard; static void clipboard_received_cb( GtkClipboard* clipboard, GtkSelectionData* selection_data, Playlist* pl); static void clipboard_get_cb( GtkClipboard* clipboard, GtkSelectionData* selection_data, guint info, Playlist* pl); static void clipboard_clear_cb( GtkClipboard* clipboard, Playlist* pl); /*-----------------------------------------------------------------*/ // // prototypes // static void row_activated_cb( GtkTreeView* view, GtkTreePath* path, GtkTreeViewColumn* column, Playlist* pl); static void selection_changed_cb( GtkTreeSelection* selection, Playlist* pl); static void check_renderer_toggled_cb( GtkCellRendererToggle* renderer, gchar* path_str, Playlist* pl); /*-----------------------------------------------------------------*/ // // boiler plate // static void playlist_class_init(PlaylistClass* class); static void playlist_init(Playlist* pl); static GtkWidgetClass *parent_class = NULL; GType playlist_get_type() { static GType playlist_type = 0; if(!playlist_type) { GTypeInfo playlist_info = { sizeof(PlaylistClass), NULL, NULL, (GClassInitFunc) playlist_class_init, NULL, NULL, sizeof(Playlist), 0, (GInstanceInitFunc) playlist_init }; playlist_type = g_type_register_static(GTK_TYPE_TREE_VIEW, "Playlist", &playlist_info, 0); } return playlist_type; } static void playlist_class_init(PlaylistClass* klass) { // nothing to do GtkObjectClass* object_class; GtkWidgetClass* widget_class; object_class = (GtkObjectClass*)klass; widget_class = (GtkWidgetClass*)klass; parent_class = gtk_type_class(gtk_widget_get_type()); } static void playlist_init(Playlist* pl) { // nothing to do } /*-----------------------------------------------------------------*/ // // playlist management // GtkWidget* playlist_new() { Playlist* pl; pl = g_object_new(playlist_get_type(), NULL); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pl), TRUE); // create treeview model pl->model = GTK_TREE_MODEL(gtk_list_store_new(PL_N_COLUMNS, GDK_TYPE_PIXBUF,//icon G_TYPE_BOOLEAN, //check G_TYPE_INT, //number G_TYPE_STRING, //title G_TYPE_STRING, //artist G_TYPE_STRING, //album G_TYPE_STRING, //track G_TYPE_STRING, //year G_TYPE_STRING, //genre G_TYPE_STRING, //time G_TYPE_STRING, //quality G_TYPE_STRING, //rating G_TYPE_STRING, //comment G_TYPE_STRING, //filename G_TYPE_INT, //bytes G_TYPE_INT //seconds )); // set model gtk_tree_view_set_model(GTK_TREE_VIEW(pl), pl->model); // create and append columns as prefs playlist_init_columns(pl); // signals for treeview widget g_signal_connect( G_OBJECT(pl), "row-activated", G_CALLBACK(row_activated_cb), pl); // enable multiple selection pl->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(pl)); gtk_tree_selection_set_mode(pl->selection, GTK_SELECTION_MULTIPLE); // signals for treeview selection g_signal_connect( G_OBJECT(pl->selection), "changed", G_CALLBACK(selection_changed_cb), pl); // drag and drop supports //dnd_enable(GTK_WIDGET(pl)); dnd_connect(GTK_WIDGET(pl)); // clipboard supports // TODO: supports generic clipboard with GtkClipboard // ... // initilize playlist playlist_set_filename(pl, NULL); return GTK_WIDGET(pl); } static void playlist_add_column(Playlist* pl, gint index) { GtkCellRenderer* renderer; GtkTreeViewColumn* column; g_return_if_fail(index >= 0 && index < PL_N_COLUMNS); switch(index) { case PL_ICON_COLUMN: renderer = gtk_cell_renderer_pixbuf_new(); column = gtk_tree_view_column_new_with_attributes( "", renderer, "pixbuf", PL_ICON_COLUMN, NULL); gtk_tree_view_column_set_min_width(column, 20); gtk_tree_view_column_set_fixed_width(column, 20); gtk_tree_view_append_column(GTK_TREE_VIEW(pl), column); break; case PL_CHECK_COLUMN: renderer = gtk_cell_renderer_toggle_new(); column = gtk_tree_view_column_new_with_attributes( "", renderer, "active", PL_CHECK_COLUMN, NULL); gtk_tree_view_column_set_min_width(column, 20); gtk_tree_view_column_set_fixed_width(column, 20); gtk_tree_view_append_column(GTK_TREE_VIEW(pl), column); g_signal_connect( G_OBJECT(renderer), "toggled", G_CALLBACK(check_renderer_toggled_cb), pl); break; case PL_NUMBER_COLUMN: case PL_TITLE_COLUMN: case PL_ARTIST_COLUMN: case PL_ALBUM_COLUMN: case PL_YEAR_COLUMN: case PL_TRACK_COLUMN: case PL_GENRE_COLUMN: case PL_TIME_COLUMN: case PL_QUALITY_COLUMN: case PL_RATING_COLUMN: case PL_COMMENT_COLUMN: case PL_FILENAME_COLUMN: case PL_BYTES_COLUMN: case PL_SECONDS_COLUMN: renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _(column_title[index]), renderer, "text", index, NULL); gtk_tree_view_column_set_min_width(column, 20); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_append_column(GTK_TREE_VIEW(pl), column); break; } } void playlist_init_columns(Playlist* pl) { GList* columns; GtkTreeViewColumn* column; gchar** prefs_columns; gint index; columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(pl)); while(columns != NULL && columns->data != NULL) { GtkTreeViewColumn* column = columns->data; gtk_tree_view_remove_column(GTK_TREE_VIEW(pl), column); columns = g_list_next(columns); } g_list_free(columns); prefs_columns = g_strsplit(prefs.playlist_columns, ",", 0); for(index = 0; prefs_columns[index] != NULL; index++) { playlist_add_column(pl, prefs_get_playlist_column_index(prefs_columns[index])); } } // FIXME: this function works two puzzled way. :-( // when filename paramter is NULL, it just cleans-up playlist. // otherwise, it cleans-up and reads the specified file. void playlist_set_filename(Playlist* pl, const gchar* filename) { g_return_if_fail(IS_PLAYLIST(pl)); // save current(old) playlist if(pl->filename != NULL) { playlist_write(pl); g_free(pl->filename); } // clear tree list view gtk_list_store_clear(GTK_LIST_STORE(pl->model)); pl->filename = NULL; pl->total_bytes = pl->total_seconds = pl->total_count = 0; pl->check_bytes = pl->check_seconds = pl->check_count = 0; pl->current = 0; if(filename) { pl->filename = g_strdup(filename); playlist_read(pl); dnd_enable(GTK_WIDGET(pl)); } // TODO: restore play status icon } void playlist_read(Playlist* pl) { playlist_import_pls(pl, pl->filename); playlist_shuffle(pl); } void playlist_write(Playlist* pl) { playlist_export_pls(pl, pl->filename); } /*-----------------------------------------------------------------*/ void playlist_cut(Playlist* pl) { playlist_copy(pl); playlist_clear(pl); } static void copy_selection_foreach_cb(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, Playlist* pl) { gchar* filename; gtk_tree_model_get(model, iter, PL_FILENAME_COLUMN, &filename, -1); g_ptr_array_add(clipboard, filename); } void playlist_copy(Playlist* pl) { g_return_if_fail(IS_PLAYLIST(pl)); // make empty clipboard if(clipboard) g_ptr_array_free(clipboard, TRUE); clipboard = g_ptr_array_new(); gtk_tree_selection_selected_foreach(pl->selection, (GtkTreeSelectionForeachFunc)copy_selection_foreach_cb, pl); } void playlist_paste(Playlist* pl) { guint index; gchar* filename; g_return_if_fail(!playlist_clipboard_is_empty(pl)); for(index = 0; index < clipboard->len; index++) { filename = g_ptr_array_index(clipboard, index); playlist_add_file(pl, filename, TRUE); } playlist_renumber(pl); playlist_write(pl); } //see comments of playlist_clear() //static void clear_selection_foreach_cb(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, Playlist* pl) //{ // playlist_remove_row(pl, &iter); //} void playlist_clear(Playlist* pl) { GtkTreeIter iter; GtkTreePath* path; gboolean valid; gint row_count; g_return_if_fail(IS_PLAYLIST(pl)); // save current cursor gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl), &path, NULL); // the following codes doesn't works for me. :-( // in callback, gtk_list_store_remove() modifies the iterator itself // so, ..._foreach() iterates only for odd(or even) rows. // //gtk_tree_selection_selected_foreach(pl->selection, // (GtkTreeSelectionForeachFunc)cut_selection_foreach_cb, pl); valid = gtk_tree_model_get_iter_first(pl->model, &iter); while(valid) { if(gtk_tree_selection_iter_is_selected(pl->selection, &iter)) { playlist_remove_row(pl, &iter); //FIXME: just workaround :-( valid = (iter.stamp != 0); } else { valid = gtk_tree_model_iter_next(pl->model, &iter); } } // restore cursor if(path != NULL) { gtk_tree_view_set_cursor(GTK_TREE_VIEW(pl), path, NULL, FALSE); gtk_tree_path_free(path); } pl->current = 0; playlist_renumber(pl); playlist_write(pl); } void playlist_select_all(Playlist* pl) { gtk_tree_selection_select_all(pl->selection); } void playlist_select_none(Playlist* pl) { gtk_tree_selection_unselect_all(pl->selection); } void playlist_invert_selection(Playlist* pl) { GtkTreeIter iter; gboolean valid; valid = gtk_tree_model_get_iter_first(pl->model, &iter); while(valid) { if(gtk_tree_selection_iter_is_selected(pl->selection, &iter)) gtk_tree_selection_unselect_iter(pl->selection, &iter); else gtk_tree_selection_select_iter(pl->selection, &iter); valid = gtk_tree_model_iter_next(pl->model, &iter); } } gboolean playlist_show_dialog(Playlist* pl) { GtkWidget *dlg; GtkWidget *fileinfo_vb; GtkWidget *taginfo_vb; GtkWidget *filename_hb; GtkWidget *filename_lbl; GtkWidget *filename_ent; GtkWidget *taginfo_hb; GtkWidget *tag_frm; GtkWidget *tag_tbl; GtkWidget *title_lbl; GtkWidget *artist_lbl; GtkWidget *album_lbl; GtkWidget *comment_lbl; GtkWidget *date_lbl; GtkWidget *track_lbl; GtkWidget *genre_lbl; GtkWidget *title_ent; GtkWidget *artist_ent; GtkWidget *album_ent; GtkWidget *comment_ent; GtkWidget *date_ent; GtkWidget *track_ent; GtkWidget *genre_cmb; GtkWidget *genre_ent; GtkWidget *tag_lbl; GtkWidget *info_frm; GtkWidget *info_text_lbl; GtkWidget *info_lbl; Tag* tag; gchar temp_str[300]; gchar time_str[20]; gchar size_str[20]; gchar* filename; gchar* filename_str; GtkTreeIter iter; GtkTreePath* path; g_return_if_fail(IS_PLAYLIST(pl)); // get the focused row to show properties gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl), &path, NULL); if(path == NULL) return FALSE; if(!gtk_tree_model_get_iter(pl->model, &iter, path)) return FALSE; gtk_tree_model_get(pl->model, &iter, PL_FILENAME_COLUMN, &filename, -1); // read tags tag = tag_new_from_file(filename); // create dialog and widgets dlg = gtk_dialog_new_with_buttons(_("Properties"), liteamp_get_app_window(), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_window_set_default_size(GTK_WINDOW(dlg), 300, 200); gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK); fileinfo_vb = GTK_DIALOG(dlg)->vbox; taginfo_vb = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(fileinfo_vb), taginfo_vb, TRUE, TRUE, 0); filename_hb = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(taginfo_vb), filename_hb, FALSE, TRUE, 3); filename_lbl = gtk_label_new(_("Filename")); gtk_box_pack_start(GTK_BOX(filename_hb), filename_lbl, FALSE, FALSE, 0); gtk_label_set_justify(GTK_LABEL(filename_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(filename_lbl), 1, 0.5); filename_ent = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(filename_hb), filename_ent, TRUE, TRUE, 0); taginfo_hb = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(taginfo_vb), taginfo_hb, TRUE, TRUE, 0); tag_frm = gtk_frame_new(NULL); gtk_box_pack_start(GTK_BOX(taginfo_hb), tag_frm, TRUE, TRUE, 0); tag_tbl = gtk_table_new(7, 2, FALSE); gtk_container_add(GTK_CONTAINER(tag_frm), tag_tbl); gtk_container_set_border_width(GTK_CONTAINER(tag_tbl), 3); gtk_table_set_row_spacings(GTK_TABLE(tag_tbl), 2); gtk_table_set_col_spacings(GTK_TABLE(tag_tbl), 2); title_lbl = gtk_label_new(_("Title")); gtk_table_attach(GTK_TABLE(tag_tbl), title_lbl, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(title_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(title_lbl), 1, 0.5); artist_lbl = gtk_label_new(_("Artist")); gtk_table_attach(GTK_TABLE(tag_tbl), artist_lbl, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(artist_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(artist_lbl), 1, 0.5); album_lbl = gtk_label_new(_("Album")); gtk_table_attach(GTK_TABLE(tag_tbl), album_lbl, 0, 1, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(album_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(album_lbl), 1, 0.5); comment_lbl = gtk_label_new(_("Comment")); gtk_table_attach(GTK_TABLE(tag_tbl), comment_lbl, 0, 1, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(comment_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(comment_lbl), 1, 0.5); date_lbl = gtk_label_new(_("Date")); gtk_table_attach(GTK_TABLE(tag_tbl), date_lbl, 0, 1, 4, 5, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(date_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(date_lbl), 1, 0.5); track_lbl = gtk_label_new(_("Track")); gtk_table_attach(GTK_TABLE(tag_tbl), track_lbl, 0, 1, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(track_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(track_lbl), 1, 0.5); genre_lbl = gtk_label_new(_("Genre")); gtk_table_attach(GTK_TABLE(tag_tbl), genre_lbl, 0, 1, 6, 7, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_label_set_justify(GTK_LABEL(genre_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(genre_lbl), 1, 0.5); title_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), title_ent, 1, 2, 0, 1, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); artist_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), artist_ent, 1, 2, 1, 2, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); album_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), album_ent, 1, 2, 2, 3, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); comment_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), comment_ent, 1, 2, 3, 4, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); date_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), date_ent, 1, 2, 4, 5, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); track_ent = gtk_entry_new(); gtk_table_attach(GTK_TABLE(tag_tbl), track_ent, 1, 2, 5, 6, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); genre_cmb = gtk_combo_new(); gtk_table_attach(GTK_TABLE(tag_tbl), genre_cmb, 1, 2, 6, 7, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); genre_ent = GTK_COMBO(genre_cmb)->entry; tag_lbl = gtk_label_new(_("Tag")); gtk_frame_set_label_widget(GTK_FRAME(tag_frm), tag_lbl); gtk_label_set_justify(GTK_LABEL(tag_lbl), GTK_JUSTIFY_LEFT); info_frm = gtk_frame_new(NULL); gtk_box_pack_start(GTK_BOX(taginfo_hb), info_frm, TRUE, TRUE, 0); info_text_lbl = gtk_label_new(""); gtk_container_add(GTK_CONTAINER(info_frm), info_text_lbl); gtk_label_set_justify(GTK_LABEL(info_text_lbl), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(info_text_lbl), 0, 0); gtk_misc_set_padding(GTK_MISC(info_text_lbl), 2, 2); info_lbl = gtk_label_new(_("Info")); gtk_frame_set_label_widget(GTK_FRAME(info_frm), info_lbl); gtk_label_set_justify(GTK_LABEL(info_lbl), GTK_JUSTIFY_LEFT); gtk_widget_show_all(dlg); // update tag values to widget filename_str = filename_to_utf8(filename); gtk_entry_set_text(GTK_ENTRY(filename_ent), filename_str); g_free(filename_str); gtk_entry_set_text(GTK_ENTRY(title_ent), tag->title); if(tag->album) { gtk_entry_set_text(GTK_ENTRY(album_ent), tag->album); } if(tag->artist) { gtk_entry_set_text(GTK_ENTRY(artist_ent), tag->artist); } if(tag->genre) { gtk_entry_set_text(GTK_ENTRY(genre_ent), tag->genre); } if(tag->comment) { gtk_entry_set_text(GTK_ENTRY(comment_ent), tag->comment); } if(tag->year > 0) { g_snprintf(temp_str, sizeof(temp_str), "%u", tag->year); gtk_entry_set_text(GTK_ENTRY(date_ent), temp_str); } if(tag->track > 0) { g_snprintf(temp_str, sizeof(temp_str), "%u", tag->track); gtk_entry_set_text(GTK_ENTRY(track_ent), temp_str); } if(tag->channels > 0 && tag->rate > 0 && tag->bitrate > 0) { g_snprintf(temp_str, sizeof(temp_str), _("Channels: %d\nSample Rate: %.1fKHz\nBit Rate: %.1fKB/s\nFrames: %u\nPlay Time: %s\nFile Size: %s"), tag->channels, tag->rate / 1000.0, tag->bitrate / 1000.0, tag->frames, format_time_str(time_str, sizeof(time_str), tag->seconds), format_size_str(size_str, sizeof(size_str), file_length(filename))); gtk_label_set_text(GTK_LABEL(info_text_lbl), temp_str); } tag_free(tag); // disable editable widgets now gtk_widget_set_sensitive(filename_ent, FALSE); gtk_widget_set_sensitive(title_ent, FALSE); gtk_widget_set_sensitive(album_ent, FALSE); gtk_widget_set_sensitive(artist_ent, FALSE); gtk_widget_set_sensitive(genre_ent, FALSE); gtk_widget_set_sensitive(comment_ent, FALSE); gtk_widget_set_sensitive(date_ent, FALSE); gtk_widget_set_sensitive(track_ent, FALSE); if(gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK) { //TODO: write back tags to file //... } gtk_widget_destroy(dlg); } /*-----------------------------------------------------------------*/ void playlist_prev(Playlist* pl) { gint row, row_count; GtkTreeIter iter; gboolean check; g_return_if_fail(IS_PLAYLIST(pl)); playlist_set_current_icon(pl, ICON_BLANK); row_count = playlist_get_row_count(pl); row = pl->current; while(--row != pl->current) { // no infinite loop // wrap around if needed if(row < 0) { if(!prefs.play_loop) { playlist_stop(pl); return; } row = row_count - 1; } if(!playlist_get_row(pl, &iter, row)) break; gtk_tree_model_get(pl->model, &iter, PL_CHECK_COLUMN, &check, -1); if(check) { pl->current = row; playlist_start(pl); return; } } } void playlist_next(Playlist* pl) { gint row, row_count; GtkTreeIter iter; gboolean check; g_return_if_fail(IS_PLAYLIST(pl)); playlist_set_current_icon(pl, ICON_BLANK); row_count = playlist_get_row_count(pl); row = pl->current; while(++row != pl->current) { // no infinite loop // wrap around if needed if(row >= row_count) { if(!prefs.play_loop) { playlist_stop(pl); return; } row = 0; } if(!playlist_get_row(pl, &iter, row)) break; gtk_tree_model_get(pl->model, &iter, PL_CHECK_COLUMN, &check, -1); if(check) { pl->current = row; playlist_start(pl); return; } } } void playlist_start(Playlist* pl) { gchar* filename; g_return_if_fail(IS_PLAYLIST(pl)); filename = playlist_get_current_file(pl); if(filename != NULL) { decoder_play(filename); liteamp_update_ui_play_info(filename); playlist_set_current_icon(pl, ICON_START); } else { liteamp_update_ui_play_info(NULL); playlist_set_current_icon(pl, ICON_BLANK); } } void playlist_stop(Playlist* pl) { //if(!get_decoder_playing()) return; //if(liteamp.play_status != PLAY_STATUS_STOP) return; decoder_stop(); liteamp_update_ui_play_info(NULL); playlist_set_current_icon(pl, ICON_BLANK); } void playlist_pause(Playlist* pl) { g_return_if_fail(IS_PLAYLIST(pl)); decoder_pause(); if(liteamp.play_status == PLAY_STATUS_PAUSE) { liteamp.play_status = PLAY_STATUS_PLAY; playlist_set_current_icon(pl, ICON_START); } else { liteamp.play_status = PLAY_STATUS_PAUSE; playlist_set_current_icon(pl, ICON_PAUSE); } liteamp_update_ui_play_status(); } void playlist_refresh(Playlist* pl) { g_return_if_fail(IS_PLAYLIST(pl)); playlist_init_columns(pl); playlist_renumber(pl); } /*-----------------------------------------------------------------*/ void playlist_add_file(Playlist* pl, const gchar* filename, gboolean check) { Tag* tag; gint number; gchar year_str[10]; gchar track_str[10]; gchar time_str[20]; gchar quality_str[20]; gint bytes; GtkTreeIter iter; GtkTreePath* path; g_return_if_fail(IS_PLAYLIST(pl)); if(!g_file_test(filename, G_FILE_TEST_EXISTS)) { g_print("file not found: %s\n", filename); return; } bytes = file_length(filename); // FIXME: check the file is valid mp3 or ogg file if(bytes <= 0) return; tag = tag_new_from_file(filename); if(!tag) return; // FIXME: check the file is valid mp3 or ogg file if(tag->seconds <= 0) return; if(tag->year > 0) g_snprintf(year_str, sizeof(year_str), "%u", tag->year); else year_str[0] = 0; if(tag->track > 0) g_snprintf(track_str, sizeof(track_str), "%u", tag->track); else track_str[0] = 0; if(tag->seconds > 0) { format_time_str(time_str, sizeof(time_str), tag->seconds); } else { time_str[0] = 0; } if(tag->channels > 0 && tag->rate > 0 && tag->bitrate > 0) g_snprintf(quality_str, sizeof(quality_str), _("%dCh %dKHz %dKB/s"), tag->channels, tag->rate / 1000, tag->bitrate / 1000); else quality_str[0] = 0; gtk_list_store_append(GTK_LIST_STORE(pl->model), &iter); path = gtk_tree_model_get_path(pl->model, &iter); number = gtk_tree_path_get_indices(path)[0] + 1; gtk_list_store_set(GTK_LIST_STORE(pl->model), &iter, PL_ICON_COLUMN, NULL, PL_CHECK_COLUMN, check, PL_NUMBER_COLUMN, number, PL_TITLE_COLUMN, NVL(tag->title), PL_ARTIST_COLUMN, NVL(tag->artist), PL_ALBUM_COLUMN, NVL(tag->album), PL_YEAR_COLUMN, year_str, PL_TRACK_COLUMN, track_str, PL_GENRE_COLUMN, NVL(tag->genre), PL_TIME_COLUMN, time_str, PL_QUALITY_COLUMN, quality_str, PL_RATING_COLUMN, 0, PL_COMMENT_COLUMN, NVL(tag->comment), PL_FILENAME_COLUMN, filename, PL_BYTES_COLUMN, bytes, PL_SECONDS_COLUMN, tag->seconds, -1); // update summary values for status text pl->total_bytes += bytes; pl->total_seconds += tag->seconds; pl->total_count++; if(check) { pl->check_bytes += bytes; pl->check_seconds += tag->seconds; pl->check_count++; } tag_free(tag); g_free(pl->play_order); pl->play_order = NULL; } void playlist_add_dir(Playlist* pl, const gchar* dirname, gboolean check) { GDir* dir; const gchar* name; gchar* filename; g_return_if_fail(IS_PLAYLIST(pl)); g_print("add dir: %s\n", dirname); dir = g_dir_open(dirname, 0, NULL); if(!dir) return; while((name = g_dir_read_name(dir)) != NULL) { filename = g_build_filename(dirname, name, NULL); if(g_file_test(filename, G_FILE_TEST_IS_DIR)) { // recursive call playlist_add_dir(pl, filename, check); } else { playlist_add_file(pl, filename, check); } g_free(filename); } g_dir_close(dir); } void playlist_import_pls(Playlist* pl, const gchar* pls_filename) { gint num, row_count; gchar key[256]; gchar* filename; gboolean check; g_return_if_fail(IS_PLAYLIST(pl)); g_snprintf(key, sizeof(key), "=%s=/playlist/", pls_filename); gnome_config_push_prefix(key); row_count = gnome_config_get_int("NumberOfEntries=0"); for(num = 1; num <= row_count; num++) { g_snprintf(key, sizeof(key), "File%d", num); filename = gnome_config_get_string(key); // custom extension g_snprintf(key, sizeof(key), "Check%d=true", num); check = gnome_config_get_bool(key); playlist_add_file(pl, filename, check); g_free(filename); } gnome_config_pop_prefix(); } void playlist_export_pls(Playlist* pl, const gchar* pls_filename) { GtkTreeIter iter; gboolean valid; gint num; gchar key[256]; gchar* filename; gboolean check; g_return_if_fail(IS_PLAYLIST(pl)); g_snprintf(key, sizeof(key), "=%s=/playlist/", pls_filename); gnome_config_clean_section(key); gnome_config_push_prefix(key); gnome_config_set_int("NumberOfEntries", playlist_get_row_count(pl)); num = 0; valid = gtk_tree_model_get_iter_first(pl->model, &iter); while(valid) { num++; gtk_tree_model_get(pl->model, &iter, PL_FILENAME_COLUMN, &filename, PL_CHECK_COLUMN, &check, -1); g_snprintf(key, sizeof(key), "File%d", num); gnome_config_set_string(key, filename); g_free(filename); if(!check) { g_snprintf(key, sizeof(key), "Check%d", num); gnome_config_set_bool(key, check); } valid = gtk_tree_model_iter_next(pl->model, &iter); } gnome_config_pop_prefix(); gnome_config_sync(); } void playlist_update_row(Playlist* pl, GtkTreeIter* iter) { g_return_if_fail(IS_PLAYLIST(pl)); //TODO: save tag info here //gtk_list_store_get(GTK_LIST_STORE(pl->model), &iter, // -1); } void playlist_remove_row(Playlist* pl, GtkTreeIter* iter) { gboolean check; gint bytes, seconds; g_return_if_fail(IS_PLAYLIST(pl)); gtk_tree_model_get(pl->model, iter, PL_CHECK_COLUMN, &check, PL_BYTES_COLUMN, &bytes, PL_SECONDS_COLUMN, &seconds, -1); // update summary values for status text pl->total_bytes -= bytes; pl->total_seconds -= seconds; pl->total_count--; if(check) { pl->check_bytes -= bytes; pl->check_seconds -= seconds; pl->check_count--; } gtk_list_store_remove(GTK_LIST_STORE(pl->model), iter); } void playlist_renumber(Playlist* pl) { gint row, row_count; GtkTreeIter iter; g_return_if_fail(IS_PLAYLIST(pl)); row_count = playlist_get_row_count(pl); for(row = 0; row < row_count; row++) { if(!playlist_get_row(pl, &iter, row)) break; gtk_list_store_set(GTK_LIST_STORE(pl->model), &iter, PL_NUMBER_COLUMN, row + 1, -1); } } void playlist_shuffle(Playlist* pl) { gint i, j, row, row_count; g_free(pl->play_order); row_count = playlist_get_row_count(pl); pl->play_order = g_new0(gint, row_count); if(prefs.play_shuffle) { // make an shuffled play order srand(clock()); for(i = 0; i < row_count; i++) { RAND_AGAIN: row = (int)((double)row_count * rand() / (RAND_MAX + 1.0)); // see if it's not duplicated? for(j = 0; j < i; j++) { if(pl->play_order[j] == row) goto RAND_AGAIN; } pl->play_order[i] = row; } } else { // make an not shuffled play order for(i = 0; i < row_count; i++) { pl->play_order[i] = i; } } } /*-----------------------------------------------------------------*/ gint playlist_get_row_count(Playlist* pl) { gint ret; g_return_val_if_fail(IS_PLAYLIST(pl), 0); ret = gtk_tree_model_iter_n_children(pl->model, NULL); g_assert(ret == pl->total_count);//DEBUG return ret; } gboolean playlist_get_row(Playlist* pl, GtkTreeIter* iter, gint row) { g_return_val_if_fail(IS_PLAYLIST(pl), FALSE); if(!pl->play_order) playlist_shuffle(pl); return gtk_tree_model_iter_nth_child(pl->model, iter, NULL, pl->play_order[row]); } gboolean playlist_get_current_row(Playlist* pl, GtkTreeIter* iter) { g_return_val_if_fail(IS_PLAYLIST(pl), FALSE); if(!pl->play_order) playlist_shuffle(pl); return gtk_tree_model_iter_nth_child(pl->model, iter, NULL, pl->play_order[pl->current]); } gchar* playlist_get_current_file(Playlist* pl) { GtkTreeIter iter; gchar* filename; g_return_if_fail(IS_PLAYLIST(pl)); if(!playlist_get_current_row(pl, &iter)) return NULL; gtk_tree_model_get(pl->model, &iter, PL_FILENAME_COLUMN, &filename, -1); return filename; } void playlist_set_current_icon(Playlist* pl, const gchar* filename) { GtkTreeIter iter; GdkPixbuf* pixbuf; g_return_if_fail(IS_PLAYLIST(pl)); if(!playlist_get_current_row(pl, &iter)) return; pixbuf = (filename) ? create_pixbuf(filename) : NULL; gtk_list_store_set(GTK_LIST_STORE(pl->model), &iter, PL_ICON_COLUMN, pixbuf, -1); } gboolean playlist_clipboard_is_empty(Playlist* pl) { return ((clipboard == NULL) || (clipboard->len <= 0)); } gchar* playlist_get_status_text(Playlist* pl) { gchar check_time[20]; gchar check_size[20]; gchar total_time[20]; gchar total_size[20]; format_time_str(check_time, sizeof(check_time), pl->check_seconds); format_time_str(total_time, sizeof(total_time), pl->total_seconds); format_size_str(check_size, sizeof(check_size), pl->check_bytes); format_size_str(total_size, sizeof(total_size), pl->total_bytes); return g_strdup_printf( _("Titles: %d / %d, Play Time: %s / %s, File Size: %s / %s"), pl->check_count, pl->total_count, check_time, total_time, check_size, total_size); } /*-----------------------------------------------------------------*/ // // callbacks // /** * an playlist item is activated(double clicked!). * play it now! */ static void row_activated_cb( GtkTreeView* view, GtkTreePath* path, GtkTreeViewColumn* column, Playlist* pl) { playlist_stop(pl); pl->current = gtk_tree_path_get_indices(path)[0]; if(!pl->play_order) playlist_shuffle(pl); // FIXME: somewhat ugly :-( just workaround if(prefs.play_shuffle) { gint row_count, i; row_count = playlist_get_row_count(pl); for(i = 0; i < row_count; i++) { if(pl->current == pl->play_order[i]) { pl->current = i; break; } } } playlist_start(pl); } /** * selection changed */ static void selection_changed_cb( GtkTreeSelection* selection, Playlist* pl) { //probably something useful... } /** * checked or not for an each playlist item */ static void check_renderer_toggled_cb( GtkCellRendererToggle* renderer, gchar* path_str, Playlist* pl) { GtkTreePath* path; GtkTreeIter iter; gboolean check; gint bytes, seconds; path = gtk_tree_path_new_from_string(path_str); gtk_tree_model_get_iter(pl->model, &iter, path); gtk_tree_model_get(pl->model, &iter, PL_CHECK_COLUMN, &check, PL_BYTES_COLUMN, &bytes, PL_SECONDS_COLUMN, &seconds, -1); check = !check; gtk_list_store_set(GTK_LIST_STORE(pl->model), &iter, PL_CHECK_COLUMN, check, -1); g_free(path); // update summary values for status text if(check) { pl->check_bytes += bytes; pl->check_seconds += seconds; pl->check_count++; } else { pl->check_bytes -= bytes; pl->check_seconds -= seconds; pl->check_count--; } liteamp_update_ui_status(); playlist_write(pl); } /** * callback for gtk_clipboard_request_contents() */ static void clipboard_received_cb( GtkClipboard* clipboard, GtkSelectionData* selection_data, Playlist* pl) { } /** * callback for gtk_clipboard_set_with_data() */ static void clipboard_get_cb( GtkClipboard* clipboard, GtkSelectionData* selection_data, guint info, Playlist* pl) { } /** * callback for gtk_clipboard_set_with_data() */ static void clipboard_clear_cb( GtkClipboard* clipboard, Playlist* pl) { } /*playlist.c*/