From 567021eac56855b16bde1ea2d103becf00552e18 Mon Sep 17 00:00:00 2001 From: simpoir Date: Sun, 15 Mar 2009 03:21:29 +0000 Subject: cleaned overlayer --- diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py index c5dca45..0ad1d91 100644 --- a/src/sugar/activity/activity.py +++ b/src/sugar/activity/activity.py @@ -76,6 +76,7 @@ from sugar.graphics.xocolor import XoColor from sugar.datastore import datastore from sugar.session import XSMPClient from sugar import wm +from sugar.tutorius.services import ObjectStore _ = lambda msg: gettext.dgettext('sugar-toolkit', msg) @@ -508,6 +509,8 @@ class Activity(Window, gtk.Container): """ Window.__init__(self) + ObjectStore().activity = self + # process titles will only show 15 characters # but they get truncated anyway so if more characters # are supported in the future we will get a better view diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py index 7ecf86b..915b11b 100644 --- a/src/sugar/tutorius/actions.py +++ b/src/sugar/tutorius/actions.py @@ -19,6 +19,7 @@ This module defines Actions that can be done and undone on a state from sugar.tutorius import gtkutils from dialog import TutoriusDialog +from sugar.tutorius.services import ObjectStore import overlayer @@ -125,11 +126,11 @@ class BubbleMessage(Action): Show the dialog """ # get or inject overlayer - self.overlay = gtkutils.activity()._overlayer #FIXME:handle subwin + self.overlay = ObjectStore().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 = overlayer.TextBubble(text=self._message, tailpos=self._tailpos) #TODO: add tail handling self._bubble.show() self.overlay.put(self._bubble, x, y) self.overlay.queue_draw() diff --git a/src/sugar/tutorius/gtkutils.py b/src/sugar/tutorius/gtkutils.py index 450872d..073a7f3 100644 --- a/src/sugar/tutorius/gtkutils.py +++ b/src/sugar/tutorius/gtkutils.py @@ -18,12 +18,6 @@ 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 diff --git a/src/sugar/tutorius/overlayer.py b/src/sugar/tutorius/overlayer.py index 0da1841..23a203b 100644 --- a/src/sugar/tutorius/overlayer.py +++ b/src/sugar/tutorius/overlayer.py @@ -23,11 +23,15 @@ import gtk import cairo import pangocairo -class CanvasDrawable(object): - """Defines the CanvasDrawable protocol""" - should_expose = None - def drawWithContext(self, context): pass - +#class CanvasDrawable(object): +# """Defines the CanvasDrawable protocol""" +# no_expose = None +# def draw_with_context(self, context): +# """ +# Draws the cairo widget with the passed cairo context. +# This will be called if the widget is child of an overlayer. +# """ +# pass class Overlayer(gtk.Layout): """ @@ -41,57 +45,27 @@ class Overlayer(gtk.Layout): # 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) + self.__render_handle = None 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). + + @param child the child to add + @param x the horizontal coordinate for positionning + @param y the vertical coordinate for positionning """ - if hasattr(child, "should_expose"): + if hasattr(child, "no_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 + child.no_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): """ @@ -134,11 +108,11 @@ class Overlayer(gtk.Layout): 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() + 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 @@ -146,12 +120,15 @@ class Overlayer(gtk.Layout): #draw overlay for drawn_child in self.get_children(): - if hasattr(drawn_child, "drawWithContext"): - drawn_child.drawWithContext(ctx) + if hasattr(drawn_child, "draw_with_context"): + drawn_child.draw_with_context(ctx) def __size_allocate(self, widget, allocation): - """set size allocation (actual gtk widget size) and propagate it to overlayed child""" + """ + 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 @@ -161,26 +138,35 @@ class Overlayer(gtk.Layout): class TextBubble(gtk.Widget): + """ + A CanvasDrawableWidget drawing a round textbox and a tail pointing + to a specified 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 + @param speaker the widget to compute the tail position from + @param tailpos (optional) position relative to the bubble to use as + the tail position, 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 = None + self.__text_dimentions = None + self.__exposer = None # refer to expose event handler + self.label = text self.speaker = speaker self.tailpos = tailpos - self.lineWidth = 5 + self.line_width = 5 self.connect("expose-event", self.__on_expose) - def drawWithContext(self, context): + def draw_with_context(self, context): """ Draw using the passed cairo context instead of creating a new cairo context. This eases blending between multiple cairo-rendered @@ -191,46 +177,58 @@ class TextBubble(gtk.Widget): height = self.allocation.height xradius = width/2 yradius = height/2 - width -= self.lineWidth - height -= self.lineWidth + width -= self.line_width + height -= self.line_width + + # bubble border + context.move_to(self.line_width, yradius) + context.curve_to(self.line_width, self.line_width, + self.line_width, self.line_width, xradius, self.line_width) + context.curve_to(width, self.line_width, + width, self.line_width, width, yradius) + context.curve_to(width, height, width, height, xradius, height) + context.curve_to(self.line_width, height, + self.line_width, height, self.line_width, yradius) + context.set_line_width(self.line_width) + context.set_source_rgb(0.0, 0.0, 0.0) + context.stroke() # 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_line_width(self.line_width) 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.stroke_preserve() + context.set_source_rgb(1.0, 1.0, 0.0) + context.fill() + + # bubble painting. Redrawing the inside after the tail will combine + # both shapes. + # TODO: we could probably generate the shape at initialization to + # lighten computations. + context.move_to(self.line_width, yradius) + context.curve_to(self.line_width, self.line_width, + self.line_width, self.line_width, xradius, self.line_width) + context.curve_to(width, self.line_width, + width, self.line_width, width, yradius) context.curve_to(width, height, width, height, xradius, height) - context.curve_to(self.lineWidth, height, - self.lineWidth, height, self.lineWidth, yradius) + context.curve_to(self.line_width, height, + self.line_width, height, self.line_width, 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() + context.fill() # text # FIXME create layout in realize method + context.set_source_rgb(0.0, 0.0, 0.0) 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), + 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) @@ -238,36 +236,24 @@ class TextBubble(gtk.Widget): context.identity_matrix() def do_realize(self): + """ Setup gdk window creation. """ self.set_flags(gtk.REALIZED | gtk.NO_WINDOW) self.window = self.get_parent_window() def __on_expose(self, widget, event): """Redraw event callback.""" - # TODO + # TODO: handle gtk window management in case there is no overlay 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) + self.draw_with_context(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 + # realized. We create a fake surface to use builtin math. surf = cairo.SVGSurface("/dev/null", 0, 0) ctx = cairo.Context(surf) pangoctx = pangocairo.CairoContext(ctx) @@ -277,13 +263,16 @@ class TextBubble(gtk.Widget): del text_layout, pangoctx, ctx, surf def do_size_request(self, requisition): + """Fill requisition with size occupied by the widget.""" width, height = self.__text_dimentions - # FIXME bogus values follows + # FIXME bogus values follows. will need to replace them with + # padding relative to font size and line border size requisition.width = int(width+30) requisition.height = int(height+40) def do_size_allocate(self, allocation): + """Save zone allocated to the widget.""" self.allocation = allocation def _get_label(self): @@ -297,9 +286,10 @@ class TextBubble(gtk.Widget): elif not (self.__exposer or value): self.__exposer = self.connect(self.__on_expose) - should_expose = property(fset=_set_will_expose) + no_expose = property(fset=_set_will_expose, + doc="Whether the widget should handle exposition events or not.") - label = property(fget=_get_label, fset=_set_label,\ + 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) -- cgit v0.9.1