From: Dirk M. <di...@fr...> - 2008-06-24 20:17:21
|
Author: dmeyer Date: Tue Jun 24 16:17:24 2008 New Revision: 3304 Log: Move the generic logic and some widgets from freevo WIP to kaa.candy2. This module will get some more love the next days to clean up some strange stuff. Added: trunk/WIP/candy2/ trunk/WIP/candy2/setup.py trunk/WIP/candy2/src/ trunk/WIP/candy2/src/__init__.py trunk/WIP/candy2/src/animation/ trunk/WIP/candy2/src/animation/__init__.py trunk/WIP/candy2/src/animation/behaviour.py trunk/WIP/candy2/src/animation/core.py trunk/WIP/candy2/src/core.py trunk/WIP/candy2/src/decorator.py trunk/WIP/candy2/src/properties.py trunk/WIP/candy2/src/stage.py trunk/WIP/candy2/src/timeline.py trunk/WIP/candy2/src/widgets/ trunk/WIP/candy2/src/widgets/__init__.py trunk/WIP/candy2/src/widgets/container.py trunk/WIP/candy2/src/widgets/core.py trunk/WIP/candy2/src/widgets/label.py trunk/WIP/candy2/src/widgets/progressbar.py trunk/WIP/candy2/src/widgets/rectangle.py trunk/WIP/candy2/src/widgets/text.py trunk/WIP/candy2/src/xmlparser.py Added: trunk/WIP/candy2/setup.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/setup.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,40 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# setup.py - Setup script for kaa.candy +# ----------------------------------------------------------------------------- +# $Id: setup.py 2653 2007-04-22 17:39:14Z dmeyer $ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2004-2005 Jason Tackaberry <ta...@sa...> +# +# First Edition: Jason Tackaberry <ta...@sa...> +# Maintainer: Jason Tackaberry <ta...@sa...> +# +# 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 MER- +# CHANTABILITY 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., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# ----------------------------------------------------------------------------- + +# python imports +import sys + +try: + # kaa base imports + from kaa.distribution.core import Extension, setup +except ImportError: + print 'kaa.base not installed' + sys.exit(1) + +setup(module = 'candy', version = '0.1') Added: trunk/WIP/candy2/src/__init__.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/__init__.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,88 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# kaa.candy - Third generation Canvas System using Clutter as backend +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +import kaa + +from decorator import gui_execution + +class Mainloop(object): + """ + Clutter mainloop. + """ + + def run(self): + # Import clutter only in the gobject thread + # This function will be the running mainloop + import clutter + clutter.threads_init() + clutter.init() + clutter.main() + def quit(self): + # Import clutter only in the gobject thread + import clutter + clutter.main_quit() + +def init(): + """ + Set the mainloop and load the widgets into our namespace + """ + @gui_execution() + def load_modules(): + import xmlparser as _xmlparser + global xmlparser + xmlparser = _xmlparser + global Properties + from properties import Properties + global Timeline, MasterTimeline + from timeline import Timeline, MasterTimeline + global Color, Font + from core import Color, Font + global Widget, Template, Group, Text, Texture, Imlib2Texture, CairoTexture, Container, Label, Rectangle, Progressbar + from widgets import Widget, Template, Group, Text, Texture, Imlib2Texture, CairoTexture, Container, Label, Rectangle, Progressbar + global Stage + from stage import Stage + import animation + # set generic notifier and start the clutter thread + kaa.main.select_notifier('generic') + kaa.gobject_set_threaded(Mainloop()) + load_modules() + +# we need an extra init function and that function _must_ be +# called directly when the application starts. It is not possible +# to import clutter in a thread while we are in an import ourself +# +# The following code is a hack around this problem: +# +# import imp +# # release the lock of this import (ouch!) +# imp.release_lock() +# try: +# init() +# finally: +# # set the lock back so nobody will notice what we have done +# imp.acquire_lock() Added: trunk/WIP/candy2/src/animation/__init__.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/animation/__init__.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1 @@ +import core Added: trunk/WIP/candy2/src/animation/behaviour.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/animation/behaviour.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,27 @@ +# clutter imports +import clutter + +# gui imports +from kaa.candy import Color + +class BehaviourColor(clutter.Behaviour): + """ + Behaviour to change the color of an actor. + """ + __gtype_name__ = 'BehaviourColor' + + def __init__ (self, alpha, start_color, end_color): + clutter.Behaviour.__init__(self) + self.set_alpha(alpha) + self._start = start_color + self._end = end_color + + def do_alpha_notify(self, alpha_value): + color = [] + for pos in range(4): + start = self._start[pos] + diff = self._end[pos] - start + alpha = float(alpha_value) / clutter.MAX_ALPHA + color.append(start + int(diff * alpha)) + for actor in self.get_actors(): + actor.set_color(Color(*color)) Added: trunk/WIP/candy2/src/animation/core.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/animation/core.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,297 @@ +# python imports +import logging + +# kaa imports +import kaa +import kaa.candy + +import clutter + +from behaviour import BehaviourColor + + +# get logging object +log = logging.getLogger('gui') + +# frames per second (hard-coded) +FPS = 25 + +class XMLDict(dict): + """ + XML parser dict helper class. + """ + def update(self, **kwargs): + super(XMLDict, self).update(**kwargs) + return self + +class Template(object): + """ + Template to create an animation on demand. All XML parser will create such an + object to parse everything during theme parsing. + """ + def __init__(self, style, cls, kwargs): + self.style = style + self._cls = cls + self._kwargs = kwargs + + def __call__(self, widget): + """ + Create the animation bound to the given widget. + """ + return self._cls(widget, **self._kwargs) + + @classmethod + def from_XML(cls, element): + """ + Parse the XML element for parameter and create a Template. + """ + animation = kaa.candy.xmlparser.get_class(element.node, element.style) + if not animation: + return None + return cls(element.style, animation, animation.parse_XML(element)) + + +class Animation(kaa.InProgress): + """ + Base animation class. + """ + + __template__ = Template + __gui_name__ = 'animation' + + running = [] + + def __init__(self, secs): + super(Animation, self).__init__() + timeline = clutter.Timeline(int(float(secs) * FPS), FPS) + timeline.set_loop(False) + # FIXME: do not hardcode alpha function + self.alpha = clutter.Alpha(timeline, clutter.ramp_inc_func) + self._refs = None + + def start(self, *refs): + """ + Start the animation. + """ + # print refs[0].get_alpha().get_timeline() + self.running.append(self) + # store references or the animation won't run + self._refs = refs + timeline = self.alpha.get_timeline() + timeline.rewind() + timeline.start() + timeline.connect('completed', self.finish) + self.alpha = None + + def finish(self, result): + """ + Callback when the animation is finished. + """ + self.running.remove(self) + # run callback + # deleted references for gc + for behaviour in self._refs: + behaviour.stop() + self._refs = None + kaa.MainThreadCallback(super(Animation, self).finish, None)() + + def stop(self): + """ + Stop the animation. + """ + timeline.stop() + self.finish() + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the animation. + """ + return XMLDict(secs=float(element.secs)) + + # def __del__(self): + # print '__del__', self + + +class List(object): + """ + Animation template for new defined animations from the define-animation XML + node. It contains other animation nodes and optional a properties node. + """ + __gui_name__ = 'define-animation' + + def __init__(self, animations, properties): + self._animations = animations + self._properties = properties + + def __call__(self, widget): + """ + Create the animation bound to the given widget. + """ + # FIXME: better return InProgressList object + if self._properties: + self._properties.apply(widget) + return [ a(widget) for a in self._animations ] + + @classmethod + def from_XML(cls, element): + """ + Parse the XML element for parameter and create an AnimationTemplate. + """ + properties = None + animations = [] + for element in element: + if element.node == 'properties': + properties = kaa.candy.Properties.from_XML(element) + elif element.node == 'animation': + a = element.xmlcreate() + if a is None: + log.error('unknown animation %s', element.style) + else: + animations.append(a) + else: + log.error('unknown element %s in define-animation', element.node) + return cls(animations, properties) + +class ExclusiveBehaviour(object): + + effects = [] + + def add_widget(self, widget): + running = widget.get_userdata('running_animations') + if running is None: + running = {} + widget.set_userdata('running_animations', running) + for effect in self.effects: + if effect in running: + running[effect].remove(widget) + running[effect] = self + # real add function + self.apply(widget) + + def remove_widget(self, widget): + running = widget.get_userdata('running_animations') + for effect, animation in running.items(): + if animation == self: + del running[effect] + # real remove function + self.remove(widget) + + def stop(self): + for widget in self.get_actors()[:]: + self.remove_widget(widget) + + +class Scale(Animation): + """ + Zoom-out the given object. + """ + __gui_style__ = 'scale' + + class Behaviour(ExclusiveBehaviour, clutter.BehaviourScale): + effects = [ 'scale' ] + + def __init__(self, obj, secs, x_factor, y_factor): + super(Scale, self).__init__(secs) + s = obj.get_scale() + scale = Scale.Behaviour(s[0], s[1], x_factor, y_factor, self.alpha) + scale.apply(obj) + # give references to start function + self.start(scale) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the animation. + """ + return super(Scale, cls).parse_XML(element).update( + x_factor=float(element.x_factor), y_factor=float(element.y_factor)) + + +class Opacity(Animation): + """ + Fade in or out the given object. + """ + __gui_style__ = 'opacity' + + class Behaviour(ExclusiveBehaviour, clutter.BehaviourOpacity): + effects = [ 'opacity' ] + + def __init__(self, obj, secs, stop): + super(Opacity, self).__init__(secs) + opacity = Opacity.Behaviour(obj.get_opacity(), stop, self.alpha) + opacity.add_widget(obj) + # give references to start function + self.start(opacity) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the animation. + """ + return super(Opacity, cls).parse_XML(element).update( + stop=int(element.stop)) + + +class Move(Animation): + """ + Move the given object. + """ + __gui_style__ = 'move' + + class Behaviour(ExclusiveBehaviour, clutter.BehaviourPath): + effects = [ 'move' ] + + def __init__(self, obj, secs, x=None, y=None): + super(Move, self).__init__(secs) + x0, y0 = obj.get_position() + if x is None: + x = x0 + if y is None: + y = y0 + path = Move.Behaviour(self.alpha, ((x0, y0), (x, y))) + path.add_widget(obj) + # give references to start function + self.start(path) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the animation. + """ + return super(Move, cls).parse_XML(element).update( + x=int(element.x), y=int(element.y)) + + +class ColorChange(Animation): + """ + Color change animation. + """ + __gui_style__ = 'color' + + class Behaviour(ExclusiveBehaviour, BehaviourColor): + effects = [ 'color' ] + + def __init__(self, obj, secs, color): + super(ColorChange, self).__init__(secs) + a = ColorChange.Behaviour(self.alpha, obj.get_color(), color) + a.add_widget(obj) + # give references to start function + self.start(a) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the animation. + """ + return super(Move, cls).parse_XML(element).update( + x=int(element.x), y=int(element.y)) + + + +# register the animations +kaa.candy.xmlparser.register(List) +kaa.candy.xmlparser.register(Scale) +kaa.candy.xmlparser.register(Opacity) +kaa.candy.xmlparser.register(Move) +kaa.candy.xmlparser.register(ColorChange) Added: trunk/WIP/candy2/src/core.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/core.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,66 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# core.py - Basic Classes +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +__all__ = [ 'Color', 'Font' ] + +class Color(list): + """ + Color object. + """ + def __init__(self, *col): + if len(col) > 1: + return super(Color, self).__init__(col) + # Convert a 32-bit (A)RGB color + if col == None: + return super(Color, self).__init__((0,0,0,255)) + if hasattr(col[0], 'red'): + # clutter.Color object + return super(Color, self).__init__(( + col[0].red, col[0].green, col[0].blue, col[0].alpha)) + # convert 0x???????? string + col = long(col[0], 16) + a = 255 - ((col >> 24) & 0xff) + r = (col >> 16) & 0xff + g = (col >> 8) & 0xff + b = (col >> 0) & 0xff + super(Color, self).__init__((r,g,b,a)) + + def to_cairo(self): + """ + Convert to list used by cairo (float values from 0-1) + """ + return [ x / 255.0 for x in self ] + +class Font(object): + """ + Font object + """ + def __init__(self, name): + self.name, size = name.split(':') + self.size = int(size) + Added: trunk/WIP/candy2/src/decorator.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/decorator.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,177 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# decorator.py - Decorator to switch between main and clutter thread +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +__all__ = [ 'gui_execution' ] + +# python imports +import logging +import sys +import threading +import gobject + +# kaa imports +import kaa + +# get logging object +log = logging.getLogger('gui') + +# thread the clutter mainloop is running in +gobject_thread = None + +def gobject_execute(callback): + """ + Execute the callback in the gobject thread. + """ + try: + callback.exception = None + callback.result = callback() + except Exception, e: + callback.exception = sys.exc_info() + finally: + callback.event.set() + +def set_gobject_thread(dummy): + """ + Set the current thread as gobject_thread. + """ + global gobject_thread + gobject_thread = threading.currentThread() + + +class Lock(object): + """ + Class to lock the clutter thread. While a lock is active the gobject + mainloop will block in idle_add executing all gui_execution callbacks + without redrawing. You can use the lock() and unlock() functions. + """ + instance = None + counter = 0 + _locked = False + + def lock(self, auto_unlock): + """ + Lock the clutter thread. If auto_unlock is True, the lock will unlock + on the next iteration of the kaa mainloop by itself. + """ + Lock.counter += 1 + if auto_unlock: + kaa.signals['step'].connect_once(self.unlock) + self._locked = True + if Lock.instance: + return self + self._gobject_event = threading.Event() + self._callbacks = [] + self._stopping = False + self._main_event = threading.Event() + Lock.instance = self + gobject.idle_add(self._gobject_callback, None) + return self + + def unlock(self): + """ + Unlock the clutter thread. + """ + if not self._locked: + return + self._locked = False + Lock.counter -= 1 + if Lock.counter: + return + Lock.instance._stopping = True + Lock.instance._gobject_event.set() + Lock.instance._main_event.wait() + Lock.instance = None + + def _gobject_callback(self, arg=None): + """ + GObject loop handler. + """ + while not self._stopping: + self._gobject_event.wait() + while self._callbacks: + gobject_execute(self._callbacks.pop(0)) + self._main_event.set() + + +class gui_execution(object): + """ + Decorator to force the execution of the function in the clutter mainloop + and _blocking_ the mainloop during that time. If this class is used + inside a with statement, the clutter mainloop will be locked to prevent + redrawing, everything inside is still executed inside the kaa mainloop. + This should only be used to synchronize the two mainloops, for larger + blocks of code it is better to switch to the clutter loop completly. + """ + + @classmethod + def lock(cls): + """ + Lock the gui thread. It will automaticly unlock on the next + iteration of the mainloop. Return value is the lock object + to manually unlock the clutter thread. + """ + return Lock().lock(True) + + def __enter__(self): + """ + with statement enter + """ + self._lock = Lock() + return self._lock.lock(False) + + def __exit__(self, type, value, traceback): + """ + with statement exit + """ + self._lock.unlock() + self._lock = None + return False + + def __call__(self, func): + """ + Decorator function. + """ + def newfunc(*args, **kwargs): + if gobject_thread == threading.currentThread(): + return func(*args, **kwargs) + if gobject_thread is None: + gobject.idle_add(set_gobject_thread, None) + callback = kaa.Callback(func, *args, **kwargs) + callback.event = threading.Event() + if Lock.instance: + Lock.instance._callbacks.append(callback) + Lock.instance._gobject_event.set() + else: + gobject.idle_add(gobject_execute, callback) + callback.event.wait() + if callback.exception: + exc_type, exc_value, exc_tb_or_stack = callback.exception + raise exc_type, exc_value, exc_tb_or_stack + return callback.result + newfunc.func_name = func.func_name + return newfunc Added: trunk/WIP/candy2/src/properties.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/properties.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,61 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# properties.py - Template Property Handling +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +class Properties(dict): + """ + Properties class to apply the given properties to a widget. + """ + __gui_name__ = 'properties' + + def apply(self, widget): + """ + Apply to the given widget. + """ + for func, value in self.items(): + getattr(widget, 'set_' + func)(*value) + if func == 'anchor_point': + widget.move_by(*value) + + @classmethod + def from_XML(cls, element): + """ + Parse the XML element for parameter and create a Properties object. + """ + properties = cls() + for key, value in element.attributes(): + if key in ('opacity', 'depth'): + value = [ int(value) ] + elif key in ('scale','anchor_point'): + value = [ float(x) for x in value.split(',') ] + if key in ('scale','anchor_point'): + value = int(value[0] * element.get_scale_factor()[0]), \ + int(value[1] * element.get_scale_factor()[1]) + else: + value = [ value ] + properties[key] = value + return properties Added: trunk/WIP/candy2/src/stage.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/stage.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,80 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# stage.py - Clutter Stage (thread safe) +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +# clutter imports +import clutter + +# kaa imports +import kaa + +# kaa.candy imports +from decorator import gui_execution + +class Stage(object): + """ + Window main window. + """ + def __init__(self, (width, height)): + self.signals = kaa.Signals('key-press') + self._geomertry = width, height + self._stage = clutter.Stage() + self._stage.set_size(width, height) + self._stage.connect('key-press-event', self.handle_key) + self._stage.set_color(clutter.Color(0, 0, 0, 0xff)) + self._keysyms = {} + # get list of clutter key code. We must access the module + # first before it is working, therefor we access Left. + clutter.keysyms.Left + for name in dir(clutter.keysyms): + if not name.startswith('_'): + self._keysyms[getattr(clutter.keysyms, name)] = name + self._stage.show() + + def handle_key(self, stage, event): + """ + Translate clutter keycode to name and emit signal in main loop. + """ + key = self._keysyms.get(event.keyval) + if key is not None: + kaa.MainThreadCallback(self.signals['key-press'].emit)(key) + + @gui_execution() + def add(self, child, visible=True): + """ + Add the child to the screen. + """ + if visible: + child.show() + self._stage.add(child) + + @gui_execution() + def remove(self, child): + """ + Remove the child from the screen. + """ + self._stage.remove(child) Added: trunk/WIP/candy2/src/timeline.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/timeline.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,90 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# timeline.py - Timeline Classes +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +__all__ = [ 'Timeline', 'MasterTimeline' ] + +import sys +# check if kaa.candy is initialized in the thread +if not 'clutter' in sys.modules.keys(): + raise RuntimeError('kaa.candy not initialized') + +import clutter + +#: Basic Timeline class from clutter +Timeline = clutter.Timeline + +class MasterTimeline(object): + """ + Python implementation of clutter.Score since the clutter.Score + prints warnings. + """ + def __init__(self): + """ + Create a new MasterTimeline + """ + self._timelines = [] + self._signals = [] + + def append(self, timeline, parent=None): + """ + Append a timeline + """ + if parent: + signal = parent.connect('completed', lambda x: timeline.start()) + self._signals.append((parent, signal)) + self._timelines.append(timeline) + + def start(self): + """ + Start the MasterTimeline + """ + self._timelines[0].start() + + def stop(self): + """ + Stop the MasterTimeline + """ + for timeline in self._timelines: + if timeline.is_playing(): + timeline.stop() + + def is_playing(self): + """ + Return True if at least one child timeline is_playing() + """ + for timeline in self._timelines: + if timeline.is_playing(): + return True + return False + + def __del__(self): + """ + Delete MasterTimeline by disconnecting the children + """ + for parent, signal in self._signals: + parent.disconnect(signal) Added: trunk/WIP/candy2/src/widgets/__init__.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/__init__.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,39 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# kaa.candy.widgets.py - Basic Widgets for kaa.candy +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +import sys +# check if kaa.candy is initialized in the thread +if not 'clutter' in sys.modules.keys(): + raise RuntimeError('kaa.candy not initialized') + +from core import Template, Widget, Group, Texture, Imlib2Texture, CairoTexture +from container import Container +from label import Label +from rectangle import Rectangle +from progressbar import Progressbar +from text import Text Added: trunk/WIP/candy2/src/widgets/container.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/container.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,124 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# .py - +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +# python imports +import logging + +# kaa imports +import kaa + +# gui imports +import kaa.candy +import core + +# get logging object +log = logging.getLogger('gui') + +class Container(core.Group): + """ + Container widget with other widgets in it. + """ + __gui_name__ = 'container' + context_sensitive = True + + def __init__(self, pos, size, widgets, dependency=None, context=None): + super(Container, self).__init__(pos, size, context) + for template in widgets: + try: + child = template(context) + if child.context_sensitive: + child.set_userdata('template', template) + self.add(child) + except: + log.exception('render') + if dependency and context: + self.set_dependency(*dependency) + + def set_context(self, context): + """ + Set a new context for the widget and redraw it. + """ + super(Container, self).set_context(context) + for child in self.get_children()[:]: + if not child.context_sensitive or child.try_context(context) or \ + child.get_userdata('removing'): + continue + try: + # FIXME: this code needs some updates + child.set_userdata('removing', True) + template = child.get_userdata('template') + new = template(context) + new.set_userdata('template', template) + self.add(new) + a1 = child.animate('hide', context=context) or [] + a2 = new.animate('show', context=context) or [] + self.remove(child, kaa.InProgressList(a1 + a2)) + except: + log.exception('render') + + def get_element(self, name): + """ + Get child element with the given name. + """ + for child in self.get_children(): + if child.get_name() == name: + return child + if isinstance(child, Container): + result = child.get_element(name) + if result is not None: + return result + return None + + def remove(self, child, delay=None): + """ + Remove the given child and hide it. + """ + if delay is not None and not delay.is_finished(): + return delay.connect_once(self.remove, child).set_ignore_caller_args() + child.hide() + super(Container, self).remove(child) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the widget. + """ + widgets = [] + for child in element: + w = child.xmlcreate() + if not w: + log.error('unable to parse %s', child.node) + else: + widgets.append(w) + return super(Container, cls).parse_XML(element).update( + dependency=(element.depends or '').split(' '), + widgets=widgets) + + +# register widgets to the core +kaa.candy.xmlparser.register(Container) Added: trunk/WIP/candy2/src/widgets/core.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/core.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,348 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# core.py - Core Widgets and Template +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +__all__ = [ 'Template', 'Widget', 'Group', 'Text', 'Texture', 'Imlib2Texture', 'CairoTexture'] + +# python imports +import logging +import time + +# clutter imports +import clutter +import clutter.cluttercairo +import pango +import cairo +import gtk + +# kaa imports +import kaa.imlib2 + +# kaa.candy imports +import kaa.candy + +# get logging object +log = logging.getLogger('kaa.candy') + +class XMLDict(dict): + """ + XML parser dict helper class. + """ + def update(self, **kwargs): + super(XMLDict, self).update(**kwargs) + return self + +class Template(object): + """ + Template to create a widget on demand. All XML parser will create such an + object to parse everything at once. + """ + def __init__(self, type, cls, kwargs, properties=None): + self.type = type + self._cls = cls + self._kwargs = kwargs + self._properties = properties + self._userdata = {} + + def __call__(self, context=None, **override): + """ + Create the widget with the given context and override some + constructor arguments. + """ + kwargs = self._kwargs + if override: + kwargs = self._kwargs.copy() + for key, value in override.items(): + if key == 'x': + kwargs['pos'] = value, kwargs['pos'][1] + elif key == 'y': + kwargs['pos'] = kwargs['pos'][0], value + elif key == 'width': + kwargs['size'] = value, kwargs['size'][1] + elif key == 'height': + kwargs['size'] = kwargs['size'][0], value + else: + kwargs[key] = value + t1 = time.time() + if self._cls.context_sensitive: + kwargs['context'] = context + try: + widget = self._cls(**kwargs) + except TypeError, e: + log.exception('error creating %s%s', self._cls, kwargs.keys()) + return None + if self._properties: + self._properties.apply(widget) + log.info('Create %s: %s secs', self.type, time.time() - t1) + return widget + + def get_userdata(self, key): + return self._userdata.get(key) + + def set_userdata(self, key, value): + self._userdata[key] = value + + def get(self, attr): + return self._kwargs.get(attr) + + def set_property(self, key, *value): + if self._properties is None: + self._properties = kaa.candy.Properties() + self._properties[key] = value + + @classmethod + def get_class_from_XML(cls, element): + return kaa.candy.xmlparser.get_class(element.node, element.style) + + @classmethod + def from_XML(cls, element): + """ + Parse the XML element for parameter and create a Template. + """ + properties = element.properties + if properties is not None: + element.remove(properties) + properties = kaa.candy.Properties.from_XML(properties) + animations = {} + for child in element.get_children('define-animation'): + element.remove(child) + animations[child.style] = child.xmlcreate() + widget = cls.get_class_from_XML(element) + if widget is None: + log.error('undefined widget %s:%s', element.node, element.style) + kwargs = widget.parse_XML(element) + template = cls(element.node, widget, kwargs, properties) + if animations: + template.set_property('animations', animations) + return template + + +class Widget(object): + """ + Basic widget. All widgets from the backend must inherit from it. + """ + context_sensitive = False + __template__ = Template + + def __init__(self, pos=None, size=None, context=None): + self._animations = [] + if size is not None: + if size[0] is not None: + self.set_width(size[0]) + if size[1] is not None: + self.set_height(size[1]) + if pos is not None: + self.set_position(*pos) + self._context = context + self._depends = [] + self._userdata = {} + + def set_parent(self, parent): + """ + Set the parent widget + """ + if self.get_parent(): + self.get_parent().remove(self) + if parent: + parent.add(self) + + def unparent(self): + """ + Remove the widget + """ + if self.get_parent(): + self.get_parent().remove(self) + + def get_context(self, key=None): + """ + Get the context the widget is in. + """ + if key is None: + return self._context + return self._context.get(key) + + def set_context(self, context): + """ + Set a new context. + """ + self._context = context + + def try_context(self, context): + """ + Check if the widget is capable of the given context based on its + dependencies and return False if not or set the context and return + True if it is. + """ + for var, value in self._depends: + if value != eval(var, context): + return False + self.set_context(context) + return True + + def set_dependency(self, *dependencies): + """ + Set list of dependencies this widget has based on the context. + """ + self._depends = [ (str(d), eval(str(d), self._context)) for d in dependencies ] + + def set_animations(self, animations): + self._animations = animations + + def get_userdata(self, key): + return self._userdata.get(key) + + def set_userdata(self, key, value): + self._userdata[key] = value + + def animate(self, name, *args, **kwargs): + """ + Animate the object with the given animation. + """ + if name in self._animations: + return self._animations[name](self) + a = kaa.candy.xmlparser.get_class('animation', name) + if a: + return a(self, *args, **kwargs) + if not name in ('hide', 'show'): + log.error('no animation named %s', name) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the widget. + """ + return XMLDict(pos=element.pos, size=(element.width, element.height)) + + # def __del__(self): + # print '__del__', self + + +class Group(Widget, clutter.Group): + """ + Group widget. + """ + def __init__(self, pos=None, size=None, context=None): + clutter.Group.__init__(self) + Widget.__init__(self, pos, size, context) + self._max_size = size + + def get_max_size(self): + return self._max_size + + def get_max_width(self): + return self._max_size[0] + + def get_max_height(self): + return self._max_size[1] + + def add(self, child, visible=True): + """ + Add a child and set it visible. + """ + if visible: + child.show() + super(Group, self).add(child) + + +class Text(Widget, clutter.Label): + """ + Pango based text widget. + Used by the higher level text widget + """ + ALIGN_CENTER = pango.ALIGN_CENTER + + def __init__(self, pos, size, context=None): + clutter.Label.__init__(self) + Widget.__init__(self, pos, size, context) + + def set_color(self, color): + super(Text, self).set_color(clutter.Color(*color)) + + +class Texture(Widget, clutter.Texture): + """ + Clutter Texture widget. + """ + def __init__(self, pos=None, size=None, context=None): + clutter.Texture.__init__(self) + Widget.__init__(self, pos, size, context) + + def set_from_file(self, filename): + """ + Set content based on a filename. + """ + pixbuf = gtk.gdk.pixbuf_new_from_file(filename) + self.set_pixbuf(pixbuf) + + +class Imlib2Texture(Texture): + """ + Imlib2 based Texture widget. + """ + class Context(kaa.imlib2.Image): + """ + Imlib2 context to draw on. + """ + def __init__(self, texture, image): + super(Imlib2Texture.Context, self).__init__(image) + self.__texture = texture + + def __del__(self): + """ + Redraw clutter Texture object. + """ + self.__texture.set_from_rgb_data(self.get_raw_data(), True, + self.width, self.height, 1, 4, 0) + + def __init__(self, pos, size, context=None): + super(Imlib2Texture, self).__init__(pos, size, context) + self._image = kaa.imlib2.new(size) + + def imlib2_create(self): + """ + Return Imlib2 context image. + """ + return Imlib2Texture.Context(self, self._image._image) + + +class CairoTexture(Widget, clutter.cluttercairo.CairoTexture): + """ + Cairo based Texture widget, + """ + def __init__(self, pos, size, context=None): + clutter.cluttercairo.CairoTexture.__init__(self, *size) + Widget.__init__(self, pos, None, context) + + def clear(self): + """ + Clear the complete surface + """ + context = self.cairo_create() + context.set_operator(cairo.OPERATOR_CLEAR) + context.set_source_rgba(255,255,255,255) + context.paint() + del context Added: trunk/WIP/candy2/src/widgets/label.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/label.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,142 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# label.py - Label Widget +# ----------------------------------------------------------------------------- +# $Id$ +# +# This code is much faster than the pango based code +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +# python imports +import cairo +import re + +# kaa.candy imports +import core +import kaa.candy + +class Label(core.CairoTexture): + """ + Text label widget based on cairo. + """ + __gui_name__ = 'label' + context_sensitive = True + _font_instance = None + _font_cache = {} + + def __init__(self, pos, size, font, color, text, align='left', + context=None): + depends = [] + if context: + def replace_context(matchobj): + if not matchobj.groups()[0] in depends: + depends.append(matchobj.groups()[0]) + return eval(matchobj.groups()[0], context) + text = re.sub('\$([a-zA-Z_\.]*)', replace_context, text) + if size[1] is None: + size = size[0], Label.get_font_height(font.name, font.size)[2] + super(Label, self).__init__(pos, size, context) + self.set_dependency(*depends) + self._font = font + self._color = color + self._text = text + self._align = align + self._render() + + def set_color(self, color): + """ + Set a new color + """ + self._color = color + self._render() + + def get_color(self): + """ + Return color object. + """ + return self._color + + @classmethod + def get_font_height(cls, name, size): + info = cls._font_cache.get((name, size)) + if info: + return info + if cls._font_instance is None: + cls._font_instance = core.CairoTexture(None, (200,200)) + c = cls._font_instance.cairo_create() + c.select_font_face(name, cairo.FONT_SLANT_NORMAL) + c.set_font_size(size) + extents = c.font_extents() + info = extents[0], -c.text_extents('ARG:FIXME')[1], extents[0] + extents[1] + cls._font_cache[(name, size)] = info + return info + + def _render(self): + """ + Render the text. + """ + try: + self.clear() + # draw new text string + context = self.cairo_create() + except cairo.Error, e: + # surface already gone + return + context.set_operator(cairo.OPERATOR_SOURCE) + context.set_source_rgba(*self._color.to_cairo()) + context.select_font_face(self._font.name, cairo.FONT_SLANT_NORMAL) + context.set_font_size(self._font.size) + x, y, w, h = context.text_extents(self._text)[:4] + # http://www.tortall.net/mu/wiki/CairoTutorial + if self._align == 'left': + x = -x + if self._align == 'center': + x = (self.get_property('surface_width') - w) / 2 - x + if self._align == 'right': + x = self.get_property('surface_width') - w - x + if x < 0: + x = 0 + w = self.get_property('surface_width') + s = cairo.LinearGradient(0, 0, w, 0) + c = self._color.to_cairo() + s.add_color_stop_rgba(0, *c) + # 50 pixel fading + s.add_color_stop_rgba(1 - (50.0 / w), *c) + s.add_color_stop_rgba(1, c[0], c[1], c[2], 0) + context.set_source(s) + context.move_to(x, context.font_extents()[0]) + context.show_text(self._text) + #del context + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the widget. + """ + return super(Label, cls).parse_XML(element).update( + font=element.font, color=element.color, + text=element.content, align=element.align) + +# register widget to the core +kaa.candy.xmlparser.register(Label) Added: trunk/WIP/candy2/src/widgets/progressbar.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/progressbar.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,54 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# progressbar.py - Progressbar Widget +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + +# gui imports +import kaa.candy +import core + +class Progressbar(core.Group): + """ + Widget showing a progressbar. Only the bar is drawn, the border has + to be created ouside this widget. + """ + __gui_name__ = 'progressbar' + + def __init__(self, pos, size, progress): + super(Progressbar, self).__init__(pos, size) + self._width = size[0] + self._max = 0 + self._progress = progress(pos=(0,0), size=size) + self._progress.set_width(1) + self._progress.show() + self.add(self._progress) + + @kaa.candy.gui_execution() + def set_max(self, max): + """ + Set maximum value of the progress. + """ + self._max = max + + @kaa.candy.gui_execution() + def set_progress(self, value): + """ + Set a new progress and redraw the widget. + """ + pos = float(value) / max(self._max, value, 0.1) + self._progress.set_width(int(max(pos * self._width, 1))) + + @classmethod + def parse_XML(cls, element): + """ + Parse the XML element for parameter to create the widget. + """ + return super(Progressbar, cls).parse_XML(element).update( + progress=element[0].xmlcreate()) + +# register widget to the core +kaa.candy.xmlparser.register(Progressbar) Added: trunk/WIP/candy2/src/widgets/rectangle.py ============================================================================== --- (empty file) +++ trunk/WIP/candy2/src/widgets/rectangle.py Tue Jun 24 16:17:24 2008 @@ -0,0 +1,113 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------------- +# rectangle.py - Rectange Widgets +# ----------------------------------------------------------------------------- +# $Id$ +# +# ----------------------------------------------------------------------------- +# kaa-candy - Third generation Canvas System using Clutter as backend +# Copyright (C) 2008 Dirk Meyer, Jason Tackaberry +# +# Please see the file AUTHORS for a complete list of authors. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version +# 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +# ----------------------------------------------------------------------------- + +# kaa.candy imports +import kaa.candy +import core + +class Rectangle(core.CairoTexture): + """ + Rectange with border and round corners based on cairo. + """ + __gui_name__ = 'rectangle' + + def __init__(self, pos, size, color=None, border_size=0, + border_color=None, radius=0): + super(Rectangle, self).__init__(pos, size) + self._radius = radius + self._color = color + self._border_size = border_size + self._border_color = border_color + ... [truncated message content] |