[Sugar-devel] [PATCH clock 2/2] New feature: grab the hands of the clock

Gonzalo Odiard gonzalo at laptop.org
Sun Jan 1 09:50:47 EST 2012


Hi Gary,
These changes do not need the new Gtk3 env.

Gonzalo

2012/1/1 Gary Martin <garycmartin at googlemail.com>

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


More information about the Sugar-devel mailing list