1use 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}