Exemple #1
0
    def calculate_slice_totals(self, sl, list_store):
        """
        Calculate the totals for activities taking place during the given
        timeslice, and add the appropriate column values to the provided list 
        store.
        """
        q = self.session.query(Activity)
        q = q.filter(Activity.start_time >= sl.start_date)
        q = q.filter(Activity.start_time <= sl.end_date)
        q = q.filter(Activity.sport == self.metrics_sport)
        activities = q.all()
        log.debug("Found %s activities for slice: %s" % (len(activities), sl.season.name))

        total_distance = Decimal("0")
        total_duration = Decimal("0")

        for acti in activities:
            log.debug("   %s - %s" % (acti.start_time, acti.sport.name))
            total_distance += acti.distance
            total_duration += acti.duration

        speed = calculate_speed(self.session, total_distance, total_duration)
        pace = calculate_pace(self.session, total_distance, total_duration)

        list_store.append([
            "%s %s" % (sl.season.name, sl.start_date.year),
            "%.2f" % (total_distance / 1000),
            format_time_str(total_duration),
            "%.2f" % (speed),
            "%.2f" % (pace / 60),
            "hr",
        ])
Exemple #2
0
    def init_metrics_tab(self):
        """ On startup initialization of the Metrics tab. """
        # Setup metrics treeview columns:
        log.debug("Initializing metrics tab.")
        period_column = gtk.TreeViewColumn("Period")
        distance_column = gtk.TreeViewColumn("Distance (km)")
        time_column = gtk.TreeViewColumn("Time")
        avg_speed_column = gtk.TreeViewColumn("Speed (km/hr)")
        pace_column = gtk.TreeViewColumn("Pace (min/km)")
        avg_hr_column = gtk.TreeViewColumn("Heart Rate")

        self.metrics_tv.append_column(period_column)
        self.metrics_tv.append_column(distance_column)
        self.metrics_tv.append_column(time_column)
        self.metrics_tv.append_column(avg_speed_column)
        self.metrics_tv.append_column(pace_column)
        self.metrics_tv.append_column(avg_hr_column)

        cell = gtk.CellRendererText()

        period_column.pack_start(cell, expand=False)
        distance_column.pack_start(cell, expand=False)
        time_column.pack_start(cell, expand=False)
        avg_speed_column.pack_start(cell, expand=False)
        pace_column.pack_start(cell, expand=False)
        avg_hr_column.pack_start(cell, expand=False)

        period_column.set_attributes(cell, text=0)
        distance_column.set_attributes(cell, text=1)
        time_column.set_attributes(cell, text=2)
        avg_speed_column.set_attributes(cell, text=3)
        pace_column.set_attributes(cell, text=4)
        avg_hr_column.set_attributes(cell, text=5)

        self.populate_metrics_timeslice_combo()
Exemple #3
0
 def activity_showmap_cb(self, widget):
     """
     Callback for when user selected Show Map from the activity popup menu.
     """
     activity = self.get_selected_activity()
     log.debug("Opening map window for: %s" % activity)
     activity_details_window = BrowserWindow(activity)
     activity_details_window.show_all()
Exemple #4
0
    def generate_html(self):
        """ 
        Generate HTML to render a Google Map for the configured activity. 
        """

        filepath = "/tmp/granola-%s-%s.html" % (self.activity.id,
                self.activity.sport.name)
        f = open(filepath, "w")

        if len(self.activity.laps) == 0:
            f.write("<html><body>No trackpoints</body></html>")
            f.close()
            return filepath
        elif len(self.activity.laps[0].tracks) == 0:
            f.write("<html><body>No trackpoints</body></html>")
            f.close()
            return filepath
        elif len(self.activity.laps[0].tracks[0].trackpoints) == 0:
            f.write("<html><body>No trackpoints</body></html>")
            f.close()
            return filepath

        (maxLat, maxLon, minLat, minLon, centerLat, centerLon) = \
                self._calculate_center_coords(self.activity)
        span_km = distance_between_coords(maxLat, maxLon, minLat, minLon)
        log.debug("Distance between coordinates: %s" % span_km)
        zoom_level = get_zoom_level(span_km)
        log.debug("Zoom level: %s" % zoom_level)

        title = "Granola Activity Map: %s (%s)" % (self.activity.start_time,
                self.activity.sport.name)
        center_coords = self.activity.laps[0].tracks[0].trackpoints[0]

        f.write(HTML_HEADER % (title, centerLat, centerLon, zoom_level))

        # TODO: iterating trackpoints a second time but probably faster
        # than doing the string concatenation we'd have to do otherwise.
        for lap in self.activity.laps:
            for track in lap.tracks:
                for trackpoint in track.trackpoints:
                    if trackpoint.latitude is None:
                        # TODO: Empty data in a trackpoint indicates a pause, 
                        # could display this easily.
                        continue
                    f.write("new GLatLng(%s, %s)," % (trackpoint.latitude, 
                        trackpoint.longitude))
        f.write("""                        ], "#0000ff", 3);""")
        #f.write("""map.addOverlay(new GMarker(new GLatLng(%s, %s)));""" %
        #        (maxLat, maxLon))
        #f.write("""map.addOverlay(new GMarker(new GLatLng(%s, %s)));""" %
        #        (minLat, minLon))

        f.write(HTML_FOOTER % (self.map_width, self.map_height))
        f.close()
        return filepath
Exemple #5
0
 def apply_prefs(self, widget):
     """
     Callback when apply button is pressed. Write settings to disk and
     close the window.
     """
     log.debug("Applying preferences.")
     import_folder = self.import_folder_chooser.get_filename()
     log.debug("   import_folder = %s" % import_folder)
     self.config.set("import", "import_folder", import_folder)
     write_config(self.config)
     self.preferences_dialog.destroy()
Exemple #6
0
    def populate_metrics(self):
        """ 
        Populate metrics. 

        Call this whenever we need to update the metrics displayed based on
        some action by the user.
        """

        log.debug("Populating metrics.")
        metrics_liststore = self.build_metrics_liststore()
        self.metrics_tv.set_model(metrics_liststore)
Exemple #7
0
def connect_to_db():
    """
    Open a connection to our database.

    Should be called only once when this module is first imported.
    """
    db_str = "sqlite:///%s" % SQLITE_DB
    log.debug("Connecting to database: %s" % db_str)

    # Set echo True to see lots of sqlalchemy output:
    db = create_engine(db_str, echo=False)

    return db
Exemple #8
0
    def scan_dir(self, directory):
        """ Scan a directory for new data files to import. """
        if not os.path.exists(directory):
            raise Exception("No such directory: %s" % directory)
        log.debug("Scanning %s for new data." % directory)

        session = Session()

        for root, dirs, files in os.walk(directory):
            for file in files:
                if file.endswith(".tcx"):
                    #                    try:
                    self.import_file(session, os.path.join(root, file))
                    session.commit()
Exemple #9
0
    def show_activity(self, activity):
        """ Display the given activity on the map. """
        # If we're already displaying this activity, don't do anything.
        # Useful check for some scenarios where the use changes a combo.
        if activity == self.current_activity:
            log.debug("Already displaying activity: %s" % activity)
            return

        if self.temp_file:
            log.info("Removing: %s" % self.temp_file)
            commands.getstatusoutput("rm %s" % self.temp_file)

        self.current_activity = activity
        generator = HtmlGenerator(activity, self.map_width, self.map_height)
        self.temp_file = generator.generate_html()
        log.debug("Wrote activity HTML to: %s" % self.temp_file)
        self._browser.open("file://%s" % self.temp_file)
Exemple #10
0
    def build_metrics_liststore(self):
        """
        Return a ListStore with totals for the metrics tab.

        Results depend heavily on the values currently selected in the 
        timeslice and sport comboboxes.
        """
        list_store = gtk.ListStore(
                str, # period
                str, # distance
                str, #time
                str, # avg speed
                str, # pace
                str, # avg heart rate
        )

        log.debug("Calculating metrics:")

        # Now things get interesting. Start with the earliest activity, 
        # determine which season it falls into, then calculate the actual
        # season start/end dates for each season that follows it up until
        # we cross the date of our last activity. Then construct queries.

        # Grab the first activity date:
        q = self.session.query(Activity).order_by(Activity.start_time)
        q = q.filter(Activity.sport == self.metrics_sport).limit(1)
        first_activity = q.first()
        if first_activity is None:
            # No activies for the current type, not much we can do here:
            return
        log.debug("First %s activity: %s" % (self.metrics_sport, 
            first_activity.start_time))

        q = self.session.query(Activity).order_by(Activity.start_time.desc())
        q = q.filter(Activity.sport == self.metrics_sport).limit(1)
        last_activity = q.first()
        log.debug("Last %s activity: %s" % (self.metrics_sport, 
            last_activity.start_time))

        iter = self.metrics_timeslice_combo.get_active_iter()
        timeslice = self.metrics_timeslice_combo.get_model().get_value(iter, 0)
        log.debug("Current metrics timeslice: %s" % timeslice)
        seasons = None
        if timeslice == "monthly":
            seasons = MONTHLY_SEASONS
        elif timeslice == "yearly":
            seasons = YEARLY_SEASONS

        slices = build_season_slices(seasons, first_activity.start_time, 
                last_activity.start_time)

        for s in slices:
            self.calculate_slice_totals(s, list_store)

        return list_store
Exemple #11
0
    def populate_metrics_timeslice_combo(self):
        """ Populate the metrics timeslice dropdown. """
        log.debug("Populating metrics timeslice dropdown.")
        timeslice_liststore = gtk.ListStore(str)
        self.metrics_timeslice_combo.set_model(timeslice_liststore)

        cell = gtk.CellRendererText()
        self.metrics_timeslice_combo.pack_start(cell, True)
        self.metrics_timeslice_combo.add_attribute(cell, 'text', 0)

        # TODO: I18N problem here:
        self.metrics_timeslice_combo.append_text("monthly")
        self.metrics_timeslice_combo.append_text("yearly")
        #self.metrics_timeslice_combo.append_text("my seasons")

        # Activate the first item:
        iter = timeslice_liststore.get_iter_first()
        self.metrics_timeslice_combo.set_active_iter(iter)
Exemple #12
0
    def __init__(self, config):
        log.debug("Opening Preferences dialog.")
        self.config = config

        glade_file = 'granola/glade/prefs-dialog.glade'
        self.glade_xml = gtk.Builder()
        self.glade_xml.add_from_file(find_file_on_path(glade_file))
        self.preferences_dialog = self.glade_xml.get_object("prefs_dialog")

        signals = {
            'on_apply_button_clicked': self.apply_prefs,
            'on_cancel_button_clicked': self.cancel,
        }
        self.glade_xml.connect_signals(signals)

        self.import_folder_chooser = self.glade_xml.get_object(
                "import_folder_filechooserbutton")
        self.import_folder_chooser.set_filename(
                self.config.get("import", "import_folder"))

        self.preferences_dialog.show_all()
Exemple #13
0
    def activity_delete_cb(self, widget):
        """
        Confirm dialog if the user actually wishes to delete an activity.
        """
        dialog = gtk.Dialog(title="Are you sure?",  
                flags=gtk.DIALOG_MODAL,
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                      gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        result = dialog.run()
        dialog.destroy()

        if result == gtk.RESPONSE_ACCEPT:
            activity = self.get_selected_activity()
            log.debug("Deleting! %s" % activity)
            self.session.delete(activity)
            self.session.commit()

            # TODO: More expensive than it needs to be, could just delete row from
            # model?
            self.populate_activities()
            self.populate_metrics()
        else:
            log.debug("NOT deleting activity.")
Exemple #14
0
    def populate_activities(self):
        """ 
        Populate activity list. 
        
        Called not just when we initialize the UI but also when the user 
        changes the Sport combobox and we need to change the list of 
        activities displayed.
        """
        # TODO: Is it possible to keep the model here, and "filter" it 
        # with GTK? Would be much more efficient.

        # Grab current selection if there is one, we'll preserve it if that
        # activity is present in the new list.
        tree_selection = self.activity_tv.get_selection()
        (model, iter) = tree_selection.get_selected()
        preserve_activity_id = None
        if (iter != None):
            preserve_activity_id = model.get_value(iter, 0)


        model = self.build_activity_liststore()
        self.activity_tv.set_model(model)

        iter = model.get_iter_first() # Points to the row we'll select

        if (preserve_activity_id is not None):
            log.debug("Searching for activity to preserve.")
            search_iter = model.get_iter_first()
            while search_iter is not None:
                log.debug("Examining: %s" % model.get_value(search_iter, 0))
                if model.get_value(search_iter, 0) == preserve_activity_id:
                    log.debug("Re-selecting activity: %s" % 
                            preserve_activity_id)
                    iter = search_iter
                    break
                search_iter = model.iter_next(search_iter)
        else:
            log.debug("No activity selected, auto-selecting first row.")

        if iter is not None:
            tree_selection.select_iter(iter)
            self.display_activity(self.get_selected_activity())
Exemple #15
0
def create_first_slice(seasons, activity_date):
    """
    Create the appropriate season slice for the given activity date.

    Uses the given list of seasons to identify which season this
    activity falls into.
    """
    the_season = None
    i = 0
    season_index = None
    for s in seasons:
        if s.month <= activity_date.month and s.day <= activity_date.day:
            the_season = s
            season_index = i
        i = i + 1

    # If we couldn't find a season that starts before our activity date,
    # we want the last one. (which must wrap around over the year)
    if the_season is None:
        the_season = seasons[-1]
        season_index = len(seasons) - 1

    # Should have found the season with the start date closest to our 
    # activity by now, so we create the slice for it:
    log.debug("found season %s: %s - %s" % (the_season.name, 
        the_season.month, the_season.day))

    # Previous year if the season month is greater than our activity's:
    start_year = activity_date.year
    if the_season.month > activity_date.month or (the_season.month ==
            activity_date.month and the_season.day > activity_date.day):
        start_year = start_year - 1

    start_date = datetime(year=start_year, month=the_season.month, 
            day=the_season.day)
    log.debug("start_date = %s" % start_date)

    # Now for the end date:
    next_season = seasons[(season_index + 1) % len(seasons)]
    log.debug("next season %s: %s - %s" % (next_season.name, 
        next_season.month, next_season.day))
    return SeasonSlice(the_season, start_date, next_season)
Exemple #16
0
 def cancel(self, widget):
     """
     Don't apply any settings and close the window.
     """
     log.debug("Cancel button pressed, closing preferences dialog.")
     self.preferences_dialog.destroy()
Exemple #17
0
    def __init__(self, config):
        log.debug("Starting GTK UI.")
        gtk.gdk.threads_init()
        self.config = config
        self.session = Session()

        glade_file = 'granola/glade/mainwindow.glade'
        self.glade_xml = gtk.Builder()
        self.glade_xml.add_from_file(find_file_on_path(glade_file))
        main_window = self.glade_xml.get_object('main_window')
        self.activity_hbox = self.glade_xml.get_object(
                'activity_hbox')

        # Filter main list of activities based on this, None = show all.
        # Storing just the string name here.
        self.filter_sport = None
        self.metrics_sport = None

        # References to various widgets used throughout the class:
        self.activity_popup_menu = self.glade_xml.get_object(
                'activity_popup_menu')
        self.activity_tv = self.glade_xml.get_object('activity_treeview')
        self.metrics_tv = self.glade_xml.get_object('metrics_treeview')
        self.sport_filter_combobox = self.glade_xml.get_object(
                'sport_filter_combobox')
        self.metrics_sport_combo = self.glade_xml.get_object(
                'metrics_sport_combo')
        self.metrics_timeslice_combo = self.glade_xml.get_object(
                'metrics_timeslice_combo')

        self.init_ui()

        signals = {
            'on_quit_menu_item_activate': self.shutdown,
            'on_main_window_destroy': self.shutdown,
            'on_prefs_menu_item_activate': self.open_prefs_dialog_cb,
            'on_activity_treeview_button_press_event':
                self.activity_tv_click_cb,
            'on_activity_treeview_row_activated':
                self.activity_tv_doubleclick_cb,
            'on_sport_filter_combobox_changed':
                self.activities_sport_combo_cb,
            'on_metrics_sport_combo_changed':
                self.metrics_sport_combo_cb,
            'on_metrics_timeslice_combo_changed':
                self.metrics_timeslice_combo_cb,

            'on_activity_popup_delete_activate':
                self.activity_delete_cb,
            'on_activity_popup_showmap_activate':
                self.activity_showmap_cb,
        }
        self.glade_xml.connect_signals(signals)


        self.running = self.session.query(Sport).filter(
                Sport.name == RUNNING).one()
        self.biking = self.session.query(Sport).filter(
                Sport.name == BIKING).one()

        self.browser_widget = BrowserWidget()
        self.activity_hbox.pack_start(self.browser_widget)

        self.populate_activities()
        self.populate_metrics()

        main_window.show_all()
Exemple #18
0
def build_season_slices(seasons, first_activity_date, last_activity_date):
    """
    Return a list of all season slices using the configured season boundaries,
    first activity date, and last activity date. 
    """

    starting_slice = create_first_slice(seasons, first_activity_date)

    log.debug("Building all season slices:")
    log.debug("   starting slice: %s" % starting_slice)
    log.debug("   last activity: %s" % last_activity_date)
    # What season does the starting slice point to?
    season_index = 0
    for s in seasons:
        if s.month == starting_slice.start_date.month and \
                s.day == starting_slice.start_date.day:
                    break
        else:
            season_index += 1
    log.debug("season_index = %s" % season_index)

    # Keep building season slices until one ends beyond the date of
    # our last activity:
    all_slices = [starting_slice]
    while all_slices[-1].end_date <= last_activity_date:
        log.debug("Building new slice:")
        season_index = (season_index + 1) % len(seasons)
        next_season = seasons[season_index]
        log.debug("   next_season = %s" % next_season)
        start_date = all_slices[-1].end_date + timedelta(seconds=1)
        log.debug("   start_date = %s" % start_date)
        new_slice = SeasonSlice(next_season, start_date, 
                seasons[(season_index + 1) % len(seasons)])
        log.debug("   new_slice = %s" % new_slice)
        all_slices.append(new_slice)

    log.debug("Seasons:")
    for s in all_slices:
        log.debug(s)

    return all_slices
Exemple #19
0
def debug_activity(activity):
    """ Log debug info on this activity. """
    log.debug("Activity: %s" % activity.start_time)
    i = 1
    for lap in activity.laps:
        log.debug("   Lap %s:" % i)
        log.debug("      Start Time: %s" % lap.start_time)
        log.debug("      Duration: %s seconds" % lap.duration)
        log.debug("      Distance: %s meters" % lap.distance)
        log.debug("      Max speed: %s meters/second" % lap.speed_max)
        log.debug("      Calories: %s" % lap.calories)
        log.debug("      Max heart rate: %s bpm" % lap.heart_rate_max)
        log.debug("      Avg heart rate: %s bpm" % lap.heart_rate_avg)
        i += 1
Exemple #20
0
    def init_activities_tab(self):
        """ On startup initialization of the Activities tab. """
        # Setup activity treeview columns:
        log.debug("Initializing activities tab.")
        sport_column = gtk.TreeViewColumn("Sport")
        date_column = gtk.TreeViewColumn("Date")
        distance_column = gtk.TreeViewColumn("Distance (km)")
        time_column = gtk.TreeViewColumn("Time")
        avg_speed_column = gtk.TreeViewColumn("Speed (km/hr)")
        pace_column = gtk.TreeViewColumn("Pace (min/km)")
        heart_rate_column = gtk.TreeViewColumn("Heart Rate")

        self.activity_tv.append_column(sport_column)
        self.activity_tv.append_column(date_column)
        self.activity_tv.append_column(distance_column)
        self.activity_tv.append_column(time_column)
        self.activity_tv.append_column(avg_speed_column)
        self.activity_tv.append_column(pace_column)
        self.activity_tv.append_column(heart_rate_column)

        cell = gtk.CellRendererText()

        sport_column.pack_start(cell, expand=False)
        date_column.pack_start(cell, expand=False)
        distance_column.pack_start(cell, expand=False)
        time_column.pack_start(cell, expand=False)
        avg_speed_column.pack_start(cell, expand=False)
        pace_column.pack_start(cell, expand=False)
        heart_rate_column.pack_start(cell, expand=False)

        sport_column.set_attributes(cell, text=5)
        date_column.set_attributes(cell, text=1)
        distance_column.set_attributes(cell, text=2)
        time_column.set_attributes(cell, text=3)
        avg_speed_column.set_attributes(cell, text=4)
        pace_column.set_attributes(cell, text=6)
        heart_rate_column.set_attributes(cell, text=7)

        self.lap_tv = self.glade_xml.get_object('lap_treeview')

        # Setup lap treeview columns:
        number_column = gtk.TreeViewColumn("Lap")
        distance_column = gtk.TreeViewColumn("Distance (km)")
        time_column = gtk.TreeViewColumn("Time")
        avg_speed_column = gtk.TreeViewColumn("Speed (km/hr)")
        avg_hr_column = gtk.TreeViewColumn("Avg HR")
        max_hr_column = gtk.TreeViewColumn("Max HR")

        self.lap_tv.append_column(number_column)
        self.lap_tv.append_column(distance_column)
        self.lap_tv.append_column(time_column)
        self.lap_tv.append_column(avg_speed_column)
        self.lap_tv.append_column(avg_hr_column)
        self.lap_tv.append_column(max_hr_column)

        cell = gtk.CellRendererText()

        number_column.pack_start(cell, expand=False)
        distance_column.pack_start(cell, expand=False)
        time_column.pack_start(cell, expand=False)
        avg_speed_column.pack_start(cell, expand=False)
        avg_hr_column.pack_start(cell, expand=False)
        max_hr_column.pack_start(cell, expand=False)

        number_column.set_attributes(cell, text=0)
        distance_column.set_attributes(cell, text=1)
        time_column.set_attributes(cell, text=2)
        avg_speed_column.set_attributes(cell, text=3)
        avg_hr_column.set_attributes(cell, text=4)
        max_hr_column.set_attributes(cell, text=5)

        self.populate_sport_combos()