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    /// 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    let instance = &*(ptr as *mut T::Instance);
196    let imp = instance.imp();
197
198    let n_chars = if n_chars == u32::MAX {
199        None
200    } else {
201        Some(n_chars)
202    };
203
204    imp.delete_text(position, n_chars)
205}
206
207unsafe extern "C" fn entry_buffer_deleted_text<T: EntryBufferImpl>(
208    ptr: *mut ffi::GtkEntryBuffer,
209    position: u32,
210    n_chars: u32,
211) {
212    let instance = &*(ptr as *mut T::Instance);
213    let imp = instance.imp();
214
215    let n_chars = if n_chars == u32::MAX {
216        None
217    } else {
218        Some(n_chars)
219    };
220
221    imp.deleted_text(position, n_chars)
222}
223
224unsafe extern "C" fn entry_buffer_get_text<T: EntryBufferImpl>(
225    ptr: *mut ffi::GtkEntryBuffer,
226    n_bytes: *mut usize,
227) -> *const libc::c_char {
228    let instance = &*(ptr as *mut T::Instance);
229    let imp = instance.imp();
230
231    let ret = imp.text();
232    if !n_bytes.is_null() {
233        *n_bytes = ret.len();
234    }
235    // Ensures that the returned text stays alive for as long as
236    // the entry buffer instance
237
238    static QUARK: OnceLock<glib::Quark> = OnceLock::new();
239    let quark = *QUARK.get_or_init(|| glib::Quark::from_str("gtk4-rs-subclass-entry-buffer-text"));
240
241    let fullptr = ret.into_glib_ptr();
242    imp.obj().set_qdata(
243        quark,
244        PtrHolder(fullptr, |ptr| {
245            glib::ffi::g_free(ptr as *mut _);
246        }),
247    );
248    fullptr
249}
250
251unsafe extern "C" fn entry_buffer_get_length<T: EntryBufferImpl>(
252    ptr: *mut ffi::GtkEntryBuffer,
253) -> u32 {
254    let instance = &*(ptr as *mut T::Instance);
255    let imp = instance.imp();
256
257    imp.length()
258}
259
260unsafe extern "C" fn entry_buffer_insert_text<T: EntryBufferImpl>(
261    ptr: *mut ffi::GtkEntryBuffer,
262    position: u32,
263    charsptr: *const libc::c_char,
264    n_chars: u32,
265) -> u32 {
266    let instance = &*(ptr as *mut T::Instance);
267    let imp = instance.imp();
268    let text: Borrowed<GString> = from_glib_borrow(charsptr);
269
270    let chars = text_n_chars(&text, n_chars);
271    imp.insert_text(position, chars)
272}
273
274unsafe extern "C" fn entry_buffer_inserted_text<T: EntryBufferImpl>(
275    ptr: *mut ffi::GtkEntryBuffer,
276    position: u32,
277    charsptr: *const libc::c_char,
278    length: u32,
279) {
280    let instance = &*(ptr as *mut T::Instance);
281    let imp = instance.imp();
282    let text: Borrowed<GString> = from_glib_borrow(charsptr);
283
284    let chars = text_n_chars(&text, length);
285    imp.inserted_text(position, chars)
286}
287
288#[doc(alias = "get_text_n_chars")]
289fn text_n_chars(text: &str, n_chars: u32) -> &str {
290    if n_chars != u32::MAX && n_chars > 0 {
291        let mut iter = text
292            .char_indices()
293            .skip((n_chars - 1) as _)
294            .map(|(pos, _)| pos);
295        iter
296            .next()
297            .expect(
298                "The passed text to EntryBuffer contains fewer characters than what's passed as a length",
299            );
300        let pos_end = iter.next().unwrap_or(text.len());
301        &text[..pos_end]
302    } else if n_chars == 0 {
303        // Avoid doing skipping to -1 char
304        ""
305    } else {
306        text
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use super::text_n_chars;
313    #[std::prelude::v1::test]
314    fn n_chars_max_length_ascii() {
315        assert_eq!(text_n_chars("gtk-rs bindings", 6), "gtk-rs");
316        assert_eq!(text_n_chars("gtk-rs bindings", u32::MAX), "gtk-rs bindings");
317    }
318
319    #[std::prelude::v1::test]
320    #[should_panic]
321    fn n_chars_max_length_ascii_panic() {
322        assert_eq!(text_n_chars("gtk-rs", 7), "gtk-rs");
323    }
324
325    #[std::prelude::v1::test]
326    fn n_chars_max_length_utf8() {
327        assert_eq!(text_n_chars("👨👩👧👦", 2), "👨👩");
328        assert_eq!(text_n_chars("👨👩👧👦", 0), "");
329        assert_eq!(text_n_chars("👨👩👧👦", 4), "👨👩👧👦");
330        assert_eq!(text_n_chars("👨👩👧👦", u32::MAX), "👨👩👧👦");
331        assert_eq!(text_n_chars("كتاب", 2), "كت");
332    }
333
334    #[std::prelude::v1::test]
335    fn n_chars_max_length_utf8_ascii() {
336        assert_eq!(text_n_chars("👨g👩t👧k👦", 2), "👨g");
337        assert_eq!(text_n_chars("👨g👩t👧k👦", 5), "👨g👩t👧");
338        assert_eq!(text_n_chars("كaتاب", 3), "كaت");
339    }
340
341    #[std::prelude::v1::test]
342    #[should_panic]
343    fn n_chars_max_length_utf8_panic() {
344        assert_eq!(text_n_chars("👨👩👧👦", 5), "👨👩");
345    }
346}