Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsimpoir <simpoir@Luyten.local>2009-03-14 22:59:34 (GMT)
committer simpoir <simpoir@Luyten.local>2009-03-14 22:59:34 (GMT)
commitd8c59c1a13663ed0d09784ca2a37fcf29ceb91ec (patch)
treeae5db4c947a368505528596c3a7003268228fe17 /src
parentadd05c487de3aec42801d39220a8f1f969e7c38a (diff)
Functionnal overlay
Conflicts: source/external/source/sugar-toolkit/src/sugar/tutorius/Makefile.am source/external/source/sugar-toolkit/src/sugar/tutorius/actions.py Conflicts: source/activities/Writus.activity/TAbiWordActivity.py
Diffstat (limited to 'src')
-rw-r--r--src/sugar/graphics/window.py10
-rw-r--r--src/sugar/tutorius/Makefile.am3
-rw-r--r--src/sugar/tutorius/actions.py51
-rw-r--r--src/sugar/tutorius/gtkutils.py11
-rw-r--r--src/sugar/tutorius/overlayer.py308
5 files changed, 377 insertions, 6 deletions
diff --git a/src/sugar/graphics/window.py b/src/sugar/graphics/window.py
index 1ad2bca..a3006a6 100644
--- a/src/sugar/graphics/window.py
+++ b/src/sugar/graphics/window.py
@@ -23,6 +23,7 @@ import gobject
import gtk
from sugar.graphics.icon import Icon
+from sugar.tutorius.overlayer import Overlayer
class UnfullscreenButton(gtk.Window):
@@ -97,9 +98,16 @@ class Window(gtk.Window):
self._hbox.pack_start(self._event_box)
self._event_box.show()
- self.add(self._vbox)
+## self.add(self._vbox)
self._vbox.show()
+ self._overlayer = Overlayer()
+## self._overlayer.inject(self._vbox)
+ self._overlayer.put(self._vbox, 0, 0)
+ self._overlayer._Overlayer__overlayed = self._vbox
+ self.add(self._overlayer)
+ self._overlayer.show()
+
self._is_fullscreen = False
self._unfullscreen_button = UnfullscreenButton()
self._unfullscreen_button.set_transient_for(self)
diff --git a/src/sugar/tutorius/Makefile.am b/src/sugar/tutorius/Makefile.am
index 43cb622..6fd32c7 100644
--- a/src/sugar/tutorius/Makefile.am
+++ b/src/sugar/tutorius/Makefile.am
@@ -6,4 +6,5 @@ sugar_PYTHON = \
actions.py \
gtkutils.py \
filters.py \
- services.py
+ services.py \
+ overlayer.py
diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py
index 34802c3..7ecf86b 100644
--- a/src/sugar/tutorius/actions.py
+++ b/src/sugar/tutorius/actions.py
@@ -17,7 +17,9 @@
This module defines Actions that can be done and undone on a state
"""
+from sugar.tutorius import gtkutils
from dialog import TutoriusDialog
+import overlayer
class Action(object):
@@ -25,7 +27,7 @@ class Action(object):
def __init__(self):
object.__init__(self)
- def do(self):
+ def do(self, **kwargs):
"""
Perform the action
"""
@@ -58,7 +60,7 @@ class OnceWrapper(object):
self._called = True
self._action.do()
self._need_undo = True
-
+
def undo(self):
"""
Undo the action if it's been done
@@ -97,5 +99,48 @@ class DialogMessage(Action):
if self._dialog:
self._dialog.destroy()
self._dialog = None
-
+
+
+class BubbleMessage(Action):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param message A string to display to the user
+ @param pos A list of the form [x, y]
+ @param speaker treeish representation of the speaking widget
+ """
+ def __init__(self, message, pos=[0,0], speaker=None, tailpos=None):
+ Action.__init__(self)
+ self._message = message
+ self.position = pos
+
+ self.overlay = None
+ self._bubble = None
+ self._speaker = None
+ self._tailpos = tailpos
+
+
+ def do(self):
+ """
+ Show the dialog
+ """
+ # get or inject overlayer
+ self.overlay = gtkutils.activity()._overlayer #FIXME:handle subwin
+
+ if not self._bubble:
+ x, y = self.position
+ self._bubble = overlayer.TextBubble(text=self._message, tailpos=self._tailpos) #TODO: add tail
+ self._bubble.show()
+ self.overlay.put(self._bubble, x, y)
+ self.overlay.queue_draw()
+
+ def undo(self):
+ """
+ Destroy the dialog
+ """
+ if self._bubble:
+## self.overlay.remove(self._bubble)
+## self.overlay.bin_window.clear()
+ self._bubble.destroy()
+ self._bubble = None
diff --git a/src/sugar/tutorius/gtkutils.py b/src/sugar/tutorius/gtkutils.py
index 7196469..450872d 100644
--- a/src/sugar/tutorius/gtkutils.py
+++ b/src/sugar/tutorius/gtkutils.py
@@ -18,6 +18,12 @@
Utility classes and functions that are gtk related
"""
+def activity(activity=None, singleton=[]):
+ if activity:
+ singleton.append(activity)
+ return singleton[0]
+
+
def find_widget(base, target_fqdn):
"""Find a widget by digging into a parent widget's children tree
@param base the parent widget
@@ -39,7 +45,10 @@ def find_widget(base, target_fqdn):
path.pop(0)
while len(path) > 0:
- obj = obj.get_children()[int(path.pop(0))]
+ try:
+ obj = obj.get_children()[int(path.pop(0))]
+ except:
+ break
return obj
diff --git a/src/sugar/tutorius/overlayer.py b/src/sugar/tutorius/overlayer.py
new file mode 100644
index 0000000..0da1841
--- /dev/null
+++ b/src/sugar/tutorius/overlayer.py
@@ -0,0 +1,308 @@
+"""
+This guy manages drawing of overlayed widgets. The class responsible for drawing
+management (Overlayer) and overlayable widgets are defined here.
+"""
+# Copyright (C) 2009, Tutorius.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+import gtk
+import cairo
+import pangocairo
+
+class CanvasDrawable(object):
+ """Defines the CanvasDrawable protocol"""
+ should_expose = None
+ def drawWithContext(self, context): pass
+
+
+class Overlayer(gtk.Layout):
+ """
+ This guy manages drawing of overlayed widgets. Those can be standard GTK
+ widgets or special "cairoDrawable" widgets which support the defined
+ interface (see the put method).
+ """
+ def __init__(self):
+ gtk.Layout.__init__(self)
+
+ # no overlayed child yet
+ self.__overlayed = None
+
+## win.connect("expose-event", self.__transparent_overlay)
+ self.__realizer = self.connect("expose-event", self.__init_realized)
+ self.connect("size-allocate", self.__size_allocate)
+ self.show()
+
+## win.show_all()
+
+## win.connect_after("expose-event", self.__expose_overlay)
+
+ def reparent(self, parent):
+ """Reparent this widget as child of parent instead."""
+ self.__realizer = self.connect("expose-event", self.__init_realized)
+ gtk.Layout.reparent(self, parent)
+
+ def inject(self, child):
+ """
+ Insert this widget at "child" level so that it can overlay "child".
+ """
+ assert not self.__overlayed, "trying to reinject while already injected"
+ parent = child.parent
+ self.__overlayed = child
+ child.reparent(self)
+ parent.add(self)
+ self.__realizer = parent.connect("expose-event", self.__init_realized)
+
+ def put(self, child, x, y):
+ """
+ Adds a child widget to be overlayed. This can be, overlay widgets or
+ normal GTK widgets (though normal widgets will alwas appear under
+ cairo widgets due to the rendering chain).
+ """
+ if hasattr(child, "should_expose"):
+ # if the widget has the CanvasDrawable protocol, use it.
+ # protocol is defined as
+ # should_expose property with setter to flag as not drawn using
+ # expose-events
+ # drawWithContext(context) method to pass a context to draw with.
+ child.should_expose = True
+ gtk.Layout.put(self, child, x, y)
+
+ def eject(self):
+ """
+ Remove this widget from the widget hierarchy without affecting its
+ children.
+ """
+ parent = self.parent
+ parent.disconnect(self.__render_handle)
+ assert hasattr(parent, "remove"), "parent should be a container"
+ self.parent.remove(self)
+ self.__overlayed.reparent(parent)
+ self.__overlayed = None
+
+ def __init_realized(self, widget, event):
+ """
+ Initializer to set once widget is realized.
+ Since an expose event is signaled only to realized widgets, we set this
+ callback for the first expose run. It should also be called after
+ beign reparented to ensure the window used for drawing is set up.
+ """
+ assert hasattr(self.window, "set_composited"), \
+ "compositing not supported or widget not realized."
+ self.disconnect(self.__realizer)
+ del self.__realizer
+
+ # use RGBA on overlayer so that when we paint we don't cover what's
+ # under
+ screen = self.get_screen()
+ rgba_map = screen.get_rgba_colormap()
+ self.set_colormap(rgba_map) # will fail if already realized
+ # app paintable will ensure that what we draw isn't erased by gtk
+ self.parent.set_app_paintable(True)
+
+ # the parent is composited, so we can access gtk's rendered buffer
+ # and overlay over. If we don't composite, we won't be able to read
+ # pixels and background will be black.
+ self.window.set_composited(True)
+ self.__render_handle = self.parent.connect_after("expose-event", \
+ self.__expose_overlay)
+
+ def __expose_overlay(self, widget, event):
+ """expose event handler to draw the thing."""
+ #get our child (in this case, the event box)
+ child = widget.get_child()
+
+ #create a cairo context to draw to the window
+ ctx = widget.window.cairo_create()
+
+ #the source data is the (composited) event box
+ ctx.set_source_pixmap(child.window,
+ child.allocation.x,
+ child.allocation.y)
+
+ #draw no more than our expose event intersects our child
+## region = gtk.gdk.region_rectangle(child.allocation)
+## rect = gtk.gdk.region_rectangle(event.area)
+## region.intersect(rect)
+## ctx.region (region)
+## ctx.clip()
+
+ ctx.set_operator(cairo.OPERATOR_OVER)
+ # has to be blended and a 1.0 alpha would not make it blend
+ ctx.paint_with_alpha(0.99)
+
+ #draw overlay
+ for drawn_child in self.get_children():
+ if hasattr(drawn_child, "drawWithContext"):
+ drawn_child.drawWithContext(ctx)
+
+
+ def __size_allocate(self, widget, allocation):
+ """set size allocation (actual gtk widget size) and propagate it to overlayed child"""
+ self.allocation = allocation
+ # One may wonder why using size_request instead of size_allocate;
+ # Since widget is laid out in a Layout box, the Layout will honor the
+ # requested size. Using size_allocate could make a nasty nested loop in
+ # some cases.
+ self.__overlayed.set_size_request(allocation.width, allocation.height)
+
+
+class TextBubble(gtk.Widget):
+ def __init__(self, text, speaker=None, tailpos=None):
+ """
+ Creates a new cairo rendered text bubble.
+
+ @param text the text to render in the bubble
+ @param speaker the widget to associate with the bubble tail
+ @param tailpos an optional position for the tail if no speaker
+ """
+ gtk.Widget.__init__(self)
+
+ ##self.set_app_paintable(True) # else may be blank
+ # FIXME ensure previous call does not interfere with widget stacking
+ self.label = text
+ self.speaker = speaker
+ self.tailpos = tailpos
+ self.lineWidth = 5
+
+ self.connect("expose-event", self.__on_expose)
+
+ def drawWithContext(self, context):
+ """
+ Draw using the passed cairo context instead of creating a new cairo
+ context. This eases blending between multiple cairo-rendered
+ widgets.
+ """
+ context.translate(self.allocation.x, self.allocation.y)
+ width = self.allocation.width
+ height = self.allocation.height
+ xradius = width/2
+ yradius = height/2
+ width -= self.lineWidth
+ height -= self.lineWidth
+
+ # TODO fetch speaker coordinates
+
+ # draw bubble tail
+ if self.tailpos:
+## speaker_alloc = self.speaker.get_allocation()
+ context.move_to(xradius-40, yradius)
+ context.line_to(self.tailpos[0], self.tailpos[1])
+## context.line_to(speaker_alloc.x-self.allocation.x,
+## speaker_alloc.y - self.allocation.y)
+ context.line_to(xradius+40, yradius)
+ context.set_source_rgb(1.0, 1.0, 0.0)
+ context.fill_preserve()
+ context.set_line_width(self.lineWidth)
+ context.set_source_rgb(0.0, 0.0, 0.0)
+ context.stroke()
+
+ # bubble
+ context.move_to(self.lineWidth, yradius)
+ context.curve_to(self.lineWidth, self.lineWidth,
+ self.lineWidth, self.lineWidth, xradius, self.lineWidth)
+ context.curve_to(width, self.lineWidth,
+ width, self.lineWidth, width, yradius)
+ context.curve_to(width, height, width, height, xradius, height)
+ context.curve_to(self.lineWidth, height,
+ self.lineWidth, height, self.lineWidth, yradius)
+ context.set_source_rgb(1.0, 1.0, 0.0)
+ context.fill_preserve()
+ context.set_line_width(self.lineWidth)
+ context.set_source_rgb(0.0, 0.0, 0.0)
+ context.stroke()
+
+ # text
+ # FIXME create layout in realize method
+ pangoctx = pangocairo.CairoContext(context)
+ text_layout = pangoctx.create_layout()
+ text_layout.set_text(self.__label)
+ pangoctx.move_to(int((self.allocation.width-self.__text_dimentions[0])/2),
+ int((self.allocation.height-self.__text_dimentions[1])/2))
+ pangoctx.show_layout(text_layout)
+
+ # work done. Be kind to next cairo widgets and reset matrix.
+ context.identity_matrix()
+
+ def do_realize(self):
+ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW)
+ self.window = self.get_parent_window()
+
+ def __on_expose(self, widget, event):
+ """Redraw event callback."""
+ # TODO
+ ctx = self.window.cairo_create()
+
+ # set drawing region. Useless since this widget has its own window.
+## region = gtk.gdk.region_rectangle(self.allocation)
+## region.intersect(gtk.gdk.region_rectangle(event.area))
+## ctx.region(region)
+## ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
+## ctx.clip()
+
+ ##import pdb; pdb.set_trace()
+ ##ctx.set_operator(cairo.OPERATOR_CLEAR)
+ ##ctx.paint()
+ ##ctx.set_operator(cairo.OPERATOR_OVER)
+
+ self.drawWithContext(ctx)
+
+ return True
+
+ def _set_label(self, value):
+ """Sets the label and flags the widget to be redrawn."""
+ # yes we use the setter
+ self.__label = value
+ # FIXME hack to calculate size. necessary because may not have been
+ # realized
+ surf = cairo.SVGSurface("/dev/null", 0, 0)
+ ctx = cairo.Context(surf)
+ pangoctx = pangocairo.CairoContext(ctx)
+ text_layout = pangoctx.create_layout()
+ text_layout.set_text(value)
+ self.__text_dimentions = text_layout.get_pixel_size()
+ del text_layout, pangoctx, ctx, surf
+
+ def do_size_request(self, requisition):
+ width, height = self.__text_dimentions
+
+ # FIXME bogus values follows
+ requisition.width = int(width+30)
+ requisition.height = int(height+40)
+
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+
+ def _get_label(self):
+ """Getter method for the label property"""
+ return self.__label
+
+ def _set_will_expose(self, value):
+ """setter for will_expose property"""
+ if self.__exposer and not value:
+ self.disconnect(self.__exposer)
+ elif not (self.__exposer or value):
+ self.__exposer = self.connect(self.__on_expose)
+
+ should_expose = property(fset=_set_will_expose)
+
+ label = property(fget=_get_label, fset=_set_label,\
+ doc="Text label which is to be painted on the top of the widget")
+
+gobject.type_register(TextBubble)
+
+
+# vim:set ts=4 sts=4 sw=4 et: