Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/TextThought.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/TextThought.py')
-rw-r--r--src/TextThought.py277
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)