Hi Gary,<br>These changes do not need the new Gtk3 env.<br><br>Gonzalo<br><br><div class="gmail_quote">2012/1/1 Gary Martin <span dir="ltr"><<a href="mailto:garycmartin@googlemail.com">garycmartin@googlemail.com</a>></span><br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi Manuel,<br>
<br>
Just wanted to ping say thanks for all the effort on Clock, and apologise for not moving forward on the patches. I don't yet have a suitable Sugar development environment to test any of this new gtk3 dependent work, and I have a minimal network connection for at least a week or two so no chance of downloading/building new test environments.<br>

<br>
Regards, and a Happy New Year!<br>
<span class="HOEnZb"><font color="#888888">--Gary<br>
</font></span><div class="HOEnZb"><div class="h5"><br>
On 30 Dec 2011, at 15:00, Manuel Quiñones wrote:<br>
<br>
> This is a feature often requested by pedagogues.  Moving the hands of<br>
> the clock is very useful for the children to learn the relation of<br>
> hours, minutes, and seconds.<br>
><br>
> This patch needs the previously sent patch for porting to Cairo:<br>
> <a href="http://lists.sugarlabs.org/archive/sugar-devel/2011-December/034993.html" target="_blank">http://lists.sugarlabs.org/archive/sugar-devel/2011-December/034993.html</a><br>
><br>
> Adds a new toggle button in the toolbar, with the graphic of a hand.<br>
> When active, the hands of the clock can be grabbed.  When deactivated,<br>
> the clock returns to the actual hour.<br>
><br>
> In the future, it would be good to update the displayed time in the<br>
> label with the customized time, and use the talking button to say the<br>
> customized time.<br>
><br>
> Signed-off-by: Manuel Quiñones <<a href="mailto:manuq@laptop.org">manuq@laptop.org</a>><br>
> ---<br>
> clock.py       |  193 +++++++++++++++++++++++++++++++++++++++++++++++++-------<br>
> icons/grab.svg |    7 ++<br>
> 2 files changed, 177 insertions(+), 23 deletions(-)<br>
> create mode 100644 icons/grab.svg<br>
><br>
> diff --git a/clock.py b/clock.py<br>
> index 7635b2e..0bae06c 100755<br>
> --- a/clock.py<br>
> +++ b/clock.py<br>
> @@ -309,6 +309,12 @@ class ClockActivity(activity.Activity):<br>
>         button.connect("toggled", self._speak_time_clicked_cb)<br>
>         display_toolbar.insert(button, -1)<br>
><br>
> +        # Button to toggle drag & drop<br>
> +        button = ToggleToolButton("grab")<br>
> +        button.set_tooltip(_("Toolbar", "Grab the hands"))<br>
> +        button.connect("toggled", self._dragdrop_clicked_cb)<br>
> +        display_toolbar.insert(button, -1)<br>
> +<br>
>     def _make_display(self):<br>
>         """Prepare the display of the clock.<br>
><br>
> @@ -358,6 +364,7 @@ class ClockActivity(activity.Activity):<br>
>         or digital).<br>
>         """<br>
>         self._clock.set_display_mode(display_mode)<br>
> +        self._clock.queue_draw()<br>
><br>
>     def _write_time_clicked_cb(self, button):<br>
>         """The user clicked on the "write time" button to print the<br>
> @@ -378,6 +385,9 @@ class ClockActivity(activity.Activity):<br>
>         if self._speak_time:<br>
>             self._write_and_speak(True)<br>
><br>
> +    def _dragdrop_clicked_cb(self, button):<br>
> +        self._clock.change_grab_hands_mode(button.get_active())<br>
> +<br>
>     def _minutes_changed_cb(self, clock):<br>
>         """Minutes have changed on the clock face: we have to update<br>
>         the display of the time in full letters if the user has chosen<br>
> @@ -481,10 +491,34 @@ class ClockFace(gtk.DrawingArea):<br>
>         self._mode = _MODE_SIMPLE_CLOCK<br>
><br>
>         # SVG Background handle<br>
> -        self._svg_handle = None<br>
> +        self._svg_handle = rsvg.Handle(file="clock.svg")<br>
><br>
> +        # This are calculated on widget resize<br>
> +        self._center_x = None<br>
> +        self._center_y = None<br>
> +        self._width = None<br>
> +        self._height = None<br>
>         self._radius = -1<br>
>         self._line_width = 2<br>
> +        self._hour_size = None<br>
> +        self._minutes_size = None<br>
> +        self._seconds_size = None<br>
> +<br>
> +        self._hour_angle = None<br>
> +        self._minutes_angle = None<br>
> +        self._seconds_angle = None<br>
> +<br>
> +        # When dragging a hand, this is the name of the hand.  If<br>
> +        # None, it means that no hand is being drag.<br>
> +        self._dragging_hand = None<br>
> +<br>
> +        # In grab hands mode, the clock is not updated each second.<br>
> +        # Instead, it is stopped and the child can drag the hands.<br>
> +        self._grab_hands_mode = False<br>
> +<br>
> +        # Tolerance for dragging hands, in radians.  Try to change<br>
> +        # this value to improve dragging:<br>
> +        self._angle_e = 0.3<br>
><br>
>         # Color codes (approved colors for XO screen:<br>
>         # <a href="http://wiki.laptop.org/go/XO_colors" target="_blank">http://wiki.laptop.org/go/XO_colors</a>)<br>
> @@ -509,7 +543,9 @@ class ClockFace(gtk.DrawingArea):<br>
>         self.connect("size-allocate", self._size_allocate_cb)<br>
><br>
>         # The masks to capture the events we are interested in<br>
> -        self.add_events(gdk.EXPOSURE_MASK | gdk.VISIBILITY_NOTIFY_MASK)<br>
> +        self.add_events(gdk.EXPOSURE_MASK | gdk.VISIBILITY_NOTIFY_MASK<br>
> +            | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK<br>
> +            | gtk.gdk.BUTTON1_MOTION_MASK)<br>
><br>
>         # Define a new signal to notify the application when minutes<br>
>         # change.  If the user wants to display the time in full<br>
> @@ -518,12 +554,38 @@ class ClockFace(gtk.DrawingArea):<br>
>         gobject.signal_new("time_minute", ClockFace,<br>
>           gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])<br>
><br>
> +        # Event handlers for drag & drop:<br>
> +        self._press_id = None<br>
> +        self._motion_id = None<br>
> +        self._release_id = None<br>
> +<br>
>     def set_display_mode(self, mode):<br>
>         """Set the type of clock to display (simple, nice, digital).<br>
>         'mode' is one of MODE_XXX_CLOCK constants.<br>
>         """<br>
>         self._mode = mode<br>
><br>
> +    def change_grab_hands_mode(self, start_dragging):<br>
> +        """Connect or disconnect the callbacks for doing drag & drop<br>
> +        of the hands of the clock.<br>
> +        """<br>
> +        if start_dragging:<br>
> +            self._grab_hands_mode = True<br>
> +            self._press_id = self.connect("button-press-event",<br>
> +                                          self._press_cb)<br>
> +            self._motion_id = self.connect("motion-notify-event",<br>
> +                                           self._motion_cb)<br>
> +            self._release_id = self.connect("button-release-event",<br>
> +                                        self._release_cb)<br>
> +        else:<br>
> +            self._grab_hands_mode = False<br>
> +            self.disconnect(self._press_id)<br>
> +            self.disconnect(self._motion_id)<br>
> +            self.disconnect(self._release_id)<br>
> +<br>
> +            # Update again the clock every seconds.<br>
> +            gobject.timeout_add(1000, self._update_cb)<br>
> +<br>
>     def _size_allocate_cb(self, widget, allocation):<br>
>         """We know the size of the widget on the screen, so we keep<br>
>         the parameters which are important for our rendering (center<br>
> @@ -537,9 +599,9 @@ class ClockFace(gtk.DrawingArea):<br>
>         self._width = allocation.width<br>
>         self._height = allocation.height<br>
>         self._line_width = int(self._radius / 150)<br>
> -<br>
> -        # Reload the svg handle<br>
> -        self._svg_handle = rsvg.Handle(file="clock.svg")<br>
> +        self._hour_size = self._radius * 0.5<br>
> +        self._minutes_size = self._radius * 0.8<br>
> +        self._seconds_size = self._radius * 0.7<br>
><br>
>         self.initialized = True<br>
><br>
> @@ -729,10 +791,6 @@ class ClockFace(gtk.DrawingArea):<br>
>     def _draw_hands(self):<br>
>         """Draw the hands of the analog clocks.<br>
>         """<br>
> -        hours = self._time.hour<br>
> -        minutes = self._time.minute<br>
> -        seconds = self._time.second<br>
> -<br>
>         cr = self.window.cairo_create()<br>
>         cr.set_line_cap(cairo.LINE_CAP_ROUND)<br>
><br>
> @@ -742,10 +800,10 @@ class ClockFace(gtk.DrawingArea):<br>
>         cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba())<br>
>         cr.set_line_width(8 * self._line_width)<br>
>         cr.move_to(self._center_x, self._center_y)<br>
> -        cr.line_to(int(self._center_x + self._radius * 0.5 *<br>
> -            math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)),<br>
> -            int(self._center_y + self._radius * 0.5 *<br>
> -            - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes)))<br>
> +        sin, cos = math.sin(self._hour_angle), math.cos(self._hour_angle)<br>
> +        cr.line_to(<br>
> +            int(self._center_x + self._hour_size * sin),<br>
> +            int(self._center_y - self._hour_size * cos))<br>
>         cr.stroke()<br>
><br>
>         # Minute hand:<br>
> @@ -753,10 +811,10 @@ class ClockFace(gtk.DrawingArea):<br>
>         cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba())<br>
>         cr.set_line_width(6 * self._line_width)<br>
>         cr.move_to(self._center_x, self._center_y)<br>
> -        cr.line_to(int(self._center_x + self._radius * 0.8 *<br>
> -                math.sin(math.pi / 30 * minutes)),<br>
> -                   int(self._center_y + self._radius * 0.8 *<br>
> -                - math.cos(math.pi / 30 * minutes)))<br>
> +        sin, cos = math.sin(self._minutes_angle), math.cos(self._minutes_angle)<br>
> +        cr.line_to(<br>
> +            int(self._center_x + self._minutes_size * sin),<br>
> +            int(self._center_y - self._minutes_size * cos))<br>
>         cr.stroke()<br>
><br>
>         # Seconds hand:<br>
> @@ -764,10 +822,10 @@ class ClockFace(gtk.DrawingArea):<br>
>         cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba())<br>
>         cr.set_line_width(2 * self._line_width)<br>
>         cr.move_to(self._center_x, self._center_y)<br>
> -        cr.line_to(int(self._center_x + self._radius * 0.7 *<br>
> -                math.sin(math.pi / 30 * seconds)),<br>
> -                int(self._center_y + self._radius * 0.7 *<br>
> -                - math.cos(math.pi / 30 * seconds)))<br>
> +        sin, cos = math.sin(self._seconds_angle), math.cos(self._seconds_angle)<br>
> +        cr.line_to(<br>
> +            int(self._center_x + self._seconds_size * sin),<br>
> +            int(self._center_y - self._seconds_size * cos))<br>
>         cr.stroke()<br>
><br>
>     def _draw_numbers(self):<br>
> @@ -803,12 +861,100 @@ class ClockFace(gtk.DrawingArea):<br>
>             self.queue_draw()<br>
>             self.window.process_updates(True)<br>
><br>
> +    def _press_cb(self, widget, event):<br>
> +        mouse_x, mouse_y, state = event.window.get_pointer()<br>
> +        if not (state & gtk.gdk.BUTTON1_MASK):<br>
> +            return<br>
> +<br>
> +        # Calculate the angle from the center of the clock to the<br>
> +        # mouse pointer:<br>
> +        adjacent = mouse_x - self._center_x<br>
> +        opposite = -1 * (mouse_y - self._center_y)<br>
> +        pointer_angle = math.atan2(adjacent, opposite)<br>
> +<br>
> +        # If the angle is negative, convert it to the equal angle<br>
> +        # between 0 and 2*pi:<br>
> +        if pointer_angle < 0:<br>
> +            pointer_angle += math.pi * 2<br>
> +<br>
> +        def normalize(angle):<br>
> +            """Return the equal angle that is minor than 2*pi."""<br>
> +            return angle - (math.pi * 2) * int(angle / (math.pi * 2))<br>
> +<br>
> +        def are_near(hand_angle, angle):<br>
> +            """Return True if the angles are similar in the unit circle."""<br>
> +            return (normalize(hand_angle) >= angle - self._angle_e and<br>
> +                    normalize(hand_angle) < angle + self._angle_e)<br>
> +<br>
> +        def smaller_size(hand_size, adjacent, opposite):<br>
> +            """Return True if the distance is smaller than the hand size."""<br>
> +            return math.hypot(adjacent, opposite) <= hand_size<br>
> +<br>
> +        # Check if we can start dragging a hand of the clock:<br>
> +        if are_near(self._hour_angle, pointer_angle):<br>
> +            if smaller_size(self._hour_size, adjacent, opposite):<br>
> +                self._dragging_hand = 'hour'<br>
> +        elif are_near(self._minutes_angle, pointer_angle):<br>
> +            if smaller_size(self._minutes_size, adjacent, opposite):<br>
> +                self._dragging_hand = 'minutes'<br>
> +        elif are_near(self._seconds_angle, pointer_angle):<br>
> +            if smaller_size(self._seconds_size, adjacent, opposite):<br>
> +                self._dragging_hand = 'seconds'<br>
> +<br>
> +    def _motion_cb(self, widget, event):<br>
> +        if self._dragging_hand is None:<br>
> +            return<br>
> +<br>
> +        if event.is_hint:<br>
> +            mouse_x, mouse_y, state = event.window.get_pointer()<br>
> +        else:<br>
> +            mouse_x = event.x<br>
> +            mouse_y = event.y<br>
> +            state = event.state<br>
> +<br>
> +        if not state & gtk.gdk.BUTTON1_MASK:<br>
> +            return<br>
> +<br>
> +        # Calculate the angle from the center of the clock to the<br>
> +        # mouse pointer:<br>
> +        adjacent = mouse_x - self._center_x<br>
> +        opposite = -1 * (mouse_y - self._center_y)<br>
> +        pointer_angle = math.atan2(adjacent, opposite)<br>
> +<br>
> +        # If the angle is negative, convert it to the equal angle<br>
> +        # between 0 and 2*pi:<br>
> +        if pointer_angle < 0:<br>
> +            pointer_angle += math.pi * 2<br>
> +<br>
> +        # Update the angle of the hand being drag:<br>
> +        if self._dragging_hand == 'hour':<br>
> +            self._hour_angle = pointer_angle<br>
> +        elif self._dragging_hand == 'minutes':<br>
> +            self._minutes_angle = pointer_angle<br>
> +        elif self._dragging_hand == 'seconds':<br>
> +            self._seconds_angle = pointer_angle<br>
> +<br>
> +        # Force redraw of the clock:<br>
> +        self.queue_draw()<br>
> +<br>
> +    def _release_cb(self, widget, event):<br>
> +        self._dragging_hand = None<br>
> +        self.queue_draw()<br>
> +<br>
>     def _update_cb(self):<br>
>         """Called every seconds to update the time value.<br>
>         """<br>
>         # update the time and force a redraw of the clock<br>
>         self._time = datetime.now()<br>
><br>
> +        hours = self._time.hour<br>
> +        minutes = self._time.minute<br>
> +        seconds = self._time.second<br>
> +<br>
> +        self._hour_angle = math.pi / 6 * hours + math.pi / 360 * minutes<br>
> +        self._minutes_angle = math.pi / 30 * minutes<br>
> +        self._seconds_angle = math.pi / 30 * seconds<br>
> +<br>
>         gobject.idle_add(self._redraw_canvas)<br>
><br>
>         # When the minutes change, we raise the 'time_minute'<br>
> @@ -820,8 +966,9 @@ class ClockFace(gtk.DrawingArea):<br>
>             self._old_minute = self._time.minute<br>
><br>
>         # Keep running this timer as long as the clock is active<br>
> -        # (ie. visible)<br>
> -        return self._active<br>
> +        # (ie. visible) or the mode changes to dragging the hands of<br>
> +        # the clock.<br>
> +        return self._active and not self._grab_hands_mode<br>
><br>
>     def get_time(self):<br>
>         """Public access to the time member of the clock face.<br>
> diff --git a/icons/grab.svg b/icons/grab.svg<br>
> new file mode 100644<br>
> index 0000000..9ca34fb<br>
> --- /dev/null<br>
> +++ b/icons/grab.svg<br>
> @@ -0,0 +1,7 @@<br>
> +<?xml version="1.0" encoding="UTF-8"?><br>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "<a href="http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" target="_blank">http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd</a>" [<br>
> +  <!ENTITY fill_color "#FFFFFF"><br>
> +  <!ENTITY stroke_color "#FFFFFF"><br>
> +]><br>
> +<svg xmlns="<a href="http://www.w3.org/2000/svg" target="_blank">http://www.w3.org/2000/svg</a>" width="50" height="50"><br>
> +<path d="M11,18 Q13,16 15,19 L15,31 Q16,33 17,31 L17,7 Q19,5 21,7 L21,25 Q22,27 23,25 L23,7 Q25,5 27,7 L27,25 Q28,27 29,25 L29,7 Q31,5 33,7 L33,25 Q34,27 35,25 L35,12 Q37,10 39,12 L39,37 Q39,44 33,45 L17,45 Q11,45 11,37" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:.3"/></svg><br>

> --<br>
> 1.7.7.4<br>
><br>
<br>
_______________________________________________<br>
Sugar-devel mailing list<br>
<a href="mailto:Sugar-devel@lists.sugarlabs.org">Sugar-devel@lists.sugarlabs.org</a><br>
<a href="http://lists.sugarlabs.org/listinfo/sugar-devel" target="_blank">http://lists.sugarlabs.org/listinfo/sugar-devel</a><br>
</div></div></blockquote></div><br>