gtk4/subclass/
entry_buffer.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3// rustdoc-stripper-ignore-next
4//! Traits intended for subclassing [`EntryBuffer`].
5use std::sync::OnceLock;
6
7use glib::{translate::*, GString};
8
9use super::PtrHolder;
10use crate::{ffi, prelude::*, subclass::prelude::*, EntryBuffer};
11
12pub trait EntryBufferImpl: ObjectImpl + ObjectSubclass<Type: IsA<EntryBuffer>> {
13    fn delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
14        self.parent_delete_text(position, n_chars)
15    }
16
17    fn deleted_text(&self, position: u32, n_chars: Option<u32>) {
18        self.parent_deleted_text(position, n_chars)
19    }
20
21    #[doc(alias = "get_length")]
22    fn length(&self) -> u32 {
23        self.parent_length()
24    }
25
26    #[doc(alias = "get_text")]
27    fn text(&self) -> GString {
28        self.parent_text()
29    }
30    fn insert_text(&self, position: u32, chars: &str) -> u32 {
31        self.parent_insert_text(position, chars)
32    }
33
34    fn inserted_text(&self, position: u32, chars: &str) {
35        self.parent_inserted_text(position, chars)
36    }
37}
38
39pub trait EntryBufferImplExt: EntryBufferImpl {
40    fn parent_delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
41        unsafe {
42            let data = Self::type_data();
43            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
44            let f = (*parent_class)
45                .delete_text
46                .expect("No parent class impl for \"delete_text\"");
47            f(
48                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
49                position,
50                n_chars.unwrap_or(u32::MAX),
51            )
52        }
53    }
54
55    fn parent_deleted_text(&self, position: u32, n_chars: Option<u32>) {
56        unsafe {
57            let data = Self::type_data();
58            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
59            if let Some(f) = (*parent_class).deleted_text {
60                f(
61                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
62                    position,
63                    n_chars.unwrap_or(u32::MAX),
64                )
65            }
66        }
67    }
68
69    fn parent_length(&self) -> u32 {
70        unsafe {
71            let data = Self::type_data();
72            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
73            let f = (*parent_class)
74                .get_length
75                .expect("No parent class impl for \"get_length\"");
76            f(self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0)
77        }
78    }
79
80    fn parent_text(&self) -> GString {
81        unsafe {
82            let data = Self::type_data();
83            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
84            let f = (*parent_class)
85                .get_text
86                .expect("No parent class impl for \"get_text\"");
87            let mut n_bytes = 0;
88            let res = f(
89                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
90                &mut n_bytes,
91            );
92            FromGlibContainer::from_glib_none_num(res, n_bytes as _)
93        }
94    }
95
96    fn parent_insert_text(&self, position: u32, text: &str) -> u32 {
97        unsafe {
98            let data = Self::type_data();
99            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
100            let f = (*parent_class)
101                .insert_text
102                .expect("No parent class impl for \"insert_text\"");
103
104            f(
105                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
106                position,
107                text.to_glib_none().0,
108                text.chars().count() as u32,
109            )
110        }
111    }
112
113    fn parent_inserted_text(&self, position: u32, text: &str) {
114        unsafe {
115            let data = Self::type_data();
116            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
117            if let Some(f) = (*parent_class).inserted_text {
118                f(
119                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
120                    position,
121                    text.to_glib_none().0,
122                    text.chars().count() as u32,
123                )
124            }
125        }
126    }
127}
128
129impl<T: EntryBufferImpl> EntryBufferImplExt for T {}
130
131unsafe impl<T: EntryBufferImpl> IsSubclassable<T> for EntryBuffer {
132    fn class_init(class: &mut glib::Class<Self>) {
133        Self::parent_class_init::<T>(class);
134
135        assert_initialized_main_thread!();
136
137        let klass = class.as_mut();
138        klass.delete_text = Some(entry_buffer_delete_text::<T>);
139        klass.deleted_text = Some(entry_buffer_deleted_text::<T>);
140        klass.get_length = Some(entry_buffer_get_length::<T>);
141        klass.get_text = Some(entry_buffer_get_text::<T>);
142        klass.insert_text = Some(entry_buffer_insert_text::<T>);
143        klass.inserted_text = Some(entry_buffer_inserted_text::<T>);
144    }
145}
146
147unsafe extern "C" fn entry_buffer_delete_text<T: EntryBufferImpl>(
148    ptr: *mut ffi::GtkEntryBuffer,
149    position: u32,
150    n_chars: u32,
151) -> u32 {
152    let instance = &*(ptr as *mut T::Instance);
153    let imp = instance.imp();
154
155    let n_chars = if n_chars == u32::MAX {
156        None
157    } else {
158        Some(n_chars)
159    };
160
161    imp.delete_text(position, n_chars)
162}
163
164unsafe extern "C" fn entry_buffer_deleted_text<T: EntryBufferImpl>(
165    ptr: *mut ffi::GtkEntryBuffer,
166    position: u32,
167    n_chars: u32,
168) {
169    let instance = &*(ptr as *mut T::Instance);
170    let imp = instance.imp();
171
172    let n_chars = if n_chars == u32::MAX {
173        None
174    } else {
175        Some(n_chars)
176    };
177
178    imp.deleted_text(position, n_chars)
179}
180
181unsafe extern "C" fn entry_buffer_get_text<T: EntryBufferImpl>(
182    ptr: *mut ffi::GtkEntryBuffer,
183    n_bytes: *mut usize,
184) -> *const libc::c_char {
185    let instance = &*(ptr as *mut T::Instance);
186    let imp = instance.imp();
187
188    let ret = imp.text();
189    if !n_bytes.is_null() {
190        *n_bytes = ret.len();
191    }
192    // Ensures that the returned text stays alive for as long as
193    // the entry buffer instance
194
195    static QUARK: OnceLock<glib::Quark> = OnceLock::new();
196    let quark = *QUARK.get_or_init(|| glib::Quark::from_str("gtk4-rs-subclass-entry-buffer-text"));
197
198    let fullptr = ret.into_glib_ptr();
199    imp.obj().set_qdata(
200        quark,
201        PtrHolder(fullptr, |ptr| {
202            glib::ffi::g_free(ptr as *mut _);
203        }),
204    );
205    fullptr
206}
207
208unsafe extern "C" fn entry_buffer_get_length<T: EntryBufferImpl>(
209    ptr: *mut ffi::GtkEntryBuffer,
210) -> u32 {
211    let instance = &*(ptr as *mut T::Instance);
212    let imp = instance.imp();
213
214    imp.length()
215}
216
217unsafe extern "C" fn entry_buffer_insert_text<T: EntryBufferImpl>(
218    ptr: *mut ffi::GtkEntryBuffer,
219    position: u32,
220    charsptr: *const libc::c_char,
221    n_chars: u32,
222) -> u32 {
223    let instance = &*(ptr as *mut T::Instance);
224    let imp = instance.imp();
225    let text: Borrowed<GString> = from_glib_borrow(charsptr);
226
227    let chars = text_n_chars(&text, n_chars);
228    imp.insert_text(position, chars)
229}
230
231unsafe extern "C" fn entry_buffer_inserted_text<T: EntryBufferImpl>(
232    ptr: *mut ffi::GtkEntryBuffer,
233    position: u32,
234    charsptr: *const libc::c_char,
235    length: u32,
236) {
237    let instance = &*(ptr as *mut T::Instance);
238    let imp = instance.imp();
239    let text: Borrowed<GString> = from_glib_borrow(charsptr);
240
241    let chars = text_n_chars(&text, length);
242    imp.inserted_text(position, chars)
243}
244
245#[doc(alias = "get_text_n_chars")]
246fn text_n_chars(text: &str, n_chars: u32) -> &str {
247    if n_chars != u32::MAX && n_chars > 0 {
248        let mut iter = text
249            .char_indices()
250            .skip((n_chars - 1) as _)
251            .map(|(pos, _)| pos);
252        iter
253            .next()
254            .expect(
255                "The passed text to EntryBuffer contains fewer characters than what's passed as a length",
256            );
257        let pos_end = iter.next().unwrap_or(text.len());
258        &text[..pos_end]
259    } else if n_chars == 0 {
260        // Avoid doing skipping to -1 char
261        ""
262    } else {
263        text
264    }
265}
266
267#[cfg(test)]
268mod test {
269    use super::text_n_chars;
270    #[std::prelude::v1::test]
271    fn n_chars_max_length_ascii() {
272        assert_eq!(text_n_chars("gtk-rs bindings", 6), "gtk-rs");
273        assert_eq!(text_n_chars("gtk-rs bindings", u32::MAX), "gtk-rs bindings");
274    }
275
276    #[std::prelude::v1::test]
277    #[should_panic]
278    fn n_chars_max_length_ascii_panic() {
279        assert_eq!(text_n_chars("gtk-rs", 7), "gtk-rs");
280    }
281
282    #[std::prelude::v1::test]
283    fn n_chars_max_length_utf8() {
284        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 2), "šŸ‘ØšŸ‘©");
285        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 0), "");
286        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 4), "šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦");
287        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", u32::MAX), "šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦");
288        assert_eq!(text_n_chars("كتاب", 2), "كت");
289    }
290
291    #[std::prelude::v1::test]
292    fn n_chars_max_length_utf8_ascii() {
293        assert_eq!(text_n_chars("šŸ‘ØgšŸ‘©tšŸ‘§kšŸ‘¦", 2), "šŸ‘Øg");
294        assert_eq!(text_n_chars("šŸ‘ØgšŸ‘©tšŸ‘§kšŸ‘¦", 5), "šŸ‘ØgšŸ‘©tšŸ‘§");
295        assert_eq!(text_n_chars("كaتاب", 3), "كaت");
296    }
297
298    #[std::prelude::v1::test]
299    #[should_panic]
300    fn n_chars_max_length_utf8_panic() {
301        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 5), "šŸ‘ØšŸ‘©");
302    }
303}