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`](crate::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: EntryBufferImplExt + ObjectImpl {
13    /// Deletes a sequence of characters from the buffer.
14    ///
15    /// @n_chars characters are deleted starting at @position.
16    /// If @n_chars is negative, then all characters until the
17    /// end of the text are deleted.
18    ///
19    /// If @position or @n_chars are out of bounds, then they
20    /// are coerced to sane values.
21    ///
22    /// Note that the positions are specified in characters,
23    /// not bytes.
24    /// ## `position`
25    /// position at which to delete text
26    /// ## `n_chars`
27    /// number of characters to delete
28    ///
29    /// # Returns
30    ///
31    /// The number of characters deleted.
32    fn delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
33        self.parent_delete_text(position, n_chars)
34    }
35
36    fn deleted_text(&self, position: u32, n_chars: Option<u32>) {
37        self.parent_deleted_text(position, n_chars)
38    }
39
40    /// Retrieves the length in characters of the buffer.
41    ///
42    /// # Returns
43    ///
44    /// The number of characters in the buffer.
45    #[doc(alias = "get_length")]
46    fn length(&self) -> u32 {
47        self.parent_length()
48    }
49
50    #[doc(alias = "get_text")]
51    fn text(&self) -> GString {
52        self.parent_text()
53    }
54    /// Inserts @n_chars characters of @chars into the contents of the
55    /// buffer, at position @position.
56    ///
57    /// If @n_chars is negative, then characters from chars will be inserted
58    /// until a null-terminator is found. If @position or @n_chars are out of
59    /// bounds, or the maximum buffer text length is exceeded, then they are
60    /// coerced to sane values.
61    ///
62    /// Note that the position and length are in characters, not in bytes.
63    /// ## `position`
64    /// the position at which to insert text.
65    /// ## `chars`
66    /// the text to insert into the buffer.
67    /// ## `n_chars`
68    /// the length of the text in characters, or -1
69    ///
70    /// # Returns
71    ///
72    /// The number of characters actually inserted.
73    fn insert_text(&self, position: u32, chars: &str) -> u32 {
74        self.parent_insert_text(position, chars)
75    }
76
77    fn inserted_text(&self, position: u32, chars: &str) {
78        self.parent_inserted_text(position, chars)
79    }
80}
81
82mod sealed {
83    pub trait Sealed {}
84    impl<T: super::EntryBufferImplExt> Sealed for T {}
85}
86
87pub trait EntryBufferImplExt: sealed::Sealed + ObjectSubclass {
88    fn parent_delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
89        unsafe {
90            let data = Self::type_data();
91            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
92            let f = (*parent_class)
93                .delete_text
94                .expect("No parent class impl for \"delete_text\"");
95            f(
96                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
97                position,
98                n_chars.unwrap_or(u32::MAX),
99            )
100        }
101    }
102
103    fn parent_deleted_text(&self, position: u32, n_chars: Option<u32>) {
104        unsafe {
105            let data = Self::type_data();
106            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
107            if let Some(f) = (*parent_class).deleted_text {
108                f(
109                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
110                    position,
111                    n_chars.unwrap_or(u32::MAX),
112                )
113            }
114        }
115    }
116
117    fn parent_length(&self) -> u32 {
118        unsafe {
119            let data = Self::type_data();
120            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
121            let f = (*parent_class)
122                .get_length
123                .expect("No parent class impl for \"get_length\"");
124            f(self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0)
125        }
126    }
127
128    fn parent_text(&self) -> GString {
129        unsafe {
130            let data = Self::type_data();
131            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
132            let f = (*parent_class)
133                .get_text
134                .expect("No parent class impl for \"get_text\"");
135            let mut n_bytes = 0;
136            let res = f(
137                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
138                &mut n_bytes,
139            );
140            FromGlibContainer::from_glib_none_num(res, n_bytes as _)
141        }
142    }
143
144    fn parent_insert_text(&self, position: u32, text: &str) -> u32 {
145        unsafe {
146            let data = Self::type_data();
147            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
148            let f = (*parent_class)
149                .insert_text
150                .expect("No parent class impl for \"insert_text\"");
151
152            f(
153                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
154                position,
155                text.to_glib_none().0,
156                text.chars().count() as u32,
157            )
158        }
159    }
160
161    fn parent_inserted_text(&self, position: u32, text: &str) {
162        unsafe {
163            let data = Self::type_data();
164            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
165            if let Some(f) = (*parent_class).inserted_text {
166                f(
167                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
168                    position,
169                    text.to_glib_none().0,
170                    text.chars().count() as u32,
171                )
172            }
173        }
174    }
175}
176
177impl<T: EntryBufferImpl> EntryBufferImplExt for T {}
178
179unsafe impl<T: EntryBufferImpl> IsSubclassable<T> for EntryBuffer {
180    fn class_init(class: &mut glib::Class<Self>) {
181        Self::parent_class_init::<T>(class);
182
183        assert_initialized_main_thread!();
184
185        let klass = class.as_mut();
186        klass.delete_text = Some(entry_buffer_delete_text::<T>);
187        klass.deleted_text = Some(entry_buffer_deleted_text::<T>);
188        klass.get_length = Some(entry_buffer_get_length::<T>);
189        klass.get_text = Some(entry_buffer_get_text::<T>);
190        klass.insert_text = Some(entry_buffer_insert_text::<T>);
191        klass.inserted_text = Some(entry_buffer_inserted_text::<T>);
192    }
193}
194
195unsafe extern "C" fn entry_buffer_delete_text<T: EntryBufferImpl>(
196    ptr: *mut ffi::GtkEntryBuffer,
197    position: u32,
198    n_chars: u32,
199) -> u32 {
200    let instance = &*(ptr as *mut T::Instance);
201    let imp = instance.imp();
202
203    let n_chars = if n_chars == u32::MAX {
204        None
205    } else {
206        Some(n_chars)
207    };
208
209    imp.delete_text(position, n_chars)
210}
211
212unsafe extern "C" fn entry_buffer_deleted_text<T: EntryBufferImpl>(
213    ptr: *mut ffi::GtkEntryBuffer,
214    position: u32,
215    n_chars: u32,
216) {
217    let instance = &*(ptr as *mut T::Instance);
218    let imp = instance.imp();
219
220    let n_chars = if n_chars == u32::MAX {
221        None
222    } else {
223        Some(n_chars)
224    };
225
226    imp.deleted_text(position, n_chars)
227}
228
229unsafe extern "C" fn entry_buffer_get_text<T: EntryBufferImpl>(
230    ptr: *mut ffi::GtkEntryBuffer,
231    n_bytes: *mut usize,
232) -> *const libc::c_char {
233    let instance = &*(ptr as *mut T::Instance);
234    let imp = instance.imp();
235
236    let ret = imp.text();
237    if !n_bytes.is_null() {
238        *n_bytes = ret.len();
239    }
240    // Ensures that the returned text stays alive for as long as
241    // the entry buffer instance
242
243    static QUARK: OnceLock<glib::Quark> = OnceLock::new();
244    let quark = *QUARK.get_or_init(|| glib::Quark::from_str("gtk4-rs-subclass-entry-buffer-text"));
245
246    let fullptr = ret.into_glib_ptr();
247    imp.obj().set_qdata(
248        quark,
249        PtrHolder(fullptr, |ptr| {
250            glib::ffi::g_free(ptr as *mut _);
251        }),
252    );
253    fullptr
254}
255
256unsafe extern "C" fn entry_buffer_get_length<T: EntryBufferImpl>(
257    ptr: *mut ffi::GtkEntryBuffer,
258) -> u32 {
259    let instance = &*(ptr as *mut T::Instance);
260    let imp = instance.imp();
261
262    imp.length()
263}
264
265unsafe extern "C" fn entry_buffer_insert_text<T: EntryBufferImpl>(
266    ptr: *mut ffi::GtkEntryBuffer,
267    position: u32,
268    charsptr: *const libc::c_char,
269    n_chars: u32,
270) -> u32 {
271    let instance = &*(ptr as *mut T::Instance);
272    let imp = instance.imp();
273    let text: Borrowed<GString> = from_glib_borrow(charsptr);
274
275    let chars = text_n_chars(&text, n_chars);
276    imp.insert_text(position, chars)
277}
278
279unsafe extern "C" fn entry_buffer_inserted_text<T: EntryBufferImpl>(
280    ptr: *mut ffi::GtkEntryBuffer,
281    position: u32,
282    charsptr: *const libc::c_char,
283    length: u32,
284) {
285    let instance = &*(ptr as *mut T::Instance);
286    let imp = instance.imp();
287    let text: Borrowed<GString> = from_glib_borrow(charsptr);
288
289    let chars = text_n_chars(&text, length);
290    imp.inserted_text(position, chars)
291}
292
293#[doc(alias = "get_text_n_chars")]
294fn text_n_chars(text: &str, n_chars: u32) -> &str {
295    if n_chars != u32::MAX && n_chars > 0 {
296        let mut iter = text
297            .char_indices()
298            .skip((n_chars - 1) as _)
299            .map(|(pos, _)| pos);
300        iter
301            .next()
302            .expect(
303                "The passed text to EntryBuffer contains fewer characters than what's passed as a length",
304            );
305        let pos_end = iter.next().unwrap_or(text.len());
306        &text[..pos_end]
307    } else if n_chars == 0 {
308        // Avoid doing skipping to -1 char
309        ""
310    } else {
311        text
312    }
313}
314
315#[cfg(test)]
316mod test {
317    use super::text_n_chars;
318    #[std::prelude::v1::test]
319    fn n_chars_max_length_ascii() {
320        assert_eq!(text_n_chars("gtk-rs bindings", 6), "gtk-rs");
321        assert_eq!(text_n_chars("gtk-rs bindings", u32::MAX), "gtk-rs bindings");
322    }
323
324    #[std::prelude::v1::test]
325    #[should_panic]
326    fn n_chars_max_length_ascii_panic() {
327        assert_eq!(text_n_chars("gtk-rs", 7), "gtk-rs");
328    }
329
330    #[std::prelude::v1::test]
331    fn n_chars_max_length_utf8() {
332        assert_eq!(text_n_chars("👨👩👧👦", 2), "👨👩");
333        assert_eq!(text_n_chars("👨👩👧👦", 0), "");
334        assert_eq!(text_n_chars("👨👩👧👦", 4), "👨👩👧👦");
335        assert_eq!(text_n_chars("👨👩👧👦", u32::MAX), "👨👩👧👦");
336        assert_eq!(text_n_chars("كتاب", 2), "كت");
337    }
338
339    #[std::prelude::v1::test]
340    fn n_chars_max_length_utf8_ascii() {
341        assert_eq!(text_n_chars("👨g👩t👧k👦", 2), "👨g");
342        assert_eq!(text_n_chars("👨g👩t👧k👦", 5), "👨g👩t👧");
343        assert_eq!(text_n_chars("كaتاب", 3), "كaت");
344    }
345
346    #[std::prelude::v1::test]
347    #[should_panic]
348    fn n_chars_max_length_utf8_panic() {
349        assert_eq!(text_n_chars("👨👩👧👦", 5), "👨👩");
350    }
351}