sysd-manager 2.18.0

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::{borrow::Cow, sync::LazyLock};

use glib::Quark;
use gtk::{glib::translate::IntoGlib, pango, prelude::*};
use tracing::debug;

use super::palette::{blue, green, grey, red, yellow};
use crate::{
    systemd::{self, enums::ActiveState, generate_file_uri},
    systemd_gui::is_dark,
};
use base::enums::UnitDBusLevel;

pub struct UnitInfoWriter {
    pub buffer: gtk::TextBuffer,
    pub text_iterator: gtk::TextIter,
}

//const TAG_NAME_HYPER_LINK: &str = "hyperlink";
const TAG_NAME_ACTIVE: &str = "active";
const TAG_NAME_ACTIVE_DARK: &str = "active_dark";
const TAG_NAME_RED: &str = "red";
const TAG_NAME_RED_DARK: &str = "red_dark";
const TAG_NAME_DISABLE: &str = "disable";
const TAG_NAME_DISABLE_DARK: &str = "disable_dark";
const TAG_NAME_GREY: &str = "grey";
const TAG_NAME_GREY_DARK: &str = "grey_dark";
const TAG_NAME_STATUS: &str = "blue";
const TAG_NAME_STATUS_DARK: &str = "blue_dark";
pub static TAG_DATA_LINK: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("link"));

/* const TAG_NAME_YELLOW_DARK: &str = "yellow_dark";
const TAG_NAME_YELLOW: &str = "yellow";
 */
pub const PROP_UNDERLINE: &str = "underline";

pub const SPECIAL_GLYPH_TREE_VERTICAL: &str = "";
pub const SPECIAL_GLYPH_TREE_SPACE: &str = "  ";
pub const SPECIAL_GLYPH_TREE_RIGHT: &str = "└─";
pub const SPECIAL_GLYPH_TREE_BRANCH: &str = "├─";

const PROP_WEIGHT: &str = "weight";
const PROP_FOREGROUND: &str = "foreground";

pub enum HyperLinkType {
    File,
    Unit(UnitDBusLevel),
    Http,
    Man,
    None,
}

impl HyperLinkType {
    fn ensure_link_type<'a>(&self, link: &'a str) -> Cow<'a, str> {
        match self {
            HyperLinkType::File => {
                if link.starts_with("file://") {
                    Cow::from(link)
                } else {
                    Cow::from(generate_file_uri(link))
                }
            }
            HyperLinkType::Unit(level) => {
                if link.starts_with("unit://") {
                    Cow::from(link)
                } else {
                    Cow::from(format!("unit://{}?{}", link, level.short()))
                }
            }
            HyperLinkType::Http => Cow::from(link),
            HyperLinkType::Man => Cow::from(link),
            HyperLinkType::None => Cow::from(link),
        }
    }
}

impl UnitInfoWriter {
    pub fn new(buf: gtk::TextBuffer, iter: gtk::TextIter) -> Self {
        Self {
            buffer: buf,
            text_iterator: iter,
        }
    }

    pub fn insert(&mut self, text: &str) {
        self.buffer.insert(&mut self.text_iterator, text);
    }

    pub fn insertln(&mut self, text: &str) {
        self.buffer.insert(&mut self.text_iterator, text);
        self.buffer.insert(&mut self.text_iterator, "\n");
    }

    pub fn newline(&mut self) {
        self.buffer.insert(&mut self.text_iterator, "\n");
    }

    pub fn insert_active(&mut self, text: &str) {
        self.insert_tag(text, Self::create_active_tag, None, HyperLinkType::None);
    }

    pub fn insert_red(&mut self, text: &str) {
        self.insert_tag(text, Self::create_red_tag, None, HyperLinkType::None);
    }

    /*     pub fn insert_yellow(&mut self, text: &str) {
        self.insert_tag(text, Self::create_yellow_tag, None, HyperLinkType::None);
    } */

    pub fn insert_disable(&mut self, text: &str) {
        self.insert_tag(text, Self::create_disable_tag, None, HyperLinkType::None);
    }

    pub fn insert_grey(&mut self, text: &str) {
        self.insert_tag(text, Self::create_grey_tag, None, HyperLinkType::None);
    }

    /*     pub fn insert_bold(&mut self, text: &str) {
        self.insert_tag(text, Self::create_bold_tag, None, HyperLinkType::None);
    } */

    pub fn insert_status(&mut self, text: &str) {
        self.insert_tag(text, Self::create_status_tag, None, HyperLinkType::None);
    }

    pub fn hyperlink(&mut self, text: &str, link: &str, type_: HyperLinkType) {
        self.insert_tag(text, Self::create_hyperlink_tag, Some(link), type_);
    }

    pub fn insert_state(&mut self, state: ActiveState) {
        let glyph = state.glyph_str();

        match state {
            systemd::enums::ActiveState::Active
            | systemd::enums::ActiveState::Reloading
            | systemd::enums::ActiveState::Activating
            | systemd::enums::ActiveState::Refreshing => self.insert_active(glyph),

            systemd::enums::ActiveState::Inactive | systemd::enums::ActiveState::Deactivating => {
                self.insert(glyph);
            }
            _ => self.insert_red(glyph),
        }
    }

    fn create_hyperlink_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        buf.create_tag(
            None,
            &[
                //  (PROP_FOREGROUND, &"blue".to_value()),
                (PROP_UNDERLINE, &pango::Underline::SingleLine.to_value()),
            ],
        )
    }

    fn create_active_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        let (color, name) = if is_dark() {
            (green(), TAG_NAME_ACTIVE_DARK)
        } else {
            (green(), TAG_NAME_ACTIVE)
        };

        let tag_op = buf.tag_table().lookup(name);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(name),
            &[
                (PROP_FOREGROUND, &color.get_color().to_value()),
                (PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
            ],
        )
    }

    fn create_red_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        let (color, name) = if is_dark() {
            (red(), TAG_NAME_RED_DARK)
        } else {
            (red(), TAG_NAME_RED)
        };

        let tag_op = buf.tag_table().lookup(name);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(name),
            &[
                (PROP_FOREGROUND, &color.get_color().to_value()),
                (PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
            ],
        )
    }

    fn create_disable_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        let (color, name) = if is_dark() {
            (yellow(), TAG_NAME_DISABLE_DARK)
        } else {
            (yellow(), TAG_NAME_DISABLE)
        };

        let tag_op = buf.tag_table().lookup(name);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(name),
            &[
                (PROP_FOREGROUND, &color.get_color().to_value()),
                (PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
            ],
        )
    }

    /*     fn create_bold_tag(buf: &gtk::TextBuffer, _is_dark: bool) -> Option<gtk::TextTag> {
        const NAME: &str = "BOLD_SIMPLE";

        let tag_op = buf.tag_table().lookup(NAME);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(NAME),
            &[(PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value())],
        )
    } */

    fn create_grey_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        let (color, name) = if is_dark() {
            (grey(), TAG_NAME_GREY_DARK)
        } else {
            (grey(), TAG_NAME_GREY)
        };

        let tag_op = buf.tag_table().lookup(name);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(name),
            &[(PROP_FOREGROUND, &color.get_color().to_value())],
        )
    }

    fn create_status_tag(buf: &gtk::TextBuffer) -> Option<gtk::TextTag> {
        let (color, name) = if is_dark() {
            (blue(), TAG_NAME_STATUS_DARK)
        } else {
            (blue(), TAG_NAME_STATUS)
        };

        let tag_op = buf.tag_table().lookup(name);
        if tag_op.is_some() {
            return tag_op;
        }

        buf.create_tag(
            Some(name),
            &[
                (PROP_FOREGROUND, &color.get_color().to_value()),
                (PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
            ],
        )
    }

    fn insert_tag(
        &mut self,
        text: &str,
        create_tag: impl Fn(&gtk::TextBuffer) -> Option<gtk::TextTag>,
        link: Option<&str>,
        type_: HyperLinkType,
    ) {
        let start_offset = self.text_iterator.offset();
        self.buffer.insert(&mut self.text_iterator, text);

        let tag = create_tag(&self.buffer);

        if let Some(tag) = tag {
            if let Some(link) = link {
                let link = type_.ensure_link_type(link);

                let link_value = link.to_value();
                debug!("text {text} link {link_value:?}");
                unsafe {
                    tag.set_qdata(*TAG_DATA_LINK, link_value);
                }
            }

            let start_iter = self.buffer.iter_at_offset(start_offset);
            self.buffer
                .apply_tag(&tag, &start_iter, &self.text_iterator);
        }
    }

    pub fn char_count(&self) -> i32 {
        self.buffer.char_count()
    }
}