diff options
Diffstat (limited to 'src/TextThought.py')
-rw-r--r-- | src/TextThought.py | 277 |
1 files changed, 198 insertions, 79 deletions
diff --git a/src/TextThought.py b/src/TextThought.py index 1fef255..d376da0 100644 --- a/src/TextThought.py +++ b/src/TextThought.py @@ -20,7 +20,14 @@ # Boston, MA 02110-1301 USA # +# In order to support an on-screen keyboard, a textview widget is used +# instead of capturing individual keyboard events. The down side to +# this is that the maintenance of text attributes is mangled. The good +# news is that much of the complexity disappears. +# --Walter Bender <walter@sugarlabs.org> 2013 + import gtk +import gobject import pango import utils import os @@ -37,7 +44,7 @@ UNDO_REMOVE_ATTR=66 UNDO_REMOVE_ATTR_SELECTION=67 class TextThought (ResizableThought): - def __init__ (self, coords, pango_context, thought_number, save, undo, loading, background_color, foreground_color, name="thought"): + def __init__ (self, coords, pango_context, thought_number, save, undo, loading, background_color, foreground_color, name="thought", fixed=None, parent=None): super (TextThought, self).__init__(coords, save, name, undo, background_color, foreground_color) self.index = 0 @@ -56,6 +63,10 @@ class TextThought (ResizableThought): self.current_attrs = [] self.double_click = False self.orig_text = None + self._parent = parent + self._fixed = fixed + self.textview = None + self._textview_handler = None if prefs.get_direction () == gtk.TEXT_DIR_LTR: self.pango_context.set_base_dir (pango.DIRECTION_LTR) @@ -71,7 +82,7 @@ class TextThought (ResizableThought): self.all_okay = True - def index_from_bindex (self, bindex): + def index_from_bindex (self, bindex): if bindex == 0: return 0 index = 0 @@ -102,7 +113,7 @@ class TextThought (ResizableThought): self.attrlist = pango.AttrList () # TODO: splice instead of own method it = self.attributes.get_iterator() - + while 1: at = it.get_attrs() for x in at: @@ -123,7 +134,7 @@ class TextThought (ResizableThought): self.attrlist.splice(ins_style, self.index, len(ins_text)) else: show_text = self.text - + it = self.attributes.get_iterator() while(1): found = False @@ -199,13 +210,13 @@ class TextThought (ResizableThought): self.emit("update-attrs", bold, italics, underline, pango_font) return show_text - def recalc_text_edges (self): + def recalc_text_edges (self): if (not hasattr(self, "layout")): return del self.layout - + show_text = self.attrs_changed () - + r,g,b = utils.selected_colors["fill"] r *= 65536 g *= 65536 @@ -246,12 +257,12 @@ class TextThought (ResizableThought): self.ul = (self.lr[0] - margin[0] - margin[2] - text_w, tmp1) """ - def recalc_edges (self): + def recalc_edges (self): self.lr = (self.ul[0]+self.width, self.ul[1]+self.height) if not self.creating: self.recalc_text_edges() - def commit_text (self, context, string, mode, font_combo_box, font_sizes_combo_box): + def commit_text (self, context, string, mode, font_combo_box, font_sizes_combo_box): font_name = font_combo_box.get_active_text() font_size = font_sizes_combo_box.get_active_text() self.set_font(font_name, font_size) @@ -260,7 +271,7 @@ class TextThought (ResizableThought): self.emit ("title_changed", self.text) self.emit ("update_view") - def add_text (self, string): + def add_text (self, string): if self.index > self.end_index: left = self.text[:self.end_index] right = self.text[self.index:] @@ -276,12 +287,12 @@ class TextThought (ResizableThought): bleft = self.bytes[:self.b_f_i (self.index)] bright = self.bytes[self.b_f_i (self.end_index):] change = self.index - self.end_index + len(string) - else: + else: left = self.text[:self.index] right = self.text[self.index:] bleft = self.bytes[:self.b_f_i(self.index)] bright = self.bytes[self.b_f_i(self.index):] - change = len(string) + change = len(string) it = self.attributes.get_iterator() changes= [] @@ -317,7 +328,7 @@ class TextThought (ResizableThought): changes.append(x) if it.next() == False: break - + del self.attributes self.attributes = pango.AttrList() for x in changes: @@ -329,14 +340,14 @@ class TextThought (ResizableThought): self.index += len (string) self.bytes = bleft + str(len(string)) + bright self.bindex = self.b_f_i (self.index) - self.end_index = self.index + self.end_index = self.index - def draw (self, context): + def draw (self, context): self.recalc_edges () ResizableThought.draw(self, context) if self.creating: return - + (textx, texty) = (self.min_x, self.min_y) if self.am_primary: r, g, b = utils.primary_colors["text"] @@ -364,14 +375,21 @@ class TextThought (ResizableThought): context.set_source_rgb (0,0,0) context.stroke () - def process_key_press (self, event, mode): + def process_key_press (self, event, mode): + # Since we are using textviews, we don't use the + # keypress code anymore + if not self.editing: + return False + else: + return True + modifiers = gtk.accelerator_get_default_mod_mask () shift = event.state & modifiers == gtk.gdk.SHIFT_MASK handled = True clear_attrs = True if not self.editing: return False - + if (event.state & modifiers) & gtk.gdk.CONTROL_MASK: if event.keyval == gtk.keysyms.a: self.index = self.bindex = 0 @@ -410,7 +428,7 @@ class TextThought (ResizableThought): else: handled = False - if clear_attrs: + if clear_attrs: del self.current_attrs self.current_attrs = [] @@ -422,7 +440,7 @@ class TextThought (ResizableThought): return handled - def undo_text_action (self, action, mode): + def undo_text_action (self, action, mode): self.undo.block () if action.undo_type == UndoManager.DELETE_LETTER or action.undo_type == UndoManager.DELETE_WORD: real_mode = not mode @@ -442,7 +460,7 @@ class TextThought (ResizableThought): self.add_text (action.text) self.rebuild_byte_table () self.bindex = self.b_f_i (self.index) - + del self.attributes self.attributes = pango.AttrList() map(lambda a : self.attributes.change(a), attrs) @@ -452,7 +470,7 @@ class TextThought (ResizableThought): self.emit ("grab_focus", False) self.undo.unblock () - def delete_char (self): + def delete_char (self): if self.index == self.end_index == len (self.text): return if self.index > self.end_index: @@ -477,7 +495,7 @@ class TextThought (ResizableThought): changes= [] old_attrs = [] accounted = -change - + it = self.attributes.get_iterator() while (1): (start,end) = it.range() @@ -516,11 +534,11 @@ class TextThought (ResizableThought): changes.append(x) if it.next() == False: break - + del self.attributes self.attributes = pango.AttrList() map(lambda a : self.attributes.change(a), changes) - + self.undo.add_undo (UndoManager.UndoAction (self, UndoManager.DELETE_LETTER, self.undo_text_action, self.b_f_i (self.index), local_text, len(local_text), local_bytes, old_attrs, changes)) @@ -528,7 +546,7 @@ class TextThought (ResizableThought): self.bytes = bleft+bright self.end_index = self.index - def backspace_char (self): + def backspace_char (self): if self.index == self.end_index == 0: return if self.index > self.end_index: @@ -594,12 +612,11 @@ class TextThought (ResizableThought): changes.append(x) if it.next() == False: break - - + del self.attributes self.attributes = pango.AttrList() map(lambda a : self.attributes.change(a), changes) - + self.text = left+right self.bytes = bleft+bright self.end_index = self.index @@ -609,7 +626,7 @@ class TextThought (ResizableThought): if self.index < 0: self.index = 0 - def move_index_back (self, mod): + def move_index_back (self, mod): if self.index <= 0: self.index = 0 return @@ -617,7 +634,7 @@ class TextThought (ResizableThought): if not mod: self.end_index = self.index - def move_index_forward (self, mod): + def move_index_forward (self, mod): if self.index >= len(self.text): self.index = len(self.text) return @@ -625,7 +642,7 @@ class TextThought (ResizableThought): if not mod: self.end_index = self.index - def move_index_up (self, mod): + def move_index_up (self, mod): tmp = self.text.decode () lines = tmp.splitlines () if len (lines) == 1: @@ -659,7 +676,7 @@ class TextThought (ResizableThought): if not mod: self.end_index = self.index - def move_index_down (self, mod): + def move_index_down (self, mod): tmp = self.text.decode () lines = tmp.splitlines () if len (lines) == 1: @@ -683,7 +700,7 @@ class TextThought (ResizableThought): if not mod: self.end_index = self.index - def move_index_horizontal(self, mod, home=False): + def move_index_horizontal(self, mod, home=False): lines = self.text.splitlines () loc = 0 line = 0 @@ -698,12 +715,18 @@ class TextThought (ResizableThought): return line += 1 - def process_button_down (self, event, coords): + def process_button_down (self, event, coords): + if not self._parent.move_mode and self.textview is None: + self._create_textview() + if self.textview is not None: + self.textview.grab_focus() + if ResizableThought.process_button_down(self, event, coords): return True - if not self.editing: - return False + # With textview, we are always editing + # if not self.editing: + # return False modifiers = gtk.accelerator_get_default_mod_mask () @@ -722,6 +745,7 @@ class TextThought (ResizableThought): self.index = len(self.text) self.end_index = 0 # and mark all self.double_click = True + elif event.button == 2: x = int ((coords[0] - self.min_x)*pango.SCALE) y = int ((coords[1] - self.min_y)*pango.SCALE) @@ -734,15 +758,87 @@ class TextThought (ResizableThought): if os.name != 'nt': clip = gtk.Clipboard (selection="PRIMARY") self.paste_text (clip) - + del self.current_attrs - self.current_attrs = [] + self.current_attrs = [] self.recalc_edges() self.emit ("update_view") + logging.debug('calling selection_changed') self.selection_changed() - def process_button_release (self, event, transformed): + def _create_textview(self): + # When the button is pressed inside a text thought, + # create a textview (necessary for invoking the + # on-screen keyboard) instead of processing the text + # by grabbing keyboard events. + if self.textview is None: + self.textview = gtk.TextView() + margin = utils.margin_required (utils.STYLE_NORMAL) + x, y, w, h = self._textview_rescale() + self.textview.set_size_request(w, h) + self._fixed.put(self.textview, x, y) + self.textview.set_justification(gtk.JUSTIFY_CENTER) + self.textview.modify_font( + pango.FontDescription(utils.default_font)) + self.textview.get_buffer().set_text(self.text) + self.textview.show() + if self._textview_handler is None: + self._textview_handler = self.textview.connect( + 'focus-out-event', self._textview_focus_out_cb) + ''' + self.copy_handler = self.textview.connect( + 'copy-clipboard', self._textview_copy_cb) + self.paste_handler = self.textview.connect( + 'paste-clipboard', self._textview_paste_cb) + self.select_handler = self.textview.connect( + 'select-all', self._textview_select_cb) + ''' + self.textview.grab_focus() + self._fixed.show() + + def _textview_rescale(self): + margin = utils.margin_required(utils.STYLE_NORMAL) + w = int((self.width - margin[0] - margin[2]) \ + * self._parent.scale_fac) + h = int((self.height - margin[1] - margin[3]) \ + * self._parent.scale_fac) + xo = gtk.gdk.screen_width() \ + * (1. - self._parent.scale_fac) / 2. + yo = gtk.gdk.screen_height() \ + * (1. - self._parent.scale_fac) / 2. + x = (self.ul[0] + margin[0]) * self._parent.scale_fac + y = (self.ul[1] + margin[1]) * self._parent.scale_fac + return int(x + xo), int(y + yo), int(w), int(h) + + def _textview_copy_cb(self, widget=None, event=None): + logging.debug('copy') + return False + + def _textview_paste_cb(self, widget=None, event=None): + logging.debug('paste') + return False + + def _textview_select_cb(self, widget=None, event=None): + logging.debug('select all') + return False + + def _textview_focus_out_cb(self, widget=None, event=None): + self._textview_process() + return False + + def _textview_process(self): + self.index = 0 + self.end_index = len(self.text) + self.delete_char() + bounds = self.textview.get_buffer().get_bounds() + self.add_text(self.textview.get_buffer().get_text( + bounds[0], bounds[1], True)) + self.emit ("title_changed", self.text) + self.emit ("update_view") + return False + + def process_button_release (self, event, transformed): if self.orig_size: if self.creating: orig_size = self.width >= MIN_SIZE or self.height >= MIN_SIZE @@ -757,15 +853,21 @@ class TextThought (ResizableThought): self.double_click = False return ResizableThought.process_button_release(self, event, transformed) - def selection_changed (self): + def selection_changed (self): (start, end) = (min(self.index, self.end_index), max(self.index, self.end_index)) + logging.debug('emit text_selection_changed') self.emit ("text_selection_changed", start, end, self.text[start:end]) - def handle_motion (self, event, transformed): + def handle_motion (self, event, transformed): if ResizableThought.handle_motion(self, event, transformed): self.recalc_edges() return True + if self.textview is not None and self.ul is not None: + x, y, w, h = self._textview_rescale() + self.textview.set_size_request(w, h) + self._fixed.move(self.textview, x, y) + if not self.editing or self.resizing: return False @@ -784,7 +886,7 @@ class TextThought (ResizableThought): return False - def export (self, context, move_x, move_y): + def export (self, context, move_x, move_y): utils.export_thought_outline (context, self.ul, self.lr, self.background_color, self.am_selected, self.am_primary, utils.STYLE_NORMAL, (move_x, move_y)) @@ -867,7 +969,7 @@ class TextThought (ResizableThought): if not it.next(): break - def rebuild_byte_table (self): + def rebuild_byte_table (self): # Build the Byte table del self.bytes self.bytes = '' @@ -941,12 +1043,14 @@ class TextThought (ResizableThought): self.recalc_edges() def copy_text (self, clip): + logging.debug('copy text') if self.end_index > self.index: clip.set_text (self.text[self.index:self.end_index]) else: clip.set_text (self.text[self.end_index:self.index]) def cut_text (self, clip): + logging.debug('cut text') if self.end_index > self.index: clip.set_text (self.text[self.index:self.end_index]) else: @@ -958,6 +1062,7 @@ class TextThought (ResizableThought): self.emit ("update_view") def paste_text (self, clip): + logging.debug('paste text') text = clip.wait_for_text() if not text: return @@ -968,7 +1073,7 @@ class TextThought (ResizableThought): self.bindex = self.bindex_from_index (self.index) self.emit ("update_view") - def delete_surroundings(self, imcontext, offset, n_chars, mode): + def delete_surroundings(self, imcontext, offset, n_chars, mode): # TODO: Add in Attr stuff orig = len(self.text) left = self.text[:offset] @@ -979,7 +1084,7 @@ class TextThought (ResizableThought): new = len(self.text) if self.index > len(self.text): self.index = len(self.text) - + change = old - new changes = [] old_attrs = [] @@ -1025,7 +1130,7 @@ class TextThought (ResizableThought): changes.append(x) if it.next() == False: break - + del self.attributes self.attributes = pango.AttrList() map(lambda x : self.attributes.change(x), changes) @@ -1037,14 +1142,14 @@ class TextThought (ResizableThought): self.bindex = self.bindex_from_index (self.index) self.emit ("update_view") - def preedit_changed (self, imcontext, mode): + def preedit_changed (self, imcontext, mode): self.preedit = imcontext.get_preedit_string () if self.preedit[0] == '': self.preedit = None self.recalc_edges () self.emit ("update_view") - def retrieve_surroundings (self, imcontext, mode): + def retrieve_surroundings (self, imcontext, mode): imcontext.set_surrounding (self.text, -1, self.bindex) return True @@ -1071,18 +1176,18 @@ class TextThought (ResizableThought): self.recalc_edges() self.emit("update_view") self.undo.unblock() - - def create_attribute(self, attribute, start, end): + + def create_attribute(self, attribute, start, end): if attribute == 'bold': return pango.AttrWeight(pango.WEIGHT_BOLD, start, end) elif attribute == 'italic': return pango.AttrStyle(pango.STYLE_ITALIC, start, end) elif attribute == 'underline': return pango.AttrUnderline(pango.UNDERLINE_SINGLE, start, end) - - def set_attribute(self, active, attribute): - if not self.editing: - return + + def set_attribute(self, active, attribute): + # if not self.editing: + # return if attribute == 'bold': pstyle, ptype, pvalue = (pango.WEIGHT_NORMAL, pango.ATTR_WEIGHT, pango.WEIGHT_BOLD) @@ -1090,14 +1195,14 @@ class TextThought (ResizableThought): pstyle, ptype, pvalue = (pango.STYLE_NORMAL, pango.ATTR_STYLE, pango.STYLE_ITALIC) elif attribute == 'underline': pstyle, ptype, pvalue = (pango.UNDERLINE_NONE, pango.ATTR_UNDERLINE, pango.UNDERLINE_SINGLE) - + index, end_index = (self.index, self.end_index) init = min(index, end_index) end = max(index, end_index) if not active: attr = pango.AttrStyle(pstyle, init, end) - #if index == end_index: + #if index == end_index: # self.current_attrs.change(attr) #else: self.attributes.change(attr) @@ -1116,11 +1221,11 @@ class TextThought (ResizableThought): self.undo_attr_cb,\ attr)) return - + it = self.attributes.get_iterator() old_attrs = self.attributes.copy() changed = [] - + while True: r = it.range() if r[0] <= init and r[1] >= end: @@ -1135,7 +1240,7 @@ class TextThought (ResizableThought): if not it.next(): break - + del self.attributes self.attributes = pango.AttrList() map(lambda x : self.attributes.change(x), changed) @@ -1149,15 +1254,15 @@ class TextThought (ResizableThought): old_attrs, self.attributes.copy())) else: - if index == end_index: + if index == end_index: attr = self.create_attribute(attribute, index, end_index) self.undo.add_undo(UndoManager.UndoAction(self, UNDO_ADD_ATTR, self.undo_attr_cb, attr)) self.current_attrs.append(attr) #self.attributes.insert(attr) - else: - attr = self.create_attribute(attribute, init, end) + else: + attr = self.create_attribute(attribute, init, end) old_attrs = self.attributes.copy() self.attributes.change(attr) self.undo.add_undo(UndoManager.UndoAction(self, UNDO_ADD_ATTR_SELECTION, @@ -1165,34 +1270,36 @@ class TextThought (ResizableThought): old_attrs, self.attributes.copy())) self.recalc_edges() + self.remove_textview() - def set_bold (self, active): + def set_bold (self, active): self.set_attribute(active, 'bold') - - def set_italics (self, active): + + def set_italics (self, active): self.set_attribute(active, 'italic') - def set_underline (self, active): + def set_underline (self, active): self.set_attribute(active, 'underline') - def set_font (self, font_name, font_size): - if not self.editing: - return - + def set_font (self, font_name, font_size): + # With textview, we are always editing + # if not self.editing: + # return + start = min(self.index, self.end_index) end = max(self.index, self.end_index) - - pango_font = pango.FontDescription(font_name+" "+font_size) - + + pango_font = pango.FontDescription(font_name+" "+font_size) + attr = pango.AttrFontDesc (pango_font, start, end) if start == end: self.undo.add_undo(UndoManager.UndoAction(self, UNDO_ADD_ATTR, self.undo_attr_cb, attr)) - try: + try: self.current_attrs.change(attr) - except AttributeError: + except AttributeError: self.current_attrs.append(attr) else: old_attrs = self.attributes.copy() @@ -1203,19 +1310,31 @@ class TextThought (ResizableThought): self.attributes.copy())) self.recalc_edges() - def inside(self, inside): + def inside(self, inside): + # FIXME: with switch to textview, we don't need cursor update if self.editing: + if self.textview is not None: + self.textview.grab_focus() self.emit ("change_mouse_cursor", gtk.gdk.XTERM) else: ResizableThought.inside(self, inside) - def enter(self): + def enter(self): if self.editing: return self.orig_text = self.text self.editing = True - def leave(self): + def remove_textview(self): + if self.textview is not None: + self._textview_process() + self._textview_handler = None + self.textview.hide() + self.textview.destroy() + self.textview = None + + def leave(self): + self.remove_textview() if not self.editing: return ResizableThought.leave(self) |