diff options
Diffstat (limited to 'caribou/ui')
-rw-r--r-- | caribou/ui/Makefile.am | 1 | ||||
-rw-r--r-- | caribou/ui/animation.py | 61 | ||||
-rw-r--r-- | caribou/ui/keyboard.py | 359 | ||||
-rw-r--r-- | caribou/ui/main.py | 115 | ||||
-rw-r--r-- | caribou/ui/opacity.py | 45 | ||||
-rw-r--r-- | caribou/ui/preferences_window.py | 99 | ||||
-rw-r--r-- | caribou/ui/scan.py | 551 | ||||
-rw-r--r-- | caribou/ui/window.py | 189 |
8 files changed, 616 insertions, 804 deletions
diff --git a/caribou/ui/Makefile.am b/caribou/ui/Makefile.am index 8813480..fb24db4 100644 --- a/caribou/ui/Makefile.am +++ b/caribou/ui/Makefile.am @@ -2,7 +2,6 @@ caribou_uidir = $(pkgpythondir)/ui/ caribou_ui_PYTHON = \ __init__.py \ - animation.py \ i18n.py \ keyboard.py \ scan.py \ diff --git a/caribou/ui/animation.py b/caribou/ui/animation.py deleted file mode 100644 index 44a9dfd..0000000 --- a/caribou/ui/animation.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Caribou - text entry and UI navigation application -# -# Copyright (C) 2009 Eitan Isaacson <eitan@monotonous.org> -# -# This program 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 2.1 of the License, or (at your -# option) any later version. -# -# This program 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 program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import clutter - -class AnimatedWindowBase(object): - def __init__(self, ease=clutter.EASE_IN_QUAD): - if self.__class__ == AnimatedWindowBase: - raise TypeError, \ - "AnimatedWindowBase is an abstract class, " \ - "must be subclassed with a gtk.Window" - - self._actor = clutter.Rectangle() - self.ease = ease - - def _on_new_frame(self, timeline, timing): - x, y = self._actor.get_position() - self.move(int(x), int(y)) - - def animated_move(self, x, y): - orig_x, orig_y = self.get_position() - self._actor.set_position(orig_x, orig_y) - self._actor.set_size(self.allocation.width, self.allocation.height) - animation = self._actor.animate( - self.ease, 250, "x", x, "y", y) - timeline = animation.get_timeline() - timeline.connect('new-frame', self._on_new_frame) - - return animation - -if __name__ == "__main__": - import gtk - class AnimatedWindow(gtk.Window, AnimatedWindowBase): - def __init__(self): - gtk.Window.__init__(self) - AnimatedWindowBase.__init__(self) - - aw = AnimatedWindow() - aw.show_all() - aw.move(100, 100) - aw.animated_move(200, 200) - gtk.main() - - diff --git a/caribou/ui/keyboard.py b/caribou/ui/keyboard.py index b3098d3..2dc924b 100644 --- a/caribou/ui/keyboard.py +++ b/caribou/ui/keyboard.py @@ -24,12 +24,12 @@ import caribou.common.const as const from caribou.common.settings_manager import SettingsManager -import scan from preferences_window import PreferencesWindow -import gconf +from gi.repository import GConf import gobject -import gtk -import pango +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import Pango import sys import virtkey import os @@ -48,23 +48,23 @@ from xml.dom import minidom import gettext import i18n -KEY_MASKS = {'shift': gtk.gdk.SHIFT_MASK, - 'lock': gtk.gdk.LOCK_MASK, - 'control': gtk.gdk.CONTROL_MASK, - 'mod1': gtk.gdk.MOD1_MASK, - 'mod2': gtk.gdk.MOD2_MASK, - 'mod3': gtk.gdk.MOD3_MASK, - 'mod4': gtk.gdk.MOD4_MASK, - 'mod5': gtk.gdk.MOD5_MASK, - 'button1': gtk.gdk.BUTTON1_MASK, - 'button2': gtk.gdk.BUTTON2_MASK, - 'button3': gtk.gdk.BUTTON3_MASK, - 'button4': gtk.gdk.BUTTON4_MASK, - 'button5': gtk.gdk.BUTTON5_MASK} +KEY_MASKS = {'shift': Gdk.ModifierType.SHIFT_MASK, + 'lock': Gdk.ModifierType.LOCK_MASK, + 'control': Gdk.ModifierType.CONTROL_MASK, + 'mod1': Gdk.ModifierType.MOD1_MASK, + 'mod2': Gdk.ModifierType.MOD2_MASK, + 'mod3': Gdk.ModifierType.MOD3_MASK, + 'mod4': Gdk.ModifierType.MOD4_MASK, + 'mod5': Gdk.ModifierType.MOD5_MASK, + 'button1': Gdk.ModifierType.BUTTON1_MASK, + 'button2': Gdk.ModifierType.BUTTON2_MASK, + 'button3': Gdk.ModifierType.BUTTON3_MASK, + 'button4': Gdk.ModifierType.BUTTON4_MASK, + 'button5': Gdk.ModifierType.BUTTON5_MASK} class BaseKey(object): '''An abstract class the represents a key on the keyboard. - Inheriting classes also need to inherit from gtk.Button or any + Inheriting classes also need to inherit from Gtk.Button or any of it's subclasses.''' def __init__(self, label = '', value = '', key_type = 'normal', @@ -75,70 +75,101 @@ class BaseKey(object): self.fill = False self.label = label or value if self.key_type == const.DUMMY_KEY_TYPE: - self.set_relief(gtk.RELIEF_NONE) + self.set_relief(Gtk.ReliefStyle.NONE) self.set_sensitive(False) elif self.key_type == const.PREFERENCES_KEY_TYPE: - image = gtk.Image() - image.set_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_BUTTON) + image = Gtk.Image() + image.set_from_stock(Gtk.STOCK_PREFERENCES, + Gtk.IconSize.BUTTON) self.set_image(image) else: if label: - label_markup = gtk.Label() + label_markup = Gtk.Label() label_markup.set_markup(self.label) self.add(label_markup) else: self.set_label(self.label) - self.connect('size-allocate', self._on_size_allocate) + for name in ["normal_color", "mouse_over_color", "default_colors"]: + getattr(SettingsManager, name).connect("value-changed", + self._colors_changed) - def _on_size_allocate(self, widget, allocation): - widget.set_property('width-request', allocation.height * self.width) + for name in ["default_font", "key_font"]: + getattr(SettingsManager, name).connect("value-changed", + self._key_font_changed) - def set_font(self, font): - label = self.get_child() - if not isinstance(label, gtk.Label): - return - rcstyle = label.get_modifier_style() - rcstyle.font_desc = pango.FontDescription(font) + if not SettingsManager.default_font.value: + self._key_font_changed(None, None) - label.modify_style(rcstyle) - label.queue_resize() + if not SettingsManager.default_colors.value: + self._colors_changed(None, None) - def reset_font(self): - label = self.get_child() - if not isinstance(label, gtk.Label): - return - rcstyle = label.get_modifier_style() - rcstyle.font_desc = None - label.modify_style(rcstyle) - label.queue_resize() + def _colors_changed(self, setting, value): + if SettingsManager.default_colors.value: + self._normal_color = None + self._mouse_over_color = None + self.reset_color() + else: + self._normal_color = SettingsManager.normal_color.value + self._mouse_over_color = SettingsManager.mouse_over_color.value + self.set_color(self._normal_color, self._mouse_over_color) - def set_color(self, normal_color, mouse_over_color): - rcstyle = self.get_modifier_style() + def _key_font_changed(self, setting, value): + if SettingsManager.default_font.value: + self.reset_font() + else: + self.set_font(SettingsManager.key_font.value) + + def scan_highlight(self, color): + if color is None: + self._colors_changed(None, None) + else: + self.set_color(color) + + def _on_image_key_mapped(self, key): + print + key_width = key.get_allocated_height() + icon_size = Gtk.IconSize.MENU + image = Gtk.Image() + for size in [Gtk.IconSize.MENU, + Gtk.IconSize.SMALL_TOOLBAR, + Gtk.IconSize.LARGE_TOOLBAR, + Gtk.IconSize.BUTTON, + Gtk.IconSize.DND, + Gtk.IconSize.DIALOG]: + pixbuf = image.render_icon_pixbuf(Gtk.STOCK_PREFERENCES, size) + pixel_size = pixbuf.get_width() + print size, pixel_size, key_width + if pixel_size > key_width: + break + icon_size = size + image.set_from_stock(Gtk.STOCK_PREFERENCES, icon_size) + self.set_image(image) + + def set_font(self, font): + raise NotImplemented - rcstyle.bg[gtk.STATE_NORMAL] = gtk.gdk.Color(normal_color) - rcstyle.bg[gtk.STATE_PRELIGHT] = gtk.gdk.Color(mouse_over_color) + def reset_font(self): + raise NotImplemented - self.modify_style(rcstyle) + def set_color(self, normal_color, mouse_over_color): + raise NotImplemented def reset_color(self): - rcstyle = self.get_modifier_style() - rcstyle.bg[gtk.STATE_NORMAL] = None - rcstyle.bg[gtk.STATE_PRELIGHT] = None - self.modify_style(rcstyle) + raise NotImplemented def _get_value(self): return self._value def _set_value(self, value): if self.key_type == const.NORMAL_KEY_TYPE: - if type(value) == str or type(value) == unicode: + if type(value) == str: value = value.decode('utf-8') + if type(value) == unicode: if len(value) == 1: - self._value = gtk.gdk.unicode_to_keyval(ord(value)) + self._value = Gdk.unicode_to_keyval(ord(value)) else: - key_value = gtk.gdk.keyval_from_name(value) + key_value = Gdk.keyval_from_name(value) if key_value: self._value = key_value elif self.key_type == const.MASK_KEY_TYPE: @@ -151,38 +182,93 @@ class BaseKey(object): value = property(_get_value, _set_value) -class Key(gtk.Button, BaseKey): +class Key(Gtk.Button, BaseKey): def __init__(self, label = '', value = '', key_type = 'normal', width = 1, fill = False): - gtk.Button.__init__(self) + gobject.GObject.__init__(self) BaseKey.__init__(self, label, value, key_type, width, fill) -class ModifierKey(gtk.ToggleButton, BaseKey): - def __init__(self, label = '', value = '', key_type = 'normal', - width = 1, fill = False): - gtk.ToggleButton.__init__(self) - BaseKey.__init__(self, label, value, key_type, width, fill) + def set_font(self, font): + child = self.get_child() + if isinstance(child, Gtk.Label): + child.modify_font(Pango.font_description_from_string(font)) + if child is not None: + child.queue_resize() -class KeyboardLayout(gtk.Alignment): + def reset_font(self): + label = self.get_child() + if not isinstance(label, Gtk.Label): + return + label.modify_font(None) + + def set_color(self, normal_color, mouse_over_color=None): + self.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse(normal_color)[1]) + if mouse_over_color: + self.modify_bg(Gtk.StateType.PRELIGHT, + Gdk.color_parse(mouse_over_color)[1]) + + def reset_color(self): + self.modify_bg(Gtk.StateType.NORMAL, None) + self.modify_bg(Gtk.StateType.PRELIGHT, None) + +class ModifierKey(Gtk.ToggleButton, Key): + pass + +class KeyboardLayout(Gtk.Table): + KEY_SPAN = 4 def __init__(self, name): - super(KeyboardLayout, self).__init__(0, 0, 0, 0) + gobject.GObject.__init__(self) self.layout_name = name self.rows = [] - self.vbox = gtk.VBox() - self.vbox.set_homogeneous(True) - self.add(self.vbox) + self.set_homogeneous(True) def add_row(self, row): + row_num = len(self.rows) self.rows.append(row) - alignment = gtk.Alignment(0.5, 0.5, 1, 1) - hbox = gtk.HBox() - for key in row: - hbox.pack_start(key, expand = True, fill = key.fill) - alignment.add(hbox) - self.vbox.pack_start(alignment) + last_col = 0 + for i, key in enumerate(row): + next_col = (last_col + (key.width * self.KEY_SPAN)) + self.attach(key, last_col, next_col, + row_num * self.KEY_SPAN, (row_num + 1) * self.KEY_SPAN, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + 0, 0) + last_col = next_col + + def get_scan_rows(self): + return [filter(lambda x: x.is_sensitive(), row) for row in self.rows] + + def get_scan_blocks(self, optimal_block_size=8): + # TODO: smarter division using optimal block size. + scan_rows = self.get_scan_rows() + col_num = max([len(row) for row in scan_rows]) + blocks = [] + + for row_index in xrange(len(scan_rows)): + for col_index in xrange(max([len(row) for row in scan_rows])): + try: + key = scan_rows[row_index][col_index] + except IndexError: + continue + + try: + group = blocks[row_index/2] + except IndexError: + group = [] + blocks.append(group) + + try: + group[col_index/3].append(key) + except IndexError: + block = [] + block.append(key) + group.append(block) + + return reduce(lambda a, b: a + b, blocks) + + class KbLayoutDeserializer(object): - def __init__(self): pass @@ -282,70 +368,28 @@ class KbLayoutDeserializer(object): return value return [value] -class CaribouKeyboard(gtk.Notebook): +class CaribouKeyboard(Gtk.Notebook): __gtype_name__ = "CaribouKeyboard" def __init__(self): - gtk.Notebook.__init__(self) + gobject.GObject.__init__(self) self.set_show_tabs(False) self.vk = virtkey.virtkey() self.key_size = 30 self.current_mask = 0 self.current_page = 0 - # Settings we care about. - for name in ["normal_color", "mouse_over_color", "default_colors"]: - getattr(SettingsManager, name).connect("value-changed", - self._colors_changed) - - for name in ["default_font", "key_font"]: - getattr(SettingsManager, name).connect("value-changed", - self._key_font_changed) - - self.scan_enabled = SettingsManager.scan_enabled - - self.scan_enabled.connect("value-changed", - self._scan_enabled) - - self.scan_service = None - - self.connect('size-allocate', self._on_size_allocate) - self.row_height = -1 - def reset_row_height(self): - for i in xrange(self.get_n_pages()): - layout = self.get_nth_page(i) - for row in layout.vbox.get_children(): - row.set_property('height-request', -1) - self.row_height = -1 - - def _on_size_allocate(self, notebook, allocation): - if self.row_height > 0: - return - - for i in xrange(self.get_n_pages()): - layout = self.get_nth_page(i) - rows = layout.vbox.get_children() - height = rows[0].allocation.height - self.row_height = max(self.row_height, height) - for i in xrange(self.get_n_pages()): - layout = self.get_nth_page(i) - for row in layout.vbox.get_children(): - row.set_property('height-request', self.row_height) - - def load_kb(self, kb_location): kb_deserializer = KbLayoutDeserializer() layouts = kb_deserializer.deserialize(kb_location) self._set_layouts(layouts) - self._update_key_style() - self._enable_scanning() def _set_layouts(self, layout_list): - self._clear() + self._clear() for layout in layout_list: - self.append_page(layout) + self.append_page(layout, None) for row in layout.rows: for key in row: if key.key_type == const.LAYOUT_SWITCHER_KEY_TYPE: @@ -361,38 +405,6 @@ class CaribouKeyboard(gtk.Notebook): key.connect('clicked', self._pressed_normal_key) - def _scan_enabled(self, setting, val): - self._enable_scanning() - - def _colors_changed(self, setting, val): - self._update_key_style() - - def _key_font_changed(self, setting, val): - self.reset_row_height() - self._update_key_style() - - def _update_key_style(self): - default_colors = SettingsManager.default_colors.value - normal_color = SettingsManager.normal_color.value - mouse_over_color = SettingsManager.mouse_over_color.value - default_font = SettingsManager.default_font.value - key_font = SettingsManager.key_font.value - - n_pages = self.get_n_pages() - for i in range(n_pages): - layout = self.get_nth_page(i) - for row in layout.rows: - for button in row: - if default_colors: - button.reset_color() - else: - button.set_color(normal_color, - mouse_over_color) - if default_font: - button.reset_font() - else: - button.set_font(key_font) - def _clear(self): n_pages = self.get_n_pages() for i in range(n_pages): @@ -414,11 +426,9 @@ class CaribouKeyboard(gtk.Notebook): self.current_mask |= key.value self.vk.latch_mod(self.current_mask) - def show_all(self): + def show_all_(self): self.set_current_page(self.current_page) - gtk.Notebook.show_all(self) - #if self.scan_enabled.value: - # self.scan_service.start() + Gtk.Notebook.show_all(self) def _pressed_preferences_key(self, key): p = PreferencesWindow() @@ -426,27 +436,32 @@ class CaribouKeyboard(gtk.Notebook): p.run() p.destroy() - def _enable_scanning(self): - if self.scan_enabled.value and self.scan_service is None: - current_layout = self.get_nth_page(self.current_page) - self.scan_service = scan.ScanService( - current_layout.rows, - self.get_parent().get_parent()) - - def destroy(self): - if self.scan_enabled.value: - self.scan_service.destroy() - for id in self._gconf_connections: - self.client.notify_remove(id) - super(gtk.Notebook, self).destroy() - def _switch_to_layout(self, name): n_pages = self.get_n_pages() for i in range(n_pages): if self.get_nth_page(i).layout_name == name: self.set_current_page(i) self.current_page = i - if self.scan_enabled.value: - self.scan_service.change_keyboard( - self.get_nth_page(i).rows) break + + def get_current_layout(self): + i = self.get_current_page() + return self.get_nth_page(i) + + def get_layouts(self): + return [self.get_nth_page(i) for i in xrange(self.get_n_pages())] + +if __name__ == "__main__": + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + w = Gtk.Window() + + kb = CaribouKeyboard() + kb.load_kb('data/keyboards/qwerty.xml') + + w.add(kb) + + w.show_all() + + Gtk.main() diff --git a/caribou/ui/main.py b/caribou/ui/main.py index e5dcee2..5175bb0 100644 --- a/caribou/ui/main.py +++ b/caribou/ui/main.py @@ -1,13 +1,14 @@ -import gtk.gdk as gdk import pyatspi -import gconf -import gtk -import signal +from gi.repository import GConf +from gi.repository import Gtk +from gi.repository import Gdk -from window import CaribouWindowEntry +from window import CaribouWindowEntry, Rectangle from keyboard import CaribouKeyboard +from caribou.common.settings_manager import SettingsManager from caribou.ui.i18n import _ import caribou.common.const as const +from scan import ScanMaster debug = False @@ -20,12 +21,19 @@ class Caribou: self.__current_acc = None self.window_factory = window_factory self.kb_factory = kb_factory - self.window = window_factory(kb_factory()) - self.client = gconf.client_get_default() + kb = kb_factory() + self.window = window_factory(kb) + self.client = GConf.Client.get_default() self._register_event_listeners() - self.client.notify_add(const.CARIBOU_GCONF + "/layout", - self._on_layout_changed) - signal.signal(signal.SIGINT, self.signal_handler) + SettingsManager.layout.connect("value-changed", + self._on_layout_changed) + + # Scanning + self.scan_master = ScanMaster(self.window, kb) + SettingsManager.scan_enabled.connect("value-changed", + self._on_scan_toggled) + if SettingsManager.scan_enabled.value: + self.scan_master.start() def _register_event_listeners(self): pyatspi.Registry.registerEventListener( @@ -33,8 +41,6 @@ class Caribou: pyatspi.Registry.registerEventListener(self.on_focus, "focus") pyatspi.Registry.registerEventListener( self.on_text_caret_moved, "object:text-caret-moved") - pyatspi.Registry.registerKeystrokeListener( - self.on_key_down, mask=None, kind=(pyatspi.KEY_PRESSED_EVENT,)) def _deregister_event_listeners(self): pyatspi.Registry.deregisterEventListener( @@ -42,21 +48,27 @@ class Caribou: pyatspi.Registry.deregisterEventListener(self.on_focus, "focus") pyatspi.Registry.deregisterEventListener( self.on_text_caret_moved, "object:text-caret-moved") - pyatspi.Registry.deregisterKeystrokeListener( - self.on_key_down, mask=None, kind=pyatspi.KEY_PRESSED_EVENT) - def _on_layout_changed(self, client, connection_id, entry, args): + def _on_scan_toggled(self, setting, val): + if val: + self.scan_master.start() + else: + self.scan_master.stop() + + def _on_layout_changed(self, setting, val): self._deregister_event_listeners() self.window.destroy() self._update_window() self._register_event_listeners() def _update_window(self): - self.window = self.window_factory(self.kb_factory()) + kb = self.kb_factory() + self.scan_master.set_keyboard(kb) + self.window = self.window_factory(kb) def _get_a11y_enabled(self): try: - gconfc = gconf.client_get_default() + gconfc = GConf.Client.get_default() atspi1 = gconfc.get_bool("/desktop/gnome/interface/accessibility") atspi2 = gconfc.get_bool("/desktop/gnome/interface/accessibility2") return atspi1 or atspi2 @@ -73,7 +85,7 @@ class Caribou: def __set_text_location(self, acc): text = acc.queryText() [x, y, width, height] = text.getCharacterExtents(text.caretOffset, pyatspi.DESKTOP_COORDS) - self.window.set_cursor_location(gdk.Rectangle(x, y, width, height)) + self.window.set_cursor_location(Rectangle(x, y, width, height)) component = acc.queryComponent() entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) @@ -82,14 +94,14 @@ class Caribou: def __set_entry_location(self, acc): text = acc.queryText() - cursor_bb = gdk.Rectangle( + cursor_bb = Rectangle( *text.getCharacterExtents(text.caretOffset, pyatspi.DESKTOP_COORDS)) component = acc.queryComponent() entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) - if cursor_bb == gdk.Rectangle(0, 0, 0, 0): + if cursor_bb == Rectangle(0, 0, 0, 0): cursor_bb = entry_bb self.window.set_cursor_location(cursor_bb) @@ -99,11 +111,13 @@ class Caribou: def on_focus(self, event): acc = event.source - if pyatspi.STATE_EDITABLE in acc.getState().getStates() or event.source_role == pyatspi.ROLE_TERMINAL: - if event.source_role in (pyatspi.ROLE_TEXT, - pyatspi.ROLE_PARAGRAPH, - pyatspi.ROLE_PASSWORD_TEXT, - pyatspi.ROLE_TERMINAL): + source_role = acc.getRole() + if pyatspi.STATE_EDITABLE in acc.getState().getStates() or \ + source_role == pyatspi.ROLE_TERMINAL: + if source_role in (pyatspi.ROLE_TEXT, + pyatspi.ROLE_PARAGRAPH, + pyatspi.ROLE_PASSWORD_TEXT, + pyatspi.ROLE_TERMINAL): if event.type.startswith("focus") or event.detail1 == 1: self.__set_text_location(acc) self.__current_acc = event.source @@ -111,13 +125,13 @@ class Caribou: if debug == True: print "enter text widget in", event.host_application.name elif event.detail1 == 0 and acc == self.__current_acc: - self.window.hide_all() + self.window.hide() self.__current_acc = None self.__set_location = None if debug == True: print "leave text widget in", event.host_application.name - elif event.source_role == pyatspi.ROLE_ENTRY: + elif source_role == pyatspi.ROLE_ENTRY: if event.type.startswith("focus") or event.detail1 == 1: self.__set_entry_location(acc) self.__current_acc = event.source @@ -125,61 +139,24 @@ class Caribou: if debug == True: print "enter entry widget in", event.host_application.name elif event.detail1 == 0: - self.window.hide_all() + self.window.hide() self.__current_acc = None self.__set_location = None if debug == True: print "leave entry widget in", event.host_application.name else: if debug == True: - print _("WARNING - Caribou: unhandled editable widget:"), event.source + print _("WARNING - Caribou: unhandled editable widget:"), event.source # Firefox does not report leave entry widget events. # This could be a way to get the entry widget leave events. #else: # if event.detail1 == 1: - # self.window.hide_all() + # self.window.hide() # print "--> LEAVE EDITABLE TEXT <--" - def on_key_down(self, event): - # key binding for controlling the row column scanning - if event.event_string == "Shift_R": - # TODO: implement keyboard scanning - pass - elif event.event_string == "Control_R": - self.clean_exit() - - def signal_handler(self,signal,frame): - # Clean exit pressing Control + C - self.clean_exit() - def clean_exit(self): - if debug == True: - print "quitting ..." - result = pyatspi.Registry.deregisterEventListener(self.on_text_caret_moved, "object:text-caret-moved") - if debug == True: - print "deregisterEventListener - object:text-caret-moved ...", - if result == False: - print "OK" - else: - print "FAIL" - result = pyatspi.Registry.deregisterEventListener(self.on_focus, "object:state-changed:focused") - if debug == True: - print "deregisterEventListener - object:state-changed:focused ...", - if result == False: - print "OK" - else: - print "FAIL" - result = pyatspi.Registry.deregisterEventListener(self.on_focus, "focus") - if debug == True: - print "deregisterEventListener - focus ...", - if result == False: - print "OK" - else: - print "FAIL" - result = pyatspi.Registry.deregisterKeystrokeListener(self.on_key_down, mask=None, kind=pyatspi.KEY_PRESSED_EVENT) - if debug == True: - print "deregisterKeystrokeListener" - gtk.main_quit() + self.scan_master.stop() + self._deregister_event_listeners() diff --git a/caribou/ui/opacity.py b/caribou/ui/opacity.py index bab14d5..be742d8 100644 --- a/caribou/ui/opacity.py +++ b/caribou/ui/opacity.py @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import gtk, glib +import glib from math import sqrt class ProximityWindowBase(object): @@ -26,7 +26,7 @@ class ProximityWindowBase(object): if self.__class__ == ProximityWindowBase: raise TypeError, \ "ProximityWindowBase is an abstract class, " \ - "must be subclassed with a gtk.Window" + "must be subclassed with a Gtk.Window" self.connect('map-event', self.__onmapped) self.max_distance = max_distance if max_alpha < min_alpha: @@ -42,9 +42,12 @@ class ProximityWindowBase(object): glib.timeout_add(80, self._proximity_check) def _proximity_check(self): - x, y = self.get_pointer() - - distance = self._get_distance_to_bbox(x, y, self.allocation) + px, py = self.get_pointer() + + ww = self.get_allocated_width() + wh = self.get_allocated_height() + + distance = self._get_distance_to_bbox(px, py, ww, wh) opacity = (self.max_alpha - self.min_alpha) * \ (1 - min(distance, self.max_distance)/self.max_distance) @@ -53,28 +56,28 @@ class ProximityWindowBase(object): self.set_opacity(opacity) return self.props.visible - def _get_distance_to_bbox(self, x, y, bbox): - if x < bbox.x: - x_distance = bbox.x - x - elif x > bbox.width + bbox.x: - x_distance = bbox.width + bbox.x - x + def _get_distance_to_bbox(self, px, py, bw, bh): + if px < 0: + x_distance = float(abs(px)) + elif px > bw: + x_distance = float(px - bw) else: - x_distance = 0 + x_distance = 0.0 - if y < bbox.y: - y_distance = bbox.y - y - elif y > bbox.height + bbox.y: - y_distance = bbox.height + bbox.y - y + if py < 0: + y_distance = float(abs(px)) + elif py > bh: + y_distance = float(py - bh) else: - y_distance = 0 + y_distance = 0.0 if y_distance == 0 and x_distance == 0: return 0.0 elif y_distance != 0 and x_distance == 0: - return abs(float(y_distance)) + return y_distance elif y_distance == 0 and x_distance != 0: - return abs(float(x_distance)) + return x_distance else: - x2 = bbox.x if x_distance > 0 else bbox.x + bbox.width - y2 = bbox.y if y_distance > 0 else bbox.y + bbox.height - return sqrt((x - x2)**2 + (y - y2)**2) + x2 = 0 if x_distance > 0 else bw + y2 = 0 if y_distance > 0 else bh + return sqrt((px - x2)**2 + (py - y2)**2) diff --git a/caribou/ui/preferences_window.py b/caribou/ui/preferences_window.py index c11cbf8..0158ffb 100644 --- a/caribou/ui/preferences_window.py +++ b/caribou/ui/preferences_window.py @@ -20,12 +20,13 @@ import caribou.common.const as const from caribou.common.setting_types import * +from caribou.common.settings_manager import SettingsManager -import scan -import gconf +from gi.repository import GConf import gobject -import gtk -import pango +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import Pango import sys import virtkey import os @@ -41,25 +42,26 @@ import xml.etree.ElementTree as ET from xml.dom import minidom import gettext import i18n -from caribou.common.settings_manager import SettingsManager -class PreferencesWindow(gtk.Dialog): +class PreferencesWindow(Gtk.Dialog): __gtype_name__ = "PreferencesWindow" def __init__(self): - gtk.Dialog.__init__(self, _("Caribou Preferences"), - buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + gobject.GObject.__init__(self) + self.set_title(_("Caribou Preferences")) + self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) self.set_border_width(6) - notebook = gtk.Notebook() - self.vbox.add(notebook) + notebook = Gtk.Notebook() + vbox = self.get_content_area() + vbox.add(notebook) self._populate_settings(notebook, SettingsManager.groups) def _populate_settings(self, parent, setting, level=0): if level == 0: for s in setting: - vbox = gtk.VBox() - parent.append_page(vbox, gtk.Label(s.label)) + vbox = Gtk.VBox() + parent.append_page(vbox, Gtk.Label(label=s.label)) self._populate_settings(vbox, s, 1) else: parent.set_border_width(6) @@ -68,7 +70,7 @@ class PreferencesWindow(gtk.Dialog): for s in setting: if not isinstance(s, SettingsGroup): if table is None: - table = gtk.Table(1, 2) + table = Gtk.Table.new(1, 2, False) table.set_row_spacings(3) table.set_col_spacings(3) parent.pack_start(table, False, False, 0) @@ -76,12 +78,12 @@ class PreferencesWindow(gtk.Dialog): row += 1 else: table = None - frame = gtk.Frame() - frame.set_shadow_type(gtk.SHADOW_NONE) - label = gtk.Label() + frame = Gtk.Frame() + frame.set_shadow_type(Gtk.ShadowType.NONE) + label = Gtk.Label() label.set_markup('<b>%s</b>' % s.label) frame.set_label_widget(label) - vbox = gtk.VBox() + vbox = Gtk.VBox() frame.add(vbox) parent.pack_start(frame, False, False, 0) self._sensitivity_changed_cb(s, s.sensitive, frame, None) @@ -91,36 +93,35 @@ class PreferencesWindow(gtk.Dialog): self._populate_settings(vbox, s, level + 1) def _create_widget(self, table, row, setting, xpadding=0): - print 'create', setting.name control = None label = None value_changed_cb = None control_changed_cb = None control_changed_signal = None if isinstance(setting, BooleanSetting): - control = gtk.CheckButton(setting.label) + control = Gtk.CheckButton.new_with_label(setting.label) control.set_active(setting.value) value_changed_cb = lambda s, v, w: w.set_active(v) control_changed_cb = self._checkbutton_toggled_cb control_changed_signal = 'toggled' else: - label = gtk.Label("%s:" % setting.label) + label = Gtk.Label(label="%s:" % setting.label) label.set_alignment(0.0, 0.5) if setting.entry_type == ENTRY_COLOR: - control = gtk.ColorButton( - gtk.gdk.color_parse(setting.value)) + control = Gtk.ColorButton.new_with_color( + Gdk.color_parse(setting.value)[1]) value_changed_cb = \ - lambda s, v, w: w.set_color(gtk.gdk.color_parse(v)) + lambda s, v, w: w.set_color(Gdk.color_parse(v)) control_changed_cb = self._colorbutton_changed_cb control_changed_signal = 'color-set' elif setting.entry_type == ENTRY_FONT: - control = gtk.FontButton(setting.value) + control = Gtk.FontButton.new_with_font(setting.value) value_changed_cb = lambda s, v, w: w.set_font_name(v) control_changed_cb = self._fontbutton_changed_cb control_changed_signal = 'font-set' elif setting.entry_type == ENTRY_SPIN: - control = gtk.SpinButton() + control = Gtk.SpinButton() if isinstance(setting.value, float): control.set_digits(2) control.set_increments(0.01, 0.1) @@ -136,15 +137,16 @@ class PreferencesWindow(gtk.Dialog): "If a radio entry has children, they must be equal " \ "in quantity to the allowed values." label = None - control = gtk.Table( - len(setting.allowed) + len(setting.children), 2) + control = Gtk.Table.new( + len(setting.allowed) + len(setting.children), 2, False) control.set_row_spacings(3) control.set_col_spacings(3) radios = [] for string, localized in setting.allowed: - radios.append(gtk.RadioButton(None, localized)) + radios.append(Gtk.RadioButton.new_with_label( + [], localized)) for radio in radios[1:]: - radio.set_group(radios[0]) + radio.join_group(radios[0]) hid = setting.connect( 'value-changed', @@ -157,7 +159,11 @@ class PreferencesWindow(gtk.Dialog): for i, radio in enumerate(radios): radio.connect('toggled', self._radio_changed_cb, setting, radios, hid) - control.attach(radio, 0, 2, r, r + 1) + control.attach( + radio, 0, 2, r, r + 1, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + 0, 0) r += 1 if setting.children: self._create_widget(control, r, @@ -165,27 +171,34 @@ class PreferencesWindow(gtk.Dialog): r += 1 elif setting.entry_type == ENTRY_COMBO or setting.allowed: - control = gtk.combo_box_new_text() + control = Gtk.ComboBoxText.new() for option in setting.allowed: - control.append_text(option[1]) - control.set_active( - [a for a, b in setting.allowed].index(setting.value)) - value_changed_cb = lambda s, v, w: w.set_active( - [a for a, b in s.allowed].index(v)) + control.append(option[0], option[1]) + control.set_active_id(setting.value) + value_changed_cb = lambda s, v, w: w.set_active_id(v) control_changed_cb = self._combo_changed_cb control_changed_signal = 'changed' else: - control = gtk.Entry() + control = Gtk.Entry() control.set_text(setting.value) value_changed_cb = lambda s, v, w: w.set_text(v) control_changed_cb = self._string_changed_cb control_changed_signal = 'insert-at-cursor' if label is not None: - table.attach(label, 0, 1, row, row + 1, xpadding=xpadding) - table.attach(control, 1, 2, row, row + 1) + table.attach(label, 0, 1, row, row + 1, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + xpadding, 0) + table.attach(control, 1, 2, row, row + 1, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + 0, 0) else: - table.attach(control, 0, 2, row, row + 1, xpadding=xpadding) + table.attach(control, 0, 2, row, row + 1, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, + xpadding, 0) self._sensitivity_changed_cb(setting, setting.sensitive, control, label) @@ -232,15 +245,15 @@ class PreferencesWindow(gtk.Dialog): self._update_setting(setting, entry.get_text(), handler_id) def _combo_changed_cb(self, combo, setting, handler_id): - self._update_setting(setting, setting.allowed[combo.get_active()][0], + self._update_setting(setting, combo.get_active_id(), handler_id) if __name__ == "__main__": import signal signal.signal(signal.SIGINT, signal.SIG_DFL) - w = PreferencesWindow_() + w = PreferencesWindow() w.show_all() try: w.run() except KeyboardInterrupt: - gtk.main_quit() + Gtk.main_quit() diff --git a/caribou/ui/scan.py b/caribou/ui/scan.py index 992f1d9..16680fb 100644 --- a/caribou/ui/scan.py +++ b/caribou/ui/scan.py @@ -1,10 +1,11 @@ import gobject import pyatspi -import gtk +from gi.repository import Gdk +from gi.repository import Gtk import caribou.common.const as const from caribou.common.settings_manager import SettingsManager -# Scan constans +# Scan constants BUTTON = 'button' ROW = 'row' BLOCK = 'block' @@ -26,400 +27,192 @@ DEFAULT_STEP_TIME = 1000 DEFAULT_SCANNING_TYPE = ROW DEFAULT_SWITCH_TYPE = KEYBOARD_SWITCH_TYPE -class ScanService(): - def __init__(self, keyboard, root_window): - self.keyboard = keyboard +class ScanMaster(): + def __init__(self, root_window, keyboard=None): self.root_window = root_window - self.selected_row = None - self.selected_button = None - self.button_index = 0 - self.row_index = 0 - self.index_i = 0 - self.index_j = 0 - self.is_stop = True - self.timerid = None - self.reverse = False - self.selected_block = [] - - # Settings we are interested in. - for name in ["step_time", "reverse_scanning", "scanning_type"]: - getattr(SettingsManager, name).connect("value-changed", - self._on_switch_changed) - - for name in ["switch_type", "mouse_button", "keyboard_key"]: - getattr(SettingsManager, name).connect("value-changed", - self._on_switch_changed) - - for name in ["default_colors", "normal_color", "mouse_over_color", - "row_scanning_color", "button_scanning_color", - "cancel_scanning_color", "block_scanning_color"]: - getattr(SettingsManager, name).connect("value-changed", - self._on_color_changed) - - SettingsManager.scan_enabled.connect("value-changed", - self._on_scan_toggled) - - self._configure_scanning() - self._set_colors() - self._configure_switch() - - def destroy(self): - self.stop() - self.clean() - self._deregister_events() - - - def _configure_switch(self): - self.switch_type = SettingsManager.switch_type.value - if self.switch_type == MOUSE_SWITCH_TYPE: - self.switch_key = SettingsManager.mouse_button.value - elif self.switch_type == KEYBOARD_SWITCH_TYPE: - self.switch_key = SettingsManager.keyboard_key + self._timer = 0 + self._scan_path = None + self.started = False - try: - pyatspi.Registry.registerKeystrokeListener(self._on_key_pressed, - mask=None, kind=(pyatspi.KEY_PRESSED_EVENT,)) - pyatspi.Registry.registerKeystrokeListener(self._on_key_released, - mask=None, kind=(pyatspi.KEY_RELEASED_EVENT,)) - except: - print "Error while registering keyboard events in scan.py" - - def _deregister_events(self): - try: - pyatspi.Registry.deregisterKeystrokeListener(self._on_key_pressed, - mask=None, kind=pyatspi.KEY_PRESSED_EVENT) - pyatspi.Registry.deregisterKeystrokeListener( - self._on_key_released, - mask=None, kind=pyatspi.KEY_RELEASED_EVENT) - except: - print "Error while deregistering keyboard events in scan.py" - - def _on_switch_changed(self, settings, val): - self._deregister_events() - self._configure_switch() - - def _on_scan_toggled(self, settings, val): - if val: - self.start() - else: - self.stop() + SettingsManager.step_time.connect("value-changed", + self._on_step_time_changed) + SettingsManager.scanning_type.connect("value-changed", + self._on_scanning_type_changed) + if keyboard: + self.set_keyboard(keyboard) - def _on_color_changed(self, settings, val): - self._set_colors() + def start(self): + if self.started: return - def _on_scanning_type_changed(self, settings, val): - self._configure_scanning() + if self._timer == 0: + self._timer = gobject.timeout_add( + int(SettingsManager.step_time.value*1000), self._scan) - def _configure_scanning(self): - if not self.is_stop: - self._stop() - self.scanning_type = SettingsManager.scanning_type.value - self.reverse = SettingsManager.reverse_scanning.value - self.step_time = SettingsManager.step_time.value + self._grab_mouse_events() + + pyatspi.Registry.registerKeystrokeListener( + self._on_key_pressed, mask=0, kind=(pyatspi.KEY_PRESSED_EVENT,)) - if self.scanning_type == BLOCK: - self.selected_block = [] - else: - self.selected_block = self.keyboard - self.scanning = self.scanning_type - - def _set_colors(self): - self.default_colors = SettingsManager.default_colors.value - self.normal_color = SettingsManager.normal_color.value - self.mouse_over_color = SettingsManager.mouse_over_color.value - self.row_scanning_color = SettingsManager.row_scanning_color.value - self.button_scanning_color = \ - SettingsManager.button_scanning_color.value - self.cancel_scanning_color = \ - SettingsManager.cancel_scanning_color.value - self.block_scanning_color = SettingsManager.block_scanning_color.value - - # public start - def start(self, scanning=None): - self.scanning = scanning or self.scanning_type - self.clean() - if self.root_window and \ - self.switch_type == MOUSE_SWITCH_TYPE: - self._grab_mouse_events() - - if not self.reverse: - self.reset(self.scanning) - - # public stop def stop(self): - if self.switch_type == MOUSE_SWITCH_TYPE: - self._ungrab_mouse_events() - self.clean() - self._stop() - - #private start - def _start(self, scanning=ROW): - if self.is_stop == True and self.timerid == None: - self.is_stop = False - self.button_index = -1 - self.row_index = -1 - if scanning == ROW: - self.selected_row = [] - self.timerid = gobject.timeout_add( - int(1000*self.step_time), self._scan_row) - elif scanning == BUTTON: - self.selected_button = None - self.timerid = gobject.timeout_add( - int(1000*self.step_time), self._scan_button) - elif scanning == BLOCK: - self.selected_block = [] - self.selected_row = [] - self.clean() - self.index_i = 2 - self.index_j = 1 - self.timerid = gobject.timeout_add( - int(1000*self.step_time), self._scan_block) - - # private stop - def _stop(self): - self.is_stop = True - if self.timerid: - gobject.source_remove(self.timerid) - self.timerid = None - - def reset(self, scanning=ROW): - self._stop() - self._start(scanning) - - def clean(self): - for row in self.keyboard: - for button in row: - self.select_button(button, False) - - - def change_keyboard(self, keyboard): - if not self.is_stop: - self._stop() - self.keyboard = keyboard - self.scanning = self.scanning_type - self.start(self.scanning_type) - if self.scanning == ROW: - self.selected_block = keyboard + if self.started: return + self._ungrab_mouse_events() - def _grab_mouse_events(self): - gtk.gdk.event_handler_set(self._mouse_handler) + if self._last_block is not None: + self._multi_map(lambda x: x.reset_color(), self._last_block) - def _ungrab_mouse_events(self): - gtk.gdk.event_handler_set(gtk.main_do_event) - - def _mouse_handler(self, event): - if self.root_window.window.is_visible(): - if event.type == gtk.gdk.BUTTON_PRESS and \ - str(event.button) == self.switch_key.value: - self._handle_press() - elif event.type == gtk.gdk.BUTTON_RELEASE and \ - str(event.button) == self.switch_key.value: - self._handle_release() - elif not event.type == gtk.gdk.ENTER_NOTIFY: - gtk.main_do_event(event) - else: - gtk.main_do_event(event) - - def _scan_block(self): - if self.is_stop: - return False - # Clean the previous block - self.select_block(self.selected_block, False) - # Update indexes, three horizontal blocks, and two vertical - if self.index_j < 2: - self.index_j += 1 - elif self.index_i < 1: - self.index_i += 1 - self.index_j = 0 + if self._timer != 0: + gobject.source_remove(self._timer) + self._timer = 0 + + pyatspi.Registry.deregisterKeystrokeListener( + self._on_key_pressed, mask=0, kind=pyatspi.KEY_PRESSED_EVENT) + + def _on_scanning_type_changed(self, setting, val): + layout = self.keyboard.get_current_layout() + if SettingsManager.scanning_type.value == ROW: + self._blocks = layout.get_scan_rows() else: - self.index_j = 0 - self.index_i = 0 - - self.selected_block = [] - width = self.root_window.size_request()[0] - height = self.root_window.size_request()[1] - root_x = self.root_window.get_position()[0] - root_y = self.root_window.get_position()[1] - offset_w = self.index_j*(width/3) - offset_h = self.index_i*(height/2) - - block_window = gtk.gdk.Rectangle(root_x + offset_w, - root_y + offset_h, - width/3, - height/2) - empty_r = gtk.gdk.Rectangle() - try: - for row in self.keyboard: - line = [] - for button in row: - abs_b_x = button.get_allocation()[0] + \ - button.window.get_position()[0] - abs_b_y = button.get_allocation()[1] + \ - button.window.get_position()[1] - abs_b_r = gtk.gdk.Rectangle(abs_b_x, - abs_b_y, - button.size_request()[0], - button.size_request()[1]) - - # If button rectangle is inside the block: - intersect = block_window.intersect(abs_b_r) - # If the intersected rectangle != empty - if intersect != empty_r: - # If the witdth of intersection is bigger than half - # of button width and height, we append button to line - if (intersect.width > (abs_b_r.width / 2)) and \ - (intersect.height > (abs_b_r.height / 2)): - line.append(button) - - - if len(line) > 0: - self.selected_block.append(line) - - except Exception as e: - self.is_stop = True - return False - - self.select_block(self.selected_block, True, - self.block_scanning_color) - return True - + self._blocks = layout.get_scan_blocks() + + def _on_step_time_changed(self, setting, val): + if self._timer != 0: + gobject.source_remove(self._timer) + self._timer = gobject.timeout_add(int(1000*val), self._scan) + def _on_layout_activated(self, keyboard, layout, num): + if SettingsManager.scanning_type.value == ROW: + self._blocks = layout.get_scan_rows() + else: + self._blocks = layout.get_scan_blocks() + if SettingsManager.reverse_scanning.value: + self._scan_path = [0] + else: + self._scan_path = [-1] - def _scan_row(self): - if self.is_stop: - return False + def set_keyboard(self, keyboard): + self._last_block = None + keyboard.connect("switch-page", self._on_layout_activated) + self.keyboard = keyboard + def _multi_map(self, func, array): + if isinstance(array, list): + for item in array: + self._multi_map(func, item) else: - self.select_row(self.selected_row, - self.scanning_type == BLOCK, - self.block_scanning_color) - self.row_index += 1 - if self.row_index >= len(self.selected_block): - self.row_index = 0 - self.selected_row = self.selected_block[self.row_index] - self.select_row(self.selected_row, True, self.row_scanning_color) - return True - - def _scan_button(self): - if self.scanning == CANCEL: - self.scanning = BUTTON - self.selected_button = None - self.select_row(self.selected_row, True, self.row_scanning_color) - return True + func(array) + + def _get_element_at_path(self, array, path): + element = array + for index in path: + element = element[index] + return element + + def _get_next_reverse_block(self): + cancel = False + + if self._scan_path[-1] > 0: + self._scan_path = self._scan_path[:-1] + [0] + + self._scan_path += [self._scan_path.pop() - 1] + + try: + block = self._get_element_at_path(self._blocks, + self._scan_path) + except IndexError: + if len(self._scan_path) == 1: + block = self._blocks[-1] + self._scan_path = [-1] + else: + block = self._get_element_at_path( + self._blocks, self._scan_path[:-1]) + self._scan_path = self._scan_path[:-1] + [0] + cancel = True + + return cancel, block + + + def _get_next_block(self): + cancel = False + self._scan_path += [self._scan_path.pop() + 1] + try: + block = self._get_element_at_path(self._blocks, + self._scan_path) + except IndexError: + if len(self._scan_path) == 1: + block = self._blocks[0] + self._scan_path = [0] + else: + block = self._get_element_at_path( + self._blocks, self._scan_path[:-1]) + self._scan_path = self._scan_path[:-1] + [-1] + cancel = True + + return cancel, block + + def _scan(self): + if self._scan_path is None: return True + + if self._last_block is not None: + self._multi_map(lambda x: x.scan_highlight(None), + self._last_block) + + if SettingsManager.reverse_scanning.value: + self._cancel, next_block = self._get_next_reverse_block() else: - if self.selected_button and self.selected_button in self.selected_row: - self.select_button(self.selected_button, True, self.row_scanning_color) - - if self.is_stop: - return False - - self.button_index += 1 - if self.button_index >= len(self.selected_row): - self.select_row(self.selected_row, True, self.cancel_scanning_color) - self.button_index = -1 - self.scanning = CANCEL - return True - - self.selected_button = self.selected_row[self.button_index] - while self.selected_button.key_type == const.DUMMY_KEY_TYPE: - self.button_index += 1 - if self.button_index >= len(self.selected_row): - self.select_row(self.selected_row, True, self.cancel_scanning_color) - self.button_index = -1 - self.scanning = CANCEL - return True - - self.selected_button = self.selected_row[self.button_index] - self.select_button(self.selected_button, True, self.button_scanning_color) - return True - - def select_block(self, block, state, color=None): - for row in block: - self.select_row(row, state, color) - - def select_row(self, row, state, color=None): - for button in row: - self.select_button(button, state, color) - - def select_button(self, button, state, color=None): - if state: - button.set_color(color, self.mouse_over_color) - elif self.default_colors: - button.reset_color() + self._cancel, next_block = self._get_next_block() + + color = SettingsManager.button_scanning_color.value + + if self._cancel: + color = SettingsManager.cancel_scanning_color.value + elif isinstance(next_block, list): + if SettingsManager.scanning_type.value == ROW: + color = SettingsManager.row_scanning_color.value + else: + color = SettingsManager.block_scanning_color.value + + self._multi_map(lambda x: x.scan_highlight(color), next_block) + self._last_block = next_block + return True + + def _do_switch(self): + if self._cancel: + assert(len(self._scan_path) > 1) + self._scan_path.pop() + elif isinstance(self._last_block, list): + assert(len(self._last_block) > 0) + if SettingsManager.reverse_scanning.value: + self._scan_path.append(0) + else: + self._scan_path.append(-1) else: - button.set_color(self.normal_color, - self.mouse_over_color) + self._last_block.clicked() - def _on_key_pressed(self, event): - if event.event_string == "Escape": - SettingsManager.scan_enabled.value = False - elif self.switch_type == KEYBOARD_SWITCH_TYPE and \ - self.switch_key.value == event.event_string: - self._handle_press() - - def _on_key_released(self, event): - if self.switch_type == KEYBOARD_SWITCH_TYPE and \ - self.switch_key.value == event.event_string: - self._handle_release() - elif event.event_string != "Escape": - self._stop() - self.start() - - def _handle_press(self): - if self.reverse: - self._start(self.scanning) - - def _handle_release(self): - if self.reverse: - if not self.is_stop: - if self.scanning == ROW and \ - len(self.selected_row) > 0: - self.scanning = BUTTON - elif self.scanning == BLOCK and \ - len(self.selected_block) > 0: - self.scanning = ROW - elif self.scanning == BUTTON and \ - self.selected_button: - self.clean() - if self.selected_button.key_type == const.PREFERENCES_KEY_TYPE: - self.stop() - self.selected_button.clicked() - self.selected_button = None - self.scanning = self.scanning_type - self.reset() - - elif self.scanning == CANCEL: - self.clean() - self.scanning = self.scanning_type - self._stop() + def _grab_mouse_events(self): + Gdk.event_handler_set(self._mouse_handler, None) + def _ungrab_mouse_events(self): + Gdk.event_handler_set(Gtk.main_do_event, None) + + def _mouse_handler(self, event, user_data): + if SettingsManager.switch_type.value != MOUSE_SWITCH_TYPE or \ + event.type not in (Gdk.EventType.BUTTON_PRESS, + Gdk.EventType.ENTER_NOTIFY): + Gtk.main_do_event(event) + return + + if self.root_window.get_window().is_visible(): + if event.type == Gdk.EventType.BUTTON_PRESS and \ + str(event.button.button) == \ + SettingsManager.mouse_button.value: + self._do_switch() + elif event.type != Gdk.EventType.ENTER_NOTIFY: + Gtk.main_do_event(event) else: - if not self.is_stop: - if self.scanning == ROW and \ - len(self.selected_row) > 0: - self.scanning = BUTTON - self.reset(BUTTON) - - elif self.scanning == BLOCK and \ - len(self.selected_block) > 0: - self.scanning = ROW - self.reset(ROW) - elif self.scanning == BUTTON and \ - self.selected_button: - self.selected_button.clicked() - self.scanning = ROW - if self.selected_button.key_type \ - == const.PREFERENCES_KEY_TYPE: - self.selected_button = None - self.stop() - else: - self.selected_button = None - self.scanning = self.scanning_type - - elif self.scanning == CANCEL: - self.scanning = self.scanning_type - self.clean() - self.reset(self.scanning_type) - - + Gtk.main_do_event(event) + + def _on_key_pressed(self, event): + if SettingsManager.switch_type.value == KEYBOARD_SWITCH_TYPE and \ + SettingsManager.keyboard_key.value == event.event_string: + self._do_switch() diff --git a/caribou/ui/window.py b/caribou/ui/window.py index 0ed5c90..d6739f1 100644 --- a/caribou/ui/window.py +++ b/caribou/ui/window.py @@ -20,36 +20,52 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import animation from caribou import data_path -import gconf -import gtk -import gtk.gdk as gdk -import opacity +from opacity import ProximityWindowBase +from caribou.common.settings_manager import SettingsManager + +from gi.repository import GConf +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Clutter import os import sys +import gobject CARIBOU_GCONF_LAYOUT_KEY = '/apps/caribou/osk/layout' CARIBOU_LAYOUT_DIR = 'keyboards' -class CaribouWindow(gtk.Window): +Clutter.init("caribou") + +class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): __gtype_name__ = "CaribouWindow" + __gproperties__ = { + 'animated-window-position' : (gobject.TYPE_PYOBJECT, 'Window position', + 'Window position in X, Y coordinates', + gobject.PARAM_READWRITE) + } def __init__(self, text_entry_mech, default_placement=None, - min_alpha=1.0, max_alpha=1.0, max_distance=100): - super(CaribouWindow, self).__init__(gtk.WINDOW_POPUP) + min_alpha=1.0, max_alpha=1.0, max_distance=100, + animation_mode=Clutter.AnimationMode.EASE_IN_QUAD): + gobject.GObject.__init__(self, type=Gtk.WindowType.POPUP) + ProximityWindowBase.__init__(self, + min_alpha=min_alpha, + max_alpha=max_alpha, + max_distance=max_distance) + self.set_name("CaribouWindow") - self._vbox = gtk.VBox() + self._vbox = Gtk.VBox() self.add(self._vbox) self.keyboard = text_entry_mech - self._vbox.pack_start(text_entry_mech) + self._vbox.pack_start(text_entry_mech, True, True, 0) self.connect("size-allocate", lambda w, a: self._update_position()) - self._gconf_client = gconf.client_get_default() + self._gconf_client = GConf.Client.get_default() - self._cursor_location = gdk.Rectangle() - self._entry_location = gdk.Rectangle() + self._cursor_location = Rectangle() + self._entry_location = Rectangle() self._default_placement = default_placement or \ CaribouWindowPlacement() @@ -59,9 +75,51 @@ class CaribouWindow(gtk.Window): self.connect('show', self._on_window_show) + # animation + self.animation_mode = animation_mode + self._stage = Clutter.Stage.get_default() + self._animation = None + + def do_get_property(self, property): + if property.name == "animated-window-position": + return self.get_position() + else: + raise AttributeError, 'unknown property %s' % property.name + + def do_set_property(self, property, value): + if property.name == "animated-window-position": + if value is not None: + x, y = value + self.move(x, y) + else: + raise AttributeError, 'unknown property %s' % property.name + + def do_animate_property(self, animation, prop_name, initial_value, + final_value, progress, gvalue): + if prop_name != "animated-window-position": return False + + ix, iy = initial_value + fx, fy = final_value + dx = int((fx - ix) * progress) + dy = int((fy - iy) * progress) + new_value = (ix + dx, iy + dy) + self.move(*new_value) + return True + + def animated_move(self, x, y): + self._animation = Clutter.Animation(object=self, + mode=self.animation_mode, + duration=250) + self._animation.bind("animated-window-position", (x, y)) + + timeline = self._animation.get_timeline() + timeline.start() + + return self._animation + def destroy(self): self.keyboard.destroy() - super(gtk.Window, self).destroy() + super(Gtk.Window, self).destroy() def set_cursor_location(self, cursor_location): @@ -77,12 +135,12 @@ class CaribouWindow(gtk.Window): self._update_position() def _get_root_bbox(self): - root_window = gdk.get_default_root_window() - args = root_window.get_position() + root_window.get_size() + root_window = Gdk.get_default_root_window() + args = root_window.get_geometry() - root_bbox = gdk.Rectangle(*args) + root_bbox = Rectangle(*args) - current_screen = gtk.gdk.screen_get_default().get_number() + current_screen = Gdk.Screen.get_default().get_number() for panel in self._gconf_client.all_dirs('/apps/panel/toplevels'): orientation = self._gconf_client.get_string(panel+'/orientation') size = self._gconf_client.get_int(panel+'/size') @@ -114,9 +172,9 @@ class CaribouWindow(gtk.Window): def _update_position(self): x, y = self._calculate_position() root_bbox = self._get_root_bbox() - proposed_position = \ - gdk.Rectangle(x, y, self.allocation.width, self.allocation.height) - + proposed_position = Rectangle(x, y, self.get_allocated_width(), + self.get_allocated_height()) + x += self._default_placement.x.adjust_to_bounds(root_bbox, proposed_position) y += self._default_placement.y.adjust_to_bounds(root_bbox, proposed_position) self.move(x, y) @@ -129,23 +187,26 @@ class CaribouWindow(gtk.Window): elif axis_placement.stickto == CaribouWindowPlacement.ENTRY: bbox = self._entry_location - offset = axis_placement.get_offset(bbox) + offset = axis_placement.get_offset(bbox.x, bbox.y) if axis_placement.align == CaribouWindowPlacement.END: - offset += axis_placement.get_length(bbox) + offset += axis_placement.get_length(bbox.width, bbox.height) if axis_placement.gravitate == CaribouWindowPlacement.INSIDE: - offset -= axis_placement.get_length(self.allocation) + offset -= axis_placement.get_length( + self.get_allocated_width(), + self.get_allocated_height()) elif axis_placement.align == CaribouWindowPlacement.START: if axis_placement.gravitate == CaribouWindowPlacement.OUTSIDE: - offset -= axis_placement.get_length(self.allocation) + offset -= axis_placement.get_length( + self.get_allocated_width(), + self.get_allocated_height()) elif axis_placement.align == CaribouWindowPlacement.CENTER: - offset += axis_placement.get_length(bbox)/2 + offset += axis_placement.get_length(bbox.width, bbox.height)/2 return offset def _get_keyboard_conf(self): - layout = self._gconf_client.get_string(CARIBOU_GCONF_LAYOUT_KEY) \ - or "qwerty" + layout = SettingsManager.layout.value conf_file_path = os.path.join(data_path, CARIBOU_LAYOUT_DIR, layout) if os.path.exists(conf_file_path): @@ -163,22 +224,20 @@ class CaribouWindow(gtk.Window): def show_all(self): - gtk.Window.show_all(self) + Gtk.Window.show_all(self) self.keyboard.show_all() - def hide_all(self): - self.keyboard.hide_all() - gtk.Window.hide_all(self) + def hide(self): + self.keyboard.hide() + Gtk.Window.hide(self) def _on_window_show(self, window): child = self.get_child() border = self.get_border_width() - w, h = child.size_request() - self.resize(w + border, h + border) + req = child.size_request() + self.resize(req.width + border, req.height + border) -class CaribouWindowDocked(CaribouWindow, - animation.AnimatedWindowBase, - opacity.ProximityWindowBase): +class CaribouWindowDocked(CaribouWindow): __gtype_name__ = "CaribouWindowDocked" def __init__(self, text_entry_mech): @@ -190,9 +249,6 @@ class CaribouWindowDocked(CaribouWindow, xgravitate=CaribouWindowPlacement.INSIDE) CaribouWindow.__init__(self, text_entry_mech, placement) - animation.AnimatedWindowBase.__init__(self) - opacity.ProximityWindowBase.__init__( - self, min_alpha=0.5, max_alpha=0.8) self.connect('map-event', self.__onmapped) @@ -201,16 +257,12 @@ class CaribouWindowDocked(CaribouWindow, def _roll_in(self): x, y = self.get_position() - self.move(x + self.allocation.width, y) + self.move(x + self.get_allocated_width(), y) return self.animated_move(x, y) def _roll_out(self): x, y = self.get_position() - return self.animated_move(x + self.allocation.width, y) - - def hide_all(self): - animation = self._roll_out() - animation.connect('completed', lambda x: CaribouWindow.hide_all(self)) + return self.animated_move(x + self.get_allocated_width(), y) def hide(self): animation = self._roll_out() @@ -227,15 +279,13 @@ class CaribouWindowEntry(CaribouWindow): xgravitate=CaribouWindowPlacement.INSIDE, ygravitate=CaribouWindowPlacement.OUTSIDE) - CaribouWindow.__init__(self, text_entry_mech, placement, min_alpha=0.075, - max_alpha=0.8) + CaribouWindow.__init__(self, text_entry_mech, placement) def _calculate_axis(self, axis_placement, root_bbox): offset = CaribouWindow._calculate_axis(self, axis_placement, root_bbox) - if axis_placement.axis == 'y': - if offset + self.allocation.height > root_bbox.height + root_bbox.y: + if offset + self.get_allocated_height() > root_bbox.height + root_bbox.y: new_axis_placement = axis_placement.copy(align=CaribouWindowPlacement.START) offset = CaribouWindow._calculate_axis(self, new_axis_placement, root_bbox) @@ -266,17 +316,20 @@ class CaribouWindowPlacement(object): stickto or self.stickto, gravitate or self.gravitate) - def get_offset(self, bbox): - return bbox.x if self.axis == 'x' else bbox.y + def get_offset(self, x, y): + return x if self.axis == 'x' else y - def get_length(self, bbox): - return bbox.width if self.axis == 'x' else bbox.height + def get_length(self, width, height): + return width if self.axis == 'x' else height def adjust_to_bounds(self, root_bbox, child_bbox): - child_vector_start = self.get_offset(child_bbox) - child_vector_end = self.get_length(child_bbox) + child_vector_start - root_vector_start = self.get_offset(root_bbox) - root_vector_end = self.get_length(root_bbox) + root_vector_start + child_vector_start = self.get_offset(child_bbox.x, child_bbox.y) + child_vector_end = \ + self.get_length(child_bbox.width, child_bbox.height) + \ + child_vector_start + root_vector_start = self.get_offset(root_bbox.x, root_bbox.y) + root_vector_end = self.get_length( + root_bbox.width, root_bbox.height) + root_vector_start if root_vector_end < child_vector_end: return root_vector_end - child_vector_end @@ -299,3 +352,23 @@ class CaribouWindowPlacement(object): ystickto or self.CURSOR, ygravitate or self.OUTSIDE) + +class Rectangle(object): + def __init__(self, x=0, y=0, width=0, height=0): + self.x = x + self.y = y + self.width = width + self.height = height + +if __name__ == "__main__": + import keyboard + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + w = CaribouWindowDocked(keyboard.CaribouKeyboard()) + w.show_all() + + try: + Gtk.main() + except KeyboardInterrupt: + Gtk.main_quit() |