/* * Copyright 2014 (c) Anna Schumaker. */ extern "C" { #include #include #include #include #include #include #include } #include #include #include static std::map queue_mapping; static compare_t sort_fields[] = { COMPARE_TRACK, COMPARE_TITLE, COMPARE_LENGTH, COMPARE_ARTIST, COMPARE_ALBUM, COMPARE_YEAR, COMPARE_GENRE, COMPARE_COUNT, COMPARE_PLAYED }; static void *tempq_init(struct queue *queue) { return gui_queue_alloc(queue, "Queued Tracks", GQ_CAN_RANDOM | GQ_CAN_REPEAT); } static void tempq_added(struct queue *queue, unsigned int pos) { Tab *tab = find_tab(queue); if (tab) { tab->on_track_added(pos); tempq_save(queue, Q_ENABLED); } } static void tempq_removed(struct queue *queue, unsigned int pos) { find_tab(queue)->on_track_removed(pos); tempq_save(queue, Q_ENABLED); } static void tempq_cleared(struct queue *queue, unsigned int n) { find_tab(queue)->on_tracks_cleared(n); } static void tempq_updated(struct queue *queue, unsigned int pos) { find_tab(queue)->on_track_updated(pos); } struct queue_ops tempq_ops = { tempq_init, gui_queue_free, tempq_added, tempq_removed, tempq_cleared, tempq_save, tempq_updated, }; /** * * Tab class basics * */ Tab :: Tab(queue *pq) : tab_sorting_count(0), tab_pq(pq), tab_label(NULL) { queue_mapping[tab_pq] = this; tab_builder = Gtk::Builder::create(); tab_builder->add_from_file(gui :: share_file("QueueToolbar.ui")); tab_builder->add_from_file(gui :: share_file("QueueWindow.ui")); tab_builder->get_widget_derived("QueueToolbar", tab_toolbar); tab_builder->get_widget_derived("QueueWindow", tab_window); tab_window->init(tab_pq); tab_window->q_treeview->signal_key_press_event().connect(sigc::mem_fun(*this, &Tab :: on_key_pressed)); tab_window->q_treeview->signal_button_press_event().connect(sigc::mem_fun(*this, &Tab :: on_button_pressed), false); for (unsigned int i = 0; i < tab_window->q_treeview->get_n_columns(); i++) tab_window->q_treeview->get_column(i)->signal_clicked().connect( sigc::bind (sigc::mem_fun( *this, &Tab::on_column_clicked), i)); tab_vbox.set_margin_start(1); tab_vbox.set_margin_end(1); tab_vbox.set_homogeneous(false); tab_vbox.pack_start(*tab_toolbar, false, true, 2); tab_vbox.show(); tab_runtime_changed(); } Tab :: ~Tab() {} /** * * QNotifier implementation. * */ void Tab :: on_track_added(unsigned int row) { tab_window->q_model->on_row_inserted(row); tab_label->set_size(); tab_runtime_changed(); } void Tab :: on_track_removed(unsigned int row) { tab_window->q_model->on_row_deleted(row); tab_label->set_size(); tab_runtime_changed(); } void Tab :: on_tracks_cleared(unsigned int n) { if (n > 0) { tab_window->q_model->on_cleared(n); tab_label->set_size(); tab_runtime_changed(); } } void Tab :: on_track_updated(unsigned int row) { tab_window->q_model->on_row_changed(row); tab_runtime_changed(); } /** * * Tab internal helper functions * */ int Tab :: tab_page_num() { Gtk::Notebook *notebook = Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false); return notebook->page_num(tab_vbox); } bool Tab :: tab_is_cur() { Gtk::Notebook *notebook = Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false); return notebook->page_num(tab_vbox) == notebook->get_current_page(); } void Tab :: tab_runtime_changed() { gchar *len = string_sec2str_long(tab_pq->q_length); if (tab_is_cur()) Glib :: wrap(GTK_LABEL(gui_builder_widget("o_queue_time")), false)->set_text(len); g_free(len); } void Tab :: tab_display_sorting() { std::string text = ""; if ((tab_sorting_count > 0) && tab_is_cur()) text = "Sorting within " + tab_sorting_title; Glib :: wrap(GTK_LABEL(gui_builder_widget("o_sorting_indicator")), false)->set_text(text); } void Tab :: tab_dec_sort_count() { tab_sorting_count--; tab_display_sorting(); } void Tab :: tab_unmap() { queue_mapping.erase(tab_pq); } void Tab :: tab_focus_search() { tab_toolbar->q_search->grab_focus(); } void Tab :: tab_selected_ids(std::vector &ids) { Glib::RefPtr sel = tab_window->q_treeview->get_selection(); std::vector rows = sel->get_selected_rows(); Gtk::TreeModel::Path path; for (unsigned int i = 0; i < rows.size(); i++) { path = tab_window->q_filter->convert_path_to_child_path(rows[i]); ids.push_back(tab_window->q_model->path_to_id(path)); } sel->unselect_all(); } void Tab :: tab_queue_add(queue *pq) { std::vector ids; tab_selected_ids(ids); for (unsigned int i = 0; i < ids.size(); i++) queue_add(pq, track_get(ids[i])); } bool Tab :: tab_queue_selected(bool random) { unsigned int flags = random ? Q_RANDOM : 0; if (tempq_count() >= 10) return true; queue *pq = tempq_alloc(&tempq_ops, flags); on_pq_created(pq, tempq_count() - 1); gui_sidebar_add(gui_queue(pq)); tab_queue_add(pq); return true; } bool Tab :: tab_add_to_queue(unsigned int n) { if (n >= tempq_count()) return true; queue *pq = tempq_get(n); tab_queue_add(pq); return true; } bool Tab :: tab_add_to_playlist(enum playlist_t plist) { std::vector ids; tab_selected_ids(ids); for (unsigned int i = 0; i < ids.size(); i++) { if (plist == PL_HIDDEN) collection_ban(track_get(ids[i])); else playlist_add(plist, track_get(ids[i])); } return true; } bool Tab :: tab_favorite_selected() { return tab_add_to_playlist(PL_FAVORITED); } void Tab :: tab_ban_selected() { tab_add_to_playlist(PL_HIDDEN); } /** * * Tab callback functions * */ bool Tab :: on_key_press_event(const std::string &key) { if (key >= "0" && key <= "9") return tab_add_to_queue(atoi(key.c_str())); else if (key == "f") return tab_favorite_selected(); else if (key == "q" || key == "r") return tab_queue_selected(key == "r"); else return false; return true; } void Tab :: on_show_rc_menu() { std::string item; unsigned int size = tempq_count(); if (size == 0) { Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_add_to_pq")), false)->hide(); return; } Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_add_to_pq")), false)->show(); if (size == 10) Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_new_pq")), false)->hide(); else Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_new_pq")), false)->show(); for (unsigned int i = 0; i < 10; i++) { item = "o_pq_"; item += '0' + i; if (i < size) Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget(item.c_str())), false)->show(); else Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget(item.c_str())), false)->hide(); } } /** * * GTK-MM callback functions * */ bool Tab :: on_key_pressed(GdkEventKey *event) { std::string key = gdk_keyval_name(event->keyval); if (key.size() >= 3) { if (key.substr(0, 3) == "KP_") key = key.substr(3); } return on_key_press_event(key); } void Tab :: on_column_clicked(unsigned int col) { if (tab_sorting_count == 0) { tab_sorting_title = tab_window->q_treeview->get_column(col)->get_title(); queue_sort(tab_pq, sort_fields[col], true); } else queue_sort(tab_pq, sort_fields[col], false); tab_sorting_count++; tab_display_sorting(); Glib::signal_timeout().connect_seconds_once( sigc::mem_fun(*this, &Tab::tab_dec_sort_count), 2); } bool Tab :: on_button_pressed(GdkEventButton *button) { if (button->button != 3) return false; Gtk::TreeModel::Path path; if (tab_window->q_treeview->get_path_at_pos(button->x, button->y, path)) tab_window->q_treeview->get_selection()->select(path); on_show_rc_menu(); Glib :: wrap(GTK_MENU(gui_builder_widget("o_rc_menu")), false)->popup(button->button, button->time); return true; } /** * * Global functions * */ Tab *find_tab(queue *pq) { std::map::iterator it; it = queue_mapping.find(pq); if (it != queue_mapping.end()) return it->second; return NULL; } static Tab *find_tab(int num) { std::map::iterator it; for (it = queue_mapping.begin(); it != queue_mapping.end(); it++) { if (it->second->tab_page_num() == num) return it->second; } return NULL; } static Tab *cur_tab() { std::map::iterator it; for (it = queue_mapping.begin(); it != queue_mapping.end(); it++) { if (it->second->tab_is_cur()) return it->second; } return NULL; } static void on_switch_page(Gtk::Widget *page, int num) { Tab *tab = find_tab(num); if (tab) { tab->tab_runtime_changed(); tab->tab_display_sorting(); } else Glib :: wrap(GTK_LABEL(gui_builder_widget("o_queue_time")), false)->set_text(""); } static void on_search_changed() { Gtk::SearchEntry *search = Glib :: wrap(GTK_SEARCH_ENTRY(gui_builder_widget("o_search")), false); std::string text = search->get_text(); Tab *tab = cur_tab(); if (tab) tab->tab_window->filter(text); } void tab_focus_search() { int page = Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false)->get_current_page(); Tab *tab = find_tab(page); if (tab) tab->tab_focus_search(); } static void on_new_pq() { Tab *tab = cur_tab(); if (tab) tab->tab_queue_selected(false); } static void on_add_to_favs() { Tab *tab = cur_tab(); if (tab) tab->tab_favorite_selected(); } static void on_add_to_banned() { Tab *tab = cur_tab(); if (tab) tab->tab_ban_selected(); } static void on_add_to_queue(unsigned int n) { Tab *tab = cur_tab(); if (tab) tab->tab_add_to_queue(n); } static void init_menu_item(const std::string &pq, unsigned int n) { Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget(pq.c_str())), false)->signal_activate().connect( sigc::bind (sigc::ptr_fun(on_add_to_queue), n)); } static bool on_window_key_pressed(GdkEventKey *event) { Gtk::Notebook *notebook = Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false); std::string key = gdk_keyval_name(event->keyval); if (key.size() >= 3) { if (key.substr(0, 3) == "KP_") key = key.substr(3); } if (key == "slash") tab_focus_search(); else if (key >= "0" && key <= "9") { unsigned int n = atoi(key.c_str()); if (n < tempq_count()) notebook->set_current_page(n); } else if (key == "c") notebook->set_current_page(tempq_count()); else if (key == "h") notebook->set_current_page(tempq_count() + 1); else if (key == "m") notebook->set_current_page(tempq_count() + 3); else if (key == "p") notebook->set_current_page(tempq_count() + 2); else return false; return true; } void init_tabs() { Gtk::Window *window = Glib :: wrap(GTK_WINDOW(gui_builder_widget("o_window")), false); /* Notebook signals */ struct Gtk::Notebook *notebook = Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false); notebook->signal_switch_page().connect(sigc::ptr_fun(on_switch_page)); /* Search signals */ Gtk::SearchEntry *search = Glib :: wrap(GTK_SEARCH_ENTRY(gui_builder_widget("o_search")), false); search->signal_search_changed().connect(sigc::ptr_fun(on_search_changed)); /* Menu signals */ Glib :: wrap(GTK_MENU(gui_builder_widget("o_rc_menu")), false)->show_all(); Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_new_pq")), false)->signal_activate().connect( sigc::ptr_fun(on_new_pq)); Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_add_to_favorites")), false)->signal_activate().connect( sigc::ptr_fun(on_add_to_favs)); Glib :: wrap(GTK_MENU_ITEM(gui_builder_widget("o_add_to_banned")), false)->signal_activate().connect( sigc::ptr_fun(on_add_to_banned)); for (unsigned int i = 0; i < 10; i++) { std::string pq = "o_pq_"; pq += '0' + i; init_menu_item(pq, i); } /* Initialize other tabs */ init_history_tab(); init_collection_tab(); init_queue_tabs(); /* Setup keyboard shortcuts */ window->signal_key_press_event().connect(sigc::ptr_fun(on_window_key_pressed)); } void post_init_tabs() { post_init_queue_tabs(); unsigned int tab = 0; for (tab = 0; tab < tempq_count(); tab++) { if (queue_has_flag(tempq_get(tab), Q_ENABLED)) break; } Glib :: wrap(GTK_NOTEBOOK(gui_builder_widget("o_notebook")), false)->set_current_page(tab); } void cleanup_tabs() { while (queue_mapping.size() > 0) delete queue_mapping.begin()->second; }