Beispiel #1
0
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()