/* * Copyright 2014 (c) Anna Schumaker. */ #include #include #include #include #include #include #include #include #include #include class OcarinaPage; static std::map tab_map; static unsigned int sort_timeout_count = 0; static class QueueColumns : public Gtk::TreeModelColumnRecord { public: QueueColumns() { add(q_col_track); add(q_col_title); add(q_col_length); add(q_col_artist); add(q_col_album); add(q_col_year); add(q_col_genre); add(q_col_count); add(q_col_played); add(q_col_path); } Gtk::TreeModelColumn q_col_track; Gtk::TreeModelColumn q_col_title; Gtk::TreeModelColumn q_col_length; Gtk::TreeModelColumn q_col_artist; Gtk::TreeModelColumn q_col_album; Gtk::TreeModelColumn q_col_year; Gtk::TreeModelColumn q_col_genre; Gtk::TreeModelColumn q_col_count; Gtk::TreeModelColumn q_col_played; Gtk::TreeModelColumn q_col_path; } queue_cols; static unsigned int q_col_width[] = { 20, 300, 60, 100, 100, 45, 100, 60, 1 }; static sort_t q_col_sorts[] = { SORT_TRACK, SORT_TITLE, SORT_LENGTH, SORT_ARTIST, SORT_ALBUM, SORT_YEAR, SORT_GENRE, SORT_COUNT, SORT_PLAYED }; static void dec_sort_timeout() { if (sort_timeout_count > 0) sort_timeout_count--; if (sort_timeout_count == 0) { Gtk::Label *label; get_builder()->get_widget("o_sorting_indicator", label); label->set_text(""); } } /* * Tab class definition */ class OcarinaTab : public Gtk::HBox { public: OcarinaTab(); ~OcarinaTab(); virtual void set_size(unsigned int) = 0; virtual void set_number(unsigned int) = 0; }; OcarinaTab::OcarinaTab() {} OcarinaTab::~OcarinaTab() {} class PresetTab : public OcarinaTab { public: Gtk::Label name_label; Gtk::Label size_label; Gtk::Image tab_icon; Gtk::VBox box; PresetTab(const std::string &, const std::string &); ~PresetTab(); void set_size(unsigned int); void set_number(unsigned int); }; PresetTab::PresetTab(const std::string &name, const std::string &icon) : name_label("" + name + "", 0.5, 0.5), size_label("0", 0.5, 0.5) { tab_icon.set_from_icon_name(icon, Gtk::ICON_SIZE_MENU); tab_icon.set_alignment(0, 0.5); name_label.set_use_markup(); name_label.set_margin_right(1); name_label.set_justify(Gtk::JUSTIFY_CENTER); size_label.set_justify(Gtk::JUSTIFY_CENTER); set_spacing(5); box.pack_start(name_label); box.pack_start(size_label); pack_start(tab_icon); pack_start(box); show_all(); } PresetTab::~PresetTab() {} void PresetTab::set_number(unsigned int num) {} void PresetTab::set_size(unsigned int size) { std::stringstream ss; ss << size; size_label.set_text(ss.str()); } class PQTab : public OcarinaTab { public: Gtk::Label number_label; Gtk::Label size_label; Gtk::Image close_icon; Gtk::Button close_button; PQTab(); ~PQTab(); void set_size(unsigned int); void set_number(unsigned int); }; PQTab::PQTab() : number_label("0", 0, 0.5), size_label("0", 0.5, 0.5) { close_icon.set_from_icon_name("window-close", Gtk::ICON_SIZE_MENU); close_button.set_image(close_icon); close_button.set_relief(Gtk::RELIEF_NONE); number_label.set_justify(Gtk::JUSTIFY_CENTER); pack_start(number_label, false, false); pack_start(size_label, true, true); pack_start(close_button, false, false); show_all(); } PQTab::~PQTab() {} void PQTab::set_size(unsigned int size) { std::stringstream ss; ss << "" << size << ""; size_label.set_markup(ss.str()); } void PQTab::set_number(unsigned int num) { std::stringstream ss; ss << "" << num << ". "; number_label.set_markup(ss.str()); } /* * Ocarina class definition */ class OcarinaPage : public Gtk::VBox { private: unsigned int init_flags; Gtk::Notebook *notebook; /* Filter state */ std::set visible_ids; OcarinaTab *tab; /* Page widgets */ Gtk::HBox page_toolbar; Gtk::HSeparator page_sep; Gtk::SearchEntry page_entry; Gtk::ToggleButton page_repeat; Gtk::ScrolledWindow page_scroll; Gtk::TreeView page_view; void setup_common(Playqueue *, unsigned int); void setup_toolbar(); void setup_treeview(); void setup_columns(); void set_tab_size(); public: Gtk::ToggleButton page_random; Glib::RefPtr model; Glib::RefPtr filter; OcarinaPage(const std::string &, const std::string &, Playqueue *, unsigned int); OcarinaPage(Playqueue *, unsigned int, unsigned int); ~OcarinaPage(); bool is_current_tab(); void check_pq_flags(); void queue_selected(bool); void on_close_clicked(); void on_row_inserted(unsigned int); void on_row_deleted(unsigned int); void on_row_changed(unsigned int); void on_runtime_changed(); void on_random_toggled(); void on_repeat_toggled(); void on_row_activated(const Gtk::TreePath &, Gtk::TreeViewColumn *); void on_column_clicked(unsigned int); void on_entry_changed(); bool on_entry_key_released(GdkEventKey *); bool on_filter_visible(const Gtk::TreeIter &); void on_page_renumbered(); bool on_view_key_pressed(GdkEventKey *); }; OcarinaPage::OcarinaPage(const std::string &name, const std::string &icon, Playqueue *pq, unsigned int flags) : init_flags(flags) { tab = new PresetTab(name, icon); setup_common(pq, 0); } OcarinaPage::OcarinaPage(Playqueue *pq, unsigned int flags, unsigned int pg) : init_flags(flags) { PQTab *pqt = new PQTab(); tab = pqt; pqt->close_button.signal_clicked().connect(sigc::mem_fun(*this, &OcarinaPage::on_close_clicked)); setup_common(pq, pg); notebook->set_tab_reorderable(*this); } OcarinaPage::~OcarinaPage() { notebook->remove_page(*this); tab_map.erase(model->queue); } void OcarinaPage::setup_common(Playqueue *pq, unsigned int pg) { get_builder()->get_widget("o_notebook", notebook); model = Glib::RefPtr(new PlayqueueModel(pq)); filter = Gtk::TreeModelFilter::create(model); filter->set_visible_func(sigc::mem_fun(*this, &OcarinaPage::on_filter_visible)); set_margin_left(1); set_margin_right(1); setup_toolbar(); setup_treeview(); show_all(); /* Add to notebook */ notebook->insert_page(*this, *tab, pg); tab_map[pq] = this; set_tab_size(); } void OcarinaPage::setup_toolbar() { page_toolbar.set_spacing(5); page_toolbar.pack_start(page_entry); page_toolbar.set_margin_left(5); page_toolbar.set_margin_right(5); /* Set up entry */ page_entry.set_margin_top(5); page_entry.set_margin_bottom(5); page_entry.signal_changed().connect(sigc::mem_fun(*this, &OcarinaPage::on_entry_changed)); page_entry.signal_key_release_event().connect(sigc::mem_fun(*this, &OcarinaPage::on_entry_key_released)); /* Make buttons */ if (init_flags & PQ_RANDOM) { page_random.set_image_from_icon_name("media-playlist-shuffle"); page_toolbar.pack_start(page_random, false, false); } if (init_flags & PQ_REPEAT) { page_repeat.set_image_from_icon_name("media-playlist-repeat"); page_toolbar.pack_start(page_repeat, false, false); } pack_start(page_toolbar, false, false); pack_start(page_sep, false, false); } void OcarinaPage::setup_treeview() { /* Make page content */ page_view.append_column("#", queue_cols.q_col_track); page_view.append_column("Title", queue_cols.q_col_title); page_view.append_column("Length", queue_cols.q_col_length); page_view.append_column("Artist", queue_cols.q_col_artist); page_view.append_column("Album", queue_cols.q_col_album); page_view.append_column("Year", queue_cols.q_col_year); page_view.append_column("Genre", queue_cols.q_col_genre); page_view.append_column("Count", queue_cols.q_col_count); page_view.append_column("Played", queue_cols.q_col_played); page_view.set_tooltip_column(9); page_view.signal_row_activated().connect(sigc::mem_fun(*this, &OcarinaPage::on_row_activated)); page_view.signal_key_press_event().connect(sigc::mem_fun(*this, &OcarinaPage::on_view_key_pressed)); page_view.set_model(filter); page_view.set_rules_hint(); page_view.set_enable_search(false); page_view.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); page_view.set_rubber_banding(); page_scroll.add(page_view); setup_columns(); pack_start(page_scroll); }; void OcarinaPage::setup_columns() { std::vector columns = page_view.get_columns(); for (unsigned int i = 0; i < columns.size(); i++) { columns[i]->set_resizable(); columns[i]->set_fixed_width(q_col_width[i]); columns[i]->set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED); columns[i]->set_clickable(); columns[i]->signal_clicked().connect(sigc::bind ( sigc::mem_fun(*this, &OcarinaPage::on_column_clicked), i)); } } bool OcarinaPage::is_current_tab() { return notebook->page_num(*this) == notebook->get_current_page(); } void OcarinaPage::check_pq_flags() { if (init_flags & PQ_RANDOM) { page_random.set_active(model->queue->get_flags() & PQ_RANDOM); page_random.signal_toggled().connect(sigc::mem_fun(*this, &OcarinaPage::on_random_toggled)); } if (init_flags & PQ_REPEAT) { page_repeat.set_active(model->queue->get_flags() & PQ_REPEAT); page_repeat.signal_toggled().connect(sigc::mem_fun(*this, &OcarinaPage::on_repeat_toggled)); } } void OcarinaPage::set_tab_size() { tab->set_size(model->queue->size()); } void OcarinaPage::queue_selected(bool random) { Playqueue *pq; unsigned int track_id; std::vector::iterator it; Glib::RefPtr sel = page_view.get_selection(); if (sel->count_selected_rows() == 0) return; pq = deck::create(random); std::vector rows = sel->get_selected_rows(); for (it = rows.begin(); it != rows.end(); it++) { track_id = model->path_to_id(filter->convert_path_to_child_path(*it)); pq->add(track_id); } } void OcarinaPage::on_close_clicked() { deck :: remove(notebook->page_num(*this)); } void OcarinaPage::on_row_inserted(unsigned int row) { model->on_row_inserted(row); set_tab_size(); if (is_current_tab()) on_runtime_changed(); } void OcarinaPage::on_row_deleted(unsigned int row) { model->on_row_deleted(row); set_tab_size(); if (is_current_tab()) on_runtime_changed(); } void OcarinaPage::on_row_changed(unsigned int row) { model->on_row_changed(row); if (is_current_tab()) on_runtime_changed(); } void OcarinaPage::on_runtime_changed() { Gtk::Label *label; get_builder()->get_widget("o_queue_time", label); label->set_text(model->queue->get_length_str()); } void OcarinaPage::on_random_toggled() { if (page_random.get_active()) model->queue->set_flag(PQ_RANDOM); else model->queue->unset_flag(PQ_RANDOM); } void OcarinaPage::on_repeat_toggled() { if (page_repeat.get_active()) model->queue->set_flag(PQ_REPEAT); else model->queue->unset_flag(PQ_REPEAT); } void OcarinaPage::on_row_activated(const Gtk::TreePath &path, Gtk::TreeViewColumn *col) { Gtk::TreePath model_path = filter->convert_path_to_child_path(path); model->on_path_selected(model_path); } void OcarinaPage::on_column_clicked(unsigned int col_index) { Gtk::Label *sorting; get_builder()->get_widget("o_sorting_indicator", sorting); if (sort_timeout_count == 0) { sorting->set_text("Sorting within " + page_view.get_column(col_index)->get_title()); model->queue->reset_sort(q_col_sorts[col_index]); } else model->queue->add_sort(q_col_sorts[col_index]); sort_timeout_count++; Glib::signal_timeout().connect_seconds_once( sigc::ptr_fun(dec_sort_timeout), 2); } void OcarinaPage::on_entry_changed() { filter :: search(page_entry.get_text(), visible_ids); filter->refilter(); } bool OcarinaPage::on_entry_key_released(GdkEventKey *event) { std::string key = gdk_keyval_name(event->keyval); return key == "space"; } bool OcarinaPage::on_filter_visible(const Gtk::TreeIter &iter) { unsigned int pq_id; std::set::iterator it; if (page_entry.get_text().size() == 0) return true; pq_id = model->iter_to_id(iter); return visible_ids.find(model->queue->operator[](pq_id)) != visible_ids.end(); } void OcarinaPage::on_page_renumbered() { tab->set_number(notebook->page_num(*this)); } bool OcarinaPage::on_view_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); } if (key >= "0" && key <= "9") { unsigned int n = atoi(key.c_str()); if (n >= deck :: size()) return true; Glib::RefPtr sel = page_view.get_selection(); if (sel->count_selected_rows() == 0) return true; Playqueue *pq = deck :: get(n); std::vector::iterator it; std::vector rows = sel->get_selected_rows(); for (it = rows.begin(); it != rows.end(); it++) { unsigned int track_id = model->path_to_id(filter->convert_path_to_child_path(*it)); pq->add(track_id); } return true; } return false; } /* * Do stuff with tabs */ static void on_track_added(Playqueue *pq, unsigned int row) { std::map::iterator it; it = tab_map.find(pq); if (it != tab_map.end()) it->second->on_row_inserted(row); } static void on_track_deleted(Playqueue *pq, unsigned int row) { std::map::iterator it; it = tab_map.find(pq); if (it != tab_map.end()) it->second->on_row_deleted(row); } static void on_track_changed(Playqueue *pq, unsigned int row) { std::map::iterator it; it = tab_map.find(pq); if (it != tab_map.end()) it->second->on_row_changed(row); } static void on_switch_page(Gtk::Widget *page, int num) { Gtk::Label *label; Gtk::Notebook *notebook; OcarinaPage *tab = (OcarinaPage *)page; get_builder()->get_widget("o_queue_time", label); get_builder()->get_widget("o_notebook", notebook); if (num >= notebook->get_n_pages() - 1) label->hide(); else { tab->on_runtime_changed(); label->show(); } sort_timeout_count = 0; } static void renumber_pqs() { std::map::iterator it; for (it = tab_map.begin(); it != tab_map.end(); it++) it->second->on_page_renumbered(); } static void on_page_reordered(Gtk::Widget *page, int num) { Gtk::Notebook *notebook; OcarinaPage *tab = (OcarinaPage *)page; get_builder()->get_widget("o_notebook", notebook); if ((unsigned int)num >= deck :: size()) notebook->reorder_child(*page, deck::size() - 1); else { deck :: move(tab->model->queue, num); renumber_pqs(); } } static void on_pq_created(Playqueue *pq, unsigned int num) { OcarinaPage *page = new OcarinaPage(pq, PQ_RANDOM | PQ_REPEAT, num); page->check_pq_flags(); renumber_pqs(); } static void on_pq_removed(Playqueue *pq) { std::map::iterator it; it = tab_map.find(pq); if (it != tab_map.end()) delete it->second; renumber_pqs(); } void queue_selected(bool random) { std::map::iterator it; if (deck :: size() >= 10) return; for (it = tab_map.begin(); it != tab_map.end(); it++) { if (it->second->is_current_tab()) { it->second->queue_selected(random); return; } } } void init_tabs() { Gtk::Notebook *notebook; get_builder()->get_widget("o_notebook", notebook); new OcarinaPage("History", "document-open-recent", audio::get_recent_pq(), 0); new OcarinaPage("Collection", "media-optical", deck::get_library_pq(), PQ_RANDOM); get_callbacks()->on_pq_created = on_pq_created; get_callbacks()->on_pq_removed = on_pq_removed; get_callbacks()->on_queue_track_add = on_track_added; get_callbacks()->on_queue_track_del = on_track_deleted; get_callbacks()->on_queue_track_changed = on_track_changed; notebook->signal_switch_page().connect(sigc::ptr_fun(on_switch_page)); notebook->signal_page_reordered().connect(sigc::ptr_fun(on_page_reordered)); notebook->set_current_page(0); } void init_tabs2() { std::map::iterator it; for (it = tab_map.begin(); it != tab_map.end(); it++) it->second->check_pq_flags(); } void cleanup_tabs() { std::map::iterator it; for (it = tab_map.begin(); it != tab_map.end(); it++) delete it->second; }