1# -*- Mode: Python; py-indent-offset: 4 -*-
2# vim: tabstop=4 shiftwidth=4 expandtab
3#
4# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
5#               2010 Simon van der Linden <svdlinden@src.gnome.org>
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20# USA
21
22import sys
23import warnings
24
25from ..overrides import override, strip_boolean_result
26from ..module import get_introspection_module
27from gi import PyGIDeprecationWarning, require_version
28
29Gdk = get_introspection_module('Gdk')
30GDK2 = Gdk._version == '2.0'
31GDK3 = Gdk._version == '3.0'
32
33__all__ = []
34
35
36# https://bugzilla.gnome.org/show_bug.cgi?id=673396
37try:
38    require_version("GdkX11", Gdk._version)
39    from gi.repository import GdkX11
40    GdkX11  # pyflakes
41except (ValueError, ImportError):
42    pass
43
44if GDK2 or GDK3:
45    # Gdk.Color was deprecated since 3.14 and dropped in Gtk-4.0
46    class Color(Gdk.Color):
47        MAX_VALUE = 65535
48
49        def __init__(self, red, green, blue):
50            Gdk.Color.__init__(self)
51            self.red = red
52            self.green = green
53            self.blue = blue
54
55        def __eq__(self, other):
56            return self.equal(other)
57
58        def __repr__(self):
59            return 'Gdk.Color(red=%d, green=%d, blue=%d)' % (self.red, self.green, self.blue)
60
61        red_float = property(fget=lambda self: self.red / float(self.MAX_VALUE),
62                             fset=lambda self, v: setattr(self, 'red', int(v * self.MAX_VALUE)))
63
64        green_float = property(fget=lambda self: self.green / float(self.MAX_VALUE),
65                               fset=lambda self, v: setattr(self, 'green', int(v * self.MAX_VALUE)))
66
67        blue_float = property(fget=lambda self: self.blue / float(self.MAX_VALUE),
68                              fset=lambda self, v: setattr(self, 'blue', int(v * self.MAX_VALUE)))
69
70        def to_floats(self):
71            """Return (red_float, green_float, blue_float) triple."""
72
73            return (self.red_float, self.green_float, self.blue_float)
74
75        @staticmethod
76        def from_floats(red, green, blue):
77            """Return a new Color object from red/green/blue values from 0.0 to 1.0."""
78
79            return Color(int(red * Color.MAX_VALUE),
80                         int(green * Color.MAX_VALUE),
81                         int(blue * Color.MAX_VALUE))
82
83    Color = override(Color)
84    __all__.append('Color')
85
86if GDK3:
87    # Introduced since Gtk-3.0
88    class RGBA(Gdk.RGBA):
89        def __init__(self, red=1.0, green=1.0, blue=1.0, alpha=1.0):
90            Gdk.RGBA.__init__(self)
91            self.red = red
92            self.green = green
93            self.blue = blue
94            self.alpha = alpha
95
96        def __eq__(self, other):
97            return self.equal(other)
98
99        def __repr__(self):
100            return 'Gdk.RGBA(red=%f, green=%f, blue=%f, alpha=%f)' % (self.red, self.green, self.blue, self.alpha)
101
102        def __iter__(self):
103            """Iterator which allows easy conversion to tuple and list types."""
104
105            yield self.red
106            yield self.green
107            yield self.blue
108            yield self.alpha
109
110        def to_color(self):
111            """Converts this RGBA into a Color instance which excludes alpha."""
112
113            return Color(int(self.red * Color.MAX_VALUE),
114                         int(self.green * Color.MAX_VALUE),
115                         int(self.blue * Color.MAX_VALUE))
116
117        @classmethod
118        def from_color(cls, color):
119            """Returns a new RGBA instance given a Color instance."""
120
121            return cls(color.red_float, color.green_float, color.blue_float)
122
123    RGBA = override(RGBA)
124    __all__.append('RGBA')
125
126if GDK2:
127    class Rectangle(Gdk.Rectangle):
128
129        def __init__(self, x, y, width, height):
130            Gdk.Rectangle.__init__(self)
131            self.x = x
132            self.y = y
133            self.width = width
134            self.height = height
135
136        def __repr__(self):
137            return 'Gdk.Rectangle(x=%d, y=%d, width=%d, height=%d)' % (self.x, self.y, self.height, self.width)
138
139    Rectangle = override(Rectangle)
140    __all__.append('Rectangle')
141elif GDK3:
142    # Newer GTK/gobject-introspection (3.17.x) include GdkRectangle in the
143    # typelib. See https://bugzilla.gnome.org/show_bug.cgi?id=748832 and
144    # https://bugzilla.gnome.org/show_bug.cgi?id=748833
145    if not hasattr(Gdk, 'Rectangle'):
146        from gi.repository import cairo as _cairo
147        Rectangle = _cairo.RectangleInt
148
149        __all__.append('Rectangle')
150    else:
151        # https://bugzilla.gnome.org/show_bug.cgi?id=756364
152        # These methods used to be functions, keep aliases for backwards compat
153        rectangle_intersect = Gdk.Rectangle.intersect
154        rectangle_union = Gdk.Rectangle.union
155
156        __all__.append('rectangle_intersect')
157        __all__.append('rectangle_union')
158
159if GDK2:
160    class Drawable(Gdk.Drawable):
161        def cairo_create(self):
162            return Gdk.cairo_create(self)
163
164    Drawable = override(Drawable)
165    __all__.append('Drawable')
166elif GDK3:
167    class Window(Gdk.Window):
168        def __new__(cls, parent, attributes, attributes_mask):
169            # Gdk.Window had to be made abstract,
170            # this override allows using the standard constructor
171            return Gdk.Window.new(parent, attributes, attributes_mask)
172
173        def __init__(self, parent, attributes, attributes_mask):
174            pass
175
176        def cairo_create(self):
177            return Gdk.cairo_create(self)
178
179    Window = override(Window)
180    __all__.append('Window')
181
182if GDK2 or GDK3:
183    Gdk.EventType._2BUTTON_PRESS = getattr(Gdk.EventType, "2BUTTON_PRESS")
184    Gdk.EventType._3BUTTON_PRESS = getattr(Gdk.EventType, "3BUTTON_PRESS")
185
186    class Event(Gdk.Event):
187        _UNION_MEMBERS = {
188            Gdk.EventType.DELETE: 'any',
189            Gdk.EventType.DESTROY: 'any',
190            Gdk.EventType.MOTION_NOTIFY: 'motion',
191            Gdk.EventType.BUTTON_PRESS: 'button',
192            Gdk.EventType.BUTTON_RELEASE: 'button',
193            Gdk.EventType.KEY_PRESS: 'key',
194            Gdk.EventType.KEY_RELEASE: 'key',
195            Gdk.EventType.ENTER_NOTIFY: 'crossing',
196            Gdk.EventType.LEAVE_NOTIFY: 'crossing',
197            Gdk.EventType.FOCUS_CHANGE: 'focus_change',
198            Gdk.EventType.CONFIGURE: 'configure',
199            Gdk.EventType.PROXIMITY_IN: 'proximity',
200            Gdk.EventType.PROXIMITY_OUT: 'proximity',
201            Gdk.EventType.DRAG_ENTER: 'dnd',
202            Gdk.EventType.DRAG_LEAVE: 'dnd',
203            Gdk.EventType.DRAG_MOTION: 'dnd',
204            Gdk.EventType.DROP_START: 'dnd',
205            Gdk.EventType._2BUTTON_PRESS: 'button',
206            Gdk.EventType._3BUTTON_PRESS: 'button',
207            Gdk.EventType.PROPERTY_NOTIFY: 'property',
208            Gdk.EventType.SELECTION_CLEAR: 'selection',
209            Gdk.EventType.SELECTION_REQUEST: 'selection',
210            Gdk.EventType.SELECTION_NOTIFY: 'selection',
211            Gdk.EventType.DRAG_STATUS: 'dnd',
212            Gdk.EventType.DROP_FINISHED: 'dnd',
213            Gdk.EventType.CLIENT_EVENT: 'client',
214            Gdk.EventType.VISIBILITY_NOTIFY: 'visibility',
215            Gdk.EventType.SCROLL: 'scroll',
216            Gdk.EventType.EXPOSE: 'expose',
217            Gdk.EventType.MAP: 'any',
218            Gdk.EventType.UNMAP: 'any',
219        }
220
221        if GDK2:
222            _UNION_MEMBERS[Gdk.EventType.NO_EXPOSE] = 'no_expose'
223
224        if hasattr(Gdk.EventType, 'TOUCH_BEGIN'):
225            _UNION_MEMBERS.update(
226                {
227                    Gdk.EventType.TOUCH_BEGIN: 'touch',
228                    Gdk.EventType.TOUCH_UPDATE: 'touch',
229                    Gdk.EventType.TOUCH_END: 'touch',
230                    Gdk.EventType.TOUCH_CANCEL: 'touch',
231                })
232
233        def __getattr__(self, name):
234            real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
235            if real_event:
236                return getattr(getattr(self, real_event), name)
237            else:
238                raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
239
240        def __setattr__(self, name, value):
241            real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
242            if real_event:
243                setattr(getattr(self, real_event), name, value)
244            else:
245                Gdk.Event.__setattr__(self, name, value)
246
247        def __repr__(self):
248            base_repr = Gdk.Event.__repr__(self).strip("><")
249            return "<%s type=%r>" % (base_repr, self.type)
250
251    Event = override(Event)
252    __all__.append('Event')
253
254    # manually bind GdkEvent members to GdkEvent
255
256    modname = globals()['__name__']
257    module = sys.modules[modname]
258
259    # right now we can't get the type_info from the
260    # field info so manually list the class names
261    event_member_classes = ['EventAny',
262                            'EventExpose',
263                            'EventMotion',
264                            'EventButton',
265                            'EventScroll',
266                            'EventKey',
267                            'EventCrossing',
268                            'EventFocus',
269                            'EventConfigure',
270                            'EventProximity',
271                            'EventDND',
272                            'EventSetting',
273                            'EventGrabBroken',
274                            'EventVisibility',
275                            'EventProperty',
276                            'EventSelection',
277                            'EventOwnerChange',
278                            'EventWindowState',
279                            'EventVisibility']
280
281    if Gdk._version == '2.0':
282        event_member_classes.append('EventNoExpose')
283
284    if hasattr(Gdk, 'EventTouch'):
285        event_member_classes.append('EventTouch')
286
287    # whitelist all methods that have a success return we want to mask
288    gsuccess_mask_funcs = ['get_state',
289                           'get_axis',
290                           'get_coords',
291                           'get_root_coords']
292
293    for event_class in event_member_classes:
294        override_class = type(event_class, (getattr(Gdk, event_class),), {})
295        # add the event methods
296        for method_info in Gdk.Event.__info__.get_methods():
297            name = method_info.get_name()
298            event_method = getattr(Gdk.Event, name)
299
300            # use the _gsuccess_mask decorator if this method is whitelisted
301            if name in gsuccess_mask_funcs:
302                event_method = strip_boolean_result(event_method)
303            setattr(override_class, name, event_method)
304
305        setattr(module, event_class, override_class)
306        __all__.append(event_class)
307
308    # end GdkEvent overrides
309
310    class DragContext(Gdk.DragContext):
311        def finish(self, success, del_, time):
312            Gtk = get_introspection_module('Gtk')
313            Gtk.drag_finish(self, success, del_, time)
314
315    DragContext = override(DragContext)
316    __all__.append('DragContext')
317
318    class Cursor(Gdk.Cursor):
319
320        def __new__(cls, *args, **kwds):
321            arg_len = len(args)
322            kwd_len = len(kwds)
323            total_len = arg_len + kwd_len
324
325            if total_len == 1:
326                # Since g_object_newv (super.__new__) does not seem valid for
327                # direct use with GdkCursor, we must assume usage of at least
328                # one of the C constructors to be valid.
329                return cls.new(*args, **kwds)
330
331            elif total_len == 2:
332                warnings.warn('Calling "Gdk.Cursor(display, cursor_type)" has been deprecated. '
333                              'Please use Gdk.Cursor.new_for_display(display, cursor_type). '
334                              'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
335                              PyGIDeprecationWarning)
336                return cls.new_for_display(*args, **kwds)
337
338            elif total_len == 4:
339                warnings.warn('Calling "Gdk.Cursor(display, pixbuf, x, y)" has been deprecated. '
340                              'Please use Gdk.Cursor.new_from_pixbuf(display, pixbuf, x, y). '
341                              'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
342                              PyGIDeprecationWarning)
343                return cls.new_from_pixbuf(*args, **kwds)
344
345            elif total_len == 6:
346                if not GDK2:
347                    # pixmaps don't exist in Gdk 3.0
348                    raise ValueError("Wrong number of parameters")
349
350                warnings.warn('Calling "Gdk.Cursor(source, mask, fg, bg, x, y)" has been deprecated. '
351                              'Please use Gdk.Cursor.new_from_pixmap(source, mask, fg, bg, x, y). '
352                              'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
353                              PyGIDeprecationWarning)
354                return cls.new_from_pixmap(*args, **kwds)
355
356            else:
357                raise ValueError("Wrong number of parameters")
358
359    Cursor = override(Cursor)
360    __all__.append('Cursor')
361
362    # Gdk.Color was deprecated since 3.14 and dropped in Gtk-4.0
363    color_parse = strip_boolean_result(Gdk.color_parse)
364    __all__.append('color_parse')
365
366    # Note, we cannot override the entire class as Gdk.Atom has no gtype, so just
367    # hack some individual methods
368    def _gdk_atom_str(atom):
369        n = atom.name()
370        if n:
371            return n
372        # fall back to atom index
373        return 'Gdk.Atom<%i>' % hash(atom)
374
375    def _gdk_atom_repr(atom):
376        n = atom.name()
377        if n:
378            return 'Gdk.Atom.intern("%s", False)' % n
379        # fall back to atom index
380        return '<Gdk.Atom(%i)>' % hash(atom)
381
382    Gdk.Atom.__str__ = _gdk_atom_str
383    Gdk.Atom.__repr__ = _gdk_atom_repr
384
385
386# constants
387if GDK3:
388    SELECTION_PRIMARY = Gdk.atom_intern('PRIMARY', True)
389    __all__.append('SELECTION_PRIMARY')
390
391    SELECTION_SECONDARY = Gdk.atom_intern('SECONDARY', True)
392    __all__.append('SELECTION_SECONDARY')
393
394    SELECTION_CLIPBOARD = Gdk.atom_intern('CLIPBOARD', True)
395    __all__.append('SELECTION_CLIPBOARD')
396
397    TARGET_BITMAP = Gdk.atom_intern('BITMAP', True)
398    __all__.append('TARGET_BITMAP')
399
400    TARGET_COLORMAP = Gdk.atom_intern('COLORMAP', True)
401    __all__.append('TARGET_COLORMAP')
402
403    TARGET_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
404    __all__.append('TARGET_DRAWABLE')
405
406    TARGET_PIXMAP = Gdk.atom_intern('PIXMAP', True)
407    __all__.append('TARGET_PIXMAP')
408
409    TARGET_STRING = Gdk.atom_intern('STRING', True)
410    __all__.append('TARGET_STRING')
411
412    SELECTION_TYPE_ATOM = Gdk.atom_intern('ATOM', True)
413    __all__.append('SELECTION_TYPE_ATOM')
414
415    SELECTION_TYPE_BITMAP = Gdk.atom_intern('BITMAP', True)
416    __all__.append('SELECTION_TYPE_BITMAP')
417
418    SELECTION_TYPE_COLORMAP = Gdk.atom_intern('COLORMAP', True)
419    __all__.append('SELECTION_TYPE_COLORMAP')
420
421    SELECTION_TYPE_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
422    __all__.append('SELECTION_TYPE_DRAWABLE')
423
424    SELECTION_TYPE_INTEGER = Gdk.atom_intern('INTEGER', True)
425    __all__.append('SELECTION_TYPE_INTEGER')
426
427    SELECTION_TYPE_PIXMAP = Gdk.atom_intern('PIXMAP', True)
428    __all__.append('SELECTION_TYPE_PIXMAP')
429
430    SELECTION_TYPE_WINDOW = Gdk.atom_intern('WINDOW', True)
431    __all__.append('SELECTION_TYPE_WINDOW')
432
433    SELECTION_TYPE_STRING = Gdk.atom_intern('STRING', True)
434    __all__.append('SELECTION_TYPE_STRING')
435
436if GDK2 or GDK3:
437    import sys
438    initialized, argv = Gdk.init_check(sys.argv)
439