class MoonActivity(activity.Activity): """Moon phase activity.""" def __init__(self, handle): activity.Activity.__init__(self, handle) self._name = handle self.set_title(_("Moon")) # Defaults (Resume priority, persistent file secondary, fall-back hardcoded) if handle.object_id == None: print "Launched from home." else: print "Journal resume." self.hemisphere_view = 'north' self.show_grid = False self.activity_state = {} self.activity_state['hemisphereView'] = self.hemisphere_view self.activity_state['showGrid'] = self.show_grid self.read_and_parse_prefs(os.environ['SUGAR_ACTIVITY_ROOT'] + '/data/defaults') # Toolbox toolbox = activity.ActivityToolbox(self) view_tool_bar = gtk.Toolbar() self.toggle_grid_button = ToolButton('grid-icon') self.toggle_grid_button.set_tooltip(_("Toggle Grid View")) self.toggle_grid_handler_id = self.toggle_grid_button.connect( 'clicked', self.toggle_grid_clicked) view_tool_bar.insert(self.toggle_grid_button, -1) self.toggle_grid_button.show() self.toggle_hemisphere_button = ToolButton('hemi-icon') self.toggle_hemisphere_button.set_tooltip(_("Toggle Hemisphere View")) self.toggle_hemisphere_handler_id = self.toggle_hemisphere_button.connect( 'clicked', self.toggle_hemisphere_clicked) view_tool_bar.insert(self.toggle_hemisphere_button, -1) self.toggle_hemisphere_button.show() view_tool_bar.show() toolbox.add_toolbar(_('View'), view_tool_bar) self.set_toolbox(toolbox) toolbox.show() # Create the main activity container self.main_view = gtk.HBox() # Create event box to hold Moon image (so I can set background color) self.event_box = gtk.EventBox() colormap = gtk.gdk.colormap_get_system() self.black_alloc_color = colormap.alloc_color('black') self.white_alloc_color = colormap.alloc_color('white') self.blue_green_mask_alloc_color = colormap.alloc_color('#F00') self.red_alloc_color = colormap.alloc_color('#F20') self.blue_alloc_color = colormap.alloc_color('#04F') self.event_box.modify_bg(gtk.STATE_NORMAL, self.black_alloc_color) self.event_box.connect('size-allocate', self._moon_size_allocate_cb) # Create the Moon image widget self.image = gtk.Image() self.event_box.add(self.image) self.main_view.pack_end(self.event_box) # Moon base image for sacaling to final image self.moon_stamp = gtk.gdk.pixbuf_new_from_file("moon.jpg") # Create Moon information panel self.info_panel = gtk.VBox() self.info_panel.set_border_width(10) self.info = gtk.Label() self.info.set_justify(gtk.JUSTIFY_LEFT) self.info_panel.pack_start(self.info, False, False, 0) self.main_view.pack_start(self.info_panel, False, False, 0) # Create Moon data model self.data_model = DataModel() # Generate first set of views for display and kick off their timers self.toggle_grid_button.handler_block(self.toggle_grid_handler_id) self.toggle_hemisphere_button.handler_block( self.toggle_hemisphere_handler_id) self.update_text_information_view() self.update_moon_image_view() # Display everything self.info.show() self.info_panel.show() self.image.show() self.event_box.show() self.main_view.show() self.set_canvas(self.main_view) self.show_all() def read_and_parse_prefs(self, file_path): """Parse and set preference data from a given file.""" try: read_file = open(file_path, 'r') self.activity_state = json.read(read_file.read()) if self.activity_state.has_key('hemisphereView'): self.hemisphere_view = self.activity_state['hemisphereView'] if self.activity_state.has_key('showGrid'): self.show_grid = self.activity_state['showGrid'] read_file.close() except: pass def read_file(self, file_path): """Read state from datastore.""" self.read_and_parse_prefs(file_path) self.block_view_buttons_during_update() def write_file(self, file_path): """Write state to journal datastore and to persistent file system.""" self.activity_state['hemisphereView'] = self.hemisphere_view self.activity_state['showGrid'] = self.show_grid serialised_data = json.write(self.activity_state) to_journal = file(file_path, 'w') try: to_journal.write(serialised_data) finally: to_journal.close() to_persistent_fs = file( os.environ['SUGAR_ACTIVITY_ROOT'] + '/data/defaults', 'w') try: to_persistent_fs.write(serialised_data) finally: to_persistent_fs.close() def toggle_grid_clicked(self, widget): """Respond to toolbar button to hide/show grid lines.""" if self.show_grid == True: self.show_grid = False else: self.show_grid = True self.block_view_buttons_during_update() def toggle_hemisphere_clicked(self, widget): """Respond to toolbar button to change viewing hemisphere.""" if self.hemisphere_view == 'north': self.hemisphere_view = 'south' else: self.hemisphere_view = 'north' self.block_view_buttons_during_update() def block_view_buttons_during_update(self): """Disable view buttons while updating image to prevent multi-clicks.""" self.toggle_grid_button.handler_block(self.toggle_grid_handler_id) self.toggle_hemisphere_button.handler_block( self.toggle_hemisphere_handler_id) gobject.source_remove(self.update_moon_image_timeout) self.update_moon_image_view() def unblock_view_update_buttons(self): """Reactivate view button after updating image, stops multi-clicks.""" self.toggle_grid_button.handler_unblock(self.toggle_grid_handler_id) self.toggle_hemisphere_button.handler_unblock( self.toggle_hemisphere_handler_id) def update_text_information_view(self): """Generate Moon data and update text based information view.""" self.data_model.update_moon_calculations(time.time()) information_string = "" information_string += _("Today's Moon Information\n\n") information_string += _("Phase:\n%s\n\n") % ( self.data_model.moon_phase_name(self.data_model.phase_of_moon)) information_string += _("Julian Date:\n%.2f (astronomical)\n\n") % ( self.data_model.julian_date) information_string += _( "Age:\n%(days).0f days, %(hours).0f hours, %(minutes).0f minutes\n\n" ) % { 'days': self.data_model.days_old, 'hours': self.data_model.hours_old, 'minutes': self.data_model.minutes_old } information_string += _( "Lunation:\n%(phase).2f%% through lunation %(lunation)d\n\n") % { 'phase': self.data_model.phase_of_moon * 100, 'lunation': self.data_model.lunation } information_string += _("Surface Visibility:\n%.0f%% (estimated)\n\n" ) % (self.data_model.percent_of_full_moon * 100) information_string += _( u"Selenographic Terminator Longitude:\n%(deg).1f\u00b0%(westOrEast)s (%(riseOrSet)s)\n\n" ) % { 'deg': self.data_model.selenographic_deg, 'westOrEast': self.data_model.west_or_east, 'riseOrSet': self.data_model.rise_or_set } information_string += _( "Next Full Moon:\n%(date)s in %(days).0f days\n\n") % { 'date': time.ctime(self.data_model.next_full_moon_date), 'days': self.data_model.days_until_full_moon } information_string += _( "Next New Moon:\n%(date)s in %(days).0f days\n\n") % { 'date': time.ctime(self.data_model.next_new_moon_date), 'days': self.data_model.days_until_new_moon } information_string += _( "Next Lunar eclipse:\n%(date)s in %(days).0f days\n\n") % { 'date': time.ctime(self.data_model.next_lunar_eclipse_date), 'days': self.data_model.days_until_lunar_eclipse } information_string += _( "Next Solar eclipse:\n%(date)s in %(days).0f days\n\n") % { 'date': time.ctime(self.data_model.next_solar_eclipse_date), 'days': self.data_model.days_until_solar_eclipse } self.info.set_markup(information_string) # Calculate time to next minute cusp and set a new timer ms_to_next_min_cusp = (60 - time.gmtime()[5]) * 1000 gobject.timeout_add(ms_to_next_min_cusp, self.update_text_information_view) # Stop this timer running return False def update_moon_image_view(self): """Update Moon image view using last cached Moon data.""" self.image_pixmap = gtk.gdk.Pixmap(self.window, IMAGE_SIZE, IMAGE_SIZE) self.gc = self.image_pixmap.new_gc(foreground=self.black_alloc_color) self.image.set_from_pixmap(self.image_pixmap, None) # Erase last Moon rendering self.image_pixmap.draw_rectangle(self.gc, True, 0, 0, IMAGE_SIZE, IMAGE_SIZE) # Create a 1bit shadow mask mask_pixmap = gtk.gdk.Pixmap(None, IMAGE_SIZE, IMAGE_SIZE, depth=1) kgc = mask_pixmap.new_gc(foreground=self.black_alloc_color) wgc = mask_pixmap.new_gc(foreground=self.white_alloc_color) mask_pixmap.draw_rectangle(kgc, True, 0, 0, IMAGE_SIZE, IMAGE_SIZE) if self.data_model.phase_of_moon <= .25: # New Moon to First Quarter phase_shadow_adjust = self.data_model.phase_of_moon - abs( math.sin(self.data_model.phase_of_moon * math.pi * 4) / 18.0) arc_scale = int(IMAGE_SIZE * (1 - (phase_shadow_adjust * 4))) mask_pixmap.draw_rectangle(wgc, True, HALF_SIZE + 1, 0, HALF_SIZE, IMAGE_SIZE - 1) mask_pixmap.draw_arc(kgc, True, HALF_SIZE - int(arc_scale / 2), 0, arc_scale, IMAGE_SIZE, 17280, 11520) elif self.data_model.phase_of_moon <= .5: # First Quarter to Full Moon phase_shadow_adjust = self.data_model.phase_of_moon + abs( math.sin(self.data_model.phase_of_moon * math.pi * 4) / 18.0) arc_scale = int(IMAGE_SIZE * ((phase_shadow_adjust - .25) * 4)) mask_pixmap.draw_rectangle(wgc, True, HALF_SIZE, 0, HALF_SIZE, IMAGE_SIZE) mask_pixmap.draw_arc(wgc, True, HALF_SIZE - int(arc_scale / 2), 0, arc_scale, IMAGE_SIZE, 5760, 11520) elif self.data_model.phase_of_moon <= .75: # Full Moon to Last Quarter phase_shadow_adjust = self.data_model.phase_of_moon - abs( math.sin(self.data_model.phase_of_moon * math.pi * 4) / 18.0) arc_scale = int(IMAGE_SIZE * (1 - ((phase_shadow_adjust - .5) * 4))) mask_pixmap.draw_rectangle(wgc, True, 0, 0, HALF_SIZE + 1, IMAGE_SIZE) mask_pixmap.draw_arc(wgc, True, HALF_SIZE - int(arc_scale / 2), 0, arc_scale, IMAGE_SIZE, 17280, 11520) else: # Last Quarter to New Moon phase_shadow_adjust = self.data_model.phase_of_moon + abs( math.sin(self.data_model.phase_of_moon * math.pi * 4) / 18.0) arc_scale = int(IMAGE_SIZE * ((phase_shadow_adjust - .75) * 4)) mask_pixmap.draw_rectangle(wgc, True, 0, 0, HALF_SIZE, IMAGE_SIZE) mask_pixmap.draw_arc(kgc, True, HALF_SIZE - int(arc_scale / 2), 0, arc_scale, IMAGE_SIZE, 5760, 11520) maskgc = self.image_pixmap.new_gc(clip_mask=mask_pixmap) # Modified image based on public domain photo by John MacCooey moon_pixbuf = self.moon_stamp.scale_simple(IMAGE_SIZE, IMAGE_SIZE, gtk.gdk.INTERP_BILINEAR) # Composite bright Moon image and semi-transparant Moon for shadow detail dark_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, IMAGE_SIZE, IMAGE_SIZE) dark_pixbuf.fill(0x00000000) if (self.data_model.next_lunar_eclipse_sec == -1 and self.data_model.last_lunar_eclipse_sec > 7200) or ( self.data_model.next_lunar_eclipse_sec > 7200 and self.data_model.last_lunar_eclipse_sec == -1) or min( self.data_model.next_lunar_eclipse_sec, self.data_model.last_lunar_eclipse_sec) > 7200: # Normal Moon phase render moon_pixbuf.composite(dark_pixbuf, 0, 0, IMAGE_SIZE, IMAGE_SIZE, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, 127) self.image_pixmap.draw_pixbuf(self.gc, dark_pixbuf, 0, 0, 0, 0) self.image_pixmap.draw_pixbuf(maskgc, moon_pixbuf, 0, 0, 0, 0) else: # Reddening eclipse effect, 2hrs (7200sec) before and after (by masking out green & blue) if self.data_model.next_lunar_eclipse_sec == -1: eclipse_alpha = self.data_model.last_lunar_eclipse_sec / 7200.0 * 256 elif self.data_model.last_lunar_eclipse_sec == -1: eclipse_alpha = self.data_model.next_lunar_eclipse_sec / 7200.0 * 256 else: eclipse_alpha = min( self.data_model.next_lunar_eclipse_sec, self.data_model.last_lunar_eclipse_sec) / 7200.0 * 256 moon_pixbuf.composite(dark_pixbuf, 0, 0, IMAGE_SIZE, IMAGE_SIZE, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, 196 - eclipse_alpha / 2) self.image_pixmap.draw_pixbuf(self.gc, dark_pixbuf, 0, 0, 0, 0) del dark_pixbuf dark_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, IMAGE_SIZE, IMAGE_SIZE) moon_pixbuf.composite(dark_pixbuf, 0, 0, IMAGE_SIZE, IMAGE_SIZE, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, eclipse_alpha) rgc = self.image_pixmap.new_gc( foreground=self.blue_green_mask_alloc_color, function=gtk.gdk.AND) self.image_pixmap.draw_rectangle(rgc, True, 0, 0, IMAGE_SIZE, IMAGE_SIZE) self.image_pixmap.draw_pixbuf(self.gc, dark_pixbuf, 0, 0, 0, 0) if self.hemisphere_view == 'south': # Rotate final image for a view from north or south hemisphere rot_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, IMAGE_SIZE, IMAGE_SIZE) rot_pixbuf.get_from_drawable(self.image_pixmap, self.image_pixmap.get_colormap(), 0, 0, 0, 0, -1, -1) rot_pixbuf = rot_pixbuf.rotate_simple( gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN) self.image_pixmap.draw_pixbuf(self.gc, rot_pixbuf, 0, 0, 0, 0) if self.show_grid: # Draw grid rotated for south hemi self.draw_grid(_("SNWE")) elif self.show_grid: # Draw grid for north hemi self.draw_grid(_("NSEW")) self.image.queue_draw() # Update the Moon image in another 5min self.update_moon_image_timeout = gobject.timeout_add( 300000, self.update_moon_image_view) # Delay before view buttons can be clicked again (blocked to stop repeat clicks) gobject.timeout_add(50, self.unblock_view_update_buttons) # Stop this timer running return False def draw_grid(self, compass_text): """Draw Selenographic grid line data.""" rgc = self.image_pixmap.new_gc(foreground=self.red_alloc_color) bgc = self.image_pixmap.new_gc(foreground=self.blue_alloc_color) wgc = self.image_pixmap.new_gc(foreground=self.white_alloc_color) pango_layout = self.image.create_pango_layout("") pango_layout.set_text("0°") self.image_pixmap.draw_rectangle(bgc, True, HALF_SIZE + 2, HALF_SIZE, 24, 22) self.image_pixmap.draw_layout(wgc, HALF_SIZE + 2, HALF_SIZE, pango_layout) pango_layout.set_text("30°") self.image_pixmap.draw_rectangle(bgc, True, HALF_SIZE + 2, int(HALF_SIZE * 0.5), 36, 22) self.image_pixmap.draw_rectangle(bgc, True, HALF_SIZE + 2, int(HALF_SIZE * 1.5), 36, 22) self.image_pixmap.draw_layout(wgc, HALF_SIZE + 2, int(HALF_SIZE * 0.5), pango_layout) self.image_pixmap.draw_layout(wgc, HALF_SIZE + 2, int(HALF_SIZE * 1.5), pango_layout) pango_layout.set_text("60°") self.image_pixmap.draw_rectangle(bgc, True, HALF_SIZE + 2, int(HALF_SIZE * 0.15), 36, 22) self.image_pixmap.draw_rectangle(bgc, True, HALF_SIZE + 2, int(HALF_SIZE * 1.85), 36, 22) self.image_pixmap.draw_layout(wgc, HALF_SIZE + 2, int(HALF_SIZE * 0.15), pango_layout) self.image_pixmap.draw_layout(wgc, HALF_SIZE + 2, int(HALF_SIZE * 1.85), pango_layout) pango_layout.set_text("30°") self.image_pixmap.draw_rectangle(rgc, True, int(HALF_SIZE * 0.48) + 2, HALF_SIZE, 36, 22) self.image_pixmap.draw_rectangle(rgc, True, int(HALF_SIZE * 1.52) + 2, HALF_SIZE, 36, 22) self.image_pixmap.draw_layout(wgc, int(HALF_SIZE * 0.48) + 2, HALF_SIZE, pango_layout) self.image_pixmap.draw_layout(wgc, int(HALF_SIZE * 1.52) + 2, HALF_SIZE, pango_layout) pango_layout.set_text("60°") self.image_pixmap.draw_rectangle(rgc, True, int(HALF_SIZE * 0.15) + 2, HALF_SIZE, 36, 22) self.image_pixmap.draw_rectangle(rgc, True, int(HALF_SIZE * 1.85) + 2, HALF_SIZE, 36, 22) self.image_pixmap.draw_layout(wgc, int(HALF_SIZE * 0.15) + 2, HALF_SIZE, pango_layout) self.image_pixmap.draw_layout(wgc, int(HALF_SIZE * 1.85) + 2, HALF_SIZE, pango_layout) for i in (-1, 0, 1): self.image_pixmap.draw_line(rgc, HALF_SIZE + i, 0, HALF_SIZE + i, IMAGE_SIZE) self.image_pixmap.draw_arc(rgc, False, int(HALF_SIZE * 0.15) + i, 0, IMAGE_SIZE - int(IMAGE_SIZE * 0.15), IMAGE_SIZE, 0, 360 * 64) self.image_pixmap.draw_arc(rgc, False, int(HALF_SIZE * 0.48) + i, 0, IMAGE_SIZE - int(IMAGE_SIZE * 0.48), IMAGE_SIZE, 0, 360 * 64) self.image_pixmap.draw_line(bgc, 0, HALF_SIZE + i, IMAGE_SIZE, HALF_SIZE + i) self.image_pixmap.draw_line(bgc, int(HALF_SIZE * 0.15), int(HALF_SIZE * 0.5) + i, IMAGE_SIZE - int(HALF_SIZE * 0.15), int(HALF_SIZE * 0.5) + i) self.image_pixmap.draw_line(bgc, int(HALF_SIZE * 0.15), int(HALF_SIZE * 1.5) + i, IMAGE_SIZE - int(HALF_SIZE * 0.15), int(HALF_SIZE * 1.5) + i) self.image_pixmap.draw_line(bgc, int(HALF_SIZE * 0.5), int(HALF_SIZE * 0.15) + i, IMAGE_SIZE - int(HALF_SIZE * 0.5), int(HALF_SIZE * 0.15) + i) self.image_pixmap.draw_line(bgc, int(HALF_SIZE * 0.5), int(HALF_SIZE * 1.85) + i, IMAGE_SIZE - int(HALF_SIZE * 0.5), int(HALF_SIZE * 1.85) + i) # Key text pango_layout.set_text(_("Latitude")) self.image_pixmap.draw_layout(bgc, 0, IMAGE_SIZE - 48, pango_layout) pango_layout.set_text(_("Longitude")) self.image_pixmap.draw_layout(rgc, 0, IMAGE_SIZE - 24, pango_layout) # Compass <- fix string index to support multi-byte texts self.image_pixmap.draw_line(bgc, 45, 24, 45, 68) self.image_pixmap.draw_line(rgc, 22, 48, 68, 48) pango_layout.set_text(compass_text[0]) self.image_pixmap.draw_layout(bgc, 38, 0, pango_layout) pango_layout.set_text(compass_text[1]) self.image_pixmap.draw_layout(bgc, 38, 72, pango_layout) pango_layout.set_text(compass_text[2]) self.image_pixmap.draw_layout(rgc, 72, 36, pango_layout) pango_layout.set_text(compass_text[3]) self.image_pixmap.draw_layout(rgc, 0, 36, pango_layout) def _moon_size_allocate_cb(self, widget, allocation): global IMAGE_SIZE, HALF_SIZE new_size = min(allocation.width, allocation.height) if new_size != IMAGE_SIZE: IMAGE_SIZE = min(allocation.width, allocation.height) HALF_SIZE = IMAGE_SIZE / 2 self.update_moon_image_view()