root/src/filter_page.rs

// queue_page.rs
//
// Copyright 2023 nee <nee-git@patchouli.garden>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::data::{Action, ListPage, ListPageItem};
use crate::send;
use adw::prelude::*;
use glib::clone;
use gtk::subclass::prelude::*;
use gtk::{gio, glib};
use mpd;
use std::cell::RefCell;

#[derive(Debug, Default)]
pub struct InternalState {
    pub lists: Vec<ListPage>,
    pub active_page: Option<(ListPage, Vec<ListPageItem>)>,
}

pub struct SubPage {
    tag: mpd::Tag,
    button: gtk::Button,
    sub_pages: Vec<mpd::Tag>,
}

mod imp {
    use super::*;

    use gtk::CompositeTemplate;

    #[derive(Debug, CompositeTemplate, Default)]
    #[template(resource = "/blue/hidamari/pmpdc/ui/filter_page.ui")]
    pub struct FilterPage {
        #[template_child]
        pub stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub header_stack: TemplateChild<gtk::Stack>,

        #[template_child]
        pub close_filter_bar_button: TemplateChild<gtk::Button>,
        #[template_child]
        pub open_filter_bar_button: TemplateChild<gtk::Button>,

        #[template_child]
        pub sub_page_button_artist: TemplateChild<gtk::Button>,
        #[template_child]
        pub sub_page_button_album: TemplateChild<gtk::Button>,
        #[template_child]
        pub sub_page_button_genre: TemplateChild<gtk::Button>,
        #[template_child]
        pub sub_page_button_song: TemplateChild<gtk::Button>,
        #[template_child]
        pub filter_entry: TemplateChild<gtk::SearchEntry>,

        #[template_child]
        pub search_page_entry: TemplateChild<gtk::SearchEntry>,
        #[template_child]
        pub search_page_body: TemplateChild<gtk::Box>,

        #[template_child]
        pub open_queue_button: TemplateChild<gtk::Button>,

        pub state: RefCell<InternalState>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for FilterPage {
        const NAME: &'static str = "FilterPage";
        type Type = super::FilterPage;
        type ParentType = gtk::Box;

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);
        }

        // You must call `Widget`'s `init_template()` within `instance_init()`.
        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }
    impl ObjectImpl for FilterPage {}
    impl WidgetImpl for FilterPage {}
    impl BoxImpl for FilterPage {}
}

glib::wrapper! {
    pub struct FilterPage(ObjectSubclass<imp::FilterPage>)
        @extends gtk::Widget, gtk::Box,
        @implements gio::ActionMap, gio::ActionGroup;
}

impl FilterPage {
    pub fn new(sender: &glib::Sender<Action>) -> Self {
        let widget: Self = glib::Object::new();

        let mut sub_pages = vec![
            SubPage {
                tag: mpd::Tag::Artist,
                button: widget.imp().sub_page_button_artist.clone(),
                sub_pages: vec![mpd::Tag::Artist, mpd::Tag::Album, mpd::Tag::Track],
            },
            SubPage {
                tag: mpd::Tag::Album,
                button: widget.imp().sub_page_button_album.clone(),
                sub_pages: vec![mpd::Tag::Album, mpd::Tag::Track],
            },
            SubPage {
                tag: mpd::Tag::Genre,
                button: widget.imp().sub_page_button_genre.clone(),
                sub_pages: vec![
                    mpd::Tag::Genre,
                    mpd::Tag::Artist,
                    mpd::Tag::Album,
                    mpd::Tag::Track,
                ],
            },
        ];

        let list_pages: Vec<ListPage> = sub_pages.drain(..).map(|sp| {
            let tag = sp.tag;
            sp.button.connect_clicked(clone!(@strong sender, @weak widget => move |_| {
                widget.imp().stack.set_visible_child_full(&tag.to_string(), gtk::StackTransitionType::Crossfade);
                let cmd = Action::List {tag: tag.clone()};
                sender.send(cmd).unwrap();
            }));

            let list_page = gtk::Box::new(gtk::Orientation::Vertical, 0);
            widget.imp().stack.add_named(&list_page, Some(&tag.to_string()));
            ListPage {
                tag: tag,
                value: "".to_owned(),
                top_levels: vec![],
                sub_pages: sp.sub_pages,
            }
        }).collect();

        widget.imp().state.borrow_mut().lists = list_pages;

        widget.imp().open_filter_bar_button.connect_clicked(clone!(@weak widget => move |_| {
            widget.imp().header_stack.set_visible_child_full(&"filter", gtk::StackTransitionType::Crossfade);
            widget.imp().filter_entry.grab_focus();
            widget.imp().open_filter_bar_button.set_visible(false);
            widget.imp().close_filter_bar_button.set_visible(true);
        }));

        widget.imp().close_filter_bar_button.connect_clicked(clone!(@weak widget => move |_| {
            widget.imp().header_stack.set_visible_child_full(&"buttons", gtk::StackTransitionType::Crossfade);
            widget.imp().open_filter_bar_button.set_visible(true);
            widget.imp().close_filter_bar_button.set_visible(false);
        }));

        widget.imp().filter_entry.connect_search_changed(
            clone!(@strong sender, @weak widget => move |e| {
                let text = e.text().to_string();
                if text.len() > 2 || text.len() == 0 {
                    // send!(sender, Action::FilterActivePage {search: text});
                    widget.filter_active_page(&sender, text);
                }
            }),
        );

        widget
            .imp()
            .sub_page_button_song
            .connect_clicked(clone!(@weak widget => move |_| {
                let trans =  gtk::StackTransitionType::Crossfade;
                widget.imp().stack.set_visible_child_full("search_page", trans);
            }));

        widget
            .imp()
            .open_queue_button
            .connect_clicked(clone!(@strong sender => move |_| {
                send!(sender, Action::OpenQueuePage);
            }));

        widget.init_search_page(sender);

        widget
    }

    pub fn init_search_page(&self, sender: &glib::Sender<Action>) {
        self.imp()
            .search_page_entry
            .connect_search_changed(clone!(@strong sender => move |e| {
                let text = e.text().to_string();
                let search = if text.len() > 2 || text.len() == 0 { text} else { "".to_owned() };
                send!(sender, Action::Search {
                    tag:mpd::Tag::Title,
                    search: search
                });
            }));
    }

    pub fn did_fetch_data_before(&self) -> bool {
        self.imp()
            .stack
            .child_by_name("list_page")
            .or(self.imp().stack.child_by_name("sub_page"))
            .is_some()
    }

    pub fn set_search_items(&self, items: gtk::Overlay) {
        while let Some(c) = self.imp().search_page_body.last_child() {
            self.imp().search_page_body.remove(&c);
        }
        self.imp().search_page_body.append(&items);
    }

    pub fn set_list(&self, sender: &glib::Sender<Action>, tag: mpd::Tag, mut items: Vec<String>) {
        let found_page = self.imp().state.borrow().lists.iter().find_map(|lp| {
            if lp.tag == tag {
                Some(lp.clone())
            } else {
                None
            }
        });
        if let Some(found_page) = found_page {
            let enum_items: Vec<ListPageItem> = items
                .drain(..)
                .map(|i| ListPageItem::ListPageString { string: i })
                .collect();

            let page_box = found_page.create_items(sender, &enum_items.iter().collect());
            self.imp().state.borrow_mut().active_page = Some((found_page.clone(), enum_items));
            self.imp()
                .stack
                .child_by_name("list_page")
                .map(|s| self.imp().stack.remove(&s));

            self.imp().stack.add_named(&page_box, Some("list_page"));
            self.imp()
                .stack
                .set_visible_child_full("list_page", gtk::StackTransitionType::Crossfade);
        } else {
            println!("Error: no page found for tag: {}", tag);
        }
    }

    pub fn set_list_page(
        &self,
        sender: &glib::Sender<Action>,
        page: ListPage,
        mut results: Vec<mpd::Track>,
    ) {
        self.imp()
            .stack
            .child_by_name("sub_page")
            .map(|s| self.imp().stack.remove(&s));
        // if page.sub_pages.is_empty() && false {
        //     // page.create_track_items(&state.sender, &mut results);
        // } else {
        let mut tag_results: Vec<ListPageItem> = results
            .drain(..)
            .map(|s| {
                match page.sub_pages.first().map(|tag| tag.clone()) {
                    Some(mpd::Tag::Artist) => ListPageItem::ListPageString {
                        string: s.artist.unwrap_or(s.file),
                    },
                    Some(mpd::Tag::Album) => ListPageItem::ListPageString {
                        string: s.album.unwrap_or(s.file),
                    },
                    Some(mpd::Tag::Track) => ListPageItem::ListPageTrack { track: s },
                    _ => ListPageItem::ListPageString {
                        string: "___unknown___".to_owned(),
                    }, // TODO
                }
            })
            .collect();
        tag_results.dedup();
        let page_box = page.create_items(sender, &tag_results.iter().collect());
        self.imp().state.borrow_mut().active_page = Some((page.clone(), tag_results));
        // }
        println!("got search results");

        let transition = gtk::StackTransitionType::Crossfade;
        self.imp().stack.add_named(&page_box, Some("sub_page"));
        self.imp()
            .stack
            .set_visible_child_full("sub_page", transition);
    }

    pub fn filter_active_page(&self, sender: &glib::Sender<Action>, search: String) {
        let state = self.imp().state.borrow();
        let items = state.active_page.as_ref().map_or(vec![], |(_, items)| {
            items.iter().filter(|i| i.find(&search).is_some()).collect()
        });
        if let Some((p, _)) = self.imp().state.borrow().active_page.as_ref() {
            let page_box = p.create_items(sender, &items);
            let stack = &self.imp().stack;
            stack.child_by_name("sub_page").map(|s| stack.remove(&s));
            stack.add_named(&page_box, Some("sub_page"));
            let transition = gtk::StackTransitionType::Crossfade;
            stack.set_visible_child_full("sub_page", transition);
        };
    }
}