Ticket #1959: 0002-New-feature-grab-the-hands-of-the-clock.patch
File 0002-New-feature-grab-the-hands-of-the-clock.patch, 13.8 KB (added by manuq, 12 years ago) |
---|
-
clock.py
From df2fbf83dc0d5032d6bf174ce29cae44dac08399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= <manuq@laptop.org> Date: Fri, 30 Dec 2011 11:58:45 -0300 Subject: [PATCH clock 2/2] New feature: grab the hands of the clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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@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 b class ClockActivity(activity.Activity): 309 309 button.connect("toggled", self._speak_time_clicked_cb) 310 310 display_toolbar.insert(button, -1) 311 311 312 # Button to toggle drag & drop 313 button = ToggleToolButton("grab") 314 button.set_tooltip(_("Toolbar", "Grab the hands")) 315 button.connect("toggled", self._dragdrop_clicked_cb) 316 display_toolbar.insert(button, -1) 317 312 318 def _make_display(self): 313 319 """Prepare the display of the clock. 314 320 … … class ClockActivity(activity.Activity): 358 364 or digital). 359 365 """ 360 366 self._clock.set_display_mode(display_mode) 367 self._clock.queue_draw() 361 368 362 369 def _write_time_clicked_cb(self, button): 363 370 """The user clicked on the "write time" button to print the … … class ClockActivity(activity.Activity): 378 385 if self._speak_time: 379 386 self._write_and_speak(True) 380 387 388 def _dragdrop_clicked_cb(self, button): 389 self._clock.change_grab_hands_mode(button.get_active()) 390 381 391 def _minutes_changed_cb(self, clock): 382 392 """Minutes have changed on the clock face: we have to update 383 393 the display of the time in full letters if the user has chosen … … class ClockFace(gtk.DrawingArea): 481 491 self._mode = _MODE_SIMPLE_CLOCK 482 492 483 493 # SVG Background handle 484 self._svg_handle = None494 self._svg_handle = rsvg.Handle(file="clock.svg") 485 495 496 # This are calculated on widget resize 497 self._center_x = None 498 self._center_y = None 499 self._width = None 500 self._height = None 486 501 self._radius = -1 487 502 self._line_width = 2 503 self._hour_size = None 504 self._minutes_size = None 505 self._seconds_size = None 506 507 self._hour_angle = None 508 self._minutes_angle = None 509 self._seconds_angle = None 510 511 # When dragging a hand, this is the name of the hand. If 512 # None, it means that no hand is being drag. 513 self._dragging_hand = None 514 515 # In grab hands mode, the clock is not updated each second. 516 # Instead, it is stopped and the child can drag the hands. 517 self._grab_hands_mode = False 518 519 # Tolerance for dragging hands, in radians. Try to change 520 # this value to improve dragging: 521 self._angle_e = 0.3 488 522 489 523 # Color codes (approved colors for XO screen: 490 524 # http://wiki.laptop.org/go/XO_colors) … … class ClockFace(gtk.DrawingArea): 509 543 self.connect("size-allocate", self._size_allocate_cb) 510 544 511 545 # The masks to capture the events we are interested in 512 self.add_events(gdk.EXPOSURE_MASK | gdk.VISIBILITY_NOTIFY_MASK) 546 self.add_events(gdk.EXPOSURE_MASK | gdk.VISIBILITY_NOTIFY_MASK 547 | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK 548 | gtk.gdk.BUTTON1_MOTION_MASK) 513 549 514 550 # Define a new signal to notify the application when minutes 515 551 # change. If the user wants to display the time in full … … class ClockFace(gtk.DrawingArea): 518 554 gobject.signal_new("time_minute", ClockFace, 519 555 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []) 520 556 557 # Event handlers for drag & drop: 558 self._press_id = None 559 self._motion_id = None 560 self._release_id = None 561 521 562 def set_display_mode(self, mode): 522 563 """Set the type of clock to display (simple, nice, digital). 523 564 'mode' is one of MODE_XXX_CLOCK constants. 524 565 """ 525 566 self._mode = mode 526 567 568 def change_grab_hands_mode(self, start_dragging): 569 """Connect or disconnect the callbacks for doing drag & drop 570 of the hands of the clock. 571 """ 572 if start_dragging: 573 self._grab_hands_mode = True 574 self._press_id = self.connect("button-press-event", 575 self._press_cb) 576 self._motion_id = self.connect("motion-notify-event", 577 self._motion_cb) 578 self._release_id = self.connect("button-release-event", 579 self._release_cb) 580 else: 581 self._grab_hands_mode = False 582 self.disconnect(self._press_id) 583 self.disconnect(self._motion_id) 584 self.disconnect(self._release_id) 585 586 # Update again the clock every seconds. 587 gobject.timeout_add(1000, self._update_cb) 588 527 589 def _size_allocate_cb(self, widget, allocation): 528 590 """We know the size of the widget on the screen, so we keep 529 591 the parameters which are important for our rendering (center … … class ClockFace(gtk.DrawingArea): 537 599 self._width = allocation.width 538 600 self._height = allocation.height 539 601 self._line_width = int(self._radius / 150) 540 541 # Reload the svg handle542 self._s vg_handle = rsvg.Handle(file="clock.svg")602 self._hour_size = self._radius * 0.5 603 self._minutes_size = self._radius * 0.8 604 self._seconds_size = self._radius * 0.7 543 605 544 606 self.initialized = True 545 607 … … class ClockFace(gtk.DrawingArea): 729 791 def _draw_hands(self): 730 792 """Draw the hands of the analog clocks. 731 793 """ 732 hours = self._time.hour733 minutes = self._time.minute734 seconds = self._time.second735 736 794 cr = self.window.cairo_create() 737 795 cr.set_line_cap(cairo.LINE_CAP_ROUND) 738 796 … … class ClockFace(gtk.DrawingArea): 742 800 cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) 743 801 cr.set_line_width(8 * self._line_width) 744 802 cr.move_to(self._center_x, self._center_y) 745 cr.line_to(int(self._center_x + self._radius * 0.5 *746 math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)),747 int(self._center_ y + self._radius * 0.5 *748 - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes)))803 sin, cos = math.sin(self._hour_angle), math.cos(self._hour_angle) 804 cr.line_to( 805 int(self._center_x + self._hour_size * sin), 806 int(self._center_y - self._hour_size * cos)) 749 807 cr.stroke() 750 808 751 809 # Minute hand: … … class ClockFace(gtk.DrawingArea): 753 811 cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba()) 754 812 cr.set_line_width(6 * self._line_width) 755 813 cr.move_to(self._center_x, self._center_y) 756 cr.line_to(int(self._center_x + self._radius * 0.8 *757 math.sin(math.pi / 30 * minutes)),758 int(self._center_y + self._radius * 0.8 *759 - math.cos(math.pi / 30 * minutes)))814 sin, cos = math.sin(self._minutes_angle), math.cos(self._minutes_angle) 815 cr.line_to( 816 int(self._center_x + self._minutes_size * sin), 817 int(self._center_y - self._minutes_size * cos)) 760 818 cr.stroke() 761 819 762 820 # Seconds hand: … … class ClockFace(gtk.DrawingArea): 764 822 cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba()) 765 823 cr.set_line_width(2 * self._line_width) 766 824 cr.move_to(self._center_x, self._center_y) 767 cr.line_to(int(self._center_x + self._radius * 0.7 *768 math.sin(math.pi / 30 * seconds)),769 int(self._center_y + self._radius * 0.7 *770 - math.cos(math.pi / 30 * seconds)))825 sin, cos = math.sin(self._seconds_angle), math.cos(self._seconds_angle) 826 cr.line_to( 827 int(self._center_x + self._seconds_size * sin), 828 int(self._center_y - self._seconds_size * cos)) 771 829 cr.stroke() 772 830 773 831 def _draw_numbers(self): … … class ClockFace(gtk.DrawingArea): 803 861 self.queue_draw() 804 862 self.window.process_updates(True) 805 863 864 def _press_cb(self, widget, event): 865 mouse_x, mouse_y, state = event.window.get_pointer() 866 if not (state & gtk.gdk.BUTTON1_MASK): 867 return 868 869 # Calculate the angle from the center of the clock to the 870 # mouse pointer: 871 adjacent = mouse_x - self._center_x 872 opposite = -1 * (mouse_y - self._center_y) 873 pointer_angle = math.atan2(adjacent, opposite) 874 875 # If the angle is negative, convert it to the equal angle 876 # between 0 and 2*pi: 877 if pointer_angle < 0: 878 pointer_angle += math.pi * 2 879 880 def normalize(angle): 881 """Return the equal angle that is minor than 2*pi.""" 882 return angle - (math.pi * 2) * int(angle / (math.pi * 2)) 883 884 def are_near(hand_angle, angle): 885 """Return True if the angles are similar in the unit circle.""" 886 return (normalize(hand_angle) >= angle - self._angle_e and 887 normalize(hand_angle) < angle + self._angle_e) 888 889 def smaller_size(hand_size, adjacent, opposite): 890 """Return True if the distance is smaller than the hand size.""" 891 return math.hypot(adjacent, opposite) <= hand_size 892 893 # Check if we can start dragging a hand of the clock: 894 if are_near(self._hour_angle, pointer_angle): 895 if smaller_size(self._hour_size, adjacent, opposite): 896 self._dragging_hand = 'hour' 897 elif are_near(self._minutes_angle, pointer_angle): 898 if smaller_size(self._minutes_size, adjacent, opposite): 899 self._dragging_hand = 'minutes' 900 elif are_near(self._seconds_angle, pointer_angle): 901 if smaller_size(self._seconds_size, adjacent, opposite): 902 self._dragging_hand = 'seconds' 903 904 def _motion_cb(self, widget, event): 905 if self._dragging_hand is None: 906 return 907 908 if event.is_hint: 909 mouse_x, mouse_y, state = event.window.get_pointer() 910 else: 911 mouse_x = event.x 912 mouse_y = event.y 913 state = event.state 914 915 if not state & gtk.gdk.BUTTON1_MASK: 916 return 917 918 # Calculate the angle from the center of the clock to the 919 # mouse pointer: 920 adjacent = mouse_x - self._center_x 921 opposite = -1 * (mouse_y - self._center_y) 922 pointer_angle = math.atan2(adjacent, opposite) 923 924 # If the angle is negative, convert it to the equal angle 925 # between 0 and 2*pi: 926 if pointer_angle < 0: 927 pointer_angle += math.pi * 2 928 929 # Update the angle of the hand being drag: 930 if self._dragging_hand == 'hour': 931 self._hour_angle = pointer_angle 932 elif self._dragging_hand == 'minutes': 933 self._minutes_angle = pointer_angle 934 elif self._dragging_hand == 'seconds': 935 self._seconds_angle = pointer_angle 936 937 # Force redraw of the clock: 938 self.queue_draw() 939 940 def _release_cb(self, widget, event): 941 self._dragging_hand = None 942 self.queue_draw() 943 806 944 def _update_cb(self): 807 945 """Called every seconds to update the time value. 808 946 """ 809 947 # update the time and force a redraw of the clock 810 948 self._time = datetime.now() 811 949 950 hours = self._time.hour 951 minutes = self._time.minute 952 seconds = self._time.second 953 954 self._hour_angle = math.pi / 6 * hours + math.pi / 360 * minutes 955 self._minutes_angle = math.pi / 30 * minutes 956 self._seconds_angle = math.pi / 30 * seconds 957 812 958 gobject.idle_add(self._redraw_canvas) 813 959 814 960 # When the minutes change, we raise the 'time_minute' … … class ClockFace(gtk.DrawingArea): 820 966 self._old_minute = self._time.minute 821 967 822 968 # Keep running this timer as long as the clock is active 823 # (ie. visible) 824 return self._active 969 # (ie. visible) or the mode changes to dragging the hands of 970 # the clock. 971 return self._active and not self._grab_hands_mode 825 972 826 973 def get_time(self): 827 974 """Public access to the time member of the clock face. -
new file icons/grab.svg
diff --git a/icons/grab.svg b/icons/grab.svg new file mode 100644 index 0000000..9ca34fb
- + 1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ 3 <!ENTITY fill_color "#FFFFFF"> 4 <!ENTITY stroke_color "#FFFFFF"> 5 ]> 6 <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"> 7 <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>