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