[Sugar-devel] [PATCH clock 2/2] New feature: grab the hands of the clock
Gary Martin
garycmartin at googlemail.com
Sun Jan 1 06:16:28 EST 2012
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
>
More information about the Sugar-devel
mailing list