# ClockGTK.py (c) Kari Laitinen # http://www.naturalprogramming.com # 2006-09-18 File created. # 2009-10-26 Modifications for a more accurate clock whose # hand positions are calculated with trigonometrical # functions. # 2009-10-26 Last modification. import gtk from threading import Thread from threading import Event from time import sleep from datetime import datetime import math gtk.gdk.threads_init() # Initialization of the gtk's thread engine class ThreadThatRunsTheClock( Thread ) : def __init__( self, given_application ) : Thread.__init__( self ) self.host_application = given_application self.host_drawing_area = given_application.drawing_area self.thread_must_be_stopped = Event() print " thread init done " def stop_this_thread( self ) : self.thread_must_be_stopped.set() def run( self ) : while not self.thread_must_be_stopped.isSet() : gtk.gdk.threads_enter() # Acquiring the gtk global mutex self.host_drawing_area.queue_draw() gtk.gdk.threads_leave() # Releasing the gtk global mutex sleep( 0.2 ) # Delay of 200 milliseconds. print " run method ends " class ClockApplication : HOUR_HAND_LENGTH = 152 MINUTE_HAND_LENGTH = 188 SECOND_HAND_LENGTH = 200 def __init__( self ) : self.application_window = gtk.Window( gtk.WINDOW_TOPLEVEL ) self.application_window.set_title( "ANIMATING A GRAPHICAL CLOCK" ) self.application_window.connect( "destroy", self.exit_this_application ) self.application_window.set_size_request( 600, 530 ) self.drawing_area = gtk.DrawingArea() self.application_window.add( self.drawing_area ) self.drawing_area.connect( "expose-event", self.drawing_area_exposed ) self.drawing_area.show() self.application_window.show() self.thread_that_runs_the_clock = ThreadThatRunsTheClock( self ) self.thread_that_runs_the_clock.start() def drawing_area_exposed( self, area, event ) : graphics_context = \ self.drawing_area.get_style().fg_gc[ gtk.STATE_NORMAL ] this_drawable = self.drawing_area.window names_of_days_of_week = ( "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ) names_of_months = ( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ) date_and_time_now = datetime.today() current_year = date_and_time_now.year current_day = date_and_time_now.day month_index = date_and_time_now.month - 1 # The weekday() method of class datetime returns the day of the # week as an integer, where Monday is 0 and Sunday is 6. # The "Python week" thus begins with Monday. index_of_day_of_week = date_and_time_now.weekday() current_month = names_of_months[ month_index ] current_day_of_week = names_of_days_of_week[ index_of_day_of_week ] current_hours = date_and_time_now.hour current_minutes = date_and_time_now.minute current_seconds = date_and_time_now.second current_milliseconds = date_and_time_now.microsecond / 1000 date_string_to_screen = "%s, %s %d, %d" % ( current_day_of_week, current_month, current_day, current_year ) time_string_to_screen = "%d:%02d:%02d" % \ ( current_hours, current_minutes, current_seconds ) text_to_show = self.drawing_area.create_pango_layout( "" ) graphics_context.foreground = \ this_drawable.get_colormap().alloc_color( "black" ) text_to_show.set_text( date_string_to_screen ) this_drawable.draw_layout( graphics_context, 20, 20, text_to_show ) text_to_show.set_text( time_string_to_screen ) this_drawable.draw_layout( graphics_context, 20, 40, text_to_show ) clock_center_point_x = 300 clock_center_point_y = 280 # Note that angles are expressed in 1/64ths of a degree. # 360 degrees is thus represented by value 360*64 # Let's print an 10-point dot in the center of the clock. this_drawable.draw_arc( graphics_context, True, clock_center_point_x - 5, clock_center_point_y - 5, 10, 10, 0, 360*64 ) # The following loop prints dots on the clock circle. dot_index = 0 while dot_index < 60 : dot_angle = dot_index * math.pi / 30 - math.pi / 2 dot_position_x = int( math.cos( dot_angle ) * \ ClockApplication.SECOND_HAND_LENGTH + clock_center_point_x ) dot_position_y = int( math.sin( dot_angle ) * \ ClockApplication.SECOND_HAND_LENGTH + clock_center_point_y ) dot_diameter = 4 if ( dot_index % 5 ) == 0 : # Every 5th dot on the clock circle is a larger dot. dot_diameter = 8 this_drawable.draw_arc( graphics_context, True, dot_position_x - dot_diameter / 2, dot_position_y - dot_diameter / 2, dot_diameter, dot_diameter, 0, 360*64 ) dot_index = dot_index + 1 # For every hand of the clock, we'll first calculte the # angle of the hand in question. Then we can calculate the # the coordinates of the end point of the hand, and finally # we'll draw the hand. hour_hand_angle = ( float( current_hours * 30 ) + \ float( current_minutes / 2 ) ) \ * math.pi / 180 - math.pi /2 hour_hand_end_x = int( math.cos( hour_hand_angle ) * \ ClockApplication.HOUR_HAND_LENGTH + clock_center_point_x ) hour_hand_end_y = int( math.sin( hour_hand_angle) * \ ClockApplication.HOUR_HAND_LENGTH + clock_center_point_y ) this_drawable.draw_line( graphics_context, clock_center_point_x, clock_center_point_y, hour_hand_end_x, hour_hand_end_y ) minute_hand_angle = ( float( current_minutes ) + \ float( current_seconds ) / 60.0 ) \ * math.pi / 30 - math.pi /2 minute_hand_end_x = int( math.cos( minute_hand_angle ) * \ ClockApplication.MINUTE_HAND_LENGTH + clock_center_point_x ) minute_hand_end_y = int( math.sin( minute_hand_angle) * \ ClockApplication.MINUTE_HAND_LENGTH + clock_center_point_y ) this_drawable.draw_line( graphics_context, clock_center_point_x, clock_center_point_y, minute_hand_end_x, minute_hand_end_y ) graphics_context.foreground = \ this_drawable.get_colormap().alloc_color( "red" ) second_hand_angle = ( float( current_seconds ) + \ float( current_milliseconds ) / 1000.0 ) \ * math.pi / 30 - math.pi /2 second_hand_end_x = int( math.cos( second_hand_angle ) * \ ClockApplication.SECOND_HAND_LENGTH + clock_center_point_x ) second_hand_end_y = int( math.sin( second_hand_angle) * \ ClockApplication.SECOND_HAND_LENGTH + clock_center_point_y ) this_drawable.draw_line( graphics_context, clock_center_point_x, clock_center_point_y, second_hand_end_x, second_hand_end_y ) return True def run( self ) : gtk.main() def exit_this_application( self, widget, data=None ) : self.thread_that_runs_the_clock.stop_this_thread() gtk.main_quit() if __name__ == "__main__": this_application = ClockApplication() this_application.run() # NOTES: # Double buffering is a means to prevent the flickering of a picture # that is drawn into a window. # Automatic double buffering is associated with widgets such as # DrawingArea objects. However, it it possible to switch off # the double buffering with a statement like # self.drawing_area.set_double_buffered( False ) # When double buffering is switched off it might be possible to # use images such as # self.offscreen_image = gtk.gdk.Pixmap( self.drawing_area.window, # self.window_width, # self.window_height )