summaryrefslogtreecommitdiff
path: root/efl/eo/efl.eo.pyx
blob: cc0c60ac9baee7686234fcbc7a4f68ee589b9c10 (plain) (blame)
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
# Copyright (C) 2007-2016 various contributors (see AUTHORS)
#
# This file is part of Python-EFL.
#
# Python-EFL is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# Python-EFL is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this Python-EFL.  If not, see <http://www.gnu.org/licenses/>.

"""

:mod:`efl.eo` Module
####################

Classes
=======

.. toctree::

   class-eo.rst

"""

from cpython cimport PyObject, Py_INCREF, Py_DECREF, PyUnicode_AsUTF8String

from libc.stdint cimport uintptr_t
from efl.eina cimport Eina_Bool, \
    Eina_Hash, eina_hash_string_superfast_new, eina_hash_add, eina_hash_del, \
    eina_hash_find, EINA_LOG_DOM_DBG, EINA_LOG_DOM_INFO, \
    Eina_Iterator, eina_iterator_next, eina_iterator_free
from efl.c_eo cimport Eo as cEo, efl_object_init, efl_object_shutdown, efl_del, \
    efl_class_name_get, efl_class_get, efl_object_class_get,\
    efl_key_data_set, efl_key_data_get, \
    efl_event_callback_add, efl_event_callback_del, EFL_EVENT_DEL, \
    efl_parent_get, efl_parent_set, Efl_Event_Description, \
    efl_event_freeze, efl_event_thaw, efl_event_freeze_count_get, \
    efl_event_global_freeze, efl_event_global_thaw, \
    efl_event_global_freeze_count_get, efl_event_callback_stop, \
    efl_children_iterator_new, Efl_Event

from efl.utils.logger cimport add_logger

# Set this to public and export it in pxd if you need it in another module
cdef int PY_EFL_EO_LOG_DOMAIN = add_logger(__name__).eina_log_domain

cdef int PY_REFCOUNT(object o):
    cdef PyObject *obj = <PyObject *>o
    return obj.ob_refcnt

import atexit

######################################################################

def init():
    EINA_LOG_DOM_INFO(PY_EFL_EO_LOG_DOMAIN, "Initializing efl.eo")
    return efl_object_init()

def shutdown():
    EINA_LOG_DOM_INFO(PY_EFL_EO_LOG_DOMAIN, "Shutting down efl.eo")
    return efl_object_shutdown()

init()
atexit.register(shutdown)

def event_global_freeze_count_get():
    return efl_event_global_freeze_count_get(<const cEo *>efl_object_class_get())

def event_global_freeze():
    efl_event_global_freeze(<const cEo *>efl_object_class_get())

def event_global_thaw():
    efl_event_global_thaw(<const cEo *>efl_object_class_get())

######################################################################

"""

Object mapping is an Eina Hash table into which object type names can be
registered. These can be used to find a bindings class for an object using
the function object_from_instance.

"""
cdef Eina_Hash *object_mapping = eina_hash_string_superfast_new(NULL)


cdef void _object_mapping_register(char *name, object cls) except *:

    if eina_hash_find(object_mapping, name) != NULL:
        raise ValueError("Object type name '%s' already registered." % name)

    cdef object cls_name = cls.__name__
    if isinstance(cls_name, unicode): cls_name = PyUnicode_AsUTF8String(cls_name)

    EINA_LOG_DOM_DBG(PY_EFL_EO_LOG_DOMAIN,
        "REGISTER: %s => %s", <char *>name, <char *>cls_name)
    eina_hash_add(object_mapping, name, <PyObject *>cls)


cdef void _object_mapping_unregister(char *name):
    eina_hash_del(object_mapping, name, NULL)


cdef api object object_from_instance(cEo *obj):
    """ Create a python object from a C Eo object pointer. """
    cdef:
        void *data = NULL
        Eo o
        const char *cls_name = efl_class_name_get(efl_class_get(obj))
        type cls
        void *cls_ret

    if obj == NULL:
        return None

    data = efl_key_data_get(obj, "python-eo")
    if data != NULL:
        EINA_LOG_DOM_DBG(PY_EFL_EO_LOG_DOMAIN,
            "Returning a Python object instance for Eo of type %s.", cls_name)
        return <Eo>data

    if cls_name == NULL:
        raise ValueError(
            "Eo object at %#x does not have a type!" % <uintptr_t>obj)

    cls_ret = eina_hash_find(object_mapping, cls_name)

    if cls_ret == NULL:
        # TODO: Add here a last ditch effort to import the class from a module
        raise ValueError(
            "Eo object at %#x of type %s does not have a mapping!" % (
                <uintptr_t>obj, cls_name)
            )

    cls = <type>cls_ret

    if cls is None:
        raise ValueError(
            "Mapping for Eo object at %#x, type %s, contains None!" % (
                <uintptr_t>obj, cls_name))

    EINA_LOG_DOM_DBG(PY_EFL_EO_LOG_DOMAIN,
        "Constructing a Python object from Eo of type %s.", cls_name)

    o = cls.__new__(cls)
    o._set_obj(obj)
    return o

cdef api cEo *instance_from_object(object obj):
    cdef Eo o = obj
    return o.obj


cdef void _register_decorated_callbacks(Eo obj):
    """

    Search every attrib of the pyobj for a __decorated_callbacks__ object,
    a list actually. If found then exec the functions listed there, with their
    arguments. Must be called just after the _set_obj call.
    List items signature: ("function_name", *args)

    """
    cdef object attr_name, attrib, func_name, func
    cdef type cls = type(obj)

    # FIXME: This whole thing is really slow. Can we do it better?

    for attr_name, attrib in cls.__dict__.items():
        if "__decorated_callbacks__" in dir(attrib):
            for (func_name, *args) in getattr(attrib, "__decorated_callbacks__"):
                func = getattr(obj, func_name)
                func(*args)


######################################################################


cdef void _efl_event_del_cb(void *data, const Efl_Event *event) with gil:
    cdef:
        Eo self = <Eo>data
        const char *cls_name = efl_class_name_get(efl_class_get(self.obj))

    EINA_LOG_DOM_DBG(PY_EFL_EO_LOG_DOMAIN, "Deleting Eo: %s", cls_name)

    # This callback_stop call cause lots of warning in lots of places, mainy
    # visible in genlist/gengrid scrolling, seems this stop evas del event
    # to be emitted...didn't find the root cause, so comment out for the moment.
    # efl_event_callback_stop(self.obj)
    efl_event_callback_del(self.obj, EFL_EVENT_DEL, _efl_event_del_cb, <const void *>self)
    efl_key_data_set(self.obj, "python-eo", NULL)
    self.obj = NULL
    Py_DECREF(self)


cdef class EoIterator:

    def __iter__(self):
        return self

    def __next__(self):
        cdef:
            void* tmp
            Eina_Bool result

        if not eina_iterator_next(self.itr, &tmp):
            raise StopIteration

        return object_from_instance(<cEo *>tmp)

    def __dealloc__(self):
        eina_iterator_free(self.itr)


cdef class Eo(object):
    """

    Base class used by all the object in the EFL.

    """

    # c globals declared in eo.pxd (to make the class available to others)

    def __cinit__(self):
        self.data = dict()
        self.internal_data = dict()

    def __init__(self, *args, **kwargs):
        if type(self) is Eo:
            raise TypeError("Must not instantiate Eo, but subclasses")

    def __repr__(self):
        cdef cEo *parent = NULL
        if self.obj != NULL:
            parent = efl_parent_get(self.obj)
        return ("<%s object (Eo) at %#x (obj=%#x, parent=%#x, refcount=%d)>") % (
            type(self).__name__,
            <uintptr_t><void *>self,
            <uintptr_t>self.obj,
            <uintptr_t>parent,
            PY_REFCOUNT(self))

    def __nonzero__(self):
        return 1 if self.obj != NULL else 0

    cdef int _set_obj(self, cEo *obj) except 0:
        assert self.obj == NULL, "Object must be clean"
        assert obj != NULL, "Cannot set a NULL object"

        self.obj = obj
        efl_key_data_set(self.obj, "python-eo", <void *>self)
        efl_event_callback_add(self.obj, EFL_EVENT_DEL, _efl_event_del_cb, <const void *>self)
        Py_INCREF(self)

        # from efl 1.18 eo.parent changed behaviour, objects are now reparented
        # when, fe, swallowed. This is the hack to keep the old behavior.
        try:
            parent = object_from_instance(efl_parent_get(obj))
        except ValueError:
            parent = None
        self.internal_data["_legacy_parent"] = parent

        return 1

    def _wipe_obj_data_NEVER_USE_THIS(self):
        # only used in tests/eo/test_02_class_names.py
        # to force object_from_instance() to recreate the obj
        efl_key_data_set(self.obj, "python-eo", NULL)

    cdef int _set_properties_from_keyword_args(self, dict kwargs) except 0:
        if kwargs:
            for k, v in kwargs.items():
                setattr(self, k, v)
        return 1

    def __iter__(self):
        return EoIterator.create(efl_children_iterator_new(self.obj))

    def delete(self):
        """Delete the object and free internal resources.

        .. note:: This will not delete the python object, but only the internal
            C one. The python object will be automatically deleted by the
            garbage collector when there are no more reference to it.

        """
        efl_del(self.obj)

    def is_deleted(self):
        """Check if the object has been deleted thus leaving the object shallow.

        :return: True if the object has been deleted yet, False otherwise.
        :rtype: bool

        """
        return bool(self.obj == NULL)

    property parent:
        """The parent object

        :type: :class:`Eo`

        """
        def __set__(self, Eo parent):
            self.internal_data["_legacy_parent"] = parent
            efl_parent_set(self.obj, parent.obj)

        def __get__(self):
            return self.internal_data["_legacy_parent"]

    def parent_set(self, Eo parent):
        self.internal_data["_legacy_parent"] = parent
        efl_parent_set(self.obj, parent.obj)

    def parent_get(self):
        return self.internal_data["_legacy_parent"]

    def event_freeze(self):
        """Pause event propagation for this object."""
        efl_event_freeze(self.obj)

    def event_thaw(self):
        """Restart event propagation for this object."""
        efl_event_thaw(self.obj)

    def event_freeze_count_get(self):
        """Get the event freeze count for this object.

        :return: the freeze count
        :rtype: int

        """
        return efl_event_freeze_count_get(self.obj)