Skip to main content

fractal/components/pill/
source.rs

1use gtk::{glib, prelude::*, subclass::prelude::*};
2
3use super::Pill;
4use crate::{
5    components::{AvatarData, AvatarImageSafetySetting},
6    session::Room,
7};
8
9mod imp {
10    use std::{cell::Cell, marker::PhantomData};
11
12    use super::*;
13
14    #[repr(C)]
15    pub struct PillSourceClass {
16        parent_class: glib::object::ObjectClass,
17        pub(super) identifier: fn(&super::PillSource) -> String,
18    }
19
20    unsafe impl ClassStruct for PillSourceClass {
21        type Type = PillSource;
22    }
23
24    pub(super) fn pill_source_identifier(this: &super::PillSource) -> String {
25        let klass = this.class();
26        (klass.as_ref().identifier)(this)
27    }
28
29    #[derive(Debug, Default, glib::Properties)]
30    #[properties(wrapper_type = super::PillSource)]
31    pub struct PillSource {
32        /// A unique identifier for this source.
33        #[property(get = Self::identifier)]
34        identifier: PhantomData<String>,
35        /// The display name of this source.
36        #[property(get = Self::display_name, set = Self::set_display_name, explicit_notify)]
37        display_name: PhantomData<String>,
38        /// Whether the display name of this source is ambiguous.
39        #[property(get, set = Self::set_name_ambiguous, explicit_notify)]
40        is_name_ambiguous: Cell<bool>,
41        /// The disambiguated display name of this source.
42        ///
43        /// This is the name to display in case the identifier does not appear
44        /// next to it.
45        #[property(get = Self::disambiguated_name)]
46        disambiguated_name: PhantomData<String>,
47        /// The avatar data of this source.
48        #[property(get)]
49        avatar_data: AvatarData,
50    }
51
52    #[glib::object_subclass]
53    impl ObjectSubclass for PillSource {
54        const NAME: &'static str = "PillSource";
55        const ABSTRACT: bool = true;
56        type Type = super::PillSource;
57        type Class = PillSourceClass;
58    }
59
60    #[glib::derived_properties]
61    impl ObjectImpl for PillSource {}
62
63    impl PillSource {
64        /// A unique identifier for this source.
65        fn identifier(&self) -> String {
66            imp::pill_source_identifier(&self.obj())
67        }
68
69        /// The display name of this source.
70        fn display_name(&self) -> String {
71            self.avatar_data.display_name()
72        }
73
74        /// Set the display name of this source.
75        fn set_display_name(&self, display_name: String) {
76            if self.display_name() == display_name {
77                return;
78            }
79
80            self.avatar_data.set_display_name(display_name);
81
82            let obj = self.obj();
83            obj.notify_display_name();
84            obj.notify_disambiguated_name();
85        }
86
87        /// Set whether the display name of this source is ambiguous.
88        fn set_name_ambiguous(&self, is_ambiguous: bool) {
89            if self.is_name_ambiguous.get() == is_ambiguous {
90                return;
91            }
92
93            self.is_name_ambiguous.set(is_ambiguous);
94
95            let obj = self.obj();
96            obj.notify_is_name_ambiguous();
97            obj.notify_disambiguated_name();
98        }
99
100        /// The disambiguated display name of this source.
101        fn disambiguated_name(&self) -> String {
102            let display_name = self.display_name();
103
104            if self.is_name_ambiguous.get() {
105                format!("{display_name} ({})", self.identifier())
106            } else {
107                display_name
108            }
109        }
110    }
111}
112
113glib::wrapper! {
114    /// Parent class of objects that can be represented as a `Pill`.
115    pub struct PillSource(ObjectSubclass<imp::PillSource>);
116}
117
118/// Public trait containing implemented methods for everything that derives from
119/// `PillSource`.
120///
121/// To override the behavior of these methods, override the corresponding method
122/// of `PillSourceImpl`.
123pub trait PillSourceExt: 'static {
124    /// A unique identifier for this source.
125    #[allow(dead_code)]
126    fn identifier(&self) -> String;
127
128    /// The display name of this source.
129    fn display_name(&self) -> String;
130
131    /// Set the display name of this source.
132    fn set_display_name(&self, display_name: String);
133
134    /// Whether the display name of this source is ambiguous.
135    #[allow(dead_code)]
136    fn is_name_ambiguous(&self) -> bool;
137
138    /// Set whether the display name of this source is ambiguous.
139    fn set_is_name_ambiguous(&self, is_ambiguous: bool);
140
141    /// The disambiguated display name of this source.
142    ///
143    /// This is the name to display in case the identifier does not appear next
144    /// to it.
145    fn disambiguated_name(&self) -> String;
146
147    /// The avatar data of this source.
148    fn avatar_data(&self) -> AvatarData;
149
150    /// Connect to the signal emitted when the display name changes.
151    fn connect_display_name_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId;
152
153    /// Connect to the signal emitted when the disambiguated name changes.
154    fn connect_disambiguated_name_notify<F: Fn(&Self) + 'static>(
155        &self,
156        f: F,
157    ) -> glib::SignalHandlerId;
158
159    /// Get a `Pill` representing this source, watching the given safety
160    /// setting.
161    fn to_pill(
162        &self,
163        watched_safety_setting: AvatarImageSafetySetting,
164        watched_room: Option<Room>,
165    ) -> Pill;
166}
167
168impl<O: IsA<PillSource>> PillSourceExt for O {
169    /// A unique identifier for this source.
170    fn identifier(&self) -> String {
171        self.upcast_ref().identifier()
172    }
173
174    /// The display name of this source.
175    fn display_name(&self) -> String {
176        self.upcast_ref().display_name()
177    }
178
179    /// Set the display name of this source.
180    fn set_display_name(&self, display_name: String) {
181        self.upcast_ref().set_display_name(display_name);
182    }
183
184    /// Whether the display name of this source is ambiguous.
185    fn is_name_ambiguous(&self) -> bool {
186        self.upcast_ref().is_name_ambiguous()
187    }
188
189    /// Set whether the display name of this source is ambiguous.
190    fn set_is_name_ambiguous(&self, is_ambiguous: bool) {
191        self.upcast_ref().set_is_name_ambiguous(is_ambiguous);
192    }
193
194    /// The disambiguated display name of this source.
195    fn disambiguated_name(&self) -> String {
196        self.upcast_ref().disambiguated_name()
197    }
198
199    /// The avatar data of this source.
200    fn avatar_data(&self) -> AvatarData {
201        self.upcast_ref().avatar_data()
202    }
203
204    /// Connect to the signal emitted when the display name changes.
205    fn connect_display_name_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
206        self.upcast_ref()
207            .connect_display_name_notify(move |source| f(source.downcast_ref().unwrap()))
208    }
209
210    /// Connect to the signal emitted when the disambiguated name changes.
211    fn connect_disambiguated_name_notify<F: Fn(&Self) + 'static>(
212        &self,
213        f: F,
214    ) -> glib::SignalHandlerId {
215        self.upcast_ref()
216            .connect_disambiguated_name_notify(move |source| f(source.downcast_ref().unwrap()))
217    }
218
219    /// Get a `Pill` representing this source.
220    fn to_pill(
221        &self,
222        watched_safety_setting: AvatarImageSafetySetting,
223        watched_room: Option<Room>,
224    ) -> Pill {
225        Pill::new(self, watched_safety_setting, watched_room)
226    }
227}
228
229/// Public trait that must be implemented for everything that derives from
230/// `PillSource`.
231///
232/// Overriding a method from this Trait overrides also its behavior in
233/// `PillSourceExt`.
234pub trait PillSourceImpl: ObjectImpl {
235    /// A unique identifier for this source.
236    fn identifier(&self) -> String;
237}
238
239// Make `PillSource` subclassable.
240unsafe impl<T> IsSubclassable<T> for PillSource
241where
242    T: PillSourceImpl,
243    T::Type: IsA<PillSource>,
244{
245    fn class_init(class: &mut glib::Class<Self>) {
246        Self::parent_class_init::<T>(class.upcast_ref_mut());
247
248        let klass = class.as_mut();
249
250        klass.identifier = identifier_trampoline::<T>;
251    }
252}
253
254// Virtual method implementation trampolines.
255fn identifier_trampoline<T>(this: &PillSource) -> String
256where
257    T: ObjectSubclass + PillSourceImpl,
258    T::Type: IsA<PillSource>,
259{
260    let this = this.downcast_ref::<T::Type>().unwrap();
261    this.imp().identifier()
262}