From 40ae77291c5de5dded5a89d87b5d2b645c3c86aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= <manuq@laptop.org>
Date: Tue, 27 Dec 2011 13:25:31 -0300
Subject: [PATCH clock 1/2] Port to Cairo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This will ease the port to GTK+ 3, and it gains in nicer graphics,
sharp edges now have antialiasing.
Signed-off-by: Manuel Quiñones <manuq@laptop.org>
---
clock.py | 182 ++++++++++++++++++++++++++++++++++----------------------------
1 files changed, 100 insertions(+), 82 deletions(-)
diff --git a/clock.py b/clock.py
index 1893b77..7635b2e 100755
a
|
b
|
import pygtk |
70 | 70 | import gtk |
71 | 71 | from gtk import gdk |
72 | 72 | import pango |
| 73 | import cairo |
| 74 | import pangocairo |
| 75 | import rsvg |
73 | 76 | |
74 | 77 | OLD_TOOLBAR = False |
75 | 78 | try: |
… |
… |
except ImportError: |
82 | 85 | import math |
83 | 86 | from datetime import datetime |
84 | 87 | import threading |
85 | | import gc |
86 | 88 | import re |
87 | 89 | |
88 | 90 | from pgettext import pgettext as _ |
… |
… |
from pgettext import pgettext as _ |
90 | 92 | from sugar.activity import activity |
91 | 93 | from sugar.graphics.toggletoolbutton import ToggleToolButton |
92 | 94 | from sugar.graphics.radiotoolbutton import RadioToolButton |
| 95 | from sugar.graphics import style |
93 | 96 | |
94 | 97 | from speaker import Speaker |
95 | 98 | from timewriter import TimeWriter |
… |
… |
class ClockFace(gtk.DrawingArea): |
477 | 480 | # The display mode of the clock |
478 | 481 | self._mode = _MODE_SIMPLE_CLOCK |
479 | 482 | |
480 | | # SVG Background cache |
481 | | self._cache_pixbuf = None |
482 | | self._radius = -1 |
| 483 | # SVG Background handle |
| 484 | self._svg_handle = None |
483 | 485 | |
484 | | # The graphic context used for drawings |
485 | | self._gc = None |
| 486 | self._radius = -1 |
486 | 487 | self._line_width = 2 |
487 | 488 | |
488 | 489 | # Color codes (approved colors for XO screen: |
489 | 490 | # http://wiki.laptop.org/go/XO_colors) |
490 | | colormap = self.get_colormap() |
491 | 491 | |
492 | 492 | # XO Medium Blue |
493 | | self._COLOR_HOURS = colormap.alloc_color("#005FE4") |
| 493 | self._COLOR_HOURS = "#005FE4" |
494 | 494 | |
495 | 495 | # XO Medium Green |
496 | | self._COLOR_MINUTES = colormap.alloc_color("#00B20D") |
| 496 | self._COLOR_MINUTES = "#00B20D" |
497 | 497 | |
498 | 498 | # XO Medium Red |
499 | | self._COLOR_SECONDS = colormap.alloc_color("#E6000A") |
| 499 | self._COLOR_SECONDS = "#E6000A" |
500 | 500 | |
501 | 501 | # White |
502 | | self._COLOR_WHITE = colormap.alloc_color("#FFFFFF") |
| 502 | self._COLOR_WHITE = "#FFFFFF" |
503 | 503 | |
504 | 504 | # Black |
505 | | self._COLOR_BLACK = colormap.alloc_color("#000000") |
| 505 | self._COLOR_BLACK = "#000000" |
506 | 506 | |
507 | 507 | # gtk.Widget signals |
508 | 508 | self.connect("expose-event", self._expose_cb) |
… |
… |
class ClockFace(gtk.DrawingArea): |
538 | 538 | self._height = allocation.height |
539 | 539 | self._line_width = int(self._radius / 150) |
540 | 540 | |
541 | | # Reload the cached pixbuf |
542 | | self._cache_pixbuf = gdk.pixbuf_new_from_file_at_size("clock.svg", |
543 | | 2 * self._radius, 2 * self._radius) |
544 | | gc.collect() # Reclaim memory from old pixbuf |
| 541 | # Reload the svg handle |
| 542 | self._svg_handle = rsvg.Handle(file="clock.svg") |
545 | 543 | |
546 | 544 | self.initialized = True |
547 | 545 | |
… |
… |
class ClockFace(gtk.DrawingArea): |
558 | 556 | self.queue_resize() |
559 | 557 | |
560 | 558 | if self._active: |
561 | | self._gc = self.window.new_gc() |
562 | | |
563 | 559 | if self._mode == _MODE_NICE_CLOCK: |
564 | 560 | self._draw_nice_clock() |
565 | 561 | elif self._mode == _MODE_SIMPLE_CLOCK: |
… |
… |
class ClockFace(gtk.DrawingArea): |
608 | 604 | seconds_length = 2 * self._radius / 60 * self._time.second |
609 | 605 | |
610 | 606 | # Fill background |
611 | | self._gc.set_line_attributes(self._line_width, gdk.LINE_SOLID, \ |
612 | | gdk.CAP_BUTT, gdk.JOIN_BEVEL) |
613 | | self._gc.set_foreground(self._COLOR_WHITE) |
614 | | self.window.draw_rectangle(self._gc, True, \ |
615 | | int(self._center_x - 1.1 * self._radius), \ |
616 | | int(self._center_y - 0.8 * self._radius), \ |
617 | | int(2.2 * self._radius), \ |
618 | | int(0.55 * self._radius)) |
| 607 | cr = self.window.cairo_create() |
| 608 | cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba()) |
| 609 | cr.rectangle(int(self._center_x - 1.1 * self._radius), |
| 610 | int(self._center_y - 0.8 * self._radius), |
| 611 | int(2.2 * self._radius), |
| 612 | int(0.55 * self._radius)) |
| 613 | cr.fill() |
619 | 614 | |
620 | 615 | h = int(0.15 * self._radius) |
621 | 616 | x = int(self._center_x - self._radius) |
622 | 617 | |
623 | 618 | # Hours scale |
624 | | self._gc.set_foreground(self._COLOR_HOURS) |
| 619 | cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) |
625 | 620 | y = int(self._center_y - 0.75 * self._radius) |
626 | | self.window.draw_rectangle(self._gc, True, x, y, hours_length, h) |
| 621 | cr.rectangle(x, y, hours_length, h) |
| 622 | cr.fill() |
627 | 623 | |
628 | 624 | # Minutes scale |
629 | | self._gc.set_foreground(self._COLOR_MINUTES) |
| 625 | cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba()) |
630 | 626 | y = int(self._center_y - 0.60 * self._radius) |
631 | | self.window.draw_rectangle(self._gc, True, x, y, minutes_length, h) |
| 627 | cr.rectangle(x, y, minutes_length, h) |
| 628 | cr.fill() |
632 | 629 | |
633 | 630 | # Seconds scale |
634 | | self._gc.set_foreground(self._COLOR_SECONDS) |
| 631 | cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba()) |
635 | 632 | y = int(self._center_y - 0.45 * self._radius) |
636 | | self.window.draw_rectangle(self._gc, True, x, y, seconds_length, h) |
| 633 | cr.rectangle(x, y, seconds_length, h) |
| 634 | cr.fill() |
637 | 635 | |
638 | 636 | def _draw_time(self): |
639 | 637 | """Draw the time in colors (digital display). |
… |
… |
class ClockFace(gtk.DrawingArea): |
655 | 653 | markup_time = self._time.strftime(markup) |
656 | 654 | #markup_time = time.strftime(markup) |
657 | 655 | |
658 | | self._gc.set_foreground(self._COLOR_BLACK) |
| 656 | cr = self.window.cairo_create() |
| 657 | cr = pangocairo.CairoContext(cr) |
| 658 | cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba()) |
| 659 | pango_layout = cr.create_layout() |
659 | 660 | d = int(self._center_y + 0.3 * self._radius) |
660 | | self._draw_markup(self._center_x, d, markup_time) |
| 661 | pango_layout.set_markup(markup_time) |
| 662 | dx, dy = pango_layout.get_pixel_size() |
| 663 | pango_layout.set_alignment(pango.ALIGN_CENTER) |
| 664 | cr.translate(self._center_x - dx / 2.0, d - dy / 2.0) |
| 665 | cr.show_layout(pango_layout) |
661 | 666 | |
662 | 667 | def _draw_simple_clock(self): |
663 | 668 | """Draw the simple clock variants. |
… |
… |
class ClockFace(gtk.DrawingArea): |
671 | 676 | The simple clock background is a white disk, with hours and minutes |
672 | 677 | ticks, and the hour numbers. |
673 | 678 | """ |
| 679 | cr = self.window.cairo_create() |
| 680 | cr.set_line_width(4 * self._line_width) |
| 681 | |
674 | 682 | # Simple clock background |
675 | | self._gc.set_foreground(self._COLOR_WHITE) |
676 | | x_delta = self._center_x - self._radius |
677 | | y_delta = self._center_y - self._radius |
678 | | |
679 | | self.window.draw_arc(self._gc, True, x_delta, y_delta, |
680 | | 2 * self._radius, 2 * self._radius, 0, 360 * 64) |
681 | | self._gc.set_foreground(self.get_style().fg[gtk.STATE_NORMAL]) |
682 | | self._gc.set_line_attributes(4 * self._line_width, |
683 | | gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND) |
684 | | self.window.draw_arc(self._gc, False, x_delta, y_delta, |
685 | | 2 * self._radius, 2 * self._radius, 0, 360 * 64) |
| 683 | cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba()) |
| 684 | cr.arc(self._width / 2, self._height / 2, self._radius, 0, 2 * math.pi) |
| 685 | cr.fill_preserve() |
| 686 | cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba()) |
| 687 | cr.stroke() |
686 | 688 | |
687 | 689 | # Clock ticks |
688 | | self._gc.set_line_attributes(4 * self._line_width, |
689 | | gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND) |
690 | 690 | for i in xrange(60): |
691 | 691 | if i % 15 == 0: |
692 | 692 | inset = 0.175 * self._radius |
… |
… |
class ClockFace(gtk.DrawingArea): |
697 | 697 | |
698 | 698 | cos = math.cos(i * math.pi / 30.0) |
699 | 699 | sin = math.sin(i * math.pi / 30.0) |
700 | | self.window.draw_line(self._gc, |
701 | | int(self._center_x + (self._radius - inset) * cos), |
702 | | int(self._center_y + (self._radius - inset) * sin), |
703 | | int(self._center_x + self._radius * cos), |
704 | | int(self._center_y + self._radius * sin)) |
| 700 | cr.move_to(int(self._center_x + (self._radius - inset) * cos), |
| 701 | int(self._center_y + (self._radius - inset) * sin)) |
| 702 | cr.line_to(int(self._center_x + self._radius * cos), |
| 703 | int(self._center_y + self._radius * sin)) |
| 704 | cr.stroke() |
705 | 705 | |
706 | 706 | def _draw_nice_background(self): |
707 | 707 | """Draw the nice clock background. |
708 | 708 | |
709 | 709 | The background has been loaded from the clock.svg file to a |
710 | | pixbuf, and we just draw this pixbuf onto the pixmap where we |
711 | | will be drawing the hands. |
712 | | """ |
713 | | # We draw the background from the SVG pixbuf |
714 | | self.window.draw_pixbuf(None, self._cache_pixbuf, |
715 | | 0, 0, self._center_x - self._radius, self._center_y - self._radius) |
| 710 | rsvg handle, and we just transform this handle and render it |
| 711 | with cairo. |
| 712 | """ |
| 713 | # We transform the background SVG |
| 714 | cr = self.window.cairo_create() |
| 715 | scale_x = self._radius * 2.0 / self._svg_handle.props.width |
| 716 | scale_y = self._radius * 2.0 / self._svg_handle.props.height |
| 717 | matrix = cairo.Matrix(xx=scale_x, yy=scale_y, |
| 718 | x0=self._center_x - self._radius, |
| 719 | y0=self._center_y - self._radius) |
| 720 | cr.transform(matrix) |
| 721 | self._svg_handle.render_cairo(cr) |
716 | 722 | |
717 | 723 | def _draw_nice_clock(self): |
718 | 724 | """Draw the nice clock. |
… |
… |
class ClockFace(gtk.DrawingArea): |
727 | 733 | minutes = self._time.minute |
728 | 734 | seconds = self._time.second |
729 | 735 | |
| 736 | cr = self.window.cairo_create() |
| 737 | cr.set_line_cap(cairo.LINE_CAP_ROUND) |
| 738 | |
730 | 739 | # Hour hand: |
731 | 740 | # The hour hand is rotated 30 degrees (pi/6 r) per hour + |
732 | 741 | # 1/2 a degree (pi/360) per minute |
733 | | self._gc.set_foreground(self._COLOR_HOURS) |
734 | | self._gc.set_line_attributes(8 * self._line_width, |
735 | | gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND) |
736 | | self.window.draw_line(self._gc, self._center_x, self._center_y, |
737 | | int(self._center_x + self._radius * 0.5 * |
738 | | math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)), |
739 | | int(self._center_y + self._radius * 0.5 * |
740 | | - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes))) |
| 742 | cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) |
| 743 | cr.set_line_width(8 * self._line_width) |
| 744 | 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))) |
| 749 | cr.stroke() |
741 | 750 | |
742 | 751 | # Minute hand: |
743 | 752 | # The minute hand is rotated 6 degrees (pi/30 r) per minute |
744 | | self._gc.set_foreground(self._COLOR_MINUTES) |
745 | | self._gc.set_line_attributes(6 * self._line_width, |
746 | | gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND) |
747 | | self.window.draw_line(self._gc, self._center_x, self._center_y, |
748 | | int(self._center_x + self._radius * 0.8 * |
| 753 | cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba()) |
| 754 | cr.set_line_width(6 * self._line_width) |
| 755 | cr.move_to(self._center_x, self._center_y) |
| 756 | cr.line_to(int(self._center_x + self._radius * 0.8 * |
749 | 757 | math.sin(math.pi / 30 * minutes)), |
750 | | int(self._center_y + self._radius * 0.8 * |
| 758 | int(self._center_y + self._radius * 0.8 * |
751 | 759 | - math.cos(math.pi / 30 * minutes))) |
| 760 | cr.stroke() |
752 | 761 | |
753 | 762 | # Seconds hand: |
754 | 763 | # Operates identically to the minute hand |
755 | | self._gc.set_foreground(self._COLOR_SECONDS) |
756 | | self._gc.set_line_attributes(2 * self._line_width, |
757 | | gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND) |
758 | | self.window.draw_line(self._gc, self._center_x, self._center_y, |
759 | | int(self._center_x + self._radius * 0.7 * |
| 764 | cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba()) |
| 765 | cr.set_line_width(2 * self._line_width) |
| 766 | cr.move_to(self._center_x, self._center_y) |
| 767 | cr.line_to(int(self._center_x + self._radius * 0.7 * |
760 | 768 | math.sin(math.pi / 30 * seconds)), |
761 | 769 | int(self._center_y + self._radius * 0.7 * |
762 | 770 | - math.cos(math.pi / 30 * seconds))) |
| 771 | cr.stroke() |
763 | 772 | |
764 | 773 | def _draw_numbers(self): |
765 | 774 | """Draw the numbers of the hours. |
766 | 775 | """ |
767 | | self._gc.set_foreground(self._COLOR_HOURS) |
| 776 | cr = self.window.cairo_create() |
| 777 | cr = pangocairo.CairoContext(cr) |
| 778 | cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) |
| 779 | pango_layout = cr.create_layout() |
768 | 780 | |
769 | 781 | for i in xrange(12): |
770 | 782 | # TRANS: The format of the font used to print hour |
… |
… |
class ClockFace(gtk.DrawingArea): |
772 | 784 | hour_number = _("Hour Number", |
773 | 785 | '<markup><span lang="en" ' + |
774 | 786 | 'font_desc="Sans Bold 20">%d</span></markup>') % (i + 1) |
775 | | self._draw_markup(self._center_x + 0.75 * \ |
776 | | self._radius * math.cos((i - 2) * math.pi / 6.0), \ |
777 | | self._center_y + 0.75 * self._radius * \ |
778 | | math.sin((i - 2) * math.pi / 6.0), hour_number) |
| 787 | cr.save() |
| 788 | dx, dy = pango_layout.get_pixel_size() |
| 789 | cr.translate(- dx / 2.0 + self._center_x + 0.75 * |
| 790 | self._radius * math.cos((i - 2) * math.pi / 6.0), |
| 791 | - dy / 2.0 + self._center_y + 0.75 * self._radius * |
| 792 | math.sin((i - 2) * math.pi / 6.0)) |
| 793 | pango_layout.set_markup(hour_number) |
| 794 | cr.update_layout(pango_layout) |
| 795 | cr.show_layout(pango_layout) |
| 796 | cr.restore() |
779 | 797 | |
780 | 798 | def _redraw_canvas(self): |
781 | 799 | """Force a redraw of the clock on the screen. |