1from caribou.settings.preferences_window import PreferencesDialog
2from caribou.settings import CaribouSettings
3from .antler_settings import AntlerSettings
4from gi.repository import Gtk
5from gi.repository import Gdk
6from gi.repository import GObject
7from gi.repository import GLib
8from gi.repository import Caribou
9import os
10from math import ceil
11
12class AntlerKey(Gtk.Button):
13    def __init__(self, key, spacing=0):
14        GObject.GObject.__init__(self)
15        self.caribou_key = key.weak_ref()
16        self.set_label(self._get_key_label())
17        self._spacing = spacing
18
19        label = self.get_child()
20        label.set_use_markup(True)
21        label.props.margin = 6
22
23        ctx = self.get_style_context()
24        ctx.add_class("antler-keyboard-button")
25
26        if key.get_extended_keys ():
27            self._sublevel = AntlerSubLevel(self)
28
29        key.connect('key-pressed', self._caribou_key_pressed)
30        key.connect('key-released', self._caribou_key_released)
31
32    def set_dwell_scan(self, dwell):
33        if dwell:
34            self.set_state_flags(Gtk.StateFlags.SELECTED, False)
35        else:
36            self.unset_state_flags(Gtk.StateFlags.SELECTED)
37
38    def set_group_scan_active(self, active):
39        if active:
40            self.set_state_flags(Gtk.StateFlags.INCONSISTENT, False)
41        else:
42            self.unset_state_flags(Gtk.StateFlags.INCONSISTENT)
43
44    def _get_key_label(self):
45        label = self.caribou_key().props.label
46        return "<b>%s</b>" % GLib.markup_escape_text(label)
47
48    def _caribou_key_pressed (self, key, _key):
49        self.set_state_flags(Gtk.StateFlags.ACTIVE, False)
50
51    def _caribou_key_released (self, key, _key):
52        self.unset_state_flags(Gtk.StateFlags.ACTIVE)
53
54    def _press_caribou_key(self):
55        if self.caribou_key():
56            self.caribou_key().press()
57
58    def _release_caribou_key(self):
59        if self.caribou_key():
60            self.caribou_key().release()
61
62    def do_get_preferred_width(self):
63        w = self.caribou_key().props.width
64        h, _ = self.get_preferred_height()
65        width = int(h * w + ceil(w - 1) * self._spacing)
66        return (width, width)
67
68    def do_pressed(self):
69        self._press_caribou_key()
70
71    def do_released(self):
72        self._release_caribou_key()
73
74    def do_enter(self):
75        self.set_state_flags(Gtk.StateFlags.PRELIGHT, False)
76
77    def do_leave(self):
78        self.unset_state_flags(Gtk.StateFlags.PRELIGHT)
79
80class AntlerSubLevel(Gtk.Window):
81    def __init__(self, key):
82        GObject.GObject.__init__(self, type=Gtk.WindowType.POPUP)
83
84        self.set_decorated(False)
85        self.set_resizable(False)
86        self.set_accept_focus(False)
87        self.set_position(Gtk.WindowPosition.MOUSE)
88        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
89
90        ctx = self.get_style_context()
91        ctx.add_class("antler-keyboard-window")
92
93        key.caribou_key().connect("notify::show-subkeys", self._on_show_subkeys)
94        self._key = key
95
96        layout = AntlerLayout()
97        layout.add_row([key.caribou_key().get_extended_keys()])
98        self.add(layout)
99
100    def _on_show_subkeys(self, key, prop):
101        parent = self._key.get_toplevel()
102        if key.props.show_subkeys:
103            self.set_transient_for(parent)
104            parent.set_sensitive(False)
105            self.show_all()
106        else:
107            parent.set_sensitive(True)
108            self.hide()
109
110class AntlerLayout(Gtk.Box):
111    KEY_SPAN = 4
112
113    def __init__(self, level=None, spacing=6):
114        GObject.GObject.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
115        self.set_spacing(12)
116        self._columns = []
117        self._keys_map = {}
118        self._active_scan_group = []
119        self._dwelling_scan_group = []
120        self._spacing = spacing
121
122        ctx = self.get_style_context()
123        ctx.add_class("antler-keyboard-layout")
124
125        if level:
126            self.load_rows(level.get_rows ())
127            level.connect("selected-item-changed", self._on_active_group_changed)
128            level.connect("step-item-changed", self._on_dwelling_group_changed)
129            level.connect("scan-cleared", self._on_scan_cleared)
130
131    def add_column (self):
132        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
133        box.set_homogeneous(True)
134        box.set_spacing(self._spacing)
135        self.pack_start (box, True, True, 0)
136        self._columns.append(box)
137        return box
138
139    def _on_scan_cleared (self, level):
140        self._foreach_key(self._active_scan_group,
141                          lambda x: x.set_group_scan_active (False))
142
143        self._active_scan_group = []
144
145        self._foreach_key(self._dwelling_scan_group,
146                          lambda x: x.set_dwell_scan (False))
147
148        self._dwelling_scan_group = []
149
150    def _on_active_group_changed(self, level, active_item):
151        self._foreach_key(self._active_scan_group,
152                          lambda x: x.set_group_scan_active (False))
153
154        if isinstance(active_item, Caribou.KeyModel):
155            self._active_scan_group = [active_item]
156        else:
157            self._active_scan_group = active_item.get_keys()
158
159        self._foreach_key(self._active_scan_group,
160                          lambda x: x.set_group_scan_active (True))
161
162    def _on_dwelling_group_changed(self, level, dwell_item):
163        self._foreach_key(self._dwelling_scan_group,
164                          lambda x: x.set_dwell_scan (False))
165
166        if isinstance(dwell_item, Caribou.KeyModel):
167            self._dwelling_scan_group = [dwell_item]
168        else:
169            self._dwelling_scan_group = dwell_item.get_keys()
170
171        self._foreach_key(self._dwelling_scan_group,
172                          lambda x: x.set_dwell_scan (True))
173
174    def _foreach_key(self, keys, cb):
175        for key in keys:
176            try:
177                cb(self._keys_map[key])
178            except KeyError:
179                continue
180
181    def add_row(self, row, row_num=0):
182        x = 0
183        for c, col in enumerate(row):
184            try:
185                column = self._columns[c]
186            except IndexError:
187                column = self.add_column()
188
189            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
190            column.pack_start(box, True, True, 0)
191
192            alignboxes = {}
193
194            for i, key in enumerate(col):
195                align = key.props.align
196                if align not in alignboxes:
197                    alignbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
198                    alignbox.set_spacing(self._spacing)
199                    alignboxes[align] = alignbox
200                    if align == "left":
201                        box.pack_start(alignbox, False, False, 0)
202                    elif align == "center":
203                        box.pack_start(alignbox, True, False, 0)
204                    elif align == "right":
205                        box.pack_end(alignbox, False, False, 0)
206                else:
207                    alignbox = alignboxes[align]
208
209                antler_key = AntlerKey(key, self._spacing)
210                self._keys_map[key] = antler_key
211                alignbox.pack_start (antler_key, True, True, 0);
212
213    def load_rows(self, rows):
214        for row_num, row in enumerate(rows):
215            self.add_row([c.get_children() for c in row.get_columns()], row_num)
216
217class AntlerKeyboardView(Gtk.Notebook):
218    def __init__(self, keyboard_type='touch', keyboard_file=None,
219                 keyboard_level=None):
220        GObject.GObject.__init__(self)
221        settings = AntlerSettings()
222        self.set_show_tabs(False)
223
224        ctx = self.get_style_context()
225        ctx.add_class("antler-keyboard-window")
226
227        use_system = settings.use_system
228        use_system.connect("value-changed", self._on_use_system_theme_changed)
229
230        self._app_css_provider = Gtk.CssProvider()
231        self._load_style(
232            self._app_css_provider, "style.css",
233            [GLib.get_user_data_dir()] + list(GLib.get_system_data_dirs()))
234
235        if not use_system.value:
236            Gtk.StyleContext.add_provider_for_screen(
237                Gdk.Screen.get_default(), self._app_css_provider,
238                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
239
240        self._user_css_provider = Gtk.CssProvider()
241        self._load_style(self._user_css_provider, "user-style.css",
242                         [GLib.get_user_data_dir()])
243        Gtk.StyleContext.add_provider_for_screen(
244                Gdk.Screen.get_default(), self._user_css_provider,
245                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1)
246
247        self.scanner = Caribou.Scanner()
248        self.set_keyboard_model(keyboard_type, keyboard_file, keyboard_level)
249
250    def set_keyboard_model(self, keyboard_type, keyboard_file, keyboard_level):
251        self.keyboard_model = Caribou.KeyboardModel(keyboard_type=keyboard_type,
252                                                    keyboard_file=keyboard_file)
253
254        self.scanner.set_keyboard(self.keyboard_model)
255        self.keyboard_model.connect("notify::active-group", self._on_group_changed)
256        self.keyboard_model.connect("key-clicked", self._on_key_clicked)
257
258        self.layers = {}
259
260        for gname in self.keyboard_model.get_groups():
261            group = self.keyboard_model.get_group(gname)
262            self.layers[gname] = {}
263            group.connect("notify::active-level", self._on_level_changed)
264            for lname in group.get_levels():
265                level = group.get_level(lname)
266                layout = AntlerLayout(level)
267                layout.show()
268                self.layers[gname][lname] = self.append_page(layout, None)
269
270        self._set_to_active_layer(keyboard_level=keyboard_level)
271
272    def _on_key_clicked(self, model, key):
273        if key.props.name == "Caribou_Prefs":
274            p = PreferencesDialog(AntlerSettings())
275            p.populate_settings(CaribouSettings())
276            p.show_all()
277            p.run()
278            p.destroy()
279
280    def _on_use_system_theme_changed(self, setting, value):
281        if value:
282            Gtk.StyleContext.remove_provider_for_screen(
283                Gdk.Screen.get_default(), self._app_css_provider)
284        else:
285            Gtk.StyleContext.add_provider_for_screen(
286                Gdk.Screen.get_default(), self._app_css_provider,
287                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
288
289    def _load_style(self, provider, filename, search_path):
290        spath = search_path[:]
291        if "ANTLER_THEME_PATH" in os.environ:
292            spath.insert(0, os.environ["ANTLER_THEME_PATH"])
293
294        for directory in spath:
295            fn = os.path.join(directory, "antler", filename)
296            if os.path.exists(fn):
297                provider.load_from_path(fn)
298                break
299
300    def _on_level_changed(self, group, prop):
301        self._set_to_active_layer()
302
303    def _on_group_changed(self, kb, prop):
304        self._set_to_active_layer()
305
306    def _set_to_active_layer(self, keyboard_level=None):
307        active_group_name = self.keyboard_model.props.active_group
308        active_group = self.keyboard_model.get_group(active_group_name)
309        if keyboard_level:
310            active_level_name = keyboard_level
311        else:
312            active_level_name = active_group.props.active_level
313
314        self.set_current_page(self.layers[active_group_name][active_level_name])
315
316if __name__ == "__main__":
317    import signal
318    signal.signal(signal.SIGINT, signal.SIG_DFL)
319
320    w = Gtk.Window()
321    w.set_accept_focus(False)
322
323    kb = AntlerKeyboardView('touch')
324    w.add(kb)
325
326    w.show_all()
327
328    Gtk.main()
329