i_slint_backend_qt/
qt_accessible.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cspell:ignore descendents qobject qwidget
5
6use crate::accessible_generated::*;
7use crate::qt_window::QtWindow;
8
9use i_slint_core::accessibility::{
10    AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
11};
12use i_slint_core::item_tree::{ItemRc, ItemWeak};
13use i_slint_core::properties::{PropertyDirtyHandler, PropertyTracker};
14use i_slint_core::window::WindowInner;
15use i_slint_core::SharedVector;
16
17use cpp::*;
18use pin_project::pin_project;
19use qttypes::QString;
20
21use core::ffi::c_void;
22use std::pin::Pin;
23
24// KEEP IN SYNC WITH CONSTANTS IN C++
25const NAME: u32 = QAccessible_Text_Name;
26const DESCRIPTION: u32 = QAccessible_Text_Description;
27const VALUE: u32 = QAccessible_Text_Value;
28const CHECKED: u32 = QAccessible_Text_UserText;
29const VALUE_MINIMUM: u32 = CHECKED + 1;
30const VALUE_MAXIMUM: u32 = VALUE_MINIMUM + 1;
31const VALUE_STEP: u32 = VALUE_MAXIMUM + 1;
32const CHECKABLE: u32 = VALUE_STEP + 1;
33const EXPANDABLE: u32 = CHECKABLE + 1;
34const EXPANDED: u32 = EXPANDABLE + 1;
35const READ_ONLY: u32 = EXPANDED + 1;
36
37pub struct AccessibleItemPropertiesTracker {
38    obj: *mut c_void,
39}
40
41impl PropertyDirtyHandler for AccessibleItemPropertiesTracker {
42    fn notify(self: Pin<&Self>) {
43        let obj = self.obj;
44        cpp!(unsafe [obj as "QObject*"] {
45            QTimer::singleShot(0, [obj = QPointer(obj)]() {
46                if (!obj)
47                    return;
48
49                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
50                auto data = accessible_item->data();
51                rust!(AccessibleItemPropertiesTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
52                    data.arm_state_tracker();
53                });
54
55                QAccessible::State s = {};
56                s.checked = true; // Mark checked as changed!
57                auto event = QAccessibleStateChangeEvent(obj, s);
58                QAccessible::updateAccessibility(&event);
59            });
60        });
61    }
62}
63
64pub struct ValuePropertyTracker {
65    obj: *mut c_void,
66}
67
68impl PropertyDirtyHandler for ValuePropertyTracker {
69    fn notify(self: Pin<&Self>) {
70        let obj = self.obj;
71        cpp!(unsafe [obj as "QObject*"] {
72            QTimer::singleShot(0, [ obj = QPointer(obj)]() {
73                if (!obj)
74                    return;
75
76                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
77                auto data = accessible_item->data();
78                rust!(ValuePropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
79                    data.arm_value_tracker();
80                });
81
82                auto event = QAccessibleValueChangeEvent(obj, accessible_item->currentValue());
83                QAccessible::updateAccessibility(&event);
84            });
85        });
86    }
87}
88
89pub struct LabelPropertyTracker {
90    obj: *mut c_void,
91}
92
93impl PropertyDirtyHandler for LabelPropertyTracker {
94    fn notify(self: Pin<&Self>) {
95        let obj = self.obj;
96        cpp!(unsafe [obj as "QObject*"] {
97            QTimer::singleShot(0, [obj = QPointer(obj)]() {
98                if (!obj)
99                    return;
100
101                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
102                auto data = accessible_item->data();
103                rust!(LabelPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
104                    data.arm_label_tracker();
105                });
106
107                auto event = QAccessibleEvent(obj, QAccessible::NameChanged);
108                QAccessible::updateAccessibility(&event);
109            });
110        });
111    }
112}
113
114pub struct DescriptionPropertyTracker {
115    obj: *mut c_void,
116}
117
118impl PropertyDirtyHandler for DescriptionPropertyTracker {
119    fn notify(self: Pin<&Self>) {
120        let obj = self.obj;
121        cpp!(unsafe [obj as "QObject*"] {
122            QTimer::singleShot(0, [obj = QPointer(obj)]() {
123                if (!obj)
124                    return;
125
126                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
127                auto data = accessible_item->data();
128                rust!(DescriptionPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
129                    data.arm_description_tracker();
130                });
131
132                auto event = QAccessibleEvent(obj, QAccessible::DescriptionChanged);
133                QAccessible::updateAccessibility(&event);
134            });
135        });
136    }
137}
138
139pub struct FocusDelegationPropertyTracker {
140    obj: *mut c_void,
141}
142
143impl PropertyDirtyHandler for FocusDelegationPropertyTracker {
144    fn notify(self: Pin<&Self>) {
145        let obj = self.obj;
146        cpp!(unsafe [obj as "QObject*"] {
147            QTimer::singleShot(0, [obj = QPointer(obj)]() {
148                if (!obj)
149                    return;
150
151                auto accessible_item = static_cast<Slint_accessible_item*>(QAccessible::queryAccessibleInterface(obj));
152                auto data = accessible_item->data();
153                rust!(FocusDelegationPropertyTracker_rearm [data: Pin<&SlintAccessibleItemData> as "void*"] {
154                    data.arm_focus_delegation_tracker();
155                });
156
157                accessible_item->delegateFocus();
158            });
159        });
160    }
161}
162
163#[pin_project]
164pub struct SlintAccessibleItemData {
165    #[pin]
166    state_tracker: PropertyTracker<AccessibleItemPropertiesTracker>,
167    #[pin]
168    value_tracker: PropertyTracker<ValuePropertyTracker>,
169    #[pin]
170    label_tracker: PropertyTracker<LabelPropertyTracker>,
171    #[pin]
172    description_tracker: PropertyTracker<DescriptionPropertyTracker>,
173    #[pin]
174    focus_delegation_tracker: PropertyTracker<FocusDelegationPropertyTracker>,
175    item: ItemWeak,
176}
177
178impl SlintAccessibleItemData {
179    fn new_pin_box(obj: *mut c_void, item: &ItemWeak) -> Pin<Box<Self>> {
180        let state_tracker =
181            PropertyTracker::new_with_dirty_handler(AccessibleItemPropertiesTracker { obj });
182        let value_tracker = PropertyTracker::new_with_dirty_handler(ValuePropertyTracker { obj });
183        let label_tracker = PropertyTracker::new_with_dirty_handler(LabelPropertyTracker { obj });
184        let description_tracker =
185            PropertyTracker::new_with_dirty_handler(DescriptionPropertyTracker { obj });
186        let focus_delegation_tracker =
187            PropertyTracker::new_with_dirty_handler(FocusDelegationPropertyTracker { obj });
188
189        let result = Box::pin(Self {
190            state_tracker,
191            value_tracker,
192            label_tracker,
193            description_tracker,
194            focus_delegation_tracker,
195            item: item.clone(),
196        });
197
198        result.as_ref().arm_state_tracker();
199        result.as_ref().arm_value_tracker();
200        result.as_ref().arm_label_tracker();
201        result.as_ref().arm_description_tracker();
202        result.as_ref().arm_focus_delegation_tracker();
203
204        result
205    }
206
207    fn arm_state_tracker(self: Pin<&Self>) {
208        let item = self.item.clone();
209        let p = self.project_ref();
210        p.state_tracker.evaluate_as_dependency_root(move || {
211            if let Some(item_rc) = item.upgrade() {
212                item_rc.accessible_string_property(AccessibleStringProperty::Checkable);
213                item_rc.accessible_string_property(AccessibleStringProperty::Checked);
214                item_rc.accessible_string_property(AccessibleStringProperty::Expandable);
215                item_rc.accessible_string_property(AccessibleStringProperty::Expanded);
216                item_rc.accessible_string_property(AccessibleStringProperty::ReadOnly);
217            }
218        });
219    }
220
221    fn arm_value_tracker(self: Pin<&Self>) {
222        let item = self.item.clone();
223        let p = self.project_ref();
224        p.value_tracker.evaluate_as_dependency_root(move || {
225            if let Some(item_rc) = item.upgrade() {
226                item_rc.accessible_string_property(AccessibleStringProperty::Value);
227                item_rc.accessible_string_property(AccessibleStringProperty::ValueMinimum);
228                item_rc.accessible_string_property(AccessibleStringProperty::ValueMaximum);
229                item_rc.accessible_string_property(AccessibleStringProperty::ValueStep);
230            }
231        });
232    }
233
234    fn arm_label_tracker(self: Pin<&Self>) {
235        let item = self.item.clone();
236        let p = self.project_ref();
237        p.label_tracker.evaluate_as_dependency_root(move || {
238            if let Some(item_rc) = item.upgrade() {
239                item_rc.accessible_string_property(AccessibleStringProperty::Label);
240            }
241        });
242    }
243
244    fn arm_description_tracker(self: Pin<&Self>) {
245        let item = self.item.clone();
246        let p = self.project_ref();
247        p.description_tracker.evaluate_as_dependency_root(move || {
248            if let Some(item_rc) = item.upgrade() {
249                item_rc.accessible_string_property(AccessibleStringProperty::Description);
250            }
251        });
252    }
253
254    fn arm_focus_delegation_tracker(self: Pin<&Self>) {
255        let item = self.item.clone();
256        let p = self.project_ref();
257        p.focus_delegation_tracker.evaluate_as_dependency_root(move || {
258            if let Some(item_rc) = item.upgrade() {
259                item_rc.accessible_string_property(AccessibleStringProperty::DelegateFocus);
260            }
261        });
262    }
263}
264
265cpp! {{
266    #include <QtGui/QAccessible>
267    #include <QtWidgets/QWidget>
268
269    #include <memory>
270
271    /// KEEP IN SYNC WITH CONSTANTS IN RUST!
272    const uint32_t CHECKED { QAccessible::UserText };
273    const uint32_t VALUE_MINIMUM { CHECKED + 1 };
274    const uint32_t VALUE_MAXIMUM { VALUE_MINIMUM + 1 };
275    const uint32_t VALUE_STEP { VALUE_MAXIMUM + 1 };
276    const uint32_t CHECKABLE { VALUE_STEP + 1 };
277    const uint32_t EXPANDABLE { CHECKABLE + 1 };
278    const uint32_t EXPANDED { EXPANDABLE + 1 };
279    const uint32_t READ_ONLY { EXPANDED + 1 };
280
281    // ------------------------------------------------------------------------------
282    // Helper:
283    // ------------------------------------------------------------------------------
284
285    class Descendents {
286    public:
287        Descendents(void *root_item) {
288            rustDescendents = rust!(Descendents_ctor [root_item: *mut c_void as "void*"] ->
289                    SharedVector<ItemRc> as "void*" {
290                i_slint_core::accessibility::accessible_descendents(
291                        &*(root_item as *mut ItemRc))
292                .collect()
293            });
294        }
295
296        size_t count() const {
297            return rust!(Descendents_count [rustDescendents: SharedVector<ItemRc> as "void*"] -> usize as "size_t" {
298               rustDescendents.len()
299            });
300        }
301
302        void* itemAt(size_t index) {
303            return rust!(Descendents_itemAt [rustDescendents: SharedVector<ItemRc> as "void*",
304                                             index: usize as "size_t"]
305                    -> *mut ItemWeak as "void*" {
306                let item_rc = rustDescendents[index].clone();
307                let mut item_weak = Box::new(item_rc.downgrade());
308
309                Box::into_raw(item_weak)
310            });
311        }
312
313        QAccessible::Role roleAt(size_t index) const {
314            return rust!(Descendents_roleAt [rustDescendents: SharedVector<ItemRc> as "void*",
315                                             index: usize as "size_t"]
316                    -> u32 as "QAccessible::Role" {
317                match rustDescendents[index].accessible_role() {
318                    i_slint_core::items::AccessibleRole::None => QAccessible_Role_NoRole,
319                    i_slint_core::items::AccessibleRole::Button => QAccessible_Role_Button,
320                    i_slint_core::items::AccessibleRole::Checkbox => QAccessible_Role_CheckBox,
321                    i_slint_core::items::AccessibleRole::Combobox => QAccessible_Role_ComboBox,
322                    i_slint_core::items::AccessibleRole::List => QAccessible_Role_List,
323                    i_slint_core::items::AccessibleRole::Slider => QAccessible_Role_Slider,
324                    i_slint_core::items::AccessibleRole::Spinbox => QAccessible_Role_SpinBox,
325                    i_slint_core::items::AccessibleRole::Tab => QAccessible_Role_PageTab,
326                    i_slint_core::items::AccessibleRole::TabList => QAccessible_Role_PageTabList,
327                    i_slint_core::items::AccessibleRole::Text => QAccessible_Role_StaticText,
328                    i_slint_core::items::AccessibleRole::ProgressIndicator => QAccessible_Role_ProgressBar,
329                    i_slint_core::items::AccessibleRole::Table => QAccessible_Role_Table,
330                    i_slint_core::items::AccessibleRole::Tree => QAccessible_Role_Tree,
331                    i_slint_core::items::AccessibleRole::TextInput => QAccessible_Role_EditableText,
332                    i_slint_core::items::AccessibleRole::Switch => QAccessible_Role_CheckBox,
333                    i_slint_core::items::AccessibleRole::ListItem => QAccessible_Role_ListItem,
334                    i_slint_core::items::AccessibleRole::TabPanel => QAccessible_Role_Pane,
335                    i_slint_core::items::AccessibleRole::Groupbox => QAccessible_Role_Grouping,
336                    i_slint_core::items::AccessibleRole::Image => QAccessible_Role_Graphic,
337                    _ => QAccessible_Role_NoRole,
338                }
339            });
340        }
341
342        ~Descendents() {
343            auto descendentsPtr = &rustDescendents;
344            rust!(Descendents_dtor [descendentsPtr: *mut SharedVector<ItemRc> as "void**"] {
345                core::ptr::read(descendentsPtr);
346            });
347        }
348
349    private:
350        void *rustDescendents;
351    };
352
353    void *root_item_for_window(void *rustWindow) {
354        return rust!(root_item_for_window_ [rustWindow: &crate::qt_window::QtWindow as "void*"]
355                -> *mut c_void as "void*" {
356            let root_item = Box::new(ItemRc::new(WindowInner::from_pub(&rustWindow.window).component(), 0).downgrade());
357            Box::into_raw(root_item) as _
358        });
359    }
360
361    QString item_string_property(void *data, uint32_t what) {
362        return rust!(item_string_property_
363            [data: &SlintAccessibleItemData as "void*", what: u32 as "uint32_t"]
364                -> QString as "QString" {
365
366            if let Some(item) = data.item.upgrade() {
367                let string = match what {
368                    NAME => item.accessible_string_property(AccessibleStringProperty::Label),
369                    DESCRIPTION => item.accessible_string_property(AccessibleStringProperty::Description),
370                    VALUE => item.accessible_string_property(AccessibleStringProperty::Value),
371                    CHECKED => item.accessible_string_property(AccessibleStringProperty::Checked),
372                    VALUE_MINIMUM => item.accessible_string_property(AccessibleStringProperty::ValueMinimum),
373                    VALUE_MAXIMUM => item.accessible_string_property(AccessibleStringProperty::ValueMaximum),
374                    VALUE_STEP => item.accessible_string_property(AccessibleStringProperty::ValueStep),
375                    CHECKABLE => item.accessible_string_property(AccessibleStringProperty::Checkable),
376                    EXPANDABLE => item.accessible_string_property(AccessibleStringProperty::Expandable),
377                    EXPANDED => item.accessible_string_property(AccessibleStringProperty::Expanded),
378                    READ_ONLY => item.accessible_string_property(AccessibleStringProperty::ReadOnly),
379                    _ => None,
380                };
381                if let Some(string) = string {
382                    return QString::from(string.as_ref())
383                }
384            };
385            QString::default()
386        });
387    }
388
389    // ------------------------------------------------------------------------------
390    // Slint_accessible:
391    // ------------------------------------------------------------------------------
392
393    // Base object for accessibility support
394    class Slint_accessible : public QAccessibleInterface {
395    public:
396        Slint_accessible(QAccessible::Role role, QAccessibleInterface *parent) :
397             has_focus(false), has_focus_delegation(false), m_role(role), m_parent(parent)
398        { }
399
400        ~Slint_accessible() {
401            qDeleteAll(m_children);
402        }
403
404        virtual void *rustItem() const = 0;
405
406        // Returns the SlintWidget of the window... we have no other.
407        virtual QWidget *qwidget() const = 0;
408
409        QPoint mapToGlobal(const QPoint p) const {
410            return qwidget()->mapToGlobal(p);
411        }
412
413        QPoint mapFromGlobal(const QPoint p) const {
414            return qwidget()->mapFromGlobal(p);
415        }
416
417        void clearFocus() {
418            has_focus = false;
419            has_focus_delegation = false;
420
421            for (int i = 0; i < rawChildCount(); ++i) {
422                static_cast<Slint_accessible *>(child(i))->clearFocus();
423            }
424        }
425
426        virtual void delegateFocus() const {
427            sendFocusChangeEvent();
428        }
429
430        // Returns true if the item accepted the focus; false otherwise.
431        bool focusItem(void *item) const {
432            auto my_item = rustItem();
433            if (rust!(Slint_accessible_findItem [item: &ItemWeak as "void *", my_item: &ItemWeak as "void*"] -> bool as "bool" {
434                item == my_item
435            })) {
436                has_focus = true;
437
438                delegateFocus();
439                return true;
440            }
441
442            for (int i = 0; i < rawChildCount(); ++i) {
443                if (static_cast<Slint_accessible *>(child(i))->focusItem(item)) {
444                    return true;
445                }
446            }
447            return false;
448        }
449
450        void sendFocusChangeEvent() const {
451            auto event = QAccessibleEvent(object(), QAccessible::Focus);
452            QAccessible::updateAccessibility(&event);
453            has_focus_delegation = true;
454        }
455
456        bool isValid() const override {
457            return true;
458        }
459
460        // navigation, hierarchy
461        QAccessibleInterface *parent() const override {
462            return m_parent;
463        }
464
465        QAccessibleInterface *focusChild() const override {
466            if (has_focus_delegation) {
467                return const_cast<QAccessibleInterface *>(static_cast<const QAccessibleInterface *>(this));
468            }
469            for (int i = 0; i < childCount(); ++i)  {
470                if (auto focus = child(i)->focusChild()) return focus;
471            }
472            return nullptr;
473        }
474
475        int indexOfChild(const QAccessibleInterface *child) const override {
476            return m_children.indexOf(child->object()); // FIXME: Theoretically we can have several QAIs per QObject!
477        }
478
479        // Will *not* trigger a build of the accessibility item tree!
480        // Use this from the Slint side to make sure the accessibility
481        // item tree is not generated needlessly.
482        int rawChildCount() const {
483            return m_children.count();
484        }
485
486        /// Will *not* trigger a build of the accessibility tree!
487        QAccessibleInterface *rawChild(int index) const {
488            if (0 <= index && index < rawChildCount())
489                return QAccessible::queryAccessibleInterface(m_children[index]);
490            return nullptr;
491        }
492
493        // May trigger a build of the accessibility item tree!
494        // Use this from the Qt API side (which is triggered by the OS accessibility
495        // layer to make sure accessibility information is up-to-date.
496        int childCount() const override {
497            return rawChildCount();
498        }
499
500        QAccessibleInterface *child(int index) const override {
501            if (0 <= index && index < childCount())
502                return QAccessible::queryAccessibleInterface(m_children[index]);
503            return nullptr;
504        }
505
506        void setText(QAccessible::Text t, const QString &text) override {
507            Q_UNUSED(t); Q_UNUSED(text);
508        }
509
510        QAccessible::Role role() const override {
511            return m_role;
512        }
513
514        QRect rect() const override {
515            auto item = rustItem();
516            QRectF r = rust!(Slint_accessible_item_rect
517                [item: *const ItemWeak as "void*"] -> qttypes::QRectF as "QRectF" {
518                    if let Some(item_rc) = item.as_ref().unwrap().upgrade() {
519                        let geometry = item_rc.geometry();
520
521                        let mapped = item_rc.map_to_window(geometry.origin);
522
523                        qttypes::QRectF {
524                            x: mapped.x as _,
525                            y: mapped.y as _,
526                            width: geometry.width() as _,
527                            height: geometry.height() as _,
528                        }
529                    } else {
530                        Default::default()
531                    }
532                });
533            auto topLeft = mapToGlobal(QPoint(static_cast<int>(r.left()), static_cast<int>(r.top())));
534            auto bottomRight = mapToGlobal(QPoint(static_cast<int>(r.right()), static_cast<int>(r.bottom())));
535            return QRect(topLeft, bottomRight);
536        }
537
538        QAccessibleInterface *childAt(int x, int y) const override {
539            for (int i = 0; i < childCount(); ++i)  {
540                auto c = child(i);
541                auto r = c->rect();
542                if (r.contains(x, y)) return c;
543            }
544            return nullptr;
545        }
546
547        void updateAccessibilityTree() const;
548
549    protected:
550        mutable bool has_focus;
551        mutable bool has_focus_delegation;
552
553    private:
554        QAccessible::Role m_role = QAccessible::NoRole;
555        QAccessibleInterface *m_parent = nullptr;
556        mutable QList<QObject*> m_children;
557    };
558
559    // ------------------------------------------------------------------------------
560    // Slint_accessible_item:
561    // ------------------------------------------------------------------------------
562
563    class Slint_accessible_item : public Slint_accessible, public QAccessibleValueInterface, public QAccessibleActionInterface {
564    public:
565        Slint_accessible_item(void *item, QObject *obj, QAccessible::Role role, QAccessibleInterface *parent) :
566            Slint_accessible(role, parent), m_object(obj)
567        {
568            m_data = rust!(Slint_accessible_item_ctor [obj: *mut c_void as "QObject*",
569                    item: &ItemWeak as "void*"] ->
570                    *mut SlintAccessibleItemData as "void*" {
571                        let data = SlintAccessibleItemData::new_pin_box(obj, item);
572                        unsafe { Box::into_raw(Pin::into_inner_unchecked(data)) }
573            });
574        }
575
576        QAccessibleActionInterface *actionInterface() { return this; }
577        QAccessibleValueInterface *valueInterface() { return this; }
578
579
580        ~Slint_accessible_item() {
581            rust!(Slint_accessible_item_dtor [m_data: *mut SlintAccessibleItemData as "void*"] {
582                unsafe { Pin::new_unchecked(Box::from_raw(m_data)) };
583            });
584        }
585
586        void *rustItem() const override {
587            return rust!(Slint_accessible_item_rustItem [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> *const ItemWeak as "void*" {
588                &m_data.item
589            });
590        }
591
592        QObject *object() const override {
593            return m_object;
594        }
595
596        QWidget *qwidget() const override {
597            return dynamic_cast<Slint_accessible *>(parent())->qwidget();
598        }
599
600        void *data() const {
601            return m_data;
602        }
603
604        QWindow *window() const override {
605            return parent()->window();
606        }
607
608        void delegateFocus() const override {
609            if (!has_focus) { return; }
610
611            auto index = rust!(Slint_accessible_item_delegate_focus [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> i32 as "int" {
612                m_data.item.upgrade()
613                    .and_then(|i| { i.accessible_string_property(AccessibleStringProperty::DelegateFocus) })
614                    .and_then(|s| s.as_str().parse::<i32>().ok()).unwrap_or(-1)
615            });
616
617            if (index >= 0 && index < rawChildCount()) {
618                static_cast<Slint_accessible_item*>(rawChild(index))->sendFocusChangeEvent();
619            } else {
620                sendFocusChangeEvent();
621            }
622        }
623
624        // properties and state
625        QString text(QAccessible::Text t) const override {
626            return item_string_property(m_data, t);
627        }
628
629        QAccessible::State state() const override {
630            auto checked = item_string_property(m_data, CHECKED);
631
632            QAccessible::State state;
633            state.active = 1;
634            state.focusable = 1;
635            state.focused = has_focus_delegation;
636            state.checked = (checked == "true") ? 1 : 0;
637            state.checkable = (item_string_property(m_data, CHECKABLE) == "true") ? 1 : 0;
638            if (item_string_property(m_data, EXPANDABLE) == "true") {
639                state.expandable = 1;
640                if (item_string_property(m_data, EXPANDED) == "true") {
641                    state.expanded = 1;
642                } else {
643                    state.collapsed = 1;
644                }
645            }
646            state.readOnly = (item_string_property(m_data, READ_ONLY) == "true") ? 1 : 0;
647            return state; /* FIXME */
648        }
649
650        void *interface_cast(QAccessible::InterfaceType t) override {
651            if (t == QAccessible::ValueInterface && !item_string_property(m_data, QAccessible::Value).isEmpty()) {
652                return static_cast<QAccessibleValueInterface*>(this);
653            } else if (t == QAccessible::ActionInterface) {
654                return static_cast<QAccessibleActionInterface*>(this);
655            }
656            return QAccessibleInterface::interface_cast(t);
657        }
658
659        // AccessibleValueInterface:
660        QVariant currentValue() const override {
661            return item_string_property(m_data, QAccessible::Value);
662        }
663
664        void setCurrentValue(const QVariant &value) override {
665            QString value_string = value.toString();
666            rust!(Slint_accessible_setCurrentValue [m_data: Pin<&SlintAccessibleItemData> as "void*", value_string: qttypes::QString as "QString"] {
667                let Some(item) = m_data.item.upgrade() else {return};
668                item.accessible_action(&AccessibilityAction::SetValue(i_slint_core::format!("{value_string}")));
669            });
670        }
671
672        QVariant maximumValue() const override {
673            return item_string_property(m_data, VALUE_MAXIMUM);
674        }
675
676        QVariant minimumValue() const override {
677            return item_string_property(m_data, VALUE_MINIMUM);
678        }
679
680        QVariant minimumStepSize() const override {
681            return item_string_property(m_data, VALUE_STEP);
682        }
683
684        QStringList actionNames() const override {
685            int supported = rust!(Slint_accessible_item_supported [m_data: Pin<&SlintAccessibleItemData> as "void*"] -> SupportedAccessibilityAction as "uint" {
686                m_data.item.upgrade().map(|i| i.supported_accessibility_actions()).unwrap_or_default()
687            });
688            QStringList actions;
689            if (supported & rust!(Slint_accessible_item_an1 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Default }))
690                actions << QAccessibleActionInterface::pressAction();
691            if (supported & rust!(Slint_accessible_item_an2 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Increment }))
692                actions << QAccessibleActionInterface::increaseAction();
693            if (supported & rust!(Slint_accessible_item_an3 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Decrement }))
694                actions << QAccessibleActionInterface::decreaseAction();
695            if (supported & rust!(Slint_accessible_item_an4 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Expand }))
696                actions << QAccessibleActionInterface::pressAction();
697            return actions;
698        }
699
700        void doAction(const QString &actionName) override {
701            if (actionName == QAccessibleActionInterface::pressAction()) {
702                rust!(Slint_accessible_item_do_action1 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
703                    let Some(item) = m_data.item.upgrade() else {return};
704                    let supported_actions = item.supported_accessibility_actions();
705                    if supported_actions.contains(SupportedAccessibilityAction::Expand) {
706                        item.accessible_action(&AccessibilityAction::Expand);
707                    } else {
708                        item.accessible_action(&AccessibilityAction::Default);
709                    }
710                });
711            } else if (actionName == QAccessibleActionInterface::increaseAction()) {
712                rust!(Slint_accessible_item_do_action2 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
713                    let Some(item) = m_data.item.upgrade() else {return};
714                    item.accessible_action(&AccessibilityAction::Increment);
715                });
716            } else if (actionName == QAccessibleActionInterface::decreaseAction()) {
717                rust!(Slint_accessible_item_do_action3 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
718                    let Some(item) = m_data.item.upgrade() else {return};
719                    item.accessible_action(&AccessibilityAction::Decrement);
720                });
721            }
722        }
723
724        QStringList keyBindingsForAction(const QString &) const override {
725            return QStringList();
726        }
727
728    private:
729        QObject *m_object = nullptr;
730        mutable void *m_data = nullptr;
731    };
732
733    // ------------------------------------------------------------------------------
734    // Slint_accessible_window:
735    // ------------------------------------------------------------------------------
736
737    class Slint_accessible_window : public Slint_accessible {
738    public:
739        Slint_accessible_window(QWidget *widget, void *rust_window) :
740            Slint_accessible(QAccessible::Window, QAccessible::queryAccessibleInterface(qApp)),
741            m_widget(widget),
742            m_rustWindow(rust_window)
743        { }
744
745        ~Slint_accessible_window()
746        {
747            rust!(Slint_accessible_window_dtor [m_rustWindow: *mut c_void as "void*"] {
748                alloc::rc::Weak::from_raw(m_rustWindow as *const QtWindow); // Consume the Weak<QtWindow> we hold in our void*!
749            });
750        }
751
752        bool isUsed() const {
753            return is_used;
754        }
755
756        void *rustItem() const override {
757            return root_item_for_window(m_rustWindow);
758        }
759
760        QObject *object() const override {
761            return m_widget;
762        }
763
764        QWidget *qwidget() const override {
765            return m_widget;
766        }
767
768        QWindow *window() const override {
769            return qobject_cast<QWidget *>(object())->windowHandle();
770        }
771
772        int childCount() const override {
773            if (!is_used) { updateAccessibilityTree(); }
774            is_used = true;
775            return Slint_accessible::childCount();
776        }
777
778        // properties and state
779        QString text(QAccessible::Text t) const override {
780            switch (t) {
781                case QAccessible::Name: return qobject_cast<QWidget*>(object())->windowTitle();
782                default: return QString();
783            }
784        }
785
786        QAccessible::State state() const override {
787            QAccessible::State state;
788            state.active = 1;
789            state.focusable = 1;
790            return state;
791        }
792
793    private:
794        QWidget *m_widget;
795        void *m_rustWindow; // *const QtWindow
796        mutable bool is_used = false;
797    };
798
799    QList<QObject *> deleteStaleItems(QList<QObject *> &&current_children) {
800        // Delete no longer valid objects:
801        current_children.erase(std::remove_if(current_children.begin(), current_children.end(), [](QObject *o) {
802            auto ai = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(o));
803            Q_ASSERT(ai);
804            auto data = ai->data();
805
806            if (rust!(Slint_delete_stale_items
807                    [data: Pin<&SlintAccessibleItemData> as "void*"] -> bool as "bool" {
808                data.item.upgrade().is_none()
809            })) {
810                o->deleteLater();
811                return true;
812            } else {
813                return false;
814            }
815        }), current_children.end());
816
817        return std::move(current_children);
818    }
819
820    int indexOfItem(const QList<QObject *> &existing, void *item) {
821        for (int i = 0; i < existing.count(); ++i) {
822            auto data = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(existing[i]));
823            if (rust!(Slint_indexOfItems [data: Pin<&SlintAccessibleItemData> as "void*", item: &ItemWeak as "void*"] -> bool as "bool" {
824                data.item == *item
825            })) {
826                return i;
827            }
828        }
829        return -1;
830    }
831
832    QList<QObject *> updateItems(QList<QObject *> &&current_children,
833                                 Descendents &descendents,
834                                 Slint_accessible *parent) {
835        QList<QObject *> children = {};
836        children.reserve(descendents.count());
837
838        for (size_t i = 0; i < descendents.count(); ++i) {
839            auto item = descendents.itemAt(i);
840            auto index = indexOfItem(current_children, item);
841            QObject *object = nullptr;
842            Slint_accessible_item *ai = nullptr;
843
844            if (index == -1) {
845                // Create new item:
846                object = new QObject();
847                auto role = descendents.roleAt(i);
848                ai = new Slint_accessible_item(item, object, role, parent);
849
850                QAccessible::registerAccessibleInterface(ai);
851            } else {
852                // Reuse existing item:
853                object = current_children[index];
854                ai = dynamic_cast<Slint_accessible_item *>(QAccessible::queryAccessibleInterface(object));
855
856                current_children.removeAt(index);
857            }
858
859            Q_ASSERT(ai);
860            Q_ASSERT(object);
861
862            ai->updateAccessibilityTree();
863
864            children.append(object);
865        }
866
867        return children;
868    }
869
870    void Slint_accessible::updateAccessibilityTree() const {
871        QList<QObject *> valid_objects = deleteStaleItems(std::move(m_children));
872        auto descendents = Descendents(rustItem());
873
874        m_children = updateItems(std::move(valid_objects), descendents,
875                                 const_cast<Slint_accessible *>(this));
876    }
877}}