1# Copyright (C) 2010 by Brian Parma
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 1, or (at your option)
6# any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16
17from gi.repository import Gdk
18from gi.repository import Gtk
19from gi.repository import GObject
20
21
22class CellRendererDays(Gtk.CellRendererText):
23    '''Custom Cell Renderer for showing a ListView of 7 days with checkboxes, based off pygtk FAQ example'''
24
25    __gtype_name__ = 'CellRendererDays'
26    __gproperties__ = {
27        'days': (object, 'days', 'List of enabled days', GObject.ParamFlags.READWRITE)
28    }
29    __gsignals__ = {
30        'days-changed': (GObject.SignalFlags.RUN_FIRST, None, (str, object))
31    }
32    property_names = list(__gproperties__.keys())
33
34    def __init__(self):
35        Gtk.CellRendererText.__init__(self)
36        self.model = Gtk.ListStore(bool, str)
37        self.view = None
38        self.view_window = None
39
40        for day in [
41            'Sunday',
42            'Monday',
43            'Tuesday',
44            'Wednesday',
45            'Thursday',
46            'Friday',
47            'Saturday',
48        ]:
49            self.model.append([True, day])
50
51        self.set_property('text', 'Edit me')
52
53    def _create_view(self, treeview):
54        '''Create the Window and View to display when editing'''
55        self.view_window = Gtk.Window()
56        self.view_window.set_decorated(False)
57        self.view_window.set_property('skip-taskbar-hint', True)
58
59        self.view = Gtk.TreeView()
60
61        self.view.set_model(self.model)
62        self.view.set_headers_visible(False)
63
64        cr = Gtk.CellRendererToggle()
65        cr.connect('toggled', self._toggle)
66        col = Gtk.TreeViewColumn('Enabled', cr, active=0)
67        self.view.append_column(col)
68
69        cr = Gtk.CellRendererText()
70        col = Gtk.TreeViewColumn('Day', cr, text=1)
71        self.view.append_column(col)
72
73        # events
74        self.view.connect('focus-out-event', self._close)
75        self.view.connect('key-press-event', self._key_pressed)
76
77        # should be automatic
78        self.view_window.set_modal(False)
79        self.view_window.set_transient_for(None)  # cancel the modality of dialog
80        self.view_window.add(self.view)
81
82        # necessary for getting the (width, height) of calendar_window
83        self.view.show()
84        self.view_window.realize()
85
86    def do_set_property(self, pspec, value):
87        '''Set property overload'''
88        setattr(self, pspec.name, value)
89
90    def do_get_property(self, pspec):
91        '''Get property overload'''
92        return getattr(self, pspec.name)
93
94    def do_start_editing(
95        self, event, treeview, path, background_area, cell_area, flags
96    ):
97        '''Called when user starts editing the cell'''
98
99        if not self.get_property('editable'):
100            return
101
102        # create window/view if it doesn't exist
103        if not self.view_window:
104            self._create_view(treeview)
105        else:
106            self.view_window.show()
107
108        # set display to reflect 'days' property
109        for i, row in enumerate(self.model):
110            row[0] = self.days[i]
111
112        # position the popup below the edited cell (and try hard to keep the popup within the toplevel window)
113        (tree_x, tree_y) = treeview.get_bin_window().get_origin()[1:]
114        (tree_w, tree_h) = treeview.get_window().get_geometry()[2:4]
115        (my_w, my_h) = self.view_window.get_window().get_geometry()[2:4]
116        x = tree_x + min(cell_area.x, tree_w - my_w + treeview.get_visible_rect().x)
117        y = tree_y + min(cell_area.y, tree_h - my_h + treeview.get_visible_rect().y)
118        self.view_window.move(x, y)
119
120        # save the path so we can return it in _done, and we aren't using dialog so we can't block....
121        self._path = path
122
123        return None  # don't return any editable, our Gtk.Dialog did the work already
124
125    def _done(self):
126        '''Called when we are done editing'''
127        days = [row[0] for row in self.model]
128
129        if days != self.days:
130            self.emit('days-changed', self._path, days)
131
132        self.view_window.hide()
133
134    def _key_pressed(self, view, event):
135        '''Key pressed event handler, finish editing on Return'''
136        # event == None for day selected via doubleclick
137        if (
138            not event
139            or event.type == Gdk.EventType.KEY_PRESS
140            and Gdk.keyval_name(event.keyval) == 'Return'
141        ):
142            self._done()
143            return True
144
145    def _toggle(self, cell, path):
146        '''Checkbox toggle event handler'''
147        active = self.model[path][0]
148        self.model[path][0] = not active
149        return True
150
151    def _close(self, view, event):
152        '''Focus-out-event handler'''
153        self._done()
154        return True
155