root/src/settings.rs

// settings.rs
//
// Copyright 2020-2024 nee <nee-git@hidamari.blue>
//
// 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::booru::Booru;
use crate::data::*;
use crate::send;
pub use crate::settings_data::*;

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

mod imp {
    use super::*;

    #[derive(Debug, Default, gtk::CompositeTemplate)]
    #[template(resource = "/blue/hidamari/boorus/ui/settings.ui")]
    pub struct SettingsPage {
        #[template_child]
        pub existing_boorus: TemplateChild<adw::PreferencesGroup>,
        // #[template_child]
        // pub window: TemplateChild<gtk::ScrolledWindow>,
        #[template_child]
        pub url_entry: TemplateChild<adw::EntryRow>,
        #[template_child]
        pub add_button: TemplateChild<adw::ButtonRow>,
        #[template_child]
        pub done_button: TemplateChild<adw::ButtonRow>,
        #[template_child]
        pub back_button: TemplateChild<gtk::Button>,
    }

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

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

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

    impl ObjectImpl for SettingsPage {
        fn constructed(&self) {
            self.parent_constructed();
        }
    }

    impl WidgetImpl for SettingsPage {}
    impl BoxImpl for SettingsPage {}
}

glib::wrapper! {
    pub struct SettingsPage(ObjectSubclass<imp::SettingsPage>)
        @extends gtk::Widget, gtk::Box,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

pub enum SettingsAction {
    AllBoorusRemoved,
}

impl SettingsPage {
    pub fn new(state: &State) -> Self {
        let widget: Self = glib::Object::new();

        let sender = state.sender.clone();

        for b in &state.boorus {
            if !b.booru.is_local() {
                let list_item = make_settings_booru_list_item(
                    &*b.booru,
                    &sender,
                    &widget.imp().existing_boorus,
                );
                widget.imp().existing_boorus.add(&list_item);
            }
        }

        widget.imp().add_button.connect_activated(clone!(
            #[strong]
            sender,
            #[weak]
            widget,
            move |_| {
                let url = widget.imp().url_entry.text().to_string();
                let booru = crate::booru::make_booru(&url);
                match booru {
                    Some(b) => {
                        let list_item = make_settings_booru_list_item(
                            &*b,
                            &sender,
                            &widget.imp().existing_boorus,
                        );
                        widget.imp().existing_boorus.add(&list_item);
                        let cmd = Action::AddBooru { booru: b };
                        send!(sender, cmd);
                        widget.set_done_sensitive(true);
                    }
                    None => {
                        // TODO show a message
                        warn!("Failed to create booru for url: {url}");
                    }
                }
            }
        ));

        if state.boorus.is_empty() {
            widget.set_done_sensitive(false);
            info!("No boorus configured, yet.");
        } else {
            widget.set_done_sensitive(true);
        }

        widget
    }

    fn set_done_sensitive(&self, value: bool) {
        self.imp().done_button.set_sensitive(value);
        self.imp().back_button.set_visible(value);
        self.imp().existing_boorus.set_visible(value);
        if value {
            self.imp().done_button.add_css_class("suggested-action");
            self.imp().add_button.remove_css_class("suggested-action");
        } else {
            self.imp().done_button.remove_css_class("suggested-action");
            self.imp().add_button.add_css_class("suggested-action");
        }
    }

    pub fn do_action(&self, action: SettingsAction) {
        match action {
            SettingsAction::AllBoorusRemoved => {
                self.set_done_sensitive(false);
            }
        }
    }
}

fn make_settings_booru_list_item(
    b: &dyn Booru,
    sender: &Sender<Action>,
    listing_box: &adw::PreferencesGroup,
) -> adw::ActionRow {
    let list_item = adw::ActionRow::builder().title(b.get_domain()).build();
    let remove_button = gtk::Button::new();
    remove_button.set_tooltip_text(Some("Delete booru"));
    remove_button.add_css_class("flat");
    remove_button.set_icon_name("list-remove-symbolic");
    // remove_button.add_css_class("destructive-action");
    let domain = b.get_domain().clone();
    remove_button.connect_clicked(clone!(
        #[strong]
        sender,
        #[weak]
        listing_box,
        #[weak]
        list_item,
        #[strong]
        domain,
        move |_| {
            let cmd = Action::RemoveBooru {
                booru: domain.clone(),
            };
            listing_box.remove(&list_item);
            send!(sender, cmd);
        }
    ));
    // let remove_icon = gtk::Image::from_icon_name("list-remove-symbolic");
    // remove_button.set_child(Some(&remove_icon));
    list_item.add_suffix(&remove_button);
    list_item
}