Merge lp:~jonatan-cloutier/gtimelog/addTags into lp:~gtimelog-dev/gtimelog/trunk

Proposed by Jonatan Cloutier
Status: Needs review
Proposed branch: lp:~jonatan-cloutier/gtimelog/addTags
Merge into: lp:~gtimelog-dev/gtimelog/trunk
Diff against target: 282 lines (+105/-23)
2 files modified
src/gtimelog/gtimelog.ui (+12/-0)
src/gtimelog/main.py (+93/-23)
To merge this branch: bzr merge lp:~jonatan-cloutier/gtimelog/addTags
Reviewer Review Type Date Requested Status
GTimeLog developers Pending
Review via email: mp+153561@code.launchpad.net

Description of the change

Add a tagging feature

To post a comment you must log in.

Unmerged revisions

255. By Jonatan Cloutier

add tags feature

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/gtimelog/gtimelog.ui'
--- src/gtimelog/gtimelog.ui 2012-08-23 05:20:13 +0000
+++ src/gtimelog/gtimelog.ui 2013-03-15 14:20:28 +0000
@@ -292,6 +292,18 @@
292 </object>292 </object>
293 </child>293 </child>
294 <child>294 <child>
295 <object class="GtkRadioMenuItem" id="tags">
296 <property name="visible">True</property>
297 <property name="can_focus">False</property>
298 <property name="use_action_appearance">False</property>
299 <property name="label" translatable="yes">T_ags</property>
300 <property name="use_underline">True</property>
301 <property name="group">chronological</property>
302 <accelerator key="4" signal="activate" modifiers="GDK_MOD1_MASK"/>
303 <signal name="activate" handler="on_tags_activate" swapped="no"/>
304 </object>
305 </child>
306 <child>
295 <object class="GtkSeparatorMenuItem" id="separator2">307 <object class="GtkSeparatorMenuItem" id="separator2">
296 <property name="visible">True</property>308 <property name="visible">True</property>
297 <property name="can_focus">False</property>309 <property name="can_focus">False</property>
298310
=== modified file 'src/gtimelog/main.py'
--- src/gtimelog/main.py 2013-02-07 08:33:21 +0000
+++ src/gtimelog/main.py 2013-03-15 14:20:28 +0000
@@ -350,7 +350,7 @@
350 duration = stop - start350 duration = stop - start
351 return start, stop, duration, entry351 return start, stop, duration, entry
352352
353 def grouped_entries(self, skip_first=True):353 def grouped_entries(self, skip_first=True, trimTags=True):
354 """Return consolidated entries (grouped by entry title).354 """Return consolidated entries (grouped by entry title).
355355
356 Returns two list: work entries and slacking entries. Slacking356 Returns two list: work entries and slacking entries. Slacking
@@ -360,6 +360,8 @@
360 work = {}360 work = {}
361 slack = {}361 slack = {}
362 for start, stop, duration, entry in self.all_entries():362 for start, stop, duration, entry in self.all_entries():
363 if trimTags:
364 entry = entry.split(" '")[0]
363 if skip_first:365 if skip_first:
364 skip_first = False366 skip_first = False
365 continue367 continue
@@ -411,6 +413,35 @@
411 None, datetime.timedelta(0)) + duration413 None, datetime.timedelta(0)) + duration
412 return entries, totals414 return entries, totals
413415
416 def tagged_work_entries(self, skip_first=True):
417 """Return consolidated work entries grouped by tag.
418
419 Tag is a string following an apostrophe (') at the end of an entry.
420
421 Return two dicts:
422 - {<tag>: <entry list>}, where <tag> is a tag string
423 and <entry list> is a sorted list that contains tuples (start,
424 entry, duration); entry is stripped of its tag. An entry can
425 be present in more than one tag.
426 - {<tag>: <total duration>}, where <total duration> is the
427 total duration of work in the <category>.
428 """
429
430 work, slack = self.grouped_entries(skip_first=skip_first, trimTags=False)
431 entries = {}
432 totals = {}
433 for start, entry, duration in work:
434 if " '" in entry:
435 splitedEntries = entry.split(" '")
436 tags = splitedEntries[1:]
437 clipped_entry = splitedEntries[0]
438 for tag in tags:
439 entry_list = entries.get(tag, [])
440 entry_list.append((start, clipped_entry, duration))
441 entries[tag] = entry_list
442 totals[tag] = totals.get(tag, datetime.timedelta(0)) + duration
443 return entries, totals
444
414 def totals(self):445 def totals(self):
415 """Calculate total time of work and slacking entries.446 """Calculate total time of work and slacking entries.
416447
@@ -637,8 +668,31 @@
637 print >> output668 print >> output
638 print >> output, "By category:"669 print >> output, "By category:"
639 print >> output670 print >> output
640671
641 items = categories.items()672 self._report_groups(output, categories)
673
674 def _report_tags(self, output, tags):
675 """A helper method that lists time spent per tag.
676
677 Use this to add a section in a report looks similar to this:
678
679 Administration: 2 hours 1 min
680 Coding: 18 hours 45 min
681 Learning: 3 hours
682
683 tags is a dict of entries (<tag name>: <duration>).
684 """
685 print >> output
686 print >> output, "By tag:"
687 print >> output
688
689 self._report_groups(output, tags)
690
691 def _report_groups(self, output, groups):
692 """A helper method that lists time spent per grouped entries,
693 either categories or tags.
694 """
695 items = groups.items()
642 items.sort()696 items.sort()
643 for cat, duration in items:697 for cat, duration in items:
644 if not cat:698 if not cat:
@@ -647,9 +701,9 @@
647 print >> output, u"%-62s %s" % (701 print >> output, u"%-62s %s" % (
648 cat, format_duration_long(duration))702 cat, format_duration_long(duration))
649703
650 if None in categories:704 if None in groups:
651 print >> output, u"%-62s %s" % (705 print >> output, u"%-62s %s" % (
652 '(none)', format_duration_long(categories[None]))706 '(none)', format_duration_long(groups[None]))
653 print >> output707 print >> output
654708
655 def _plain_report(self, output, email, who, subject, period_name,709 def _plain_report(self, output, email, who, subject, period_name,
@@ -674,7 +728,6 @@
674 print >> output, " time"728 print >> output, " time"
675 work, slack = window.grouped_entries()729 work, slack = window.grouped_entries()
676 total_work, total_slacking = window.totals()730 total_work, total_slacking = window.totals()
677 categories = {}
678 if work:731 if work:
679 work = [(entry, duration) for start, entry, duration in work]732 work = [(entry, duration) for start, entry, duration in work]
680 work.sort()733 work.sort()
@@ -682,14 +735,6 @@
682 if not duration:735 if not duration:
683 continue # skip empty "arrival" entries736 continue # skip empty "arrival" entries
684737
685 if ': ' in entry:
686 cat, task = entry.split(': ', 1)
687 categories[cat] = categories.get(
688 cat, datetime.timedelta(0)) + duration
689 else:
690 categories[None] = categories.get(
691 None, datetime.timedelta(0)) + duration
692
693 entry = entry[:1].upper() + entry[1:]738 entry = entry[:1].upper() + entry[1:]
694 if estimated_column:739 if estimated_column:
695 print >> output, (u"%-46s %-14s %s" %740 print >> output, (u"%-46s %-14s %s" %
@@ -701,9 +746,14 @@
701 print >> output, ("Total work done this %s: %s" %746 print >> output, ("Total work done this %s: %s" %
702 (period_name, format_duration_long(total_work)))747 (period_name, format_duration_long(total_work)))
703748
749 entries, categories = window.categorized_work_entries()
704 if categories:750 if categories:
705 self._report_categories(output, categories)751 self._report_categories(output, categories)
706752
753 entries, tags = window.tagged_work_entries()
754 if tags:
755 self._report_tags(output, tags)
756
707 def weekly_report_categorized(self, output, email, who,757 def weekly_report_categorized(self, output, email, who,
708 estimated_column=False):758 estimated_column=False):
709 """Format a weekly report with entries displayed under categories."""759 """Format a weekly report with entries displayed under categories."""
@@ -766,27 +816,24 @@
766 print >> output816 print >> output
767 work, slack = window.grouped_entries()817 work, slack = window.grouped_entries()
768 total_work, total_slacking = window.totals()818 total_work, total_slacking = window.totals()
769 categories = {}
770 if work:819 if work:
771 for start, entry, duration in work:820 for start, entry, duration in work:
772 entry = entry[:1].upper() + entry[1:]821 entry = entry[:1].upper() + entry[1:]
773 print >> output, u"%-62s %s" % (entry,822 print >> output, u"%-62s %s" % (entry,
774 format_duration_long(duration))823 format_duration_long(duration))
775 if ': ' in entry:
776 cat, task = entry.split(': ', 1)
777 categories[cat] = categories.get(
778 cat, datetime.timedelta(0)) + duration
779 else:
780 categories[None] = categories.get(
781 None, datetime.timedelta(0)) + duration
782824
783 print >> output825 print >> output
784 print >> output, ("Total work done: %s" %826 print >> output, ("Total work done: %s" %
785 format_duration_long(total_work))827 format_duration_long(total_work))
786828
787 if len(categories) > 0:829 entries, categories = window.categorized_work_entries()
830 if categories:
788 self._report_categories(output, categories)831 self._report_categories(output, categories)
789832
833 entries, tags = window.tagged_work_entries()
834 if tags:
835 self._report_tags(output, tags)
836
790 print >> output, 'Slacking:\n'837 print >> output, 'Slacking:\n'
791838
792 if slack:839 if slack:
@@ -1053,6 +1100,7 @@
1053 spreadsheet = 'xdg-open %s'1100 spreadsheet = 'xdg-open %s'
1054 chronological = True1101 chronological = True
1055 summary_view = False1102 summary_view = False
1103 tags_view = False
1056 show_tasks = True1104 show_tasks = True
10571105
1058 enable_gtk_completion = True # False enables gvim-style completion1106 enable_gtk_completion = True # False enables gvim-style completion
@@ -1111,6 +1159,7 @@
1111 config.set('gtimelog', 'spreadsheet', self.spreadsheet)1159 config.set('gtimelog', 'spreadsheet', self.spreadsheet)
1112 config.set('gtimelog', 'chronological', str(self.chronological))1160 config.set('gtimelog', 'chronological', str(self.chronological))
1113 config.set('gtimelog', 'summary_view', str(self.summary_view))1161 config.set('gtimelog', 'summary_view', str(self.summary_view))
1162 config.set('gtimelog', 'tags_view', str(self.tags_view))
1114 config.set('gtimelog', 'show_tasks', str(self.show_tasks))1163 config.set('gtimelog', 'show_tasks', str(self.show_tasks))
1115 config.set('gtimelog', 'gtk-completion',1164 config.set('gtimelog', 'gtk-completion',
1116 str(self.enable_gtk_completion))1165 str(self.enable_gtk_completion))
@@ -1140,6 +1189,7 @@
1140 self.spreadsheet = config.get('gtimelog', 'spreadsheet')1189 self.spreadsheet = config.get('gtimelog', 'spreadsheet')
1141 self.chronological = config.getboolean('gtimelog', 'chronological')1190 self.chronological = config.getboolean('gtimelog', 'chronological')
1142 self.summary_view = config.getboolean('gtimelog', 'summary_view')1191 self.summary_view = config.getboolean('gtimelog', 'summary_view')
1192 self.tags_view = config.getboolean('gtimelog', 'tags_view')
1143 self.show_tasks = config.getboolean('gtimelog', 'show_tasks')1193 self.show_tasks = config.getboolean('gtimelog', 'show_tasks')
1144 self.enable_gtk_completion = config.getboolean('gtimelog',1194 self.enable_gtk_completion = config.getboolean('gtimelog',
1145 'gtk-completion')1195 'gtk-completion')
@@ -1440,6 +1490,7 @@
1440 self.chronological = (settings.chronological1490 self.chronological = (settings.chronological
1441 and not settings.summary_view)1491 and not settings.summary_view)
1442 self.summary_view = settings.summary_view1492 self.summary_view = settings.summary_view
1493 self.tags_view = settings.tags_view
1443 self.show_tasks = settings.show_tasks1494 self.show_tasks = settings.show_tasks
1444 self.looking_at_date = None1495 self.looking_at_date = None
1445 self.entry_watchers = []1496 self.entry_watchers = []
@@ -1454,6 +1505,8 @@
1454 chronological_menu_item.set_active(self.chronological)1505 chronological_menu_item.set_active(self.chronological)
1455 summary_menu_item = builder.get_object('summary')1506 summary_menu_item = builder.get_object('summary')
1456 summary_menu_item.set_active(self.summary_view)1507 summary_menu_item.set_active(self.summary_view)
1508 tags_menu_item = builder.get_object('tags')
1509 tags_menu_item.set_active(self.tags_view)
1457 show_task_pane_item = builder.get_object('show_task_pane')1510 show_task_pane_item = builder.get_object('show_task_pane')
1458 show_task_pane_item.set_active(self.show_tasks)1511 show_task_pane_item.set_active(self.show_tasks)
1459 # Now hook up signals.1512 # Now hook up signals.
@@ -1568,6 +1621,13 @@
1568 where = buffer.get_end_iter()1621 where = buffer.get_end_iter()
1569 where.backward_cursor_position()1622 where.backward_cursor_position()
1570 buffer.place_cursor(where)1623 buffer.place_cursor(where)
1624 elif self.tags_view:
1625 entries, totals = window.tagged_work_entries()
1626 for category, duration in sorted(totals.items()):
1627 self.write_group(category or 'no category', duration)
1628 where = buffer.get_end_iter()
1629 where.backward_cursor_position()
1630 buffer.place_cursor(where)
1571 else:1631 else:
1572 work, slack = window.grouped_entries()1632 work, slack = window.grouped_entries()
1573 for start, entry, duration in work + slack:1633 for start, entry, duration in work + slack:
@@ -1830,18 +1890,28 @@
1830 """View -> Chronological"""1890 """View -> Chronological"""
1831 self.chronological = True1891 self.chronological = True
1832 self.summary_view = False1892 self.summary_view = False
1893 self.tags_view = False
1833 self.populate_log()1894 self.populate_log()
18341895
1835 def on_grouped_activate(self, widget):1896 def on_grouped_activate(self, widget):
1836 """View -> Grouped"""1897 """View -> Grouped"""
1837 self.chronological = False1898 self.chronological = False
1838 self.summary_view = False1899 self.summary_view = False
1900 self.tags_view = False
1839 self.populate_log()1901 self.populate_log()
18401902
1841 def on_summary_activate(self, widget):1903 def on_summary_activate(self, widget):
1842 """View -> Summary"""1904 """View -> Summary"""
1843 self.chronological = False1905 self.chronological = False
1844 self.summary_view = True1906 self.summary_view = True
1907 self.tags_view = False
1908 self.populate_log()
1909
1910 def on_tags_activate(self, widget):
1911 """View -> Tags"""
1912 self.chronological = False
1913 self.summary_view = False
1914 self.tags_view = True
1845 self.populate_log()1915 self.populate_log()
18461916
1847 def daily_window(self, day=None):1917 def daily_window(self, day=None):

Subscribers

People subscribed via source and target branches