root/src/saved_searches_sidebar.rs

use gtk::glib;
use gtk::glib::clone;
use gtk::prelude::*;
use tokio::sync::mpsc::Sender;

use crate::browse_page::BrowseAction;
use crate::data::Action;
use crate::send;
use crate::settings::BooruData;
use crate::settings::SavedSearch;

#[derive(Debug)]
pub struct SavedSearchesSidebar {
    pub root: gtk::Box,
    pub searches_list: gtk::Box,
    open_button: gtk::Button,
}

impl Default for SavedSearchesSidebar {
    fn default() -> Self {
        let root = gtk::Box::new(gtk::Orientation::Vertical, 10);
        root.add_css_class("saved-searches");
        let searches_list = gtk::Box::new(gtk::Orientation::Vertical, 3);

        let header = gtk::Box::new(gtk::Orientation::Horizontal, 10);
        let label = gtk::Label::new(Some("Saved Searches"));
        label.set_hexpand(true);
        label.set_halign(gtk::Align::Start);
        label.set_margin_start(10);
        header.append(&label);

        let open_button = gtk::Button::builder()
            .icon_name("view-refresh-symbolic")
            .tooltip_text("Load All")
            .margin_end(10)
            .build();

        header.append(&open_button);

        root.append(&header);
        root.append(&searches_list);
        root.set_margin_top(20);

        Self {
            root,
            searches_list,
            open_button,
        }
    }
}

impl SavedSearchesSidebar {
    pub fn init(&self, sender: Sender<Action>) {
        self.open_button.connect_clicked(move |_| {
            send!(
                sender,
                Action::LoadSavedSearchesUntilViewed {
                    only_auto_load: false
                }
            );
        });
    }

    pub fn fill_search_list(
        &self,
        sender: &Sender<Action>,
        boorus: &[BooruData],
        saved_searches: &Vec<SavedSearch>,
    ) {
        while let Some(c) = self.searches_list.first_child() {
            self.searches_list.remove(&c);
        }

        if saved_searches.is_empty() {
            self.root.set_visible(false);
            return;
        } else {
            self.root.set_visible(true);
        }

        for s in saved_searches {
            let button = gtk::Button::new();
            let label = gtk::Label::new(Some(&s.search));
            let search_text = s.search.clone();
            let sender2 = sender.clone();
            let booru = if let Some(booru_id) = s.booru_allowlist.first() {
                booru_id.to_string()
            } else {
                "".to_string()
            };
            button.connect_clicked(move |_| {
                let cmd = Action::OpenSavedSearch {
                    search: search_text.clone(),
                    booru: booru.clone(),
                };
                send!(sender2, cmd);
            });
            button.set_child(Some(&label));

            // gesture
            let popover = Self::make_saved_search_popover(sender, boorus, s.clone());
            popover.set_parent(&button);
            let on_rightclick = clone!(
                #[weak]
                popover,
                move |(x, y)| {
                    popover.popup();
                    popover
                        .set_pointing_to(Some(&gtk::gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
                }
            );

            let on_rightclick2 = on_rightclick.clone();
            let long_press = gtk::GestureLongPress::new();
            long_press.connect_pressed(move |_, x, y| {
                on_rightclick((x, y));
            });
            let right_click = gtk::GestureClick::builder()
                .button(gtk::gdk::BUTTON_SECONDARY)
                .build();
            right_click.connect_pressed(move |_, _, x, y| {
                on_rightclick2((x, y));
            });
            button.add_controller(long_press);
            button.add_controller(right_click);

            button.connect_destroy(clone!(
                #[weak]
                popover,
                move |_| popover.unparent()
            ));

            popover.connect_closed(clone!(
                #[strong]
                sender,
                move |_| send!(
                    sender,
                    Action::BrowseAction {
                        action: BrowseAction::SavedSearchesChanged
                    }
                )
            ));

            self.searches_list.append(&button);
        }
    }

    pub fn make_saved_search_popover(
        sender: &Sender<Action>,
        boorus: &[BooruData],
        saved_search: SavedSearch,
    ) -> gtk::Popover {
        let popover = gtk::Popover::new();
        let container = gtk::Box::new(gtk::Orientation::Vertical, 3);
        container.set_margin_start(10);
        container.set_margin_end(10);
        container.set_margin_top(10);
        container.set_margin_bottom(10);

        let header = gtk::Label::new(Some("Saved Search"));
        container.append(&header);

        let auto_search_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
        let auto_search_label = gtk::Label::new(Some("Perform on startup: "));
        auto_search_label.set_halign(gtk::Align::Start);
        auto_search_label.set_hexpand(true);
        auto_search_box.append(&auto_search_label);

        let auto_search = gtk::Switch::new();
        auto_search.set_halign(gtk::Align::End);
        auto_search.set_active(saved_search.auto_load);
        auto_search.set_state(saved_search.auto_load);
        auto_search_box.set_hexpand(true);
        auto_search_box.append(&auto_search);
        let search = saved_search.search;
        auto_search.connect_state_set(clone!(
            #[strong]
            sender,
            #[strong]
            search,
            move |_, value| {
                send!(
                    sender,
                    Action::SetSavedSearchAutoLoad {
                        search: search.to_string(),
                        auto: value
                    }
                );
                glib::Propagation::Proceed
            }
        ));
        container.append(&auto_search_box);

        let default_booru_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
        let default_booru_label = gtk::Label::new(Some("Default booru: "));
        default_booru_label.set_halign(gtk::Align::Start);
        default_booru_label.set_hexpand(true);
        default_booru_box.set_hexpand(true);
        default_booru_box.set_margin_top(5);
        default_booru_box.append(&default_booru_label);

        let string_list: Vec<&str> = boorus
            .iter()
            .map(|b| b.booru.get_domain().as_str())
            .collect();
        let list = gtk::StringList::new(string_list.as_slice());
        let mut selected = 0;
        for (i, id) in string_list.into_iter().enumerate() {
            if saved_search.booru_allowlist.first().map(|s| s.as_str()) == Some(id) {
                selected = i;
            }
        }
        let default_booru = gtk::DropDown::new(Some(list), gtk::Expression::NONE);
        default_booru.set_halign(gtk::Align::End);
        default_booru.set_selected(selected as u32);
        // default_booru.set_active_id(saved_search.booru_allowlist.first().map(|s| s.as_str()));
        default_booru.connect_selected_item_notify(clone!(
            #[strong]
            sender,
            #[strong]
            search,
            move |this| {
                if let Some(booru) = this
                    .selected_item()
                    .and_then(|b| b.downcast::<gtk::StringObject>().ok())
                {
                    // info!("{:#?}", booru);
                    send!(
                        sender,
                        Action::SetSavedSearchBooru {
                            search: search.clone(),
                            booru: booru.string().to_string()
                        }
                    );
                }
            }
        ));
        default_booru_box.append(&default_booru);
        container.append(&default_booru_box);

        let delete_button = gtk::Button::new();
        let delete_button_label = gtk::Label::new(Some("Delete"));
        delete_button.set_child(Some(&delete_button_label));
        delete_button.connect_clicked(clone!(
            #[weak]
            popover,
            #[strong]
            sender,
            move |_| {
                popover.popdown();
                send!(
                    sender,
                    Action::RemoveSavedSearch {
                        search: search.clone()
                    }
                );
            }
        ));
        delete_button.set_margin_top(10);
        delete_button.set_halign(gtk::Align::Start);
        delete_button.add_css_class("destructive-action");
        container.append(&delete_button);

        popover.set_child(Some(&container));
        popover
    }
}