From 6b9c16c10aaedea38ec1d9fb8e74aae3311acd12 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 3 Dec 2009 15:43:13 -0500 Subject: Separate window placement code from the keyboard code This leaves the door open for the CaribouWindow to host things besides keyboards - so dasher, namon or any other text entry mechanism. --- src/caribou.py | 56 +++++++++-------- src/keyboard.py | 169 +-------------------------------------------------- src/window.py | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 195 deletions(-) create mode 100644 src/window.py diff --git a/src/caribou.py b/src/caribou.py index b8ebf98..4db0e28 100644 --- a/src/caribou.py +++ b/src/caribou.py @@ -20,7 +20,8 @@ import pyatspi import gtk -import keyboard +import gtk.gdk as gdk +import window import gettext import getopt import sys @@ -29,7 +30,7 @@ _ = gettext.gettext debug = False -class Test: +class Caribou: def __init__(self): self.__current_acc = None @@ -37,34 +38,35 @@ class Test: if self.__current_acc == event.source: self.__set_location(event.source) if debug == True: - print "object:text-caret-moved in", event.host_application.name, event.detail1, event.source.description + print "object:text-caret-moved in", event.host_application.name, + print event.detail1, event.source.description def __set_text_location(self, acc): text = acc.queryText() [x, y, width, height] = text.getCharacterExtents(text.caretOffset, pyatspi.DESKTOP_COORDS) - cp.set_cursor_location(gtk.gdk.Rectangle(x, y, width, height)) + caribouwindow.set_cursor_location(gdk.Rectangle(x, y, width, height)) component = acc.queryComponent() entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) - cp.set_entry_location(entry_bb) - cp.show_all() + caribouwindow.set_entry_location(entry_bb) + caribouwindow.show_all() def __set_entry_location(self, acc): text = acc.queryText() - cursor_bb = gtk.gdk.Rectangle( - *text.getCharacterExtents(text.caretOffset, + cursor_bb = gdk.Rectangle( + *text.getCharacterExtents(text.caretOffset, pyatspi.DESKTOP_COORDS)) component = acc.queryComponent() entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) - if cursor_bb == gtk.gdk.Rectangle(0, 0, 0, 0): + if cursor_bb == gdk.Rectangle(0, 0, 0, 0): cursor_bb = entry_bb - cp.set_cursor_location(cursor_bb) - cp.set_entry_location(entry_bb) + caribouwindow.set_cursor_location(cursor_bb) + caribouwindow.set_entry_location(entry_bb) - cp.show_all() + caribouwindow.show_all() def on_state_changed_focused(self, event): acc = event.source @@ -77,7 +79,7 @@ class Test: if debug == True: print "enter text widget in", event.host_application.name elif event.detail1 == 0: - cp.hide_all() + caribouwindow.hide_all() self.__current_acc = None self.__set_location = None if debug == True: @@ -91,7 +93,7 @@ class Test: if debug == True: print "enter entry widget in", event.host_application.name elif event.detail1 == 0: - cp.hide_all() + caribouwindow.hide_all() self.__current_acc = None self.__set_location = None if debug == True: @@ -103,18 +105,17 @@ class Test: # This could be a way to get the entry widget leave events. #else: # if event.detail1 == 1: - # cp.hide_all() + # caribouwindow.hide_all() # print "--> LEAVE EDITABLE TEXT <--" def on_key_down(self, event): # key binding for controling the row column scanning - # TODO: needs implementing if event.event_string == "Shift_R": + # TODO: implement keyboard scanning pass elif event.event_string == "Control_R": if debug == True: print "quitting ..." - # TODO: use for loop here? see below result = pyatspi.Registry.deregisterEventListener(self.on_text_caret_moved, "object:text-caret-moved") if debug == True: print "deregisterEventListener - object:text-caret-moved ...", @@ -129,7 +130,7 @@ class Test: print "OK" else: print "FAIL" - result = pyatspi.Registry.deregisterKeystrokeListener(self.on_key_down, mask = None, kind = (pyatspi.KEY_PRESSED_EVENT,)) + result = pyatspi.Registry.deregisterKeystrokeListener(self.on_key_down, mask=None, kind=(pyatspi.KEY_PRESSED_EVENT,)) if debug == True: print "deregisterKeystrokeListener" gtk.main_quit() @@ -148,7 +149,8 @@ def usage(): if __name__ == "__main__": try: - options, xargs = getopt.getopt(sys.argv[1:], "dhv", ["debug", "help", "version"]) + options, xargs = getopt.getopt(sys.argv[1:], "dhv", + ["debug", "help", "version"]) except getopt.GetoptError, e: print "Error: " + e.__str__() + "\n" usage() @@ -166,18 +168,14 @@ if __name__ == "__main__": print "caribou @VERSION@" sys.exit(0) - test = Test() - # TODO: make a for loop - #EVENTS = ["object:state-changed:focused", "object:text-caret-moved"] - #for f in dir(test): - # print f, isinstance(f, str) - pyatspi.Registry.registerEventListener(test.on_state_changed_focused, "object:state-changed:focused") - pyatspi.Registry.registerEventListener(test.on_text_caret_moved, "object:text-caret-moved") - pyatspi.Registry.registerKeystrokeListener(test.on_key_down, mask = None, kind = (pyatspi.KEY_PRESSED_EVENT,)) + caribou = Caribou() + pyatspi.Registry.registerEventListener(caribou.on_state_changed_focused, "object:state-changed:focused") + pyatspi.Registry.registerEventListener(caribou.on_text_caret_moved, "object:text-caret-moved") + pyatspi.Registry.registerKeystrokeListener(caribou.on_key_down, mask=None, kind=(pyatspi.KEY_PRESSED_EVENT,)) # TODO: move text entry detection to its own file - cp = keyboard.CaribouWindowEntry() - cp.hide_all() + caribouwindow = window.CaribouWindowEntry() + caribouwindow.hide_all() gtk.main() diff --git a/src/keyboard.py b/src/keyboard.py index dd55ec0..eb25335 100644 --- a/src/keyboard.py +++ b/src/keyboard.py @@ -20,13 +20,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import gtk -import gobject -import gtk.gdk as gdk -import pango import virtkey -from keyboards import qwerty -import rsvg -import cairo +import window class CaribouPredicitionArea(gtk.HBox): pass @@ -133,167 +128,9 @@ class CaribouKeyboard(gtk.Frame): self.add(data) self.show_all() -class CaribouWindow(gtk.Window): - __gtype_name__ = "CaribouWindow" - - def __init__(self, default_placement=None): - super(CaribouWindow, self).__init__(gtk.WINDOW_POPUP) - self.set_name("CaribouWindow") - - self._vbox = gtk.VBox() - self.add(self._vbox) - - self._vbox.pack_start(CaribouKeyboard(qwerty)) - - self.connect("size-allocate", lambda w, a: self._update_position()) - - self._cursor_location = gtk.gdk.Rectangle() - self._entry_location = gtk.gdk.Rectangle() - self._default_placement = default_placement or \ - CaribouKeyboardPlacement() - - def set_cursor_location(self, cursor_location): - self._cursor_location = cursor_location - self._update_position() - - def set_entry_location(self, entry_location): - self._entry_location = entry_location - self._update_position() - - def set_default_placement(self, default_placement): - self._default_placement = default_placement - self._update_position() - - def _get_root_bbox(self): - root_window = gdk.get_default_root_window() - args = root_window.get_position() + root_window.get_size() - return gdk.Rectangle(*args) - - def _calculate_position(self, placement=None): - root_bbox = self._get_root_bbox() - placement = placement or self._default_placement - - x = self._calculate_axis(placement.x, root_bbox) - y = self._calculate_axis(placement.y, root_bbox) - - - return x, y - - 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) - - 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) - - def _calculate_axis(self, axis_placement, root_bbox): - bbox = root_bbox - - if axis_placement.stickto == CaribouKeyboardPlacement.CURSOR: - bbox = self._cursor_location - elif axis_placement.stickto == CaribouKeyboardPlacement.ENTRY: - bbox = self._entry_location - - offset = axis_placement.get_offset(bbox) - - if axis_placement.align == CaribouKeyboardPlacement.END: - offset += axis_placement.get_length(bbox) - if axis_placement.gravitate == CaribouKeyboardPlacement.INSIDE: - offset -= axis_placement.get_length(self.allocation) - elif axis_placement.align == CaribouKeyboardPlacement.START: - if axis_placement.gravitate == CaribouKeyboardPlacement.OUTSIDE: - offset -= axis_placement.get_length(self.allocation) - elif axis_placement.align == CaribouKeyboardPlacement.CENTER: - offset += axis_placement.get_length(bbox)/2 - - return offset - -class CaribouWindowEntry(CaribouWindow): - __gtype_name__ = "CaribouWindowEntry" - - def __init__(self): - placement = CaribouKeyboardPlacement( - xalign=CaribouKeyboardPlacement.START, - xstickto=CaribouKeyboardPlacement.ENTRY, - ystickto=CaribouKeyboardPlacement.ENTRY, - xgravitate=CaribouKeyboardPlacement.INSIDE, - ygravitate=CaribouKeyboardPlacement.OUTSIDE) - - CaribouWindow.__init__(self, 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: - new_axis_placement = axis_placement.copy(align=CaribouKeyboardPlacement.START) - offset = CaribouWindow._calculate_axis(self, new_axis_placement, root_bbox) - - return offset - -class CaribouKeyboardPlacement(object): - START = 'start' - END = 'end' - CENTER = 'center' - - SCREEN = 'screen' - ENTRY = 'entry' - CURSOR = 'cursor' - - INSIDE = 'inside' - OUTSIDE = 'outside' - - class _AxisPlacement(object): - def __init__(self, axis, align, stickto, gravitate): - self.axis = axis - self.align = align - self.stickto = stickto - self.gravitate = gravitate - - def copy(self, align=None, stickto=None, gravitate=None): - return self.__class__(self.axis, - align or self.align, - 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_length(self, bbox): - return bbox.width if self.axis == 'x' else bbox.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 - - if root_vector_end < child_vector_end: - return root_vector_end - child_vector_end - - if root_vector_start > child_vector_start: - return root_vector_start - child_vector_start - - return 0 - - - def __init__(self, - xalign=None, xstickto=None, xgravitate=None, - yalign=None, ystickto=None, ygravitate=None): - self.x = self._AxisPlacement('x', - xalign or self.END, - xstickto or self.CURSOR, - xgravitate or self.OUTSIDE) - self.y = self._AxisPlacement('y', - yalign or self.END, - ystickto or self.CURSOR, - ygravitate or self.OUTSIDE) if __name__ == "__main__": - ckbd = CaribouWindow() - ckbd.show_all() + window = window.CaribouWindow() + window.show_all() gtk.main() diff --git a/src/window.py b/src/window.py new file mode 100644 index 0000000..cb0dbef --- /dev/null +++ b/src/window.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# +# Carbou - text entry and UI navigation application +# +# Copyright (C) 2009 Eitan Isaacson +# +# 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 gtk +import gtk.gdk as gdk +import keyboard +from keyboards import qwerty + +class CaribouWindow(gtk.Window): + __gtype_name__ = "CaribouWindow" + + def __init__(self, default_placement=None): + super(CaribouWindow, self).__init__(gtk.WINDOW_POPUP) + self.set_name("CaribouWindow") + + self._vbox = gtk.VBox() + self.add(self._vbox) + + self._vbox.pack_start(keyboard.CaribouKeyboard(qwerty)) + + self.connect("size-allocate", lambda w, a: self._update_position()) + + self._cursor_location = gdk.Rectangle() + self._entry_location = gdk.Rectangle() + self._default_placement = default_placement or \ + CaribouWindowPlacement() + + def set_cursor_location(self, cursor_location): + self._cursor_location = cursor_location + self._update_position() + + def set_entry_location(self, entry_location): + self._entry_location = entry_location + self._update_position() + + def set_default_placement(self, default_placement): + self._default_placement = default_placement + self._update_position() + + def _get_root_bbox(self): + root_window = gdk.get_default_root_window() + args = root_window.get_position() + root_window.get_size() + return gdk.Rectangle(*args) + + def _calculate_position(self, placement=None): + root_bbox = self._get_root_bbox() + placement = placement or self._default_placement + + x = self._calculate_axis(placement.x, root_bbox) + y = self._calculate_axis(placement.y, root_bbox) + + return x, y + + 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) + + 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) + + def _calculate_axis(self, axis_placement, root_bbox): + bbox = root_bbox + + if axis_placement.stickto == CaribouWindowPlacement.CURSOR: + bbox = self._cursor_location + elif axis_placement.stickto == CaribouWindowPlacement.ENTRY: + bbox = self._entry_location + + offset = axis_placement.get_offset(bbox) + + if axis_placement.align == CaribouWindowPlacement.END: + offset += axis_placement.get_length(bbox) + if axis_placement.gravitate == CaribouWindowPlacement.INSIDE: + offset -= axis_placement.get_length(self.allocation) + elif axis_placement.align == CaribouWindowPlacement.START: + if axis_placement.gravitate == CaribouWindowPlacement.OUTSIDE: + offset -= axis_placement.get_length(self.allocation) + elif axis_placement.align == CaribouWindowPlacement.CENTER: + offset += axis_placement.get_length(bbox)/2 + + return offset + +class CaribouWindowEntry(CaribouWindow): + __gtype_name__ = "CaribouWindowEntry" + + def __init__(self): + placement = CaribouWindowPlacement( + xalign=CaribouWindowPlacement.START, + xstickto=CaribouWindowPlacement.ENTRY, + ystickto=CaribouWindowPlacement.ENTRY, + xgravitate=CaribouWindowPlacement.INSIDE, + ygravitate=CaribouWindowPlacement.OUTSIDE) + + CaribouWindow.__init__(self, 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: + new_axis_placement = axis_placement.copy(align=CaribouWindowPlacement.START) + offset = CaribouWindow._calculate_axis(self, new_axis_placement, root_bbox) + + return offset + +class CaribouWindowPlacement(object): + START = 'start' + END = 'end' + CENTER = 'center' + + SCREEN = 'screen' + ENTRY = 'entry' + CURSOR = 'cursor' + + INSIDE = 'inside' + OUTSIDE = 'outside' + + class _AxisPlacement(object): + def __init__(self, axis, align, stickto, gravitate): + self.axis = axis + self.align = align + self.stickto = stickto + self.gravitate = gravitate + + def copy(self, align=None, stickto=None, gravitate=None): + return self.__class__(self.axis, + align or self.align, + 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_length(self, bbox): + return bbox.width if self.axis == 'x' else bbox.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 + + if root_vector_end < child_vector_end: + return root_vector_end - child_vector_end + + if root_vector_start > child_vector_start: + return root_vector_start - child_vector_start + + return 0 + + + def __init__(self, + xalign=None, xstickto=None, xgravitate=None, + yalign=None, ystickto=None, ygravitate=None): + self.x = self._AxisPlacement('x', + xalign or self.END, + xstickto or self.CURSOR, + xgravitate or self.OUTSIDE) + self.y = self._AxisPlacement('y', + yalign or self.END, + ystickto or self.CURSOR, + ygravitate or self.OUTSIDE) + -- cgit v1.2.1