summaryrefslogtreecommitdiffstats
path: root/vcl/qa/cppunit/a11y/atspi2/atspiwrapper.hxx
blob: 635f9b76d4070a4b41550d4187fe508d5df5c191 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/* C++ wrapper for libatspi, so to make it less obnoxious to use */

/**
 * Adding a new wrapper
 *
 * To wrap a new Atspi type (let's say, AtspiCollection), you need to:
 *
 * 1. Add <tt>DEFINE_GOBJECT_CAST(AtspiCollection, ATSPI_TYPE_COLLECTION)</tt> near the similar
 *    ones. This creates <tt>Atspi::cast<AtspiCollection*>(p)</tt> so that such a cast based on the
 *    C++ type is checked using the GType type system.
 * 2. Add a declaration for the new wrapper class above Atspi::Accessible
 *    (<tt>class Collection;</tt>) so it can be used in step 3.
 * 3. Add <tt>Atspi::Accessible::queryCollection()</tt> method.  Its definition has to be in the
 *    source file as it requires a complete type for the wrapper class.  The body just calls
 *    <tt>queryInterface<Collection>(atspi_accessible_get_collection_iface);</tt> and returns
 *    its value.
 * 4. Add the definition of the new wrapper class:
 *    <tt>class Collection : public Accessible { ... }</tt>
 *    Use the existing wrappers as inspiration, but basically:
 *    - Define the constructor that only chains up to the parent
 *    - Define each wrapper method, which generally only have to call one of the <tt>invoke()</tt>
 *      helpers to wrap the C calls.  There are a few, depending on some details of the C call:
 *      - @c GObjectWrapperBase::invoke(): this is the most basic one, that just calls the C method
 *           on @c GObjectWrapperBase::get() with the given arguments.  Use this for calls not
 *           throwing an exception and either returning a plain value, or something not handled by
 *           one of the others below.
 *      - @c AtspiWrapperBase::invokeThrow(): like @c GObjectWrapperBase::invoke(), but for C calls
 *           that take a @c GError argument for throwing exceptions.  @c invokeThrow() will
 *           transform any C exception into a a C++ exception (@c css::uno::RuntimeException)
 *      - @c AtspiWrapperBase::strInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages a
 *           C string (@c char*) return value as an @c std::string.  Use this for C calls returning
 *           a C string.
 *      - @c AtspiWrapperBase::garrayInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages
 *           a @c GArray return value as an @c std::vector.  Use this for C calls returning a
 *           @p GArray.
 *      - @c AtspiWrapperBase::hashMapInvoke(): like @c AtspiWrapperBase::invokeThrow(), but manages
 *           a @c GHashTable return value as an @c std::unordered_map.  Use this for C calls
 *           returning a @p GHashTable.
 *      - @c AtspiWrapperBase::strHashMapInvoke(): identical to @c AtspiWrapperBase::hashMapInvoke()
 *           using C strings for keys and values.
 *      .
 *     If none of those match the exact return type of the C call to wrap, use
 *     @c AtspiWrapperBase::invokeThrow() or even @c GObjectWrapperBase::invoke(), and manually
 *     manage the result value.  You can use Atspi::gmem functions to help.  Basically the idea is
 *     that you always return a self-managing object to make memory management easy (whereas it's
 *     obnoxiously hard with plain C Atspi API).
 */

#pragma once

#include <vector>
#include <unordered_map>
#include <boost/type_traits/function_traits.hpp>

#include <atspi/atspi.h>
#include <com/sun/star/container/NoSuchElementException.hpp>
#include <com/sun/star/uno/RuntimeException.hpp>

#include <cppunit/TestAssert.h>

#include <config_atspi.h>

namespace Atspi
{
/** @brief Helpers for managing GLib memory in a more C++-style */
namespace gmem
{
/** @brief Wraps a pointer to free with @c g_free() in a @c std::unique_ptr */
template <typename T> static inline auto unique_gmem(T* ptr)
{
    return std::unique_ptr<T, decltype(&g_free)>(ptr, &g_free);
}

/** @brief Wraps a @c GArray to free with @c g_array_unref() in a @c std::unique_ptr */
static inline auto unique_garray(GArray* p)
{
    return std::unique_ptr<GArray, decltype(&g_array_unref)>(p, &g_array_unref);
}

/** @brief Wraps a @c GHashTable to free with @c g_hash_table_unref() in a @c std::unique_ptr */
static inline auto unique_ghashtable(GHashTable* p)
{
    return std::unique_ptr<GHashTable, decltype(&g_hash_table_unref)>(p, &g_hash_table_unref);
}
}

// --- GObject cast wrappers based on type: usage is cast<AtspiAccessible*>(pCInstance)
#define DEFINE_GOBJECT_CAST(CType, GType)                                                          \
    template <typename P, typename T, std::enable_if_t<std::is_same_v<P, CType*>, int> = 1>        \
    P cast(T* pInstance)                                                                           \
    {                                                                                              \
        return G_TYPE_CHECK_INSTANCE_CAST(pInstance, GType, std::remove_pointer_t<P>);             \
    }

DEFINE_GOBJECT_CAST(AtspiStateSet, ATSPI_TYPE_STATE_SET)
DEFINE_GOBJECT_CAST(AtspiRelation, ATSPI_TYPE_RELATION)
DEFINE_GOBJECT_CAST(AtspiAccessible, ATSPI_TYPE_ACCESSIBLE)
DEFINE_GOBJECT_CAST(AtspiComponent, ATSPI_TYPE_COMPONENT)
DEFINE_GOBJECT_CAST(AtspiText, ATSPI_TYPE_TEXT)

#undef DEFINE_GOBJECT_CAST
// --- end GObject cast wrappers

class GLibEnumBase
{
protected:
    /**
     * @brief Retrieves the string representation of an enumeration value
     * @param gt The @c GType for the enumeration
     * @param value The enumeration value for which to get the name for
     * @param fallback Fallback value in case @p values falls outside the enumeration
     * @returns A string representing @p value
     */
    static std::string glibEnumValueName(GType gt, gint value,
                                         std::string_view fallback = "unknown")
    {
        auto klass = static_cast<GEnumClass*>(g_type_class_ref(gt));
        auto enum_value = g_enum_get_value(klass, value);
        std::string ret(enum_value ? enum_value->value_name : fallback);
        g_type_class_unref(klass);
        return ret;
    }
};

class Role : private GLibEnumBase
{
public:
    static std::string getName(AtspiRole role)
    {
        return glibEnumValueName(atspi_role_get_type(), role);
    }
};

class State : private GLibEnumBase
{
public:
    static std::string getName(AtspiStateType state)
    {
        return glibEnumValueName(atspi_state_type_get_type(), state);
    }
};

class TextGranularity : private GLibEnumBase
{
public:
    static std::string getName(AtspiTextGranularity granularity)
    {
        return glibEnumValueName(atspi_text_granularity_get_type(), granularity);
    }
};

class TextBoundaryType : private GLibEnumBase
{
public:
    static std::string getName(AtspiTextBoundaryType boundaryType)
    {
        return glibEnumValueName(atspi_text_boundary_type_get_type(), boundaryType);
    }
};

/**
 * @brief Base class for GObject wrappers
 *
 * This leverages std::shared_ptr as a cheap way of wrapping a raw pointer, and its deleter as a
 * mean of using g_object_unref() to cleanup.  This is sub-optimal as it maintains a separate
 * refcount to the GObject one, but it's easy.
 */
template <class T> class GObjectWrapperBase : public std::shared_ptr<T>
{
public:
    /* this is the boundary of C++ type safety, so we can have inheritance working
     * properly with the C types.  This should still be safe as it uses cast() which should be
     * defined for each using type with DEFINE_GOBJECT_CAST(), which uses GType validation */
    template <typename P = T*> P get() const { return cast<P>(std::shared_ptr<T>::get()); }

protected:
    /**
     * @brief Calls the C function @p f on the C object wrapped by @p this
     * @param f The C function to call
     * @param args Additional arguments to @p f
     * @returns The return value from @p f
     *
     * Calls the C function @p f similar to <tt>f(get(), args...)</tt>.  Care is taken of
     * transforming @c get() to the type actually expected as the first argument of @p f, using
     * @c get<TypeOfFsFirstArgument>(), which performs a runtime verification of the conversion.
     *
     * @note The type verification on whether @p f actually takes what get() returns is performed
     *       at runtime, so there will be no compilation error or warning if trying to use an
     *       incompatible C function.  A check will however be performed at runtime, at least
     *       helping diagnose a possible invalid conversion.
     */
    template <typename F, typename... Ts> inline auto invoke(F f, Ts... args) const
    {
        using FT = std::remove_pointer_t<F>;
        const auto p = get<typename boost::function_traits<FT>::arg1_type>();
        return f(p, args...);
    }

private:
    static void deleter(T* p)
    {
        if (p)
            g_object_unref(p);
    }

public:
    /**
     * @param pObj The raw GObject to wrap
     * @param takeRef Whether to take ownership of the object or not.  If set to @c false, it will
     *                call @c g_object_ref(pAcc) to acquire a new reference to the GObject.
     */
    GObjectWrapperBase(T* pObj = nullptr, bool takeRef = true)
        : std::shared_ptr<T>(pObj, deleter)
    {
        if (pObj && !takeRef)
            g_object_ref(pObj);
    }
};

/** @brief AtspiStateSet C++ wrapper */
class StateSet : public GObjectWrapperBase<AtspiStateSet>
{
public:
    using GObjectWrapperBase::GObjectWrapperBase;

    void add(const AtspiStateType t) { invoke(atspi_state_set_add, t); }
    StateSet compare(const StateSet& other) const
    {
        return StateSet(invoke(atspi_state_set_compare, other.get()));
    }
    bool contains(const AtspiStateType t) const { return invoke(atspi_state_set_contains, t); }
    bool operator==(const StateSet& other) const
    {
        return invoke(atspi_state_set_equals, other.get());
    }
    std::vector<AtspiStateType> getStates() const
    {
        auto garray = gmem::unique_garray(invoke(atspi_state_set_get_states));
        std::vector<AtspiStateType> states;
        for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
        {
            states.push_back(g_array_index(garray, decltype(states)::value_type, i));
        }
        return states;
    }
    bool empty() const { return invoke(atspi_state_set_is_empty); }
    void remove(AtspiStateType t) { invoke(atspi_state_set_remove, t); }
    void setByName(const std::string_view name, bool enable)
    {
        invoke(atspi_state_set_set_by_name, name.data(), enable);
    }
};

class Accessible;

/** @brief AtspiRelation C++ wrapper */
class Relation : public GObjectWrapperBase<AtspiRelation>
{
public:
    using GObjectWrapperBase::GObjectWrapperBase;

    AtspiRelationType getRelationType() const { return invoke(atspi_relation_get_relation_type); }
    int getNTargets() const { return invoke(atspi_relation_get_n_targets); }
    Accessible getTarget(int i) const;
};

/* intermediate base just for splitting out the *invoke* helpers implementations, so the actual
 * user-targeted class can hold only the actual API */
template <class T> class AtspiWrapperBase : public GObjectWrapperBase<T>
{
protected:
    using GObjectWrapperBase<T>::invoke;

    /**
     * @brief Calls the throwing C function @p f on the C object wrapped by @p this
     * @param f The C function to call
     * @param args Additional arguments to @p f
     * @returns The raw return value from @p f
     * @throws css::uno::RuntimeException Exception @c GError are translated to
     *
     * This wrapper calls @p f with parameters @p args and an additional @c GError parameter to
     * catch C exception, transforming them into C++ exceptions of type
     * @c css::uno::RuntimeException.
     *
     * @see invoke()
     */
    template <typename F, typename... Ts> inline auto invokeThrow(F f, Ts... args) const
    {
        GError* err = nullptr;
        auto ret = invoke(f, args..., &err);
        if (err)
        {
            throw css::uno::RuntimeException(OUString::fromUtf8(err->message));
        }
        return ret;
    }

    /**
     * @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a string
     * @param f The C function to call
     * @param args Additional arguments to @p f
     * @tparam E the type of exception to throw if @p f returns @c null, defaults to
     *           @c css::uno::RuntimeException
     * @returns A string holding the return value from @p f
     * @throws css::uno::RuntimeException See invokeThrow()
     * @throws E Exception to use if @p f returns null with no other error
     *
     * Just like @c invokeThrow(), but wraps the return value in an @c std::string and manages the
     * lifetime of the C function return value.
     *
     * As @c std::string cannot represent a @c null value, if @p f returned such a value without
     * throwing an exception, this method will throw an exception of type @p E
     *
     * @see invokeThrow()
     */
    template <typename E = css::uno::RuntimeException, typename F, typename... Ts>
    inline std::string strInvoke(F f, Ts... args) const
    {
        auto r = invokeThrow(f, args...);

        /* if the API returned NULL without throwing, use the specified exception because a nullptr
         * std::string is not valid, and std::logic_error isn't gonna be very useful to the caller */
        if (!r)
            throw E();

        return gmem::unique_gmem(r).get();
    }

    /**
     * @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a vector
     * @param f The C function to call
     * @param args Additional arguments to @p f
     * @tparam Vi The type of the members of the @c GArray @p f returns
     * @tparam Vo The type of the members of the returned vector
     * @returns A vector holding the return value from @p f
     * @throws css::uno::RuntimeException See invokeThrow()
     *
     * Just like @c invokeThrow(), but wraps the return in an @c std::vector<Vo> and manages the
     * lifetime of the C function return value.
     *
     * @p Vi has to be implicitly convertible to @p Vo.  A typical usage could be
     * <tt>garrayInvoke<AtspiAccessible*, Accessible>(...)</tt>, which would transform a @c GArray
     * of @c AtspiAccessible* to an @c std::vector of @c Atspi::Accessible.
     *
     * @warning You have to get @p Vi right, there is no way to validate this type is correct or
     *          not, so you won't get a compilation error nor even a warning if you give the wrong
     *          type here.
     *
     * @see invokeThrow()
     */
    template <typename Vi, typename Vo, typename F, typename... Ts>
    inline std::vector<Vo> garrayInvoke(F f, Ts... args) const
    {
        auto garray = gmem::unique_garray(invokeThrow(f, args...));
        std::vector<Vo> vec;
        for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
            vec.push_back(g_array_index(garray, Vi, i));
        return vec;
    }

    /**
     * @brief Wraps an AT-SPI call returning a GHashTable
     * @tparam Ki Type of the keys in the wrapped hash table
     * @tparam Vi Type of the values in the wrapped hash table
     * @tparam Ko Type of the keys in the wrapper map (this must be convertible from Ki)
     * @tparam Vo Type of the values in the wrapper map (this must be convertible from Kv)
     * @param f The function to call
     * @param args Arguments to pass to @p f
     * @returns A @c std::unordered_map holding the data returned by @p f.
     *
     * @see invokeThrow()
     * @see strHashMapInvoke()
     */
    template <typename Ki, typename Vi, typename Ko, typename Vo, typename F, typename... Ts>
    inline std::unordered_map<Ko, Vo> hashMapInvoke(F f, Ts... args) const
    {
        auto ghash = gmem::unique_ghashtable(invokeThrow(f, args...));
        std::unordered_map<Ko, Vo> map;
        GHashTableIter iter;
        g_hash_table_iter_init(&iter, ghash.get());
        gpointer key, value;
        while (g_hash_table_iter_next(&iter, &key, &value))
        {
            map.emplace(static_cast<Ki>(key), static_cast<Vi>(value));
        }
        return map;
    }

    /**
     * @brief Just like @c hashMapInvoke() but already specialized for strings
     * @param f The C function to call
     * @param args Arguments to @p f
     * @returns A @c std::unordered_map holding the data returned by @p f
     *
     * This is exactly the same as
     * <tt>hashMapInvoke<gchar*, gchar*, std::string, std::string>(f, args...)</tt>
     */
    template <typename F, typename... Ts> inline auto strHashMapInvoke(F f, Ts... args) const
    {
        return hashMapInvoke<gchar*, gchar*, std::string, std::string>(f, args...);
    }

public:
    using GObjectWrapperBase<T>::GObjectWrapperBase;
};

class Component;
class Text;

/**
 * @brief AtspiAccessible C++ wrapper
 *
 * This is a wrapper for the AtspiAccessible GObject class to make it a bit nicer to use in C++,
 * including a proper class with methods, regular exceptions, easy memory management, and an
 * iterator to enumerate children.
 *
 * As this class actually inherits from std::shared_ptr, you can easily use @c get() to retrieve
 * the wrapped pointer in case you need to, e.g. if some specific API is missing.  However, take
 * care of memory management on that object not to have it destroyed early (e.g. don't let anyone
 * call @c g_object_unref() on it if they didn't call g_object_ref() first).
 *
 * To use it, just wrap an initial AtspiAccessible using the class constructor.
 * @code
 * Atspi::Accessible desktop(atspi_get_desktop(0));
 * for (auto&& app: desktop) {
 *     std::cout << app->getName() << std::endl;
 * }
 * @endcode
 *
 * For details on the specific methods, see the C Atspi documentation.
 */
class Accessible : public AtspiWrapperBase<AtspiAccessible>
{
public:
    using AtspiWrapperBase<AtspiAccessible>::AtspiWrapperBase;

    void setCacheMask(AtspiCache mask) const { invoke(atspi_accessible_set_cache_mask, mask); }
    void clearCache() const { invoke(atspi_accessible_clear_cache); }

    AtspiRole getRole() const { return invokeThrow(atspi_accessible_get_role); }
    std::string getRoleName() const { return strInvoke(atspi_accessible_get_role_name); }
    std::string getName() const { return strInvoke(atspi_accessible_get_name); }
    std::string getDescription() const { return strInvoke(atspi_accessible_get_description); }

    int getChildCount() const { return invokeThrow(atspi_accessible_get_child_count); }
    Accessible getChildAtIndex(int idx) const
    {
        return Accessible(invokeThrow(atspi_accessible_get_child_at_index, idx));
    }
    int getIndexInParent() const { return invokeThrow(atspi_accessible_get_index_in_parent); }
    Accessible getParent() const { return Accessible(invokeThrow(atspi_accessible_get_parent)); }

    StateSet getStateSet() const { return StateSet(invoke(atspi_accessible_get_state_set)); }

    std::unordered_map<std::string, std::string> getAttributes() const
    {
        return strHashMapInvoke(atspi_accessible_get_attributes);
    }

    std::vector<Relation> getRelationSet() const
    {
        return garrayInvoke<AtspiRelation*, Relation>(atspi_accessible_get_relation_set);
    }

private:
    template <class I, typename F> I queryInterface(F f) const
    {
        auto pIface = invoke(f);
        if (pIface)
            return I(pIface);
        throw css::uno::RuntimeException("Not implemented");
    }

public:
    Component queryComponent() const;
    Text queryText() const;

    // convenience extensions
    class iterator
    {
    public:
        using iterator_category = std::input_iterator_tag;
        using value_type = Accessible;
        using difference_type = std::ptrdiff_t;
        using reference = value_type&;
        using pointer = value_type*;

    private:
        const Accessible* m_pAccessible;
        int m_idx;

    public:
        explicit iterator(const Accessible* pAccessible, int idx = 0)
            : m_pAccessible(pAccessible)
            , m_idx(idx)
        {
        }

        iterator(iterator const& other)
            : m_pAccessible(other.m_pAccessible)
            , m_idx(other.m_idx)
        {
        }

        iterator& operator++()
        {
            m_idx++;
            return *this;
        }

        iterator operator++(int)
        {
            iterator other = *this;
            ++(*this);
            return other;
        }

        bool operator==(iterator other) const
        {
            return m_idx == other.m_idx && m_pAccessible == other.m_pAccessible;
        }
        bool operator!=(iterator other) const { return !(*this == other); }
        value_type operator*() const
        {
            assert(m_idx < m_pAccessible->getChildCount());
            return m_pAccessible->getChildAtIndex(m_idx);
        }
    };

    iterator begin() const { return iterator(this); }
    iterator end() const
    {
        if (!get())
            return iterator(this);
        return iterator(this, std::max(0, getChildCount()));
    }
};

// we just use ATSPI's own structure here, but pass it by value
using Rect = AtspiRect;
using Point = AtspiPoint;

/** @brief AtspiComponent C++ wrapper */
class Component : public Accessible
{
public:
    Component(AtspiComponent* pObj = nullptr, bool takeRef = true)
        : Accessible(cast<AtspiAccessible*>(pObj), takeRef)
    {
    }

    bool contains(int x, int y, AtspiCoordType coordType) const
    {
        return invokeThrow(atspi_component_contains, x, y, coordType);
    }
    Accessible getAccessibleAtPoint(int x, int y, AtspiCoordType coordType) const
    {
        return invokeThrow(atspi_component_get_accessible_at_point, x, y, coordType);
    }
    Rect getExtents(AtspiCoordType coordType) const
    {
        return *gmem::unique_gmem(invokeThrow(atspi_component_get_extents, coordType));
    }
    Point getPosition(AtspiCoordType coordType) const
    {
        return *gmem::unique_gmem(invokeThrow(atspi_component_get_position, coordType));
    }
    Point getSize() const { return *gmem::unique_gmem(invokeThrow(atspi_component_get_size)); }
    AtspiComponentLayer getLayer() const { return invokeThrow(atspi_component_get_layer); }
    short getMdiZOrder() const { return invokeThrow(atspi_component_get_mdi_z_order); }
    bool grabFocus() const { return invokeThrow(atspi_component_grab_focus); }
    double getAlpha() const { return invokeThrow(atspi_component_get_alpha); }

#if HAVE_ATSPI2_SCROLL_TO
    bool scrollTo(AtspiScrollType scrollType) const
    {
        return invokeThrow(atspi_component_scroll_to, scrollType);
    }
    bool scrollToPoint(AtspiCoordType coordType, int x, int y) const
    {
        return invokeThrow(atspi_component_scroll_to_point, coordType, x, y);
    }
#endif // HAVE_ATSPI2_SCROLL_TO
};

/** @brief AtspiText C++ wrapper */
class Text : public Accessible
{
public:
    Text(AtspiText* pObj = nullptr, bool takeRef = true)
        : Accessible(cast<AtspiAccessible*>(pObj), takeRef)
    {
    }

    /** Wrapper for AtspiRange
     *
     * This is not actually required, but helps make this more C++-y (by allowing TextRange to
     * inherit it) and more LibreOffice-y (by having camelCase names) */
    struct Range
    {
        int startOffset;
        int endOffset;

        Range(int startOffset_, int endOffset_)
            : startOffset(startOffset_)
            , endOffset(endOffset_)
        {
        }

        Range(const AtspiRange* r)
            : startOffset(r->start_offset)
            , endOffset(r->end_offset)
        {
        }
    };

    /** Wrapper for AtspiTextRange */
    struct TextRange : Range
    {
        std::string content;

        TextRange(int startOffset_, int endOffset_, std::string_view content_)
            : Range(startOffset_, endOffset_)
            , content(content_)
        {
        }

        TextRange(const AtspiTextRange* r)
            : Range(r->start_offset, r->end_offset)
            , content(r->content)
        {
        }
    };

    int getCharacterCount() const { return invokeThrow(atspi_text_get_character_count); }
    std::string getText(int startOffset, int endOffset) const
    {
        return strInvoke(atspi_text_get_text, startOffset, endOffset);
    }
    int getCaretOffset() const { return invokeThrow(atspi_text_get_caret_offset); }

    std::unordered_map<std::string, std::string> getTextAttributes(int offset, int* startOffset,
                                                                   int* endOffset) const
    {
        return strHashMapInvoke(atspi_text_get_text_attributes, offset, startOffset, endOffset);
    }

    std::unordered_map<std::string, std::string>
    getAttributeRun(int offset, bool includeDefaults, int* startOffset, int* endOffset) const
    {
        return strHashMapInvoke(atspi_text_get_attribute_run, offset, includeDefaults, startOffset,
                                endOffset);
    }

    std::unordered_map<std::string, std::string> getDefaultAttributes() const
    {
        return strHashMapInvoke(atspi_text_get_default_attributes);
    }

    std::string getTextAttributeValue(int offset, std::string_view name) const
    {
        return strInvoke<css::container::NoSuchElementException>(
            atspi_text_get_text_attribute_value, offset, const_cast<char*>(name.data()));
    }

protected:
    /** Like @c invokeThrow() on C calls returning an @c AtspiTextRange */
    template <typename F, typename... Ts> inline TextRange invokeTextRange(F f, Ts... args) const
    {
        struct deleter
        {
            void operator()(AtspiTextRange* ptr)
            {
                g_free(ptr->content);
                g_free(ptr);
            }
        };

        std::unique_ptr<AtspiTextRange, deleter> r(invokeThrow(f, args...));
        return r.get();
    }

public:
    TextRange getStringAtOffset(int offset, AtspiTextGranularity granularity) const
    {
        return invokeTextRange(atspi_text_get_string_at_offset, offset, granularity);
    }

    /* the next 3 are deprecated, but LO doesn't implement getStringAtOffset() itself so it's a lot
     * trickier to test for */
    TextRange getTextBeforeOffset(int offset, AtspiTextBoundaryType boundary) const
    {
        return invokeTextRange(atspi_text_get_text_before_offset, offset, boundary);
    }
    TextRange getTextAtOffset(int offset, AtspiTextBoundaryType boundary) const
    {
        return invokeTextRange(atspi_text_get_text_at_offset, offset, boundary);
    }
    TextRange getTextAfterOffset(int offset, AtspiTextBoundaryType boundary) const
    {
        return invokeTextRange(atspi_text_get_text_after_offset, offset, boundary);
    }

    sal_Int32 getCharacterAtOffset(int offset) const
    {
        return invokeThrow(atspi_text_get_character_at_offset, offset);
    }

    Rect getCharacterExtents(int offset, AtspiCoordType type) const
    {
        return *gmem::unique_gmem(invokeThrow(atspi_text_get_character_extents, offset, type));
    }

    int getOffsetAtPoint(int x, int y, AtspiCoordType type) const
    {
        return invokeThrow(atspi_text_get_offset_at_point, x, y, type);
    }

    Rect getRangeExtents(int startOffset, int endOffset, AtspiCoordType type) const
    {
        return *gmem::unique_gmem(
            invokeThrow(atspi_text_get_range_extents, startOffset, endOffset, type));
    }

    // getBoundedRanges() ?

    int getNSelections() const { return invokeThrow(atspi_text_get_n_selections); }

    Range getSelection(gint selectionNum) const
    {
        return gmem::unique_gmem(invokeThrow(atspi_text_get_selection, selectionNum)).get();
    }

    bool addSelection(int startOffset, int endOffset) const
    {
        return invokeThrow(atspi_text_add_selection, startOffset, endOffset);
    }

    bool removeSelection(int selectionNum) const
    {
        return invokeThrow(atspi_text_remove_selection, selectionNum);
    }

    bool setSelection(int selectionNum, int startOffset, int endOffset) const
    {
        return invokeThrow(atspi_text_set_selection, selectionNum, startOffset, endOffset);
    }

#if HAVE_ATSPI2_SCROLL_TO
    bool scrollSubstringTo(int startOffset, int endOffset, AtspiScrollType type) const
    {
        return invokeThrow(atspi_text_scroll_substring_to, startOffset, endOffset, type);
    }

    bool scrollSubstringToPoint(int startOffset, int endOffset, AtspiCoordType coords, gint x,
                                gint y) const
    {
        return invokeThrow(atspi_text_scroll_substring_to_point, startOffset, endOffset, coords, x,
                           y);
    }
#endif // HAVE_ATSPI2_SCROLL_TO
};
}

// CppUnit integration
CPPUNIT_NS_BEGIN
template <> struct assertion_traits<AtspiRole>
{
    static bool equal(const AtspiRole a, const AtspiRole b) { return a == b; }

    static std::string toString(const AtspiRole role) { return Atspi::Role::getName(role); }
};
CPPUNIT_NS_END

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */