1use 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 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 #[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 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 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 ""
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}