Skip to main content

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::{GString, translate::*};
8
9use super::PtrHolder;
10use crate::{EntryBuffer, ffi, prelude::*, subclass::prelude::*};
11
12pub trait EntryBufferImpl: ObjectImpl + ObjectSubclass<Type: IsA<EntryBuffer>> {
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
82pub trait EntryBufferImplExt: EntryBufferImpl {
83    fn parent_delete_text(&self, position: u32, n_chars: Option<u32>) -> u32 {
84        unsafe {
85            let data = Self::type_data();
86            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
87            let f = (*parent_class)
88                .delete_text
89                .expect("No parent class impl for \"delete_text\"");
90            f(
91                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
92                position,
93                n_chars.unwrap_or(u32::MAX),
94            )
95        }
96    }
97
98    fn parent_deleted_text(&self, position: u32, n_chars: Option<u32>) {
99        unsafe {
100            let data = Self::type_data();
101            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
102            if let Some(f) = (*parent_class).deleted_text {
103                f(
104                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
105                    position,
106                    n_chars.unwrap_or(u32::MAX),
107                )
108            }
109        }
110    }
111
112    fn parent_length(&self) -> u32 {
113        unsafe {
114            let data = Self::type_data();
115            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
116            let f = (*parent_class)
117                .get_length
118                .expect("No parent class impl for \"get_length\"");
119            f(self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0)
120        }
121    }
122
123    fn parent_text(&self) -> GString {
124        unsafe {
125            let data = Self::type_data();
126            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
127            let f = (*parent_class)
128                .get_text
129                .expect("No parent class impl for \"get_text\"");
130            let mut n_bytes = 0;
131            let res = f(
132                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
133                &mut n_bytes,
134            );
135            FromGlibContainer::from_glib_none_num(res, n_bytes as _)
136        }
137    }
138
139    fn parent_insert_text(&self, position: u32, text: &str) -> u32 {
140        unsafe {
141            let data = Self::type_data();
142            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
143            let f = (*parent_class)
144                .insert_text
145                .expect("No parent class impl for \"insert_text\"");
146
147            f(
148                self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
149                position,
150                text.to_glib_none().0,
151                text.chars().count() as u32,
152            )
153        }
154    }
155
156    fn parent_inserted_text(&self, position: u32, text: &str) {
157        unsafe {
158            let data = Self::type_data();
159            let parent_class = data.as_ref().parent_class() as *mut ffi::GtkEntryBufferClass;
160            if let Some(f) = (*parent_class).inserted_text {
161                f(
162                    self.obj().unsafe_cast_ref::<EntryBuffer>().to_glib_none().0,
163                    position,
164                    text.to_glib_none().0,
165                    text.chars().count() as u32,
166                )
167            }
168        }
169    }
170}
171
172impl<T: EntryBufferImpl> EntryBufferImplExt for T {}
173
174unsafe impl<T: EntryBufferImpl> IsSubclassable<T> for EntryBuffer {
175    fn class_init(class: &mut glib::Class<Self>) {
176        Self::parent_class_init::<T>(class);
177
178        assert_initialized_main_thread!();
179
180        let klass = class.as_mut();
181        klass.delete_text = Some(entry_buffer_delete_text::<T>);
182        klass.deleted_text = Some(entry_buffer_deleted_text::<T>);
183        klass.get_length = Some(entry_buffer_get_length::<T>);
184        klass.get_text = Some(entry_buffer_get_text::<T>);
185        klass.insert_text = Some(entry_buffer_insert_text::<T>);
186        klass.inserted_text = Some(entry_buffer_inserted_text::<T>);
187    }
188}
189
190unsafe extern "C" fn entry_buffer_delete_text<T: EntryBufferImpl>(
191    ptr: *mut ffi::GtkEntryBuffer,
192    position: u32,
193    n_chars: u32,
194) -> u32 {
195    unsafe {
196        let instance = &*(ptr as *mut T::Instance);
197        let imp = instance.imp();
198
199        let n_chars = if n_chars == u32::MAX {
200            None
201        } else {
202            Some(n_chars)
203        };
204
205        imp.delete_text(position, n_chars)
206    }
207}
208
209unsafe extern "C" fn entry_buffer_deleted_text<T: EntryBufferImpl>(
210    ptr: *mut ffi::GtkEntryBuffer,
211    position: u32,
212    n_chars: u32,
213) {
214    unsafe {
215        let instance = &*(ptr as *mut T::Instance);
216        let imp = instance.imp();
217
218        let n_chars = if n_chars == u32::MAX {
219            None
220        } else {
221            Some(n_chars)
222        };
223
224        imp.deleted_text(position, n_chars)
225    }
226}
227
228unsafe extern "C" fn entry_buffer_get_text<T: EntryBufferImpl>(
229    ptr: *mut ffi::GtkEntryBuffer,
230    n_bytes: *mut usize,
231) -> *const libc::c_char {
232    unsafe {
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 =
245            *QUARK.get_or_init(|| glib::Quark::from_str("gtk4-rs-subclass-entry-buffer-text"));
246
247        let fullptr = ret.into_glib_ptr();
248        imp.obj().set_qdata(
249            quark,
250            PtrHolder(fullptr, |ptr| {
251                glib::ffi::g_free(ptr as *mut _);
252            }),
253        );
254        fullptr
255    }
256}
257
258unsafe extern "C" fn entry_buffer_get_length<T: EntryBufferImpl>(
259    ptr: *mut ffi::GtkEntryBuffer,
260) -> u32 {
261    unsafe {
262        let instance = &*(ptr as *mut T::Instance);
263        let imp = instance.imp();
264
265        imp.length()
266    }
267}
268
269unsafe extern "C" fn entry_buffer_insert_text<T: EntryBufferImpl>(
270    ptr: *mut ffi::GtkEntryBuffer,
271    position: u32,
272    charsptr: *const libc::c_char,
273    n_chars: u32,
274) -> u32 {
275    unsafe {
276        let instance = &*(ptr as *mut T::Instance);
277        let imp = instance.imp();
278        let text: Borrowed<GString> = from_glib_borrow(charsptr);
279
280        let chars = text_n_chars(&text, n_chars);
281        imp.insert_text(position, chars)
282    }
283}
284
285unsafe extern "C" fn entry_buffer_inserted_text<T: EntryBufferImpl>(
286    ptr: *mut ffi::GtkEntryBuffer,
287    position: u32,
288    charsptr: *const libc::c_char,
289    length: u32,
290) {
291    unsafe {
292        let instance = &*(ptr as *mut T::Instance);
293        let imp = instance.imp();
294        let text: Borrowed<GString> = from_glib_borrow(charsptr);
295
296        let chars = text_n_chars(&text, length);
297        imp.inserted_text(position, chars)
298    }
299}
300
301#[doc(alias = "get_text_n_chars")]
302fn text_n_chars(text: &str, n_chars: u32) -> &str {
303    if n_chars != u32::MAX && n_chars > 0 {
304        let mut iter = text
305            .char_indices()
306            .skip((n_chars - 1) as _)
307            .map(|(pos, _)| pos);
308        iter
309            .next()
310            .expect(
311                "The passed text to EntryBuffer contains fewer characters than what's passed as a length",
312            );
313        let pos_end = iter.next().unwrap_or(text.len());
314        &text[..pos_end]
315    } else if n_chars == 0 {
316        // Avoid doing skipping to -1 char
317        ""
318    } else {
319        text
320    }
321}
322
323#[cfg(test)]
324mod test {
325    use super::text_n_chars;
326    #[std::prelude::v1::test]
327    fn n_chars_max_length_ascii() {
328        assert_eq!(text_n_chars("gtk-rs bindings", 6), "gtk-rs");
329        assert_eq!(text_n_chars("gtk-rs bindings", u32::MAX), "gtk-rs bindings");
330    }
331
332    #[std::prelude::v1::test]
333    #[should_panic]
334    fn n_chars_max_length_ascii_panic() {
335        assert_eq!(text_n_chars("gtk-rs", 7), "gtk-rs");
336    }
337
338    #[std::prelude::v1::test]
339    fn n_chars_max_length_utf8() {
340        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 2), "šŸ‘ØšŸ‘©");
341        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 0), "");
342        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 4), "šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦");
343        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", u32::MAX), "šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦");
344        assert_eq!(text_n_chars("كتاب", 2), "كت");
345    }
346
347    #[std::prelude::v1::test]
348    fn n_chars_max_length_utf8_ascii() {
349        assert_eq!(text_n_chars("šŸ‘ØgšŸ‘©tšŸ‘§kšŸ‘¦", 2), "šŸ‘Øg");
350        assert_eq!(text_n_chars("šŸ‘ØgšŸ‘©tšŸ‘§kšŸ‘¦", 5), "šŸ‘ØgšŸ‘©tšŸ‘§");
351        assert_eq!(text_n_chars("كaتاب", 3), "كaت");
352    }
353
354    #[std::prelude::v1::test]
355    #[should_panic]
356    fn n_chars_max_length_utf8_panic() {
357        assert_eq!(text_n_chars("šŸ‘ØšŸ‘©šŸ‘§šŸ‘¦", 5), "šŸ‘ØšŸ‘©");
358    }
359}