Exemplo n.º 1
0
 def test_datetime_from_day_time(self):
     day = dt.date(2018, 8, 13)
     time = dt.time(23, 10)
     expected = dt.datetime(2018, 8, 13, 23, 10)  # 2018-08-13 23:10
     self.assertEqual(dt.datetime.from_day_time(day, time), expected)
     day = dt.date(2018, 8, 13)
     time = dt.time(0, 10)
     expected = dt.datetime(2018, 8, 14, 0, 10)  # 2018-08-14 00:10
     self.assertEqual(dt.datetime.from_day_time(day, time), expected)
Exemplo n.º 2
0
    def figure_time(self, str_time):
        if not str_time:
            return None

        isPM = "pm" in str_time

        # strip everything non-numeric and consider hours to be first number
        # and minutes - second number
        numbers = re.split("\D", str_time)
        numbers = [x for x in numbers if x!=""]

        hours, minutes = None, None

        if len(numbers) == 1 and len(numbers[0]) == 4:
            hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
        else:
            if len(numbers) >= 1:
                hours = int(numbers[0])
            if len(numbers) >= 2:
                minutes = int(numbers[1])

        if isPM:
            # make sure we don't add if we're already at noon
            if hours != 12:
                hours += 12

        if (hours is None or minutes is None) or hours > 24 or minutes > 60:
            return None  # no can do

        return dt.time(hours, minutes)
Exemplo n.º 3
0
    def list(self, *times):
        """list facts within a date range"""
        (start_time, end_time), __ = dt.Range.parse(" ".join(times or []))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        self._list(start_time, end_time)
Exemplo n.º 4
0
    def load_suggestions(self):
        self.todays_facts = self.storage.get_todays_facts()

        # list of facts of last month
        now = dt.datetime.now()
        last_month = self.storage.get_facts(now - dt.timedelta(days=30), now)

        # naive recency and frequency rank
        # score is as simple as you get 30-days_ago points for each occurence
        suggestions = defaultdict(int)
        for fact in last_month:
            days = 30 - (now - dt.datetime.combine(
                fact.date, dt.time())).total_seconds() / 60 / 60 / 24
            label = fact.activity
            if fact.category:
                label += "@%s" % fact.category

            suggestions[label] += days

            if fact.tags:
                label += " #%s" % (" #".join(fact.tags))
                suggestions[label] += days

        for rec in self.storage.get_activities():
            label = rec["name"]
            if rec["category"]:
                label += "@%s" % rec["category"]
            suggestions[label] += 0

        # list of (label, score), higher scores first
        self.suggestions = sorted(suggestions.items(),
                                  key=lambda x: x[1],
                                  reverse=True)
Exemplo n.º 5
0
    def show_popup(self):
        if not self._parent_click_watcher:
            self._parent_click_watcher = self.get_toplevel().connect("button-press-event", self._on_focus_out_event)

        # we will be adding things, need datetime
        i_time_0 = dt.datetime.combine(self.start_date or dt.date.today(),
                                       self.start_time or dt.time())

        if self.start_time is None:
            # full 24 hours
            i_time = i_time_0
            interval = dt.timedelta(minutes = 15)
            end_time = i_time_0 + dt.timedelta(days = 1)
        else:
            # from start time to start time + 12 hours
            interval = dt.timedelta(minutes = 15)
            i_time = i_time_0 + interval
            end_time = i_time_0 + dt.timedelta(hours = 12)

        time = self.figure_time(self.get_text())
        focus_time = dt.datetime.combine(dt.date.today(), time) if time else None
        hours = gtk.ListStore(str)

        i, focus_row = 0, None
        while i_time < end_time:
            row_text = self._format_time(i_time)
            if self.start_time is not None:
                delta_text = (i_time - i_time_0).format()

                row_text += " (%s)" % delta_text

            hours.append([row_text])

            if focus_time and i_time <= focus_time < i_time + interval:
                focus_row = i

            i_time += interval

            i += 1

        self.time_tree.set_model(hours)

        #focus on row
        if focus_row != None:
            selection = self.time_tree.get_selection()
            selection.select_path(focus_row)
            self.time_tree.scroll_to_cell(focus_row, use_align = True, row_align = 0.4)

        #move popup under the widget
        alloc = self.get_allocation()
        w = alloc.width
        self.time_tree.set_size_request(w, alloc.height * 5)

        window = self.get_parent_window()
        dmmy, x, y= window.get_origin()

        self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
        self.popup.resize(*self.time_tree.get_size_request())
        self.popup.show_all()
Exemplo n.º 6
0
 def test_range(self):
     day = dt.hday(2020, 2, 2)
     time = dt.time(21, 20)
     base = dt.datetime.from_day_time(day, time)
     range = dt.Range(base, base + dt.timedelta(minutes=30))
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "21:20 - 21:50")
     range = dt.Range(None, base)
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "-- - 21:20")
Exemplo n.º 7
0
    def search(self, *args):
        """search for activities by name and optionally within a date range"""
        args = args or []
        search = ""
        if args:
            search = args[0]

        (start_time, end_time), __ = dt.Range.parse(" ".join(args[1:]))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        self._list(start_time, end_time, search)
Exemplo n.º 8
0
    def export(self, *args):
        args = args or []
        export_format, start_time, end_time = "html", None, None
        if args:
            export_format = args[0]
        (start_time, end_time), __ = dt.Range.parse(" ".join(args[1:]))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        facts = self.storage.get_facts(start_time, end_time)

        writer = reports.simple(facts, start_time.date(), end_time.date(), export_format)
Exemplo n.º 9
0
 def test_range(self):
     day = dt.hday(2020, 2, 2)
     time = dt.time(21, 20)
     base = dt.datetime.from_day_time(day, time)
     range = dt.Range(base, base + dt.timedelta(minutes=30))
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "21:20 - 21:50")
     range = dt.Range(None, base)
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "-- - 21:20")
     # issue #576
     start = dt.datetime(2020, 3, 8, 17, 7)
     end = dt.datetime(2020, 3, 8, 18, 6)
     range = dt.Range.from_start_end(start, end)
     self.assertEqual(range.start, start)
     self.assertEqual(range.end, end)
     # check passthrough
     range2 = dt.Range.from_start_end(range)
     self.assertEqual(range2.start, range.start)
     self.assertEqual(range2.end, range.end)
Exemplo n.º 10
0
    def figure_time(self, str_time):
        if not str_time:
            return None

        # strip everything non-numeric and consider hours to be first number
        # and minutes - second number
        numbers = re.split("\D", str_time)
        numbers = [x for x in numbers if x!=""]

        hours, minutes = None, None

        if len(numbers) == 1 and len(numbers[0]) == 4:
            hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
        else:
            if len(numbers) >= 1:
                hours = int(numbers[0])
            if len(numbers) >= 2:
                minutes = int(numbers[1])

        if (hours is None or minutes is None) or hours > 24 or minutes > 60:
            return None  # no can do

        return dt.time(hours, minutes)
Exemplo n.º 11
0
 def day_start(self):
     """Start of the hamster day."""
     day_start_minutes = self.get("day-start-minutes")
     hours, minutes = divmod(day_start_minutes, 60)
     return dt.time(hours, minutes)
Exemplo n.º 12
0
    def __init__(self):
        Controller.__init__(self, ui_file="preferences.ui")

        # create and fill activity tree
        self.activity_tree = self.get_widget('activity_list')
        self.get_widget("activities_label").set_mnemonic_widget(self.activity_tree)
        self.activity_store = ActivityStore()

        self.external_listeners = []

        self.activityColumn = gtk.TreeViewColumn(_("Name"))
        self.activityColumn.set_expand(True)
        self.activityCell = gtk.CellRendererText()
        self.external_listeners.extend([
            (self.activityCell, self.activityCell.connect('edited', self.activity_name_edited_cb, self.activity_store))
        ])
        self.activityColumn.pack_start(self.activityCell, True)
        self.activityColumn.set_attributes(self.activityCell, text=1)
        self.activityColumn.set_sort_column_id(1)
        self.activity_tree.append_column(self.activityColumn)

        self.activity_tree.set_model(self.activity_store)

        self.selection = self.activity_tree.get_selection()

        self.external_listeners.extend([
            (self.selection, self.selection.connect('changed', self.activity_changed, self.activity_store))
        ])

        # create and fill category tree
        self.category_tree = self.get_widget('category_list')
        self.get_widget("categories_label").set_mnemonic_widget(self.category_tree)
        self.category_store = CategoryStore()

        self.categoryColumn = gtk.TreeViewColumn(_("Category"))
        self.categoryColumn.set_expand(True)
        self.categoryCell = gtk.CellRendererText()

        self.external_listeners.extend([
            (self.categoryCell, self.categoryCell.connect('edited', self.category_edited_cb, self.category_store))
        ])

        self.categoryColumn.pack_start(self.categoryCell, True)
        self.categoryColumn.set_attributes(self.categoryCell, text=1)
        self.categoryColumn.set_sort_column_id(1)
        self.categoryColumn.set_cell_data_func(self.categoryCell, self.unsorted_painter)
        self.category_tree.append_column(self.categoryColumn)

        self.category_store.load()
        self.category_tree.set_model(self.category_store)

        selection = self.category_tree.get_selection()
        self.external_listeners.extend([
            (selection, selection.connect('changed', self.category_changed_cb, self.category_store))
        ])

        self.day_start = widgets.TimeInput(dt.time(5,30))
        self.get_widget("day_start_placeholder").add(self.day_start)

        self.load_config()

        # Allow enable drag and drop of rows including row move
        self.activity_tree.enable_model_drag_source(gdk.ModifierType.BUTTON1_MASK,
                                                    self.TARGETS,
                                                    gdk.DragAction.DEFAULT|
                                                    gdk.DragAction.MOVE)

        self.category_tree.enable_model_drag_dest(self.TARGETS,
                                                  gdk.DragAction.MOVE)

        self.activity_tree.connect("drag_data_get", self.drag_data_get_data)

        self.category_tree.connect("drag_data_received", self.on_category_drop)

        #select first category
        selection = self.category_tree.get_selection()
        selection.select_path((0,))

        self.prev_selected_activity = None
        self.prev_selected_category = None

        self.external_listeners.extend([
            (self.day_start, self.day_start.connect("time-entered", self.on_day_start_changed))
        ])

        self.show()
Exemplo n.º 13
0
def zero_hour(date):
    return dt.datetime.combine(date.date(), dt.time(0,0))
Exemplo n.º 14
0
 def test_roundtrips(self):
     for start_time in (
             None,
             dt.time(12, 33),
     ):
         for end_time in (
                 None,
                 dt.time(13, 34),
         ):
             for activity in (
                     "activity",
                     "#123 with two #hash",
                     "activity, with comma",
                     "17.00 tea",
             ):
                 for category in (
                         "",
                         "category",
                 ):
                     for description in (
                             "",
                             "description",
                             "with #hash",
                             "with, comma",
                             "with @at",
                             "multiline\ndescription",
                     ):
                         for tags in (
                             [],
                             ["single"],
                             ["with space"],
                             ["two", "tags"],
                             ["with @at"],
                         ):
                             start = dt.datetime.from_day_time(
                                 dt.hday.today(),
                                 start_time) if start_time else None
                             end = dt.datetime.from_day_time(
                                 dt.hday.today(),
                                 end_time) if end_time else None
                             if end and not start:
                                 # end without start is not parseable
                                 continue
                             fact = Fact(start_time=start,
                                         end_time=end,
                                         activity=activity,
                                         category=category,
                                         description=description,
                                         tags=tags)
                             for range_pos in ("head", "tail"):
                                 fact_str = fact.serialized(
                                     range_pos=range_pos)
                                 parsed = Fact.parse(fact_str,
                                                     range_pos=range_pos)
                                 self.assertEqual(fact, parsed)
                                 self.assertEqual(parsed.range.start,
                                                  fact.range.start)
                                 self.assertEqual(parsed.range.end,
                                                  fact.range.end)
                                 self.assertEqual(parsed.activity,
                                                  fact.activity)
                                 self.assertEqual(parsed.category,
                                                  fact.category)
                                 self.assertEqual(parsed.description,
                                                  fact.description)
                                 self.assertEqual(parsed.tags, fact.tags)
Exemplo n.º 15
0
    def on_enter_frame(self, scene, context):
        g = graphics.Graphics(context)

        rowcount, keys = len(self.keys), self.keys

        start_hour = 0
        if self.start_time:
            start_hour = self.start_time
        end_hour = 24 * 60
        if self.end_time:
            end_hour = self.end_time

        # push graph to the right, so it doesn't overlap
        legend_width = self.legend_width or self.longest_label(keys)

        self.graph_x = legend_width
        self.graph_x += 8  #add another 8 pixes of padding

        self.graph_width = self.width - self.graph_x

        # TODO - should handle the layout business in graphics
        self.layout = context.create_layout()
        default_font = "Sans Serif"  #pango.FontDescription(self.get_style().font_desc.to_string())
        default_font.set_size(8 * pango.SCALE)
        self.layout.set_font_description(default_font)

        #on the botttom leave some space for label
        self.layout.set_text("1234567890:")
        label_w, label_h = self.layout.get_pixel_size()

        self.graph_y, self.graph_height = 0, self.height - label_h - 4

        if not self.data:  #if we have nothing, let's go home
            return

        positions = {}
        y = 0
        bar_width = min(self.graph_height / float(len(self.keys)),
                        self.max_bar_width)
        for i, key in enumerate(self.keys):
            positions[key] = (y + self.graph_y, round(bar_width - 1))

            y = y + round(bar_width)
            bar_width = min(self.max_bar_width, (self.graph_height - y) /
                            float(max(1,
                                      len(self.keys) - i - 1)))

        max_bar_size = self.graph_width - 15

        # now for the text - we want reduced contrast for relaxed visuals
        fg_color = "#666"  #self.get_style().fg[gtk.StateType.NORMAL].to_string()
        label_color = self.colors.contrast(fg_color, 80)

        self.layout.set_alignment(pango.Alignment.RIGHT)
        self.layout.set_ellipsize(pango.ELLIPSIZE_END)

        # bars and labels
        self.layout.set_width(legend_width * pango.SCALE)

        factor = max_bar_size / float(end_hour - start_hour)

        # determine bar color
        bg_color = "#eee"  #self.get_style().bg[gtk.StateType.NORMAL].to_string()
        base_color = self.colors.contrast(bg_color, 30)

        for i, label in enumerate(keys):
            g.set_color(label_color)

            self.layout.set_text(label)
            label_w, label_h = self.layout.get_pixel_size()

            context.move_to(
                0, positions[label][0] + (positions[label][1] - label_h) / 2)
            context.show_layout(self.layout)

            if isinstance(self.data[i], list) == False:
                self.data[i] = [self.data[i]]

            for row in self.data[i]:
                bar_x = round((row[0] - start_hour) * factor)
                bar_size = round((row[1] - start_hour) * factor - bar_x)

                g.fill_area(round(self.graph_x + bar_x), positions[label][0],
                            bar_size, positions[label][1], base_color)

        #white grid and scale values
        self.layout.set_width(-1)

        context.set_line_width(1)

        pace = ((end_hour - start_hour) / 3) / 60 * 60
        last_position = positions[keys[-1]]

        grid_color = "#aaa"  # self.get_style().bg[gtk.StateType.NORMAL].to_string()

        for i in range(start_hour + 60, end_hour, pace):
            x = round((i - start_hour) * factor)

            minutes = i % (24 * 60)

            self.layout.set_markup(
                dt.time(minutes / 60, minutes %
                        60).strftime("%-I<small><sup>%M</sup></small>"))
            label_w, label_h = self.layout.get_pixel_size()

            context.move_to(self.graph_x + x - label_w / 2,
                            last_position[0] + last_position[1] + 4)
            g.set_color(label_color)
            context.show_layout(self.layout)

            g.set_color(grid_color)
            g.move_to(round(self.graph_x + x) + 0.5, self.graph_y)
            g.line_to(
                round(self.graph_x + x) + 0.5,
                last_position[0] + last_position[1])

        context.stroke()