Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Poirier <simpoir@gmail.com>2009-12-16 19:23:03 (GMT)
committer Simon Poirier <simpoir@gmail.com>2009-12-16 19:23:03 (GMT)
commit6d1cf01b2b20a8deabb6bde44777e9823b0201ce (patch)
treed076d5f5001504c7a42b4ce37cfb6f545afd552c
parent3e2e2d774322511d4d18457299ac8205bd9aed87 (diff)
screen clipping tool
-rw-r--r--addons/screenclipper.py124
-rw-r--r--data/icons/screenclip.svg26
-rw-r--r--tutorius/addon.py13
-rw-r--r--tutorius/properties.py23
-rw-r--r--tutorius/propwidgets.py110
5 files changed, 292 insertions, 4 deletions
diff --git a/addons/screenclipper.py b/addons/screenclipper.py
new file mode 100644
index 0000000..7ad6808
--- /dev/null
+++ b/addons/screenclipper.py
@@ -0,0 +1,124 @@
+# 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 TScreenClipProperty, \
+ TArrayProperty
+import gtk
+
+class ScreenClip(Action):
+ # Create the position as an array of fixed-size 2
+ position = TArrayProperty((0,0), 2, 2)
+ # Do the same for the tail position
+ clip_image = TScreenClipProperty("")
+
+ def __init__(self, position=None,
+ clip_image=None,
+ **kwargs):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param position A list of the form [x, y]
+ @param clip_image the screen clip image path
+ @param clip_rect rectangle of the clip image
+ """
+ Action.__init__(self, **kwargs)
+
+ if position:
+ self.position = position
+ if clip_image:
+ self.clip_image = clip_image
+
+ self.overlay = None
+ self._bubble = None
+
+ def do(self, overlayer=None, **kwargs):
+ """
+ Show the dialog
+ """
+ if overlayer is None:
+ raise TypeError("Missing overlayer argument")
+
+ self.overlay = overlayer
+
+ if not self._bubble:
+ # Normal gtk widgets use the parent window to draw themselves.
+ # For normal layouts, this is good, but as we are using the layout
+ # for stacking widgets, the rendering order is sometimes wrong.
+ # Thus, by adding the Image widget in a visible EventBox, we ensure
+ # the Image is drawn in its own window, and stacking is correct.
+ x, y = self.position
+ self._bubble = gtk.EventBox()
+ self._bubble.set_visible_window(True)
+ image = gtk.Image()
+ image.set_from_file(self.clip_image)
+ self._bubble.add(image)
+ self._bubble.show_all()
+ 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._bubble.destroy()
+ self._bubble = None
+
+ def enter_editmode(self, overlayer=None, *args, **kwargs):
+ """
+ Enters edit mode. The action should display itself in some way,
+ without affecting the currently running application.
+ """
+ if overlayer is None:
+ raise TypeError("Missing overlayer argument")
+
+ self.overlay = overlayer
+ assert not self._drag, "bubble action set to editmode twice"
+ x, y = self.position
+ if self.clip_image:
+ self._bubble = gtk.EventBox()
+ self._bubble.set_visible_window(True)
+ image = gtk.Image()
+ image.set_from_file(self.clip_image)
+ self._bubble.add(image)
+ self._bubble.show_all()
+ self.overlay.put(self._bubble, x, y)
+
+ self._drag = DragWrapper(self._bubble, self.position,
+ update_action_cb=self.update_property,
+ draggable=True)
+
+ def exit_editmode(self, *args):
+ if self._drag:
+ x, y = self._drag.position
+ self.position = (int(x), int(y))
+
+ self._drag.draggable = False
+ self._drag = None
+ if self._bubble:
+ self.overlay.remove(self._bubble)
+ self._bubble = None
+ self.overlay = None
+
+__action__ = {
+ "name" : ScreenClip.__name__,
+ "display_name" : "Screen Capture Clip",
+ "icon" : "screenclip",
+ "class" : ScreenClip,
+ "mandatory_props" : []
+}
+
diff --git a/data/icons/screenclip.svg b/data/icons/screenclip.svg
new file mode 100644
index 0000000..67e6680
--- /dev/null
+++ b/data/icons/screenclip.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="51" id="svg2" inkscape:version="0.47 r22583" sodipodi:docname="New document 1" version="1.1" width="51" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs4">
+ <inkscape:perspective id="perspective10" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" inkscape:vp_x="0 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="744.09448 : 526.18109 : 1" sodipodi:type="inkscape:persp3d"/>
+ </defs>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="213.0625" inkscape:cy="36.59674" inkscape:document-units="px" inkscape:guide-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="628" inkscape:window-maximized="0" inkscape:window-width="1093" inkscape:window-x="20" inkscape:window-y="20" inkscape:zoom="0.96" pagecolor="#ffffff" showgrid="false" showguides="true"/>
+ <metadata id="metadata7">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,-1001.3622)">
+ <rect height="37.728832" id="rect2816" style="color:#000000;fill:none;stroke:&stroke_color;;stroke-width:3.72792578;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:7.45585251, 7.45585251;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" width="32.628826" x="12.63153" y="1006.964"/>
+ <g id="g3609" transform="matrix(0,-1.2919257,-1.2002032,0,1263.209,1094.1684)">
+ <path d="m 38.46875,29.0625 2.125,3.625 c 0,0 -4.09196,3.6458 -4,6.4375 0.09196,2.7917 2.387558,7.1156 5.53125,6.9375 3.640064,-1.181 0.537287,-9.13245 1.0625,-12.40625 1.990315,3.0125 5.834605,10.3756 9.75,9.5 1.098405,-0.9127 2.29467,-4.6001 0.34375,-7.6875 C 51.255431,32.26285 45.84375,32 45.84375,32 L 46.5625,13.90625 43.34375,11.5 l -1.1875,19.4375 -3.6875,-8.1875 0,6.3125 z" id="path3607" style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.9485935;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" transform="translate(0,1001.3622)"/>
+ <path d="m 49.54564,1037.9255 c 0.369232,0.5291 0.738464,1.0581 1.107696,1.5872" id="path3592" style="color:#000000;fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:2.288059;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ <path d="m 40.618302,1040.3023 c 0.01806,0.1307 -0.09311,0.2591 -0.07504,0.3898 0.02955,0.2138 0.214643,0.01 0.04284,0.3101 -0.02316,0.041 -0.10904,0.034 -0.117883,0.08 -0.01769,0.092 0.178412,0.1384 0.160722,0.2303 -0.0088,0.046 -0.09472,0.039 -0.117884,0.08 -0.03276,0.058 -0.02501,0.1299 -0.03752,0.1949 -0.01251,0.065 -0.04655,0.1295 -0.03752,0.1949 0.0064,0.046 0.05357,0.077 0.08036,0.1151 0.01428,0.1034 0.04174,0.2057 0.04284,0.3101 0.004,0.3766 -0.266515,0.1154 -0.07505,0.3898" id="path3594" style="color:#000000;fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.9485935;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/tutorius/addon.py b/tutorius/addon.py
index ca729ae..1fd5143 100644
--- a/tutorius/addon.py
+++ b/tutorius/addon.py
@@ -78,9 +78,16 @@ def create(name, *args, **kwargs):
comp_metadata = _cache[name]
try:
return comp_metadata['class'](*args, **kwargs)
- except:
- logging.debug("Could not instantiate %s with parameters %s, %s"%(comp_metadata['name'],str(args), str(kwargs)))
- return None
+ except Exception, e:
+ LOGGER.error("Could not instantiate %s with parameters %s, %s"%\
+ (comp_metadata['name'],str(args), str(kwargs)))
+
+ # fetch frame information to complement exception with traceback
+ type, value, tb = sys.exc_info()
+ formatted_tb = traceback.format_tb(tb)
+ LOGGER.error('Error loadin tutorius add-on named [%s]:\n%s\n%s' % \
+ (name, '\n'.join(formatted_tb), str(e)))
+ return None
except KeyError:
logging.debug("Addon not found for class '%s'", name)
return None
diff --git a/tutorius/properties.py b/tutorius/properties.py
index b363a2a..1905117 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -37,7 +37,8 @@ from .propwidgets import PropWidget, \
IntPropWidget, \
FloatPropWidget, \
IntArrayPropWidget, \
- ResourcePropWidget
+ ResourcePropWidget, \
+ ScreenClipPropWidget
import logging
LOGGER = logging.getLogger("properties")
@@ -390,6 +391,26 @@ class TResourceProperty(TutoriusProperty):
self.default = self.validate("")
+class TScreenClipProperty(TResourceProperty):
+ """
+ Represents an image resource from a screen capture
+
+ When the system encounters a resource, it knows that it refers to a file in
+ the resource folder and that it should translate this resource name to an
+ absolute file name before it is executed.
+ """
+
+ widget_class = ScreenClipPropWidget
+
+ def __init__(self, *args, **kwargs):
+ """
+ Creates a new resource pointing to an existing resource.
+
+ @param resource_name The file name of the resource (should be only the
+ file name itself, no directory information)
+ """
+ super(TScreenClipProperty, self).__init__(*args, **kwargs)
+
class TEnumProperty(TutoriusProperty):
"""
Represents a value in a given enumeration. This means that the value will
diff --git a/tutorius/propwidgets.py b/tutorius/propwidgets.py
index 3debf98..0e6c200 100644
--- a/tutorius/propwidgets.py
+++ b/tutorius/propwidgets.py
@@ -24,6 +24,8 @@ from jarabe.journal.objectchooser import ObjectChooser
from sugar.datastore.datastore import DSObject
from sugar import mime
import uuid
+import tempfile
+import os
import logging
LOGGER = logging.getLogger("sugar.tutorius.propwidgets")
@@ -570,3 +572,111 @@ class ResourcePropWidget(PropWidget):
"""
raise RuntimeError('Cannot select a default resource')
+class ScreenClipPropWidget(PropWidget):
+ """Allows adding and changing tutorial resources."""
+ def _on_drag_end(self, widget, event, pixbuf):
+ from . import creator
+ widget.destroy()
+
+ end_x, end_y = event.get_coords()
+ width = abs(end_x - self.start_x)
+ height = abs(end_y - self.start_y)
+ x_off = min(self.start_x, end_x)
+ y_off = min(self.start_y, end_y)
+
+ cropped = pixbuf.subpixbuf(x_off, y_off, width, height)
+
+ tmp_name = tempfile.mktemp(suffix='.png')
+ try:
+ cropped.save(tmp_name, 'png')
+ creator_obj = creator.default_creator()
+ resource_id = creator_obj.set_resource(self.obj_prop, tmp_name)
+ self.obj_prop = resource_id
+ finally:
+ os.unlink(tmp_name)
+
+ self.notify()
+
+ def _on_drag_start(self, widget, event, pixbuf):
+ widget.connect('button-release-event', self._on_drag_end, pixbuf)
+ widget.connect('motion-notify-event', self._on_drag_move, pixbuf)
+ self.start_x, self.start_y = event.get_coords()
+
+ def _on_drag_move(self, widget, event, pixbuf):
+ if gtk.gdk.events_pending():
+ return
+
+ end_x, end_y = event.get_coords()
+ width = abs(end_x - self.start_x)
+ height = abs(end_y - self.start_y)
+ x_off = min(self.start_x, end_x)
+ y_off = min(self.start_y, end_y)
+
+ ctx = widget.window.cairo_create()
+ ctx.set_source_pixbuf(pixbuf, 0, 0)
+ ctx.paint()
+
+ ctx.set_source_rgb(0, 0, 0)
+ ctx.rectangle(x_off, y_off, width, height)
+ ctx.stroke()
+
+ def _get_capture(self, widget):
+ """
+ Select a resource and add it through the creator.
+ This is expected to run in the same process, alongside the creator.
+ """
+ # take screen capture
+ root = gtk.gdk.get_default_root_window()
+ width, height = root.get_size()
+ pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
+ width, height)
+ pixbuf.get_from_drawable(src=root,
+ cmap=gtk.gdk.colormap_get_system(),
+ src_x=0, src_y=0,
+ dest_x=0, dest_y=0,
+ width=width, height=height)
+
+ win = gtk.Window()
+ image = gtk.Image()
+ image.set_from_pixbuf(pixbuf)
+ win.add(image)
+ win.show_all()
+ win.set_app_paintable(True)
+ win.fullscreen()
+ win.present()
+ win.add_events(gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK | \
+ gtk.gdk.POINTER_MOTION_MASK)
+ win.connect('button-press-event', self._on_drag_start, pixbuf)
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ propwdg = gtk.Button("Clip Screen")
+ propwdg.connect_after("clicked", self._get_capture)
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ # Nothing to refresh
+ pass
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ # TODO We're assuming all reasource creation is done from the creator
+ # and not from the probe since there is a requirement to know the guid
+ # to add resources. But for this resource type, this could technically
+ # be done in the probe.
+ raise RuntimeError('Cannot select a default resource')