Exemplo n.º 1
0
 def test_explicit(self):
     '''testing whether explicit assignments work as defined'''
     activity = Fact(start_time="12:25",
                     end_time="13:25",
                     activity="case",
                     category="cat",
                     description="description non-tag#ta",
                     tags="tag, bag")
     self.assertEquals(activity.start_time, "12:25")
     self.assertEquals(activity.end_time, "13:25")
     self.assertEquals(activity.activity, "case")
     self.assertEquals(activity.category, "cat")
     self.assertEquals(activity.description, "description non-tag#ta")
     self.assertEquals(set(activity.tags), set(["bag", "tag"]))
Exemplo n.º 2
0
 def test_iter_explicit(self):
     '''testing iter with explicit assingments'''
     activity = Fact(start_time="12:34",
                     end_time="15:12",
                     activity="test activity",
                     category="test category",
                     description="test description",
                     tags="testtags")
     activity2 = Fact(start_time="12:34",
                      end_time="15:12",
                      activity="test activity",
                      category="test category",
                      description="test description",
                      tags="testtags")
     self.assertEquals([elem for elem in activity],
                       [elem for elem in activity2])
     activity3 = Fact(start_time="12:14",
                      end_time="15:12",
                      activity="test activity",
                      category="test category",
                      description="test description",
                      tags="testtags")
     self.assertNotEqual([elem for elem in activity],
                         [elem for elem in activity3])
Exemplo n.º 3
0
    def on_workspace_changed(self, screen, previous_workspace):
        if not previous_workspace:
            # wnck has a slight hiccup on init and after that calls
            # workspace changed event with blank previous state that should be
            # ignored
            return

        if not self.workspace_tracking:
            return # default to not doing anything

        current_workspace = screen.get_active_workspace()

        # rely on workspace numbers as names change
        prev = previous_workspace.get_number()
        new = current_workspace.get_number()

        # on switch, update our mapping between spaces and activities
        self.workspace_activities[prev] = self.last_activity


        activity = None
        if "name" in self.workspace_tracking:
            # first try to look up activity by desktop name
            mapping = conf.get("workspace_mapping")

            fact = None
            if new < len(mapping):
                fact = Fact(mapping[new])

                if fact.activity:
                    category_id = None
                    if fact.category:
                        category_id = runtime.storage.get_category_id(fact.category)

                    activity = runtime.storage.get_activity_by_name(fact.activity,
                                                                    category_id,
                                                                    resurrect = False)
                    if activity:
                        # we need dict below
                        activity = dict(name = activity.name,
                                        category = activity.category,
                                        description = fact.description,
                                        tags = fact.tags)


        if not activity and "memory" in self.workspace_tracking:
            # now see if maybe we have any memory of the new workspace
            # (as in - user was here and tracking Y)
            # if the new workspace is in our dict, switch to the specified activity
            if new in self.workspace_activities and self.workspace_activities[new]:
                activity = self.workspace_activities[new]

        if not activity:
            return

        # check if maybe there is no need to switch, as field match:
        if self.last_activity and \
           self.last_activity.name.lower() == activity.name.lower() and \
           (self.last_activity.category or "").lower() == (activity.category or "").lower() and \
           ", ".join(self.last_activity.tags).lower() == ", ".join(activity.tags).lower():
            return

        # ok, switch
        fact = Fact(activity.name,
                          tags = ", ".join(activity.tags),
                          category = activity.category,
                          description = activity.description);
        runtime.storage.add_fact(fact)
Exemplo n.º 4
0
Arquivo: db.py Projeto: wreckJ/hamster
    def __add_fact(self,
                   serialized_fact,
                   start_time,
                   end_time=None,
                   temporary=False):
        fact = Fact(serialized_fact, start_time=start_time, end_time=end_time)

        start_time = start_time or fact.start_time
        end_time = end_time or fact.end_time

        if not fact.activity or start_time is None:  # sanity check
            return 0

        # get tags from database - this will create any missing tags too
        tags = [(tag['id'], tag['name'], tag['autocomplete'])
                for tag in self.get_tag_ids(fact.tags)]

        # now check if maybe there is also a category
        category_id = None
        if fact.category:
            category_id = self.__get_category_id(fact.category)
            if not category_id:
                category_id = self.__add_category(fact.category)

                if trophies:
                    trophies.unlock("no_hands")

        # try to find activity, resurrect if not temporary
        activity_id = self.__get_activity_by_name(fact.activity,
                                                  category_id,
                                                  resurrect=not temporary)
        if not activity_id:
            activity_id = self.__add_activity(fact.activity, category_id,
                                              temporary)
        else:
            activity_id = activity_id['id']

        # if we are working on +/- current day - check the last_activity
        if (dt.timedelta(days=-1) <= dt.datetime.now() - start_time <=
                dt.timedelta(days=1)):
            # pull in previous facts
            facts = self.__get_todays_facts()

            previous = None
            if facts and facts[-1]["end_time"] == None:
                previous = facts[-1]

            if previous and previous['start_time'] <= start_time:
                # check if maybe that is the same one, in that case no need to restart
                if previous["activity_id"] == activity_id \
                   and set(previous["tags"]) == set([tag[1] for tag in tags]) \
                   and (previous["description"] or "") == (fact.description or ""):
                    return None

                # if no description is added
                # see if maybe previous was too short to qualify as an activity
                if not previous["description"] \
                   and 60 >= (start_time - previous['start_time']).seconds >= 0:
                    self.__remove_fact(previous['id'])

                    # now that we removed the previous one, see if maybe the one
                    # before that is actually same as the one we want to start
                    # (glueing)
                    if len(facts) > 1 and 60 >= (
                            start_time - facts[-2]['end_time']).seconds >= 0:
                        before = facts[-2]
                        if before["activity_id"] == activity_id \
                           and set(before["tags"]) == set([tag[1] for tag in tags]):
                            # resume and return
                            update = """
                                       UPDATE facts
                                          SET end_time = null
                                        WHERE id = ?
                            """
                            self.execute(update, (before["id"], ))

                            return before["id"]
                else:
                    # otherwise stop
                    update = """
                               UPDATE facts
                                  SET end_time = ?
                                WHERE id = ?
                    """
                    self.execute(update, (start_time, previous["id"]))

        # done with the current activity, now we can solve overlaps
        if not end_time:
            end_time = self.__squeeze_in(start_time)
        else:
            self.__solve_overlaps(start_time, end_time)

        # finally add the new entry
        insert = """
                    INSERT INTO facts (activity_id, start_time, end_time, description)
                               VALUES (?, ?, ?, ?)
        """
        self.execute(insert,
                     (activity_id, start_time, end_time, fact.description))

        fact_id = self.__last_insert_rowid()

        #now link tags
        insert = ["insert into fact_tags(fact_id, tag_id) values(?, ?)"
                  ] * len(tags)
        params = [(fact_id, tag[0]) for tag in tags]
        self.execute(insert, params)

        self.__remove_index([fact_id])
        return fact_id
Exemplo n.º 5
0
Arquivo: db.py Projeto: wreckJ/hamster
    def __solve_overlaps(self, start_time, end_time):
        """finds facts that happen in given interval and shifts them to
        make room for new fact
        """
        if end_time is None or start_time is None:
            return

        # possible combinations and the OR clauses that catch them
        # (the side of the number marks if it catches the end or start time)
        #             |----------------- NEW -----------------|
        #      |--- old --- 1|   |2 --- old --- 1|   |2 --- old ---|
        # |3 -----------------------  big old   ------------------------ 3|
        query = """
                   SELECT a.*, b.name, c.name as category
                     FROM facts a
                LEFT JOIN activities b on b.id = a.activity_id
                LEFT JOIN categories c on b.category_id = c.id
                    WHERE (end_time > ? and end_time < ?)
                       OR (start_time > ? and start_time < ?)
                       OR (start_time < ? and end_time > ?)
                 ORDER BY start_time
                """
        conflicts = self.fetchall(
            query,
            (start_time, end_time, start_time, end_time, start_time, end_time))

        for fact in conflicts:
            # won't eliminate as it is better to have overlapping entries than loosing data
            if start_time < fact["start_time"] and end_time > fact["end_time"]:
                continue

            # split - truncate until beginning of new entry and create new activity for end
            if fact["start_time"] < start_time < fact["end_time"] and \
               fact["start_time"] < end_time < fact["end_time"]:

                logging.info("splitting %s" % fact["name"])
                # truncate until beginning of the new entry
                self.execute(
                    """UPDATE facts
                                   SET end_time = ?
                                 WHERE id = ?""", (start_time, fact["id"]))
                fact_name = fact["name"]

                # create new fact for the end
                new_fact = Fact(fact["name"],
                                category=fact["category"],
                                description=fact["description"])
                new_fact_id = self.__add_fact(new_fact.serialized_name(),
                                              end_time, fact["end_time"])

                # copy tags
                tag_update = """INSERT INTO fact_tags(fact_id, tag_id)
                                     SELECT ?, tag_id
                                       FROM fact_tags
                                      WHERE fact_id = ?"""
                self.execute(tag_update,
                             (new_fact_id, fact["id"]))  #clone tags

                if trophies:
                    trophies.unlock("split")

            # overlap start
            elif start_time < fact["start_time"] < end_time:
                logging.info("Overlapping start of %s" % fact["name"])
                self.execute("UPDATE facts SET start_time=? WHERE id=?",
                             (end_time, fact["id"]))

            # overlap end
            elif start_time < fact["end_time"] < end_time:
                logging.info("Overlapping end of %s" % fact["name"])
                self.execute("UPDATE facts SET end_time=? WHERE id=?",
                             (start_time, fact["id"]))
Exemplo n.º 6
0
    def update_suggestions(self, text=""):
        """
            * from previous activity | set time | minutes ago | start now
            * to ongoing | set time

            * activity
            * [@category]
            * #tags, #tags, #tags

            * we will leave description for later

            all our magic is space separated, strictly, start-end can be just dash

            phases:

            [start_time] | [-end_time] | activity | [@category] | [#tag]
        """

        res = []

        fact = Fact(text)
        now = dt.datetime.now()

        # figure out what we are looking for
        # time -> activity[@category] -> tags -> description
        # presence of an attribute means that we are not looking for the previous one
        # we still might be looking for the current one though
        looking_for = "start_time"
        fields = ["start_time", "end_time", "activity", "category", "tags", "description", "done"]
        for field in reversed(fields):
            if getattr(fact, field, None):
                looking_for = field
                if text[-1] == " ":
                    looking_for = fields[fields.index(field)+1]
                break


        fragments = [f for f in re.split("[\s|#]", text)]
        current_fragment = fragments[-1] if fragments else ""


        search = extract_search(text)

        matches = []
        for match, score in self.suggestions:
            if search in match:
                if match.startswith(search):
                    score += 10**8 # boost beginnings
                matches.append((match, score))

        # need to limit these guys, sorry
        matches = sorted(matches, key=lambda x: x[1], reverse=True)[:7]

        for match, score in matches:
            label = (fact.start_time or now).strftime("%H:%M")
            if fact.end_time:
                label += fact.end_time.strftime("-%H:%M")

            markup_label = label + " " + (stuff.escape_pango(match).replace(search, "<b>%s</b>" % search) if search else match)
            label += " " + match

            res.append(DataRow(markup_label, match, label))

        # list of tuples (description, variant)
        variants = []

        if self.original_fact:
            # editing an existing fact

            variant_fact = None
            if self.original_fact.end_time is None:
                description = "stop now"
                variant_fact = self.original_fact.copy()
                variant_fact.end_time = now
            elif self.original_fact == self.todays_facts[-1]:
                # that one is too dangerous, except for the last entry
                description = "keep up"
                # Do not use Fact(..., end_time=None): it would be a no-op
                variant_fact = self.original_fact.copy()
                variant_fact.end_time = None

            if variant_fact:
                variant = variant_fact.serialized(prepend_date=False)
                variants.append((description, variant))

        else:
            # brand new fact
            description = "start now"
            variant = now.strftime("%H:%M ")
            variants.append((description, variant))

            prev_fact = self.todays_facts[-1] if self.todays_facts else None
            if prev_fact and prev_fact.end_time:
                since = stuff.format_duration(now - prev_fact.end_time)
                description = "from previous activity, %s ago" % since
                variant = prev_fact.end_time.strftime("%H:%M ")
                variants.append((description, variant))

            description = "start activity -n minutes ago (1 or 3 digits allowed)"
            variant = "-"
            variants.append((description, variant))

        text = text.strip()
        if text:
            description = "clear"
            variant = ""
            variants.append((description, variant))

        for (description, variant) in variants:
            res.append(DataRow(variant, description=description))

        self.complete_tree.set_rows(res)
Exemplo n.º 7
0
    def on_switch_activity_clicked(self, widget):
        activity, temporary = self.new_name.get_value()

        # Redmine integration - if activity is connected with Redmine, it must use new data structures to save additional data
        fact = None
        if conf.get("redmine_integration_enabled"):
            redmine_issue_subject = self.get_widget(
                "issue_combo").get_active_text()
            if redmine_issue_subject == None or redmine_issue_subject == "None":
                arbitrary_issue_id = self.get_widget(
                    "arbitrary_issue_id_entry").get_text()
                if arbitrary_issue_id == "" or arbitrary_issue_id == None:
                    fact = Fact(activity,
                                tags=self.new_tags.get_text().decode(
                                    "utf8", "replace"))
                else:
                    redcon = redmine.RedmineConnector(
                        conf.get("redmine_url"), conf.get("redmine_api_key"))
                    try:
                        redcon.get_arbitrary_issue_data(arbitrary_issue_id)
                        arbitrary_issue_id = int(arbitrary_issue_id)
                        redmine_time_activity_name = self.get_widget(
                            "time_activity_combo").get_active_text()
                        if redmine_time_activity_name == None:
                            dialog = gtk.Dialog(
                                "Failed to start tracking", self.window,
                                gtk.DIALOG_MODAL,
                                (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
                            label = gtk.Label(
                                "Redmine activity cannot be empty!")
                            dialog.vbox.pack_start(label)
                            label.show()
                            dialog.run()
                            dialog.destroy()
                            return
                        redmine_activity_id = redcon.get_redmine_activity_id(
                            redmine_time_activity_name)
                        fact = RedmineFact(
                            activity,
                            arbitrary_issue_id,
                            redmine_activity_id,
                            tags=self.new_tags.get_text().decode(
                                "utf8", "replace"))
                    except redmine.RedmineConnectionException:
                        dialog = gtk.Dialog(
                            "Failed to start tracking", self.window,
                            gtk.DIALOG_MODAL,
                            (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
                        label = gtk.Label("Invalid arbitrary issue number!")
                        dialog.vbox.pack_start(label)
                        label.show()
                        dialog.run()
                        dialog.destroy()
                        return
            else:
                redmine_time_activity_name = self.get_widget(
                    "time_activity_combo").get_active_text()
                if redmine_time_activity_name == None:
                    dialog = gtk.Dialog("Failed to start tracking",
                                        self.window, gtk.DIALOG_MODAL,
                                        (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
                    label = gtk.Label("Redmine activity cannot be empty!")
                    dialog.vbox.pack_start(label)
                    label.show()
                    dialog.run()
                    dialog.destroy()
                    return
                redcon = redmine.RedmineConnector(conf.get("redmine_url"),
                                                  conf.get("redmine_api_key"))
                redmine_issue_id = redcon.get_redmine_issue_id(
                    redmine_issue_subject)
                redmine_activity_id = redcon.get_redmine_activity_id(
                    redmine_time_activity_name)
                fact = RedmineFact(activity,
                                   redmine_issue_id,
                                   redmine_activity_id,
                                   tags=self.new_tags.get_text().decode(
                                       "utf8", "replace"))
        else:
            fact = Fact(activity,
                        tags=self.new_tags.get_text().decode(
                            "utf8", "replace"))

        if not fact.activity:
            return

        runtime.storage.add_fact(fact, temporary)
        self.new_name.set_text("")
        self.new_tags.set_text("")
Exemplo n.º 8
0
    def check_fact_based(self, fact):
        """quite possibly these all could be called from the service as
           there is bigger certainty as to what did actually happen"""

        # checks fact based trophies
        if not storage: return

        # explicit over implicit
        if not fact.activity:  # TODO - parse_activity could return None for these cases
            return

        # full plate - use all elements of syntax parsing
        derived_fact = Fact(fact.original_activity)
        if all((derived_fact.category, derived_fact.description,
                derived_fact.tags, derived_fact.start_time,
                derived_fact.end_time)):
            unlock("full_plate")

        # Jumper - hidden - made 10 switches within an hour (radical)
        if not fact.end_time:  # end time normally denotes switch
            last_ten = self.flags.setdefault('last_ten_ongoing', [])
            last_ten.append(fact)
            last_ten = last_ten[-10:]

            if len(last_ten) == 10 and (
                    last_ten[-1].start_time -
                    last_ten[0].start_time) <= dt.timedelta(hours=1):
                unlock("jumper")

        # good memory - entered three past activities during a day
        if fact.end_time and fact.start_time.date() == dt.date.today():
            good_memory = increment("past_activities",
                                    dt.date.today().strftime("%Y%m%d"))
            if good_memory == 3:
                unlock("good_memory")

        # layering - entered 4 activities in a row in one of previous days, each one overlapping the previous one
        # avoiding today as in that case the layering might be automotical
        last_four = self.flags.setdefault('last_four', [])
        last_four.append(fact)
        last_four = last_four[-4:]
        if len(last_four) == 4:
            layered = True
            for prev, next in zip(last_four, last_four[1:]):
                if next.start_time.date() == dt.date.today() or \
                   next.start_time < prev.start_time or \
                   (prev.end_time and prev.end_time < next.start_time):
                    layered = False

            if layered:
                unlock("layered")

        # wait a minute! - Switch to a new activity within 60 seconds
        if len(last_four) >= 2:
            prev, next = last_four[-2:]
            if prev.end_time is None and next.end_time is None and (
                    next.start_time -
                    prev.start_time) < dt.timedelta(minutes=1):
                unlock("wait_a_minute")

        # alpha bravo charlie – used delta times to enter at least 50 activities
        if fact.start_time and fact.original_activity.startswith("-"):
            counter = increment("hamster-time-tracker", "alpha_bravo_charlie")
            if counter == 50:
                unlock("alpha_bravo_charlie")

        # cryptic - hidden - used word shorter than 4 letters for the activity name
        if len(fact.activity) < 4:
            unlock("cryptic")

        # madness – hidden – entered an activity in all caps
        if fact.activity == fact.activity.upper():
            unlock("madness")

        # verbose - hidden - description longer than 5 words
        if fact.description and len([
                word for word in fact.description.split(" ")
                if len(word.strip()) > 2
        ]) >= 5:
            unlock("verbose")

        # overkill - used 8 or more tags on a single activity
        if len(fact.tags) >= 8:
            unlock("overkill")

        # ponies - hidden - discovered the ponies
        if fact.ponies:
            unlock("ponies")

        # TODO - after the trophies have been unlocked there is not much point in going on
        #        patrys complains about who's gonna garbage collect. should think
        #        about this
        if not check("ultra_focused"):
            activity_count = increment(
                "hamster-time-tracker",
                "focused_%s@%s" % (fact.activity, fact.category or ""))
            # focused – 100 facts with single activity
            if activity_count == 100:
                unlock("focused")

            # ultra focused – 500 facts with single activity
            if activity_count == 500:
                unlock("ultra_focused")

        # elite - hidden - start an activity at 13:37
        if dt.datetime.now().hour == 13 and dt.datetime.now().minute == 37:
            unlock("elite")
Exemplo n.º 9
0
    def __init__(self, parent=None, fact_id=None, base_fact=None):
        gobject.GObject.__init__(self)

        self._gui = load_ui_file("edit_activity.ui")
        self.window = self.get_widget('custom_fact_window')
        self.window.set_size_request(600, 200)
        self.parent = parent
        # None if creating a new fact, instead of editing one
        self.fact_id = fact_id

        self.category_entry = widgets.CategoryEntry(
            widget=self.get_widget('category'))
        self.activity_entry = widgets.ActivityEntry(
            widget=self.get_widget('activity'),
            category_widget=self.category_entry)

        self.cmdline = widgets.CmdLineEntry()
        self.get_widget("command line box").add(self.cmdline)
        self.cmdline.connect("focus_in_event", self.on_cmdline_focus_in_event)
        self.cmdline.connect("focus_out_event",
                             self.on_cmdline_focus_out_event)

        self.dayline = widgets.DayLine()
        self._gui.get_object("day_preview").add(self.dayline)

        self.description_box = self.get_widget('description')
        self.description_buffer = self.description_box.get_buffer()

        self.end_date = widgets.Calendar(
            widget=self.get_widget("end date"),
            expander=self.get_widget("end date expander"))

        self.end_time = widgets.TimeInput()
        self.get_widget("end time box").add(self.end_time)

        self.start_date = widgets.Calendar(
            widget=self.get_widget("start date"),
            expander=self.get_widget("start date expander"))

        self.start_time = widgets.TimeInput()
        self.get_widget("start time box").add(self.start_time)

        self.tags_entry = widgets.TagsEntry()
        self.get_widget("tags box").add(self.tags_entry)

        self.save_button = self.get_widget("save_button")

        # this will set self.master_is_cmdline
        self.cmdline.grab_focus()

        if fact_id:
            # editing
            self.fact = runtime.storage.get_fact(fact_id)
            self.date = self.fact.date
            self.window.set_title(_("Update activity"))
        else:
            self.window.set_title(_("Add activity"))
            self.date = hamster_today()
            self.get_widget("delete_button").set_sensitive(False)
            if base_fact:
                # start a clone now.
                self.fact = base_fact.copy(start_time=hamster_now(),
                                           end_time=None)
            else:
                self.fact = Fact(start_time=hamster_now())

        original_fact = self.fact

        self.update_fields()
        self.update_cmdline(select=True)

        self.cmdline.original_fact = original_fact

        # This signal should be emitted only after a manual modification,
        # not at init time when cmdline might not always be fully parsable.
        self.cmdline.connect("changed", self.on_cmdline_changed)
        self.description_buffer.connect("changed", self.on_description_changed)
        self.start_time.connect("changed", self.on_start_time_changed)
        self.start_date.connect("day-selected", self.on_start_date_changed)
        self.start_date.expander.connect("activate",
                                         self.on_start_date_expander_activated)
        self.end_time.connect("changed", self.on_end_time_changed)
        self.end_date.connect("day-selected", self.on_end_date_changed)
        self.end_date.expander.connect("activate",
                                       self.on_end_date_expander_activated)
        self.activity_entry.connect("changed", self.on_activity_changed)
        self.category_entry.connect("changed", self.on_category_changed)
        self.tags_entry.connect("changed", self.on_tags_changed)

        self._gui.connect_signals(self)
        self.validate_fields()
        self.window.show_all()
Exemplo n.º 10
0
        return self.fact.activity

    def get_text(self, fact):
        text = ""
        if fact.description:
            text += ": %s" % (fact.description)
        if fact.tags:
            text += " ("+", ".join(fact.tags)+")"
        return text

d = date(2005, 7, 14)
t = ti(12, 30)


facts = list([\
         Fact(activity = "#123: 1", category = "category", description = "description", id=1), \
         Fact(activity = "#123: 2", category = "category", description = "description", id=2), \
         Fact(activity = "#222: 3", category = "category", description = "description", id=6), \
         Fact(activity = "#123: 4", category = "category", description = "description", id=3), \
         Fact(activity = "#222: 5", category = "category", description = "description", id=7), \
         Fact(activity = "#123: 6", category = "category", description = "description", id=4), \
         Fact(activity = "#123: 7", category = "category", description = "description", id=5), \
        ])
rows = [ExportRow(fact) for fact in facts]

tickets = {}
for ticket, group in groupby(rows, lambda export_row: export_row.id):
    print ticket, list(group)
rows.sort(key = lambda row: row.id)
for ticket, group in groupby(rows, lambda export_row: export_row.id):
    print ticket, list(group)
Exemplo n.º 11
0
 def on_last_activity_activated(self, widget, name):
     fact = Fact(name)
     if not fact.activity:
         return
     runtime.storage.add_fact(fact)
Exemplo n.º 12
0
 def localized_fact(self):
     """makes sure fact is in our date"""
     fact = Fact(self.activity.get_text())
     fact.date = self.date
     return fact
Exemplo n.º 13
0
    def update_suggestions(self, text=""):
        """
            * from previous activity | set time | minutes ago | start now
            * to ongoing | set time

            * activity
            * [@category]
            * #tags, #tags, #tags

            * we will leave description for later

            all our magic is space separated, strictly, start-end can be just dash

            phases:

            [start_time] | [-end_time] | activity | [@category] | [#tag]
        """
        now = dt.datetime.now()

        text = text.lstrip()

        time_re = re.compile("^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$")
        time_range_re = re.compile("^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])-([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$")
        delta_re = re.compile("^-[0-9]{1,3}$")

        # when the time is filled, we need to make sure that the chunks parse correctly



        delta_fragment_re = re.compile("^-[0-9]{0,3}$")


        templates = {
            "start_time": "",
            "start_delta": ("start activity -n minutes ago", "-"),
        }

        # need to set the start_time template before
        prev_fact = self.todays_facts[-1] if self.todays_facts else None
        if prev_fact and prev_fact.end_time:
            templates["start_time"] = ("from previous activity %s ago" % stuff.format_duration(now - prev_fact.end_time),
                                       prev_fact.end_time.strftime("%H:%M "))

        variants = []

        fact = Fact(text)

        # figure out what we are looking for
        # time -> activity[@category] -> tags -> description
        # presence of each next attribute means that we are not looking for the previous one
        # we still might be looking for the current one though
        looking_for = "start_time"
        fields = ["start_time", "end_time", "activity", "category", "tags", "description", "done"]
        for field in reversed(fields):
            if getattr(fact, field, None):
                looking_for = field
                if text[-1] == " ":
                    looking_for = fields[fields.index(field)+1]
                break


        fragments = [f for f in re.split("[\s|#]", text)]
        current_fragment = fragments[-1] if fragments else ""

        if not text.strip():
            variants = [templates[name] for name in ("start_time",
                                                     "start_delta") if templates[name]]
        elif looking_for == "start_time" and text == "-":
            if len(current_fragment) > 1: # avoid blank "-"
                templates["start_delta"] = ("%s minutes ago" % (-int(current_fragment)), current_fragment)
            variants.append(templates["start_delta"])


        res = []
        for (description, variant) in variants:
            res.append(DataRow(variant, description=description))

        # regular activity
        if (looking_for in ("start_time", "end_time") and not looks_like_time(text.split(" ")[-1])) or \
            looking_for in ("activity", "category"):

            search = extract_search(text)

            matches = []
            for match, score in self.suggestions:
                if search in match:
                    if match.startswith(search):
                        score += 10**8 # boost beginnings
                    matches.append((match, score))

            matches = sorted(matches, key=lambda x: x[1], reverse=True)[:7] # need to limit these guys, sorry

            for match, score in matches:
                label = (fact.start_time or now).strftime("%H:%M")
                if fact.end_time:
                    label += fact.end_time.strftime("-%H:%M")

                markup_label = label + " " + (stuff.escape_pango(match).replace(search, "<b>%s</b>" % search) if search else match)
                label += " " + match

                res.append(DataRow(markup_label, match, label))

        if not res:
            # in case of nothing to show, add preview so that the user doesn't
            # think they are lost
            label = (fact.start_time or now).strftime("%H:%M")
            if fact.end_time:
                label += fact.end_time.strftime("-%H:%M")

            if fact.activity:
                label += " " + fact.activity
            if fact.category:
                label += "@" + fact.category

            if fact.tags:
                label += " #" + " #".join(fact.tags)

            res.append(DataRow(stuff.escape_pango(label), description="Start tracking"))

        self.complete_tree.set_rows(res)