def __init__(self, filename=None, parent=None): super(Workspace, self).__init__(parent) self.setWindowTitle(APP_NAME) if filename is None: filename = ":memory:" self._db = LogDb(filename, bounds=60, units='minutes') # Widgets self.filter_panel = FilterPanel(self) self.list_panel = ListPanel(self._db, self) self.graph_panel = GraphPanel(self._db, self) self.tabs = widgets.QTabWidget() # self.tabs.setTabPosition(widgets.QTabWidget.West) self.tabs.addTab(self.list_panel, get_icon('list.png'), 'List') self.tabs.addTab(self.graph_panel, get_icon('pie.png'), 'Graph') # Layout main = widgets.QVBoxLayout() top_section = widgets.QHBoxLayout() top_section.addStretch(0) top_section.addWidget(self.filter_panel, 1) top_section.addStretch(0) main.addLayout(top_section) main.addWidget(self.tabs, 1) self.setLayout(main) # Connections self.filter_panel.apply_filter.connect(self.apply_filter) # self.tabs.currentChanged.connect(self.tab_change) # Initialize last_entry = self._db.filter(last=True) if last_entry is not None: last_entry = last_entry['start'] self.filter_panel.initial_state(last_entry)
def start_end_edited(self): current_duration = LogDb.parse_duration(self.duration_field.text()) if current_duration is None: current_duration = self.seconds if (self.link.isChecked() or current_duration is None or current_duration > self.start_end_seconds): self.seconds = self.start_end_seconds else: self.seconds = current_duration duration_text = LogDb.format_duration(self.seconds) self.duration_field.setText(duration_text)
def duration_edited(self): new = LogDb.parse_duration(self.duration_field.text()) if new is None: if self.link.isChecked() or self.seconds is None: new = self.start_end_seconds else: new = self.seconds if self.link.isChecked() or new > self.start_end_seconds: self.end.setDateTime(self.start.dateTime().toPyDateTime() + datetime.timedelta(seconds=new)) self.seconds = new self.duration_field.setText(LogDb.format_duration(new))
def _plot(self, figure, database, activity, start, end): axes = figure.add_subplot('111') start, end = self.bracket(database, '', start, end) chunk_size = LogDb.parse_duration(self.sample_field.text()) level = self.level.value() ranked_activities = self.ranked_activities(database, activity, start, end) allowed_activities = [n[0] for n in ranked_activities] midpoints, activities = database.span_slices( start=start, span=end - start, chunk_size=chunk_size, level=level, unrecorded=False, ) # Mutate chunk_size so it's understandable by matplotlib chunk_size = chunk_size / datetime.timedelta(days=1).total_seconds() self._graph_data(activities, figure, axes, allowed_activities, midpoints, chunk_size, activity) axes.xaxis.set_ticks([ datetime.datetime(start.year, start.month, start.day, 0, 0, 0) + datetime.timedelta(days=n) for n in range( int(round((end - start) / datetime.timedelta(days=1)))) ], minor=True) axes.grid(self.day_boundary_lines.isChecked(), 'minor', 'x') figure.autofmt_xdate()
def set_entry(self, entry): self._id = entry['id'] self.activity.setText(entry['activity']) self.start.setDateTime(entry['start']) span = (entry['end'] - entry['start']).total_seconds() self.link.setChecked(abs(span - entry['duration']) < 0.01) self.end.setDateTime(entry['end']) self.duration_field.setText(LogDb.format_duration(entry['duration'])) self.activity.setFocus()
def value(self): self._duration_edited() seconds = LogDb.parse_duration(self.duration.text()) if seconds is None: return seconds elif self.forward_box.isChecked(): return datetime.timedelta(seconds=seconds) elif self.backward_box.isChecked(): return datetime.timedelta(seconds=0 - seconds)
def get_database(): """ Return a LogDb instance based on the current user's application storage location. """ # This can't be in the helper module because it creates circular imports. # So it's here. app = widgets.QApplication.instance() or widgets.QApplication(sys.argv) app.setApplicationName(APP_NAME) app.setOrganizationName(ORG_NAME) app_dir = core.QStandardPaths.writableLocation( core.QStandardPaths.AppDataLocation) # app_dir = gui.QDesktopServices.storageLocation( # gui.QDesktopServices.DataLocation # ) filename = os.path.join(app_dir, "user_data.sqlite") return LogDb(filename)
def entry(self): if self.activity.hasFocus(): self.activity.editingFinished.emit() if self.duration_field.hasFocus(): self.duration_field.editingFinished.emit() activity = self.activity.text().strip() if activity == '': activity = None start = self.start.dateTime().toPyDateTime() end = self.end.dateTime().toPyDateTime() if abs((end - start).seconds - self.seconds) < 60: duration = None else: duration = LogDb.parse_duration(self.duration_field.text()) entry = { 'id': self._id, 'activity': activity, 'start': start, 'end': end, 'duration': duration, } return entry
def _plot(self, figure, database, activity, start, end): figure.set_tight_layout(False) start = datetime.datetime.fromordinal(start.date().toordinal()) end = datetime.datetime.fromordinal( (end + datetime.timedelta(days=1)).date().toordinal()) level = self.level.value() if activity != 'unrecorded': start, end = self.bracket(database, '', start, end) activities = dict() day_labels = list() day_count = (end - start).days seconds_per_day = datetime.timedelta(days=1).total_seconds() day_totals = [ 0, ] * day_count for day_offset in range(day_count): chunk_start = start + datetime.timedelta(days=day_offset) day_labels.append(stringify_date(chunk_start)) chunk_activities = database.slice_activities( start=chunk_start, end=chunk_start + datetime.timedelta(days=1), level=level, unrecorded=False, activity=activity, ) for k, v in chunk_activities.items(): if k not in activities.keys(): activities[k] = [ 0, ] * day_count activities[k][day_offset] = v * seconds_per_day day_totals[day_offset] = sum( [v[day_offset] for v in activities.values()]) activity_totals = dict([(k, sum(v)) for k, v in activities.items()]) activity_labels = sorted(list(activity_totals.keys()), key=lambda k: activity_totals[k], reverse=True) #run_pdb() axes = figure.add_subplot('111') celltext = list() for k in activity_labels: if self.verbose_times.isChecked(): celltext.append( [LogDb.format_duration(v) for v in activities[k]] + [ LogDb.format_duration(activity_totals[k]), ]) else: data = activities[k] + [activity_totals[k]] data = [int(round(n / 60.0)) for n in data] celltext.append(data) if self.verbose_times.isChecked(): celltext.append([LogDb.format_duration(v) for v in day_totals] + [LogDb.format_duration(sum(day_totals))]) else: data = day_totals + [ sum(day_totals), ] data = [int(round(n / 60.0)) for n in data] celltext.append(data) with open(os.path.expanduser('~/Desktop/debug.csv'), 'w', newline='') as fout: writer = csv.writer(fout) writer.writerow([ '', ] + day_labels + [ 'TOTALS', ]) for i, row in enumerate(celltext): if i < len(activity_labels): writer.writerow([ activity_labels[i], ] + row) else: writer.writerow([''] + row) #writer.writerows(celltext) bg_color = mpl.rcParams['axes.facecolor'] axes.table(cellText=celltext, rowLabels=activity_labels + [ 'TOTALS', ], colLabels=day_labels + [ 'TOTALS', ], cellColours=[[ bg_color, ] * (day_count + 1)] * len(celltext), rowColours=[ bg_color, ] * (len(activity_labels) + 1), colColours=[ bg_color, ] * (len(day_labels) + 1), loc='center')
def _sample_edited(self): new = LogDb.parse_duration(self.sample_field.text()) self.sample_field.setText(LogDb.format_duration(new))
def sample(self): return LogDb.parse_duration(self.sample_field.text())
def _plot(self, figure, database, activity, start, end): weekly = self.weekly weekdays = None if hasattr(self, 'weekday_selector'): weekdays = self.weekday_selector.selection() axes = figure.add_subplot('111', xmargin=0, ymargin=0) start, end = self.bracket(database, '', start, end) chunk_size = LogDb.parse_duration(self.sample_field.text()) level = self.level.value() ranked_activities = self.ranked_activities(database, activity, start, end) allowed_activities = [n[0] for n in ranked_activities] midpoints, activities = database.stacked_slices( start=start, span=end - start, chunk_size=chunk_size, level=level, unrecorded=False, weekdays=weekdays, weekly=weekly, ) self._graph_data(activities, figure, axes, allowed_activities, midpoints, chunk_size, activity) if weekly: axes.set_xlim((0, datetime.timedelta(days=7).total_seconds())) axes.xaxis.set_label_text("Weekdays") axes.xaxis.set_ticks(( 0, datetime.timedelta(days=1).total_seconds(), datetime.timedelta(days=2).total_seconds(), datetime.timedelta(days=3).total_seconds(), datetime.timedelta(days=4).total_seconds(), datetime.timedelta(days=5).total_seconds(), datetime.timedelta(days=6).total_seconds(), datetime.timedelta(days=7).total_seconds(), )) axes.xaxis.set_ticklabels( [core.QDate.shortDayName(n) for n in range(1, 8)] #('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday') ) axes.grid(self.day_boundary_lines.isChecked(), 'major', 'x') else: axes.set_xlim((0, datetime.timedelta(days=1).total_seconds())) axes.xaxis.set_label_text("Time of day") axes.xaxis.set_ticks([ datetime.timedelta(hours=h).total_seconds() for h in range(0, 30, 6) ]) axes.xaxis.set_ticks([ datetime.timedelta(hours=h).total_seconds() for h in range(25) ], minor=True) axes.xaxis.set_ticklabels( ('Midnight', '6am', 'Noon', '6pm', 'Midnight')) axes.grid(True, 'minor', 'x') axes.yaxis.set_ticklabels(list()) date_fmt_string = core.QLocale().dateFormat() title = ("{0} Activities Between {1} and {2}".format( "Weekly" if weekly else "Daily", stringify_date(start), stringify_date(end))) if hasattr(self, 'weekday_selector'): if len(self.weekday_selector.selection()) < 7: title += "\n(" + str(self.weekday_selector) + ")" axes.set_title(title)