sysd-manager 2.19.4

Application to empower user to manage their <b>systemd units</b> via Graphical User Interface. Not only are you able to make changes to the enablement and running status of each of the units, but you will also be able to view and modify their unit files and check the journal logs.
use std::cell::OnceCell;

use gio::glib::object::Cast;
use gtk::{
    ListScrollFlags,
    glib::{self},
    prelude::*,
    subclass::prelude::*,
};
use tracing::{error, info, warn};

use crate::widget::{
    unit_list::{UnitCuratedList, UnitListPanel},
    unit_properties_selector::{
        data_browser::PropertyBrowseItem,
        data_selection::UnitPropertySelection,
        save::{self, save_column_config},
        unit_properties_selection::row::UnitPropertiesSelectionRow,
    },
};

use super::UnitPropertiesSelectionPanel;

#[derive(Default, gtk::CompositeTemplate)]
#[template(resource = "/io/github/plrigaux/sysd-manager/unit_properties_selection.ui")]
pub struct UnitPropertiesSelectionPanelImp {
    #[template_child]
    properties_selection: TemplateChild<gtk::ListView>,

    #[template_child]
    apply_button: TemplateChild<gtk::Button>,

    #[template_child]
    ok_button: TemplateChild<gtk::Button>,

    #[template_child]
    column_nb: TemplateChild<gtk::Label>,

    list_store: OnceCell<gio::ListStore>,

    selection_model: OnceCell<gtk::SingleSelection>,

    unit_list_panel: OnceCell<UnitListPanel>,
}

macro_rules! get_list_store {
    ($self:expr) => {{
        let Some(list_store) = $self.list_store.get() else {
            error!("list_store not set");
            return;
        };
        list_store
    }};
}

macro_rules! get_unit_list_panel {
    ($self:expr) => {{
        let Some(list_store) = $self.unit_list_panel.get() else {
            error!("No unit list panel");
            return;
        };
        list_store
    }};
}

#[gtk::template_callbacks]
impl UnitPropertiesSelectionPanelImp {
    #[template_callback]
    fn apply_clicked(&self, _button: &gtk::Button) {
        info!("Apply pressed");

        let list_store = get_list_store!(self);

        let mut list: Vec<UnitPropertySelection> = list_store
            .iter::<UnitPropertySelection>()
            .filter_map(|result| result.ok())
            .collect();

        save_column_config(None, &mut list, UnitCuratedList::Custom);

        let unit_list_panel = get_unit_list_panel!(self);

        unit_list_panel.set_new_columns(list);
    }

    #[template_callback]
    fn ok_clicked(&self, button: &gtk::Button) {
        info!("Ok pressed");

        self.apply_clicked(button);

        if let Err(boolerror) = button.activate_action("window.close", None) {
            warn!("bool error {boolerror}")
        };
    }

    #[template_callback]
    fn reset_clicked(&self, _button: &gtk::Button) {
        let Some(unit_list_panel) = self.unit_list_panel.get() else {
            error!("unit_list_panel is None");
            return;
        };

        let list_store = get_list_store!(self);

        list_store.remove_all(); //TBSafe

        for unit_property_column in unit_list_panel.default_displayed_columns().iter() {
            let unit_property_column = unit_property_column.copy();
            list_store.append(&unit_property_column);
        }
    }
}

impl UnitPropertiesSelectionPanelImp {
    pub fn add_new_property(&self, new_property_object: PropertyBrowseItem) {
        let new_unit_prop = UnitPropertySelection::from_browser(new_property_object);

        let list_store = get_list_store!(self);
        list_store.append(&new_unit_prop);
        self.properties_selection.scroll_to(
            list_store.n_items() - 1,
            gtk::ListScrollFlags::FOCUS,
            None,
        );
    }

    pub(super) fn set_unit_list_panel(
        &self,
        unit_list_panel: &UnitListPanel,
        column_id: Option<String>,
    ) {
        self.unit_list_panel
            .set(unit_list_panel.clone())
            .expect("Assigned only once");

        let list_store = get_list_store!(self);

        let mut columns = { unit_list_panel.current_columns_mut() };
        save::order_columns(Some(&unit_list_panel.columns()), &mut columns);

        for unit_property_column in columns.iter() {
            let unit_property_column = unit_property_column.copy(); //if no copy the column prop are in sync
            list_store.append(&unit_property_column);
        }

        if let Some(column_id) = column_id {
            for (index, id) in list_store
                .iter::<UnitPropertySelection>()
                .enumerate()
                .filter_map(|r| {
                    if let Ok(item) = r.1
                        && let Some(id) = item.id()
                    {
                        Some((r.0, id))
                    } else {
                        None
                    }
                })
            {
                if column_id == id {
                    self.properties_selection.scroll_to(
                        index as u32,
                        ListScrollFlags::FOCUS | ListScrollFlags::SELECT,
                        None,
                    );
                }
            }
        }
    }

    pub(super) fn get_list_store(&self) -> Option<&gio::ListStore> {
        self.list_store.get()
    }

    pub fn move_up(&self, position: u32) {
        if position == 0 {
            return;
        }

        let list_store = get_list_store!(self);

        if let Some(ref prop_seletion) = list_store.item(position) {
            list_store.remove(position);
            list_store.insert(position - 1, prop_seletion);
        } else {
            warn!("list_store of data None");
        };
    }

    pub fn move_down(&self, position: u32) {
        let list_store = get_list_store!(self);

        if position + 1 >= list_store.n_items() {
            return;
        }

        if let Some(ref prop_seletion) = list_store.item(position) {
            list_store.remove(position);
            list_store.insert(position + 1, prop_seletion);
        } else {
            warn!("list_store of data None");
        };
    }

    pub fn delete(&self, position: u32) {
        let Some(list_store) = self.list_store.get() else {
            warn!("list_store should not be None");
            return;
        };

        list_store.remove(position);
    }
}

// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for UnitPropertiesSelectionPanelImp {
    const NAME: &'static str = "UnitPropertiesSelectionPanel";
    type Type = UnitPropertiesSelectionPanel;
    type ParentType = gtk::Box;

    fn class_init(klass: &mut Self::Class) {
        // The layout manager determines how child widgets are laid out.
        klass.bind_template();
        klass.bind_template_callbacks();
    }

    fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
        obj.init_template();
    }
}

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

        let list_store = gio::ListStore::new::<UnitPropertySelection>();

        self.list_store.set(list_store.clone()).expect("Only once");

        //let selection_model = gtk::NoSelection::new(Some(store.clone()));

        let selection_model = gtk::SingleSelection::builder()
            .can_unselect(true)
            .model(&list_store.clone())
            .build();

        self.selection_model
            .set(selection_model.clone())
            .expect("Only once");

        self.properties_selection.set_model(Some(&selection_model));
        let factory = gtk::SignalListItemFactory::new();
        {
            let prop_selection = self.obj().clone();
            factory.connect_setup(move |_factory, item| {
                let item = item.downcast_ref::<gtk::ListItem>().unwrap();
                let row = UnitPropertiesSelectionRow::new(prop_selection.clone());
                item.set_child(Some(&row));
            });
        }

        factory.connect_bind(move |_factory, item| {
            let item = item.downcast_ref::<gtk::ListItem>().unwrap();
            let prop_selection = item.item().and_downcast::<UnitPropertySelection>().unwrap();

            let child = item
                .child()
                .and_downcast::<UnitPropertiesSelectionRow>()
                .unwrap();

            child.set_data_selection(&prop_selection, item);
        });

        factory.connect_unbind(move |_factory, item| {
            let item = item.downcast_ref::<gtk::ListItem>().unwrap();

            let child = item
                .child()
                .and_downcast::<UnitPropertiesSelectionRow>()
                .unwrap();

            child.unbind();
        });

        self.properties_selection.set_factory(Some(&factory));

        list_store
            .bind_property::<gtk::Button>("n-items", self.apply_button.as_ref(), "sensitive")
            .transform_to(|_bond, nitems: u32| Some(nitems > 0))
            .build();

        list_store
            .bind_property::<gtk::Button>("n-items", self.ok_button.as_ref(), "sensitive")
            .transform_to(|_bond, nitems: u32| Some(nitems > 0))
            .build();

        list_store
            .bind_property::<gtk::Label>("n-items", self.column_nb.as_ref(), "label")
            .transform_to(|_bond, nitems: u32| Some(nitems.to_string()))
            .build();
    }
}

impl WidgetImpl for UnitPropertiesSelectionPanelImp {}
impl BoxImpl for UnitPropertiesSelectionPanelImp {}