gtk4/subclass/
accessible_text.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 implementing the [`AccessibleText`] interface.
5
6use crate::{
7    ffi, subclass::prelude::*, AccessibleText, AccessibleTextGranularity, AccessibleTextRange,
8};
9use glib::object::Cast;
10use glib::{translate::*, GString};
11
12pub trait AccessibleTextImpl: WidgetImpl {
13    #[doc(alias = "get_attributes")]
14    fn attributes(&self, offset: u32) -> Vec<(AccessibleTextRange, GString, GString)> {
15        self.parent_attributes(offset)
16    }
17
18    #[doc(alias = "get_caret_position")]
19    fn caret_position(&self) -> u32 {
20        self.parent_caret_position()
21    }
22
23    #[doc(alias = "get_contents")]
24    fn contents(&self, start: u32, end: u32) -> Option<glib::Bytes> {
25        self.parent_contents(start, end)
26    }
27
28    #[doc(alias = "get_contents_at")]
29    fn contents_at(
30        &self,
31        offset: u32,
32        granularity: crate::AccessibleTextGranularity,
33    ) -> Option<(u32, u32, glib::Bytes)> {
34        self.parent_contents_at(offset, granularity)
35    }
36
37    #[doc(alias = "get_default_attributes")]
38    fn default_attributes(&self) -> Vec<(GString, GString)> {
39        self.parent_default_attributes()
40    }
41
42    #[cfg(feature = "v4_16")]
43    #[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
44    #[doc(alias = "get_extents")]
45    fn extents(&self, start: u32, end: u32) -> Option<graphene::Rect> {
46        self.parent_extents(start, end)
47    }
48
49    #[cfg(feature = "v4_16")]
50    #[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
51    #[doc(alias = "get_offset")]
52    fn offset(&self, point: &graphene::Point) -> Option<u32> {
53        self.parent_offset(point)
54    }
55
56    #[doc(alias = "get_selection")]
57    fn selection(&self) -> Vec<AccessibleTextRange> {
58        self.parent_selection()
59    }
60}
61
62pub trait AccessibleTextImplExt: AccessibleTextImpl {
63    fn parent_attributes(&self, offset: u32) -> Vec<(AccessibleTextRange, GString, GString)> {
64        unsafe {
65            let type_data = Self::type_data();
66            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
67                as *const ffi::GtkAccessibleTextInterface;
68
69            let func = (*parent_iface)
70                .get_attributes
71                .expect("no parent \"get_attributes\" implementation");
72
73            let mut n_ranges = std::mem::MaybeUninit::uninit();
74            let mut ranges = std::ptr::null_mut();
75            let mut attribute_names = std::ptr::null_mut();
76            let mut attribute_values = std::ptr::null_mut();
77
78            let is_set: bool = from_glib(func(
79                self.obj()
80                    .unsafe_cast_ref::<AccessibleText>()
81                    .to_glib_none()
82                    .0,
83                offset,
84                n_ranges.as_mut_ptr(),
85                &mut ranges,
86                &mut attribute_names,
87                &mut attribute_values,
88            ));
89
90            if !is_set
91                || n_ranges.assume_init() == 0
92                || ranges.is_null()
93                || attribute_names.is_null()
94                || attribute_values.is_null()
95            {
96                Vec::new()
97            } else {
98                let mut names = glib::StrV::from_glib_full(attribute_names).into_iter();
99                let mut values = glib::StrV::from_glib_full(attribute_values).into_iter();
100
101                glib::Slice::from_glib_container_num(ranges, n_ranges.assume_init())
102                    .into_iter()
103                    .flat_map(|range| {
104                        if let (Some(name), Some(value)) = (names.next(), values.next()) {
105                            Some((range, name, value))
106                        } else {
107                            None
108                        }
109                    })
110                    .collect()
111            }
112        }
113    }
114
115    fn parent_caret_position(&self) -> u32 {
116        unsafe {
117            let type_data = Self::type_data();
118            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
119                as *const ffi::GtkAccessibleTextInterface;
120
121            let func = (*parent_iface)
122                .get_caret_position
123                .expect("no parent \"get_caret_position\" implementation");
124
125            func(
126                self.obj()
127                    .unsafe_cast_ref::<AccessibleText>()
128                    .to_glib_none()
129                    .0,
130            )
131        }
132    }
133
134    fn parent_contents(&self, start: u32, end: u32) -> Option<glib::Bytes> {
135        unsafe {
136            let type_data = Self::type_data();
137            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
138                as *const ffi::GtkAccessibleTextInterface;
139
140            let func = (*parent_iface).get_contents?;
141
142            from_glib_full(func(
143                self.obj()
144                    .unsafe_cast_ref::<AccessibleText>()
145                    .to_glib_none()
146                    .0,
147                start,
148                end,
149            ))
150        }
151    }
152
153    fn parent_contents_at(
154        &self,
155        offset: u32,
156        granularity: crate::AccessibleTextGranularity,
157    ) -> Option<(u32, u32, glib::Bytes)> {
158        unsafe {
159            let type_data = Self::type_data();
160            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
161                as *const ffi::GtkAccessibleTextInterface;
162
163            let func = (*parent_iface).get_contents_at?;
164
165            let mut start = std::mem::MaybeUninit::uninit();
166            let mut end = std::mem::MaybeUninit::uninit();
167
168            let bytes = func(
169                self.obj()
170                    .unsafe_cast_ref::<AccessibleText>()
171                    .to_glib_none()
172                    .0,
173                offset,
174                granularity.into_glib(),
175                start.as_mut_ptr(),
176                end.as_mut_ptr(),
177            );
178
179            if !bytes.is_null() {
180                Some((
181                    start.assume_init(),
182                    end.assume_init(),
183                    from_glib_full(bytes),
184                ))
185            } else {
186                None
187            }
188        }
189    }
190
191    fn parent_default_attributes(&self) -> Vec<(GString, GString)> {
192        unsafe {
193            let type_data = Self::type_data();
194            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
195                as *const ffi::GtkAccessibleTextInterface;
196
197            let func = (*parent_iface)
198                .get_default_attributes
199                .expect("no parent \"get_default_attributes\" implementation");
200
201            let mut attribute_names = std::ptr::null_mut();
202            let mut attribute_values = std::ptr::null_mut();
203
204            func(
205                self.obj()
206                    .unsafe_cast_ref::<AccessibleText>()
207                    .to_glib_none()
208                    .0,
209                &mut attribute_names,
210                &mut attribute_values,
211            );
212
213            if attribute_names.is_null() || attribute_values.is_null() {
214                Vec::new()
215            } else {
216                glib::StrV::from_glib_full(attribute_names)
217                    .into_iter()
218                    .zip(glib::StrV::from_glib_full(attribute_values))
219                    .collect()
220            }
221        }
222    }
223
224    #[cfg(feature = "v4_16")]
225    #[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
226    fn parent_extents(&self, start: u32, end: u32) -> Option<graphene::Rect> {
227        unsafe {
228            let type_data = Self::type_data();
229            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
230                as *const ffi::GtkAccessibleTextInterface;
231
232            let func = (*parent_iface)
233                .get_extents
234                .expect("no parent \"get_extents\" implementation");
235
236            let mut extents = std::mem::MaybeUninit::uninit();
237
238            let filled = from_glib(func(
239                self.obj()
240                    .unsafe_cast_ref::<AccessibleText>()
241                    .to_glib_none()
242                    .0,
243                start,
244                end,
245                extents.as_mut_ptr(),
246            ));
247
248            if filled {
249                Some(graphene::Rect::unsafe_from(extents.assume_init()))
250            } else {
251                None
252            }
253        }
254    }
255
256    #[cfg(feature = "v4_16")]
257    #[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
258    fn parent_offset(&self, point: &graphene::Point) -> Option<u32> {
259        unsafe {
260            let type_data = Self::type_data();
261            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
262                as *const ffi::GtkAccessibleTextInterface;
263
264            let func = (*parent_iface)
265                .get_offset
266                .expect("no parent \"get_offset\" implementation");
267
268            let mut offset = std::mem::MaybeUninit::uninit();
269
270            let offset_set = from_glib(func(
271                self.obj()
272                    .unsafe_cast_ref::<AccessibleText>()
273                    .to_glib_none()
274                    .0,
275                point.to_glib_none().0,
276                offset.as_mut_ptr(),
277            ));
278
279            if offset_set {
280                Some(offset.assume_init())
281            } else {
282                None
283            }
284        }
285    }
286
287    fn parent_selection(&self) -> Vec<AccessibleTextRange> {
288        unsafe {
289            let type_data = Self::type_data();
290            let parent_iface = type_data.as_ref().parent_interface::<AccessibleText>()
291                as *const ffi::GtkAccessibleTextInterface;
292
293            let func = (*parent_iface)
294                .get_selection
295                .expect("no parent \"get_selection\" implementation");
296
297            let mut n_ranges = std::mem::MaybeUninit::uninit();
298            let mut ranges = std::ptr::null_mut();
299
300            let valid = from_glib(func(
301                self.obj()
302                    .unsafe_cast_ref::<AccessibleText>()
303                    .to_glib_none()
304                    .0,
305                n_ranges.as_mut_ptr(),
306                &mut ranges,
307            ));
308
309            if valid {
310                let n = n_ranges.assume_init();
311                AccessibleTextRange::from_glib_container_num_as_vec(ranges, n)
312            } else {
313                Vec::new()
314            }
315        }
316    }
317}
318
319impl<T: AccessibleTextImpl> AccessibleTextImplExt for T {}
320
321unsafe impl<T: AccessibleTextImpl> IsImplementable<T> for AccessibleText {
322    fn interface_init(iface: &mut glib::Interface<Self>) {
323        let iface = iface.as_mut();
324
325        iface.get_contents = Some(accessible_text_get_contents::<T>);
326        iface.get_contents_at = Some(accessible_text_get_contents_at::<T>);
327        iface.get_caret_position = Some(accessible_text_get_caret_position::<T>);
328        iface.get_selection = Some(accessible_text_get_selection::<T>);
329        iface.get_attributes = Some(accessible_text_get_attributes::<T>);
330        iface.get_default_attributes = Some(accessible_text_get_default_attributes::<T>);
331
332        #[cfg(feature = "v4_16")]
333        {
334            iface.get_extents = Some(accessible_text_get_extents::<T>);
335            iface.get_offset = Some(accessible_text_get_offset::<T>);
336        }
337    }
338}
339
340unsafe extern "C" fn accessible_text_get_contents<T: AccessibleTextImpl>(
341    accessible_text: *mut ffi::GtkAccessibleText,
342    start: u32,
343    end: u32,
344) -> *mut glib::ffi::GBytes {
345    let instance = &*(accessible_text as *mut T::Instance);
346    let imp = instance.imp();
347
348    let contents = imp.contents(start, end);
349    contents.into_glib_ptr()
350}
351
352unsafe extern "C" fn accessible_text_get_contents_at<T: AccessibleTextImpl>(
353    accessible_text: *mut ffi::GtkAccessibleText,
354    offset: libc::c_uint,
355    granularity: ffi::GtkAccessibleTextGranularity,
356    start: *mut libc::c_uint,
357    end: *mut libc::c_uint,
358) -> *mut glib::ffi::GBytes {
359    let instance = &*(accessible_text as *mut T::Instance);
360    let imp = instance.imp();
361
362    if let Some((r_start, r_end, bytes)) =
363        imp.contents_at(offset, AccessibleTextGranularity::from_glib(granularity))
364    {
365        if !start.is_null() {
366            *start = r_start;
367        }
368        if !end.is_null() {
369            *end = r_end;
370        }
371
372        bytes.into_glib_ptr()
373    } else {
374        std::ptr::null_mut()
375    }
376}
377
378unsafe extern "C" fn accessible_text_get_caret_position<T: AccessibleTextImpl>(
379    accessible_text: *mut ffi::GtkAccessibleText,
380) -> u32 {
381    let instance = &*(accessible_text as *mut T::Instance);
382    let imp = instance.imp();
383
384    imp.caret_position()
385}
386
387unsafe extern "C" fn accessible_text_get_selection<T: AccessibleTextImpl>(
388    accessible_text: *mut ffi::GtkAccessibleText,
389    n_ranges: *mut libc::size_t,
390    ranges: *mut *mut ffi::GtkAccessibleTextRange,
391) -> glib::ffi::gboolean {
392    let instance = &*(accessible_text as *mut T::Instance);
393    let imp = instance.imp();
394
395    let r_ranges = imp.selection();
396    let n: usize = r_ranges.len();
397    *n_ranges = n;
398
399    if n == 0 {
400        false
401    } else {
402        *ranges = r_ranges.to_glib_container().0;
403
404        true
405    }
406    .into_glib()
407}
408
409unsafe extern "C" fn accessible_text_get_attributes<T: AccessibleTextImpl>(
410    accessible_text: *mut ffi::GtkAccessibleText,
411    offset: u32,
412    n_ranges: *mut libc::size_t,
413    ranges: *mut *mut ffi::GtkAccessibleTextRange,
414    attribute_names: *mut *mut *mut libc::c_char,
415    attribute_values: *mut *mut *mut libc::c_char,
416) -> glib::ffi::gboolean {
417    let instance = &*(accessible_text as *mut T::Instance);
418    let imp = instance.imp();
419
420    let attrs = imp.attributes(offset);
421    let n: usize = attrs.len();
422    *n_ranges = n;
423
424    if n == 0 {
425        *attribute_names = std::ptr::null_mut();
426        *attribute_values = std::ptr::null_mut();
427
428        false
429    } else {
430        let mut c_ranges = glib::Slice::with_capacity(attrs.len());
431        let mut c_names = glib::StrV::with_capacity(attrs.len());
432        let mut c_values = glib::StrV::with_capacity(attrs.len());
433
434        for (range, name, value) in attrs {
435            c_ranges.push(range);
436            c_names.push(name);
437            c_values.push(value);
438        }
439
440        *ranges = c_ranges.to_glib_container().0;
441        *attribute_names = c_names.into_glib_ptr();
442        *attribute_values = c_values.into_glib_ptr();
443
444        true
445    }
446    .into_glib()
447}
448
449unsafe extern "C" fn accessible_text_get_default_attributes<T: AccessibleTextImpl>(
450    accessible_text: *mut ffi::GtkAccessibleText,
451    attribute_names: *mut *mut *mut libc::c_char,
452    attribute_values: *mut *mut *mut libc::c_char,
453) {
454    let instance = &*(accessible_text as *mut T::Instance);
455    let imp = instance.imp();
456
457    let attrs = imp.default_attributes();
458
459    if attrs.is_empty() {
460        *attribute_names = std::ptr::null_mut();
461        *attribute_values = std::ptr::null_mut();
462    } else {
463        let mut c_names = glib::StrV::with_capacity(attrs.len());
464        let mut c_values = glib::StrV::with_capacity(attrs.len());
465
466        for (name, value) in attrs {
467            c_names.push(name);
468            c_values.push(value);
469        }
470
471        *attribute_names = c_names.into_glib_ptr();
472        *attribute_values = c_values.into_glib_ptr();
473    }
474}
475
476#[cfg(feature = "v4_16")]
477#[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
478unsafe extern "C" fn accessible_text_get_extents<T: AccessibleTextImpl>(
479    accessible_text: *mut ffi::GtkAccessibleText,
480    start: u32,
481    end: u32,
482    extents: *mut graphene::ffi::graphene_rect_t,
483) -> glib::ffi::gboolean {
484    let instance = &*(accessible_text as *mut T::Instance);
485    let imp = instance.imp();
486
487    let rect = imp.extents(start, end);
488
489    if let Some(rect) = rect {
490        *extents = *rect.as_ptr();
491
492        true
493    } else {
494        false
495    }
496    .into_glib()
497}
498
499#[cfg(feature = "v4_16")]
500#[cfg_attr(docsrs, doc(cfg(feature = "v4_16")))]
501unsafe extern "C" fn accessible_text_get_offset<T: AccessibleTextImpl>(
502    accessible_text: *mut ffi::GtkAccessibleText,
503    point: *const graphene::ffi::graphene_point_t,
504    offset: *mut libc::c_uint,
505) -> glib::ffi::gboolean {
506    let instance = &*(accessible_text as *mut T::Instance);
507    let imp = instance.imp();
508
509    let pos = imp.offset(&from_glib_borrow(point));
510
511    if let Some(pos) = pos {
512        if !offset.is_null() {
513            *offset = pos;
514        }
515        true
516    } else {
517        false
518    }
519    .into_glib()
520}
521
522#[cfg(test)]
523mod test {
524    use crate as gtk4;
525    use crate::prelude::*;
526    use crate::subclass::prelude::*;
527
528    mod imp {
529        use super::*;
530
531        #[derive(Default)]
532        pub struct TestTextView {}
533
534        #[glib::object_subclass]
535        impl ObjectSubclass for TestTextView {
536            const NAME: &'static str = "TestTextView";
537            type Type = super::TestTextView;
538            type ParentType = crate::TextView;
539            type Interfaces = (crate::AccessibleText,);
540        }
541
542        impl ObjectImpl for TestTextView {}
543        impl WidgetImpl for TestTextView {}
544        impl AccessibleTextImpl for TestTextView {
545            fn attributes(
546                &self,
547                offset: u32,
548            ) -> Vec<(
549                crate::accessible_text_range::AccessibleTextRange,
550                glib::GString,
551                glib::GString,
552            )> {
553                self.parent_attributes(offset)
554            }
555
556            fn caret_position(&self) -> u32 {
557                self.parent_caret_position()
558            }
559
560            fn contents(&self, start: u32, end: u32) -> Option<glib::Bytes> {
561                self.parent_contents(start, end)
562            }
563
564            fn contents_at(
565                &self,
566                offset: u32,
567                granularity: crate::AccessibleTextGranularity,
568            ) -> Option<(u32, u32, glib::Bytes)> {
569                self.parent_contents_at(offset, granularity)
570            }
571
572            fn default_attributes(&self) -> Vec<(glib::GString, glib::GString)> {
573                self.parent_default_attributes()
574            }
575
576            fn selection(&self) -> Vec<crate::accessible_text_range::AccessibleTextRange> {
577                self.parent_selection()
578            }
579
580            #[cfg(feature = "v4_16")]
581            fn extents(&self, start: u32, end: u32) -> Option<graphene::Rect> {
582                self.parent_extents(start, end)
583            }
584
585            #[cfg(feature = "v4_16")]
586            fn offset(&self, point: &graphene::Point) -> Option<u32> {
587                self.parent_offset(point)
588            }
589        }
590
591        impl TextViewImpl for TestTextView {}
592        impl TestTextView {}
593    }
594
595    glib::wrapper! {
596        pub struct TestTextView(ObjectSubclass<imp::TestTextView>)
597        @extends crate::Widget, crate::TextView,
598        @implements crate::Accessible, crate::AccessibleText, crate::Buildable, crate::ConstraintTarget, crate::Scrollable;
599    }
600
601    impl TestTextView {}
602
603    #[crate::test]
604    fn test_accessible_text_iface() {
605        let text: TestTextView = glib::Object::new();
606        let mut iter = text.buffer().iter_at_offset(0);
607        text.buffer()
608            .insert_markup(&mut iter, "<b>Lorem Ipsum</b> dolor <i>sit.</i> amnet");
609
610        let (range, _, value) = text
611            .imp()
612            .attributes(0)
613            .into_iter()
614            .find(|(_, name, _)| name == "weight")
615            .unwrap();
616
617        assert_eq!(range.start(), 0);
618        assert_eq!(range.length(), "Lorem Ipsum".len());
619        assert_eq!(value, "700");
620
621        assert_eq!(
622            text.imp().caret_position(),
623            "Lorem Ipsum dolor sit. amnet".len() as u32
624        );
625        let pos = "Lorem Ipsum ".len();
626        let iter = text.buffer().iter_at_offset(pos as i32);
627        text.buffer().place_cursor(&iter);
628        assert_eq!(text.imp().caret_position(), pos as u32);
629
630        assert_eq!(
631            std::str::from_utf8(
632                &text
633                    .imp()
634                    .contents_at(pos as u32, crate::AccessibleTextGranularity::Character)
635                    .unwrap()
636                    .2
637            )
638            .unwrap(),
639            "d"
640        );
641        assert_eq!(
642            std::str::from_utf8(
643                &text
644                    .imp()
645                    .contents_at(pos as u32, crate::AccessibleTextGranularity::Word)
646                    .unwrap()
647                    .2
648            )
649            .unwrap(),
650            "dolor "
651        );
652        assert_eq!(
653            std::str::from_utf8(
654                &text
655                    .imp()
656                    .contents_at(pos as u32, crate::AccessibleTextGranularity::Line)
657                    .unwrap()
658                    .2
659            )
660            .unwrap(),
661            "Lorem Ipsum dolor sit. amnet"
662        );
663
664        assert_eq!(
665            "Lorem Ipsum\0",
666            std::str::from_utf8(&text.imp().contents(0, 11).unwrap()).unwrap()
667        );
668
669        assert!(text
670            .imp()
671            .default_attributes()
672            .iter()
673            .any(|(name, value)| name == "editable" && value == "true"));
674        text.buffer().select_range(
675            &text.buffer().iter_at_offset(0),
676            &text.buffer().iter_at_offset(10),
677        );
678        let selected_range = text.imp().selection()[0];
679        assert_eq!(selected_range.start(), 0);
680        assert_eq!(selected_range.length(), 10);
681
682        #[cfg(feature = "v4_16")]
683        {
684            let _extents = text.imp().extents(0, 20);
685            let _offset = text.imp().offset(&graphene::Point::new(10.0, 10.0));
686        }
687    }
688}