diff options
author | dave <drykod@gmail.com> | 2009-10-29 07:18:36 (GMT) |
---|---|---|
committer | dave <drykod@gmail.com> | 2009-10-29 07:18:36 (GMT) |
commit | f6f63108831800d0a4b38680f90d05377607d99a (patch) | |
tree | 159ec93e00e6dcb837050e6fa654bfa291587ad9 | |
parent | b0b64a0fc2e13dfbe0bc21a22053e43f4056d2f9 (diff) |
message bubble w image
-rw-r--r-- | addons/bubblemessagewimg.py | 116 | ||||
-rw-r--r-- | tutorius/overlayer.py | 180 |
2 files changed, 296 insertions, 0 deletions
diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py new file mode 100644 index 0000000..eb0f750 --- /dev/null +++ b/addons/bubblemessagewimg.py @@ -0,0 +1,116 @@ +# 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 +from sugar.tutorius.actions import Action, DragWrapper +from sugar.tutorius.properties import TStringProperty, TArrayProperty +from sugar.tutorius import overlayer +from sugar.tutorius.services import ObjectStore + +class BubbleMessageWImg(Action): + message = TStringProperty("Message") + # Create the position as an array of fixed-size 2 + position = TArrayProperty((0,0), 2, 2) + # Do the same for the tail position + tail_pos = TArrayProperty((0,0), 2, 2) + + def __init__(self, message=None, position=None, speaker=None, tail_pos=None): + """ + Shows a dialog with a given text, at the given position on the screen. + + @param message A string to display to the user + @param position A list of the form [x, y] + @param speaker treeish representation of the speaking widget + @param tail_pos The position of the tail of the bubble; useful to point to + specific elements of the interface + """ + Action.__init__(self) + + if position: + self.position = position + if tail_pos: + self.tail_pos = tail_pos + if message: + self.message = message + + self.overlay = None + self._bubble = None + self._speaker = None + + def do(self): + """ + Show the dialog + """ + # get or inject overlayer + self.overlay = ObjectStore().activity._overlayer + # FIXME: subwindows, are left to overlap this. This behaviour is + # undesirable. subwindows (i.e. child of top level windows) should be + # handled either by rendering over them, or by finding different way to + # draw the overlay. + + if not self.overlay: + self.overlay = ObjectStore().activity._overlayer + if not self._bubble: + x, y = self.position + # TODO: tails are relative to tailpos. They should be relative to + # the speaking widget. Same of the bubble position. + self._bubble = overlayer.TextBubbleWImg(text=self.message, + tailpos=self.tail_pos) + self._bubble.show() + self.overlay.put(self._bubble, x, y) + self.overlay.queue_draw() + + def undo(self): + """ + Destroy the dialog + """ + if self._bubble: + self._bubble.destroy() + self._bubble = None + + def enter_editmode(self, *args): + """ + Enters edit mode. The action should display itself in some way, + without affecting the currently running application. + """ + if not self.overlay: + self.overlay = ObjectStore().activity._overlayer + assert not self._drag, "bubble action set to editmode twice" + x, y = self.position + self._bubble = overlayer.TextBubbleWImg(text=self.message, + tailpos=self.tail_pos) + self.overlay.put(self._bubble, x, y) + self._bubble.show() + + self._drag = DragWrapper(self._bubble, self.position, True) + + def exit_editmode(self, *args): + x,y = self._drag.position + self.position = (int(x), int(y)) + if self._drag: + self._drag.draggable = False + self._drag = None + if self._bubble: + self.overlay.remove(self._bubble) + self._bubble = None + self.overlay = None + +__action__ = { + "name" : "BubbleMessageWImg", + "display_name" : "Message Bubble with image", + "icon" : "message-bubble", + "class" : BubbleMessageWImg, + "mandatory_props" : ["message"] +} + diff --git a/tutorius/overlayer.py b/tutorius/overlayer.py index 7ebe347..3250598 100644 --- a/tutorius/overlayer.py +++ b/tutorius/overlayer.py @@ -398,6 +398,186 @@ class TextBubble(gtk.Widget): gobject.type_register(TextBubble) +class TextBubbleWImg(gtk.Widget): + """ + A CanvasDrawableWidget drawing a round textbox and a tail pointing + to a specified widget. + """ + def __init__(self, text, speaker=None, tailpos=(0,0)): + """ + Creates a new cairo rendered text bubble. + + @param text the text to render in the bubble + @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) + + # FIXME: ensure previous call does not interfere with widget stacking, + # as using a gtk.Layout and stacking widgets may reveal a screwed up + # order with the cairo widget on top. + self.__label = None + + self.label = text + self.speaker = speaker + self.tailpos = tailpos + self.line_width = 5 + self.padding = 20 + + self._no_expose = False + self.__exposer = None + + 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 + 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.line_width + height -= self.line_width + # + # TODO fetch speaker coordinates + + # draw bubble tail if present + if self.tailpos != (0,0): + context.move_to(xradius-width/4, yradius) + context.line_to(self.tailpos[0], self.tailpos[1]) + context.line_to(xradius+width/4, yradius) + context.set_line_width(self.line_width) + context.set_source_rgb(*xo_line_color) + context.stroke_preserve() + + # bubble border + context.move_to(width-self.padding, 0.0) + context.line_to(self.padding, 0.0) + context.arc_negative(self.padding, self.padding, self.padding, + 3*pi/2, pi) + context.line_to(0.0, height-self.padding) + context.arc_negative(self.padding, height-self.padding, self.padding, + pi, pi/2) + context.line_to(width-self.padding, height) + context.arc_negative(width-self.padding, height-self.padding, + self.padding, pi/2, 0) + context.line_to(width, self.padding) + context.arc_negative(width-self.padding, self.padding, self.padding, + 0.0, -pi/2) + context.set_line_width(self.line_width) + context.set_source_rgb(*xo_line_color) + context.stroke_preserve() + context.set_source_rgb(*xo_fill_color) + context.fill() + + # bubble painting. Redrawing the inside after the tail will combine + if self.tailpos != (0,0): + context.move_to(xradius-width/4, yradius) + context.line_to(self.tailpos[0], self.tailpos[1]) + context.line_to(xradius+width/4, yradius) + context.set_line_width(self.line_width) + context.set_source_rgb(*xo_fill_color) + context.fill() + + context.set_source_rgb(1.0, 1.0, 1.0) + pangoctx = pangocairo.CairoContext(context) + self._text_layout.set_markup(self.__label) + text_size = self._text_layout.get_pixel_size() + pangoctx.move_to( + int((self.allocation.width-text_size[0])/2), + int((self.allocation.height-text_size[1])/2)) + pangoctx.show_layout(self._text_layout) + + # image painting + filename = "/home/dave/Pictures/Favicon_01.png" + pixbuf = gtk.gdk.pixbuf_new_from_file(filename) + x = pixbuf.get_width() + y = pixbuf.get_height() + # create a new cairo surface to place the image on + #surface = cairo.ImageSurface(0,x,y) + # create a context to the new surface + #ct = cairo.Context(surface) + + context.set_source_pixbuf(pixbuf,-50,0) + context.paint() + + # work done. Be kind to next cairo widgets and reset matrix. + 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() + if not self._no_expose: + self.__exposer = self.connect_after("expose-event", \ + self.__on_expose) + + def __on_expose(self, widget, event): + """Redraw event callback.""" + ctx = self.window.cairo_create() + + self.draw_with_context(ctx) + + return True + + def _set_label(self, value): + """Sets the label and flags the widget to be redrawn.""" + self.__label = "<b>%s</b>"%value + if not self.parent: + return + ctx = self.parent.window.cairo_create() + pangoctx = pangocairo.CairoContext(ctx) + self._text_layout = pangoctx.create_layout() + self._text_layout.set_markup(value) + del pangoctx, ctx#, surf + + def do_size_request(self, requisition): + """Fill requisition with size occupied by the widget.""" + ctx = self.parent.window.cairo_create() + pangoctx = pangocairo.CairoContext(ctx) + self._text_layout = pangoctx.create_layout() + self._text_layout.set_markup(self.__label) + + width, height = self._text_layout.get_pixel_size() + requisition.width = int(width+2*self.padding) + requisition.height = int(height+2*self.padding) + + def do_size_allocate(self, allocation): + """Save zone allocated to the widget.""" + self.allocation = allocation + + def _get_label(self): + """Getter method for the label property""" + return self.__label[3:-4] + + def _set_no_expose(self, value): + """setter for no_expose property""" + self._no_expose = value + if not (self.flags() and gtk.REALIZED): + return + + if self.__exposer and value: + self.parent.disconnect(self.__exposer) + self.__exposer = None + elif (not self.__exposer) and (not value): + self.__exposer = self.parent.connect_after("expose-event", + self.__on_expose) + + def _get_no_expose(self): + """getter for no_expose property""" + return self._no_expose + + no_expose = property(fset=_set_no_expose, fget=_get_no_expose, + doc="Whether the widget should handle exposition events or not.") + + 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(TextBubbleWImg) + class Rectangle(gtk.Widget): """ A CanvasDrawableWidget drawing a rectangle over a specified widget. |