1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, CompositeTemplate};
use sourceview::prelude::*;

use crate::{
    components::{ButtonRow, CopyableRow, ToastableDialog},
    prelude::*,
    session::model::Event,
    toast, utils,
};

mod imp {
    use std::cell::RefCell;

    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(resource = "/org/gnome/Fractal/ui/session/view/event_details_dialog.ui")]
    #[properties(wrapper_type = super::EventDetailsDialog)]
    pub struct EventDetailsDialog {
        /// The event that is displayed in the dialog.
        #[property(get, construct_only)]
        pub event: RefCell<Option<Event>>,
        #[template_child]
        pub navigation_view: TemplateChild<adw::NavigationView>,
        #[template_child]
        pub source_page: TemplateChild<adw::NavigationPage>,
        #[template_child]
        pub source_view: TemplateChild<sourceview::View>,
    }

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

        fn class_init(klass: &mut Self::Class) {
            ButtonRow::ensure_type();
            CopyableRow::ensure_type();

            Self::bind_template(klass);
            Self::Type::bind_template_callbacks(klass);

            klass.install_action("event-details-dialog.copy-source", None, |obj, _, _| {
                let clipboard = obj.clipboard();
                let buffer = obj.imp().source_view.buffer();
                let (start_iter, end_iter) = buffer.bounds();
                clipboard.set_text(&buffer.text(&start_iter, &end_iter, true));
                toast!(obj, gettext("Source copied to clipboard"))
            });
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for EventDetailsDialog {
        fn constructed(&self) {
            self.parent_constructed();

            let json_lang = sourceview::LanguageManager::default().language("json");

            let buffer = self
                .source_view
                .buffer()
                .downcast::<sourceview::Buffer>()
                .unwrap();
            buffer.set_language(json_lang.as_ref());
            utils::sourceview::setup_style_scheme(&buffer);
        }
    }

    impl WidgetImpl for EventDetailsDialog {}
    impl AdwDialogImpl for EventDetailsDialog {}
    impl ToastableDialogImpl for EventDetailsDialog {}
}

glib::wrapper! {
    /// A dialog showing the details of an event.
    pub struct EventDetailsDialog(ObjectSubclass<imp::EventDetailsDialog>)
        @extends gtk::Widget, adw::Dialog, ToastableDialog, @implements gtk::Accessible;
}

#[gtk::template_callbacks]
impl EventDetailsDialog {
    pub fn new(event: &Event) -> Self {
        glib::Object::builder().property("event", event).build()
    }

    /// View the given source.
    fn show_source(&self, title: &str, source: &str) {
        let imp = self.imp();

        imp.source_view.buffer().set_text(source);
        imp.source_page.set_title(title);
        imp.navigation_view.push_by_tag("source");
    }

    /// View the original source.
    #[template_callback]
    fn show_original_source(&self) {
        let Some(event) = self.event() else {
            return;
        };

        if let Some(source) = event.source() {
            let title = if event.is_edited() {
                gettext("Original Event Source")
            } else {
                gettext("Event Source")
            };
            self.show_source(&title, &source);
        }
    }

    /// View the source of the latest edit.
    #[template_callback]
    fn show_edit_source(&self) {
        let Some(event) = self.event() else {
            return;
        };

        let source = event.latest_edit_source();
        let title = gettext("Latest Edit Source");
        self.show_source(&title, &source);
    }
}