From 804f75ffbb8ad9c45202fb3e7e4ca03e8df8f219 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 15:12:33 +0200 Subject: [PATCH 01/72] scroll till the end of last fact In the overview, upon selection, now display completely the last fact, even if tags or description are present. --- src/hamster/widgets/facttree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hamster/widgets/facttree.py b/src/hamster/widgets/facttree.py index 9abec34f3..6febb1380 100644 --- a/src/hamster/widgets/facttree.py +++ b/src/hamster/widgets/facttree.py @@ -395,8 +395,8 @@ def set_current_fact(self, fact): if fact.y < self.y: self.y = fact.y - if (fact.y + 25) > (self.y + self.height): - self.y = fact.y - self.height + 25 + if (fact.y + fact.height) > (self.y + self.height): + self.y = fact.y + fact.height - self.height self.on_scroll() From 431e4225ee06378a52cf150414398810ad66d9ec Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 16:45:18 +0200 Subject: [PATCH 02/72] glade update --- data/edit_activity.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 11fad15fc..8be1bc1d5 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -1,5 +1,5 @@ - + @@ -197,5 +197,8 @@ + + + From 116f578530cc1a76598ed895bd9c1a2452ba0d26 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 17:07:29 +0200 Subject: [PATCH 03/72] ignore *~ backup files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 35d419491..8f68cfb0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# backups (eg. glade) +*~ *.pyc autom4te.cache/ m4/ From 0716eaffc866079f80d05a0813dda7a4f2138c86 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 17:20:38 +0200 Subject: [PATCH 04/72] remove deprecated use_action_appearance --- data/edit_activity.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 8be1bc1d5..48d258157 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -130,7 +130,6 @@ gtk-delete - False True True True @@ -152,7 +151,6 @@ gtk-cancel - False True True True @@ -168,7 +166,6 @@ gtk-save - False True True True From c486590a0333959661a9c47046a9fff7459203c4 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 21:38:00 +0200 Subject: [PATCH 05/72] add separate fields to the window --- data/edit_activity.ui | 243 +++++++++++++++++++++++++++++++++-- src/hamster/edit_activity.py | 2 +- 2 files changed, 230 insertions(+), 15 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 48d258157..acc98cceb 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -14,13 +14,13 @@ - + True False vertical 5 - + True False @@ -88,7 +88,7 @@ - + True False @@ -102,19 +102,48 @@ - + True - True - never - in + False + vertical + + + True + False + start + description + + + + + + + False + False + 0 + + - - 50 + True True - word-char - False + never + in + + + 50 + True + True + word-char + False + + + + True + True + 1 + @@ -124,7 +153,193 @@ - + + True + False + True + + + True + False + vertical + + + True + False + start + start + + + + + + + False + False + 0 + + + + + True + False + True + + + False + True + 1 + + + + + True + True + 0 + + + + + True + False + vertical + + + True + False + start + end + + + + + + + False + False + 0 + + + + + True + False + True + + + False + True + 1 + + + + + False + True + 2 + + + + + False + True + 3 + + + + + True + False + True + + + True + False + vertical + + + True + False + start + activity + + + + + + + False + False + 0 + + + + + True + True + + + False + True + 1 + + + + + True + True + 0 + + + + + True + False + vertical + + + True + False + start + category + + + + + + + False + False + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 2 + + + + + False + True + 4 + + + + True False @@ -143,7 +358,7 @@ - + True False 8 @@ -189,7 +404,7 @@ False True - 3 + 5 diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index b1d1e6532..2595afbb0 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -50,7 +50,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.activity = widgets.ActivityEntry() self.activity.connect("changed", self.on_activity_changed) - self.get_widget("activity_box").add(self.activity) + self.get_widget("command line box").add(self.activity) self.day_start = conf.day_start From b5804f055ddaf0a6d954f40f4d1c4f6f5bd7753e Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 15 Sep 2019 11:31:39 +0200 Subject: [PATCH 06/72] control activity/category sizes --- data/edit_activity.ui | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index acc98cceb..d88f6b85d 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -156,6 +156,7 @@ True False + 4 True @@ -247,10 +248,9 @@ - + True - False - True + True True @@ -279,22 +279,22 @@ True - False + True True 1 - True - True - 0 + True + False True False + 4 vertical @@ -319,16 +319,15 @@ True - False + True True 1 - False - True - 2 + True + False From b59d33c5b4089f2ba2be27d1a6a0a7cdd7e51db0 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 14 Sep 2019 21:49:26 +0200 Subject: [PATCH 07/72] rename activity to cmdline --- src/hamster/edit_activity.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 2595afbb0..f58a7cefe 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -48,9 +48,9 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # None if creating a new fact, instead of editing one self.fact_id = fact_id - self.activity = widgets.ActivityEntry() - self.activity.connect("changed", self.on_activity_changed) - self.get_widget("command line box").add(self.activity) + self.cmdline = widgets.ActivityEntry() + self.cmdline.connect("changed", self.on_cmdline_changed) + self.get_widget("command line box").add(self.cmdline) self.day_start = conf.day_start @@ -63,7 +63,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.save_button = self.get_widget("save_button") - self.activity.grab_focus() + self.cmdline.grab_focus() if fact_id: # editing fact = runtime.storage.get_fact(fact_id) @@ -84,13 +84,13 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): stripped_fact = original_fact.copy() stripped_fact.description = None label = stripped_fact.serialized(prepend_date=False) - with self.activity.handler_block(self.activity.checker): - self.activity.set_text(label) + with self.cmdline.handler_block(self.cmdline.checker): + self.cmdline.set_text(label) time_len = len(label) - len(stripped_fact.serialized_name()) - self.activity.select_region(0, time_len - 1) + self.cmdline.select_region(0, time_len - 1) self.description_buffer.set_text(original_fact.description) - self.activity.original_fact = original_fact + self.cmdline.original_fact = original_fact self._gui.connect_signals(self) self.validate_fields() @@ -125,7 +125,7 @@ def figure_description(self): def localized_fact(self): """Make sure fact has the correct start_time.""" - fact = Fact.parse(self.activity.get_text()) + fact = Fact.parse(self.cmdline.get_text()) if fact.start_time: fact.date = self.date else: @@ -140,7 +140,7 @@ def on_save_button_clicked(self, button): runtime.storage.add_fact(fact) self.close_window() - def on_activity_changed(self, combo): + def on_cmdline_changed(self, combo): self.validate_fields() def update_status(self, status, markup): @@ -240,7 +240,7 @@ def on_close(self, widget, event): self.close_window() def on_window_key_pressed(self, tree, event_key): - popups = self.activity.popup.get_property("visible"); + popups = self.cmdline.popup.get_property("visible"); if (event_key.keyval == gdk.KEY_Escape or \ (event_key.keyval == gdk.KEY_w and event_key.state & gdk.ModifierType.CONTROL_MASK)): From af402c66516b8a3b45eda43300eb0db7d02186c7 Mon Sep 17 00:00:00 2001 From: ederag Date: Mon, 16 Sep 2019 21:08:47 +0200 Subject: [PATCH 08/72] add calendar widgets --- data/edit_activity.ui | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index d88f6b85d..b2e1a5286 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -192,6 +192,20 @@ 1 + + + True + True + 2019 + 8 + 15 + + + False + True + 2 + + True @@ -233,6 +247,20 @@ 1 + + + True + True + 2019 + 8 + 15 + + + False + True + 2 + + False From 27afce36bc75979f55cbef1b95d82af7a11381f1 Mon Sep 17 00:00:00 2001 From: ederag Date: Mon, 16 Sep 2019 21:17:04 +0200 Subject: [PATCH 09/72] cmdline update activity field --- data/edit_activity.ui | 2 +- src/hamster/edit_activity.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index b2e1a5286..8b44e7b29 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -302,7 +302,7 @@ - + True True diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index f58a7cefe..ef68939c0 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -48,6 +48,8 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # None if creating a new fact, instead of editing one self.fact_id = fact_id + self.activity_entry = self.get_widget('activity') + self.cmdline = widgets.ActivityEntry() self.cmdline.connect("changed", self.on_cmdline_changed) self.get_widget("command line box").add(self.cmdline) @@ -142,6 +144,8 @@ def on_save_button_clicked(self, button): def on_cmdline_changed(self, combo): self.validate_fields() + self.fact = Fact.parse(self.cmdline.get_text()) + self.activity_entry.set_text(self.fact.activity) def update_status(self, status, markup): """Set save button sensitivity and tooltip.""" From 04296f6dafabc3f877f98df41f17ca9a32a2ba74 Mon Sep 17 00:00:00 2001 From: ederag Date: Mon, 16 Sep 2019 21:42:26 +0200 Subject: [PATCH 10/72] update category field --- data/edit_activity.ui | 2 +- src/hamster/edit_activity.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 8b44e7b29..04c3b4f2e 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -342,7 +342,7 @@ - + True True diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index ef68939c0..682168c76 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -49,6 +49,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.fact_id = fact_id self.activity_entry = self.get_widget('activity') + self.category_entry = self.get_widget('category') self.cmdline = widgets.ActivityEntry() self.cmdline.connect("changed", self.on_cmdline_changed) @@ -146,6 +147,7 @@ def on_cmdline_changed(self, combo): self.validate_fields() self.fact = Fact.parse(self.cmdline.get_text()) self.activity_entry.set_text(self.fact.activity) + self.category_entry.set_text(self.fact.category) def update_status(self, status, markup): """Set save button sensitivity and tooltip.""" From c3ac659b58ea2050744f1a6dda3179829c0323df Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 17 Sep 2019 19:27:11 +0200 Subject: [PATCH 11/72] update only while typing --- src/hamster/edit_activity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 682168c76..7ec5dbdd5 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -52,7 +52,6 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.category_entry = self.get_widget('category') self.cmdline = widgets.ActivityEntry() - self.cmdline.connect("changed", self.on_cmdline_changed) self.get_widget("command line box").add(self.cmdline) self.day_start = conf.day_start @@ -95,6 +94,10 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): 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._gui.connect_signals(self) self.validate_fields() self.window.show_all() From 88a06d6eff0cfb81721f2f8fd6200e5accb432aa Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 18 Sep 2019 23:49:20 +0200 Subject: [PATCH 12/72] back and forth updates with activity --- src/hamster/edit_activity.py | 66 +++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 7ec5dbdd5..9f564a49c 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -53,6 +53,8 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.cmdline = widgets.ActivityEntry() 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.day_start = conf.day_start @@ -68,35 +70,30 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.cmdline.grab_focus() if fact_id: # editing - fact = runtime.storage.get_fact(fact_id) - self.date = fact.date - original_fact = fact + self.fact = runtime.storage.get_fact(fact_id) + self.date = self.fact.date self.window.set_title(_("Update activity")) else: self.date = hamster_today() self.get_widget("delete_button").set_sensitive(False) if base_fact: # start a clone now. - original_fact = base_fact.copy(start_time=hamster_now(), - end_time=None) + self.fact = base_fact.copy(start_time=hamster_now(), + end_time=None) else: - original_fact = None - - if original_fact: - stripped_fact = original_fact.copy() - stripped_fact.description = None - label = stripped_fact.serialized(prepend_date=False) - with self.cmdline.handler_block(self.cmdline.checker): - self.cmdline.set_text(label) - time_len = len(label) - len(stripped_fact.serialized_name()) - self.cmdline.select_region(0, time_len - 1) - self.description_buffer.set_text(original_fact.description) + self.fact = Fact() + + 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.activity_entry.connect("changed", self.on_activity_changed) self._gui.connect_signals(self) self.validate_fields() @@ -146,11 +143,42 @@ def on_save_button_clicked(self, button): runtime.storage.add_fact(fact) self.close_window() - def on_cmdline_changed(self, combo): - self.validate_fields() - self.fact = Fact.parse(self.cmdline.get_text()) + def on_activity_changed(self, widget): + if not self.master_is_cmdline: + self.fact.activity = self.activity_entry.get_text() + self.update_cmdline() + + def on_cmdline_changed(self, widget): + if self.master_is_cmdline: + self.validate_fields() + previous_description = self.fact.description + fact = Fact.parse(self.cmdline.get_text()) + self.activity_entry.set_text(fact.activity) + self.category_entry.set_text(fact.category) + if not fact.description: + fact.description = previous_description + self.fact = fact + self.update_fields() + + def on_cmdline_focus_in_event(self, widget, event): + self.master_is_cmdline = True + + def on_cmdline_focus_out_event(self, widget, event): + self.master_is_cmdline = False + + def update_cmdline(self, select=None): + stripped_fact = self.fact.copy(description=None) + label = stripped_fact.serialized(prepend_date=False) + with self.cmdline.handler_block(self.cmdline.checker): + self.cmdline.set_text(label) + if select: + time_len = len(label) - len(stripped_fact.serialized_name()) + self.cmdline.select_region(0, time_len - 1) + + def update_fields(self): self.activity_entry.set_text(self.fact.activity) self.category_entry.set_text(self.fact.category) + self.description_buffer.set_text(self.fact.description) def update_status(self, status, markup): """Set save button sensitivity and tooltip.""" From 10224b0981fd1ec3636a2b1355da2ccf330edccf Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 19 Sep 2019 21:30:23 +0200 Subject: [PATCH 13/72] add tags --- data/edit_activity.ui | 34 ++++++++++++++++++++- src/hamster/edit_activity.py | 10 +++++++ src/hamster/lib/__init__.py | 4 ++- src/hamster/widgets/tags.py | 58 +++++++++++++++++++++++++----------- tests/stuff_test.py | 3 ++ 5 files changed, 89 insertions(+), 20 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 04c3b4f2e..2706f2e58 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -365,6 +365,38 @@ 4 + + + True + False + vertical + + + True + False + start + tags + + + + + + + False + False + 0 + + + + + + + + False + True + 5 + + True @@ -431,7 +463,7 @@ False True - 5 + 6 diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 9f564a49c..b3d2f454d 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -65,6 +65,9 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.description_buffer = self.description_box.get_buffer() self.description_buffer.connect("changed", self.on_description_changed) + self.tags_entry = widgets.TagsEntry() + self.get_widget("tags box").add(self.tags_entry) + self.save_button = self.get_widget("save_button") self.cmdline.grab_focus() @@ -94,6 +97,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # not at init time when cmdline might not always be fully parsable. self.cmdline.connect("changed", self.on_cmdline_changed) self.activity_entry.connect("changed", self.on_activity_changed) + self.tags_entry.connect("changed", self.on_tags_changed) self._gui.connect_signals(self) self.validate_fields() @@ -166,6 +170,11 @@ def on_cmdline_focus_in_event(self, widget, event): def on_cmdline_focus_out_event(self, widget, event): self.master_is_cmdline = False + def on_tags_changed(self, widget): + if not self.master_is_cmdline: + self.fact.tags = self.tags_entry.get_tags() + self.update_cmdline() + def update_cmdline(self, select=None): stripped_fact = self.fact.copy(description=None) label = stripped_fact.serialized(prepend_date=False) @@ -179,6 +188,7 @@ def update_fields(self): self.activity_entry.set_text(self.fact.activity) self.category_entry.set_text(self.fact.category) self.description_buffer.set_text(self.fact.description) + self.tags_entry.set_tags(self.fact.tags) def update_status(self, status, markup): """Set save button sensitivity and tooltip.""" diff --git a/src/hamster/lib/__init__.py b/src/hamster/lib/__init__.py index f8e354b9a..cbe8172b5 100644 --- a/src/hamster/lib/__init__.py +++ b/src/hamster/lib/__init__.py @@ -212,7 +212,9 @@ def serialized(self, prepend_date=True): """Return a string fully representing the fact.""" name = self.serialized_name() datetime = self.serialized_time(prepend_date) - return "%s %s" % (datetime, name) + # no need for space if name or datetime is missing + space = " " if name and datetime else "" + return "{}{}{}".format(datetime, space, name) def _set(self, **kwds): """Modify attributes. diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 5a6344ae0..4d5c19eaa 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -18,6 +18,7 @@ # along with Project Hamster. If not, see . from gi.repository import GObject as gobject +from gi.repository import Gdk as gdk from gi.repository import Gtk as gtk from gi.repository import Pango as pango import cairo @@ -33,7 +34,7 @@ class TagsEntry(gtk.Entry): def __init__(self): gtk.Entry.__init__(self) - self.tags = None + self.ac_tags = None # "autocomplete" tags self.filter = None # currently applied filter string self.filter_tags = [] #filtered tags @@ -53,7 +54,9 @@ def __init__(self): self.scroll_box.add(viewport) self.popup.add(self.scroll_box) - self.connect("button-press-event", self._on_button_press_event) + self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "go-down-symbolic") + + self.connect("icon-press", self._on_icon_press) self.connect("key-press-event", self._on_key_press_event) self.connect("key-release-event", self._on_key_release_event) self.connect("focus-out-event", self._on_focus_out_event) @@ -61,7 +64,7 @@ def __init__(self): self._parent_click_watcher = None # bit lame but works self.external_listeners = [ - (runtime.storage, runtime.storage.connect('tags-changed', self.refresh_tags)) + (runtime.storage, runtime.storage.connect('tags-changed', self.refresh_ac_tags)) ] self.show() self.populate_suggestions() @@ -74,8 +77,8 @@ def on_destroy(self, window): self.popup = None - def refresh_tags(self, event): - self.tags = None + def refresh_ac_tags(self, event): + self.ac_tags = None def get_tags(self): # splits the string by comma and filters out blanks @@ -93,8 +96,8 @@ def on_tag_selected(self, tag_box, tag): self.tag_box.selected_tags = tags - self.set_text("%s, " % ", ".join(tags)) - self.set_position(len(self.get_text())) + self.set_tags(tags) + self.update_tagsline(add=True) self.populate_suggestions() self.show_popup() @@ -106,9 +109,8 @@ def on_tag_unselected(self, tag_box, tag): self.tag_box.selected_tags = tags - self.set_text("%s, " % ", ".join(tags)) - self.set_position(len(self.get_text())) - + self.set_tags(tags) + self.update_tagsline(add=True) def hide_popup(self): self.popup.hide() @@ -125,7 +127,7 @@ def show_popup(self): self._parent_click_watcher = self.get_toplevel().connect("button-press-event", self._on_focus_out_event) alloc = self.get_allocation() - x, y = self.get_parent_window().get_origin() + _, x, y = self.get_parent_window().get_origin() self.popup.move(x + alloc.x,y + alloc.y + alloc.height) @@ -134,7 +136,7 @@ def show_popup(self): height = self.tag_box.count_height(w) - self.tag_box.modify_bg(gtk.StateType.NORMAL, "#eee") #self.get_style().base[gtk.StateType.NORMAL]) + #self.tag_box.modify_bg(gtk.StateType.NORMAL, "#eee") #self.get_style().base[gtk.StateType.NORMAL]) self.scroll_box.set_size_request(w, height) self.popup.resize(w, height) @@ -151,7 +153,7 @@ def refresh_activities(self): self.categories = None def populate_suggestions(self): - self.tags = self.tags or [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)] + self.ac_tags = self.ac_tags or [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)] cursor_tag = self.get_cursor_tag() @@ -160,7 +162,8 @@ def populate_suggestions(self): entered_tags = self.get_tags() self.tag_box.selected_tags = entered_tags - self.filter_tags = [tag for tag in self.tags if (tag or "").lower().startswith((self.filter or "").lower())] + self.filter_tags = [tag for tag in self.ac_tags + if (tag or "").lower().startswith((self.filter or "").lower())] self.tag_box.draw(self.filter_tags) @@ -169,9 +172,17 @@ def populate_suggestions(self): def _on_focus_out_event(self, widget, event): self.hide_popup() - def _on_button_press_event(self, button, event): - self.populate_suggestions() - self.show_popup() + def _on_icon_press(self, entry, icon_pos, event): + # toggle popup + if self.popup.get_visible(): + # remove trailing comma is any + self.update_tagsline(add=False) + self.hide_popup() + else: + # add trailing comma + self.update_tagsline(add=True) + self.populate_suggestions() + self.show_popup() def _on_key_release_event(self, entry, event): if (event.keyval in (gdk.KEY_Return, gdk.KEY_KP_Enter)): @@ -219,7 +230,18 @@ def replace_tag(self, old_tag, new_tag): else: cursor = self.get_position() - self.set_text(", ".join(tags)) + self.set_tags(tags) + self.set_position(len(self.get_text())) + + def set_tags(self, tags): + self.tags = tags + self.update_tagsline() + + def update_tagsline(self, add=False): + text = ", ".join(self.tags) + if add and text: + text = "{}, ".format(text) + self.set_text(text) self.set_position(len(self.get_text())) def _on_key_press_event(self, entry, event): diff --git a/tests/stuff_test.py b/tests/stuff_test.py index 7013d2e0d..6bd0de113 100644 --- a/tests/stuff_test.py +++ b/tests/stuff_test.py @@ -169,6 +169,9 @@ def test_spaces(self): # space between category and tag fact2 = Fact.parse("11:00 12:00 BPC-261 - Task title@Project #code") self.assertEqual(fact.serialized(), fact2.serialized()) + # empty fact + fact3 = Fact() + self.assertEqual(fact3.serialized(), "") if __name__ == '__main__': unittest.main() From 02a46e6f211e64461a837da3278385a2d3ac6998 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 21 Sep 2019 21:47:43 +0200 Subject: [PATCH 14/72] try to fix background color does nothing. --- src/hamster/widgets/tags.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 4d5c19eaa..95e8572ed 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -136,7 +136,8 @@ def show_popup(self): height = self.tag_box.count_height(w) - #self.tag_box.modify_bg(gtk.StateType.NORMAL, "#eee") #self.get_style().base[gtk.StateType.NORMAL]) + _, color = gdk.Color.parse("#000") + self.tag_box.modify_bg(gtk.StateType.NORMAL, color) #self.get_style().base[gtk.StateType.NORMAL]) self.scroll_box.set_size_request(w, height) self.popup.resize(w, height) From b55e9e3b7aee79073d606c9b4883ca385cc74572 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 21 Sep 2019 21:48:38 +0200 Subject: [PATCH 15/72] remove background noop These lines had no effect at all. --- src/hamster/widgets/tags.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 95e8572ed..40f131ef4 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -135,10 +135,6 @@ def show_popup(self): height = self.tag_box.count_height(w) - - _, color = gdk.Color.parse("#000") - self.tag_box.modify_bg(gtk.StateType.NORMAL, color) #self.get_style().base[gtk.StateType.NORMAL]) - self.scroll_box.set_size_request(w, height) self.popup.resize(w, height) self.popup.show_all() From 321469bb1aa56be5789d69ebea39ec6d661aa2ca Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 21 Sep 2019 21:59:43 +0200 Subject: [PATCH 16/72] update cmdline from category entry --- src/hamster/edit_activity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index b3d2f454d..4dcf0f292 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -97,6 +97,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # not at init time when cmdline might not always be fully parsable. self.cmdline.connect("changed", self.on_cmdline_changed) 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) @@ -152,6 +153,11 @@ def on_activity_changed(self, widget): self.fact.activity = self.activity_entry.get_text() self.update_cmdline() + def on_category_changed(self, widget): + if not self.master_is_cmdline: + self.fact.category = self.category_entry.get_text() + self.update_cmdline() + def on_cmdline_changed(self, widget): if self.master_is_cmdline: self.validate_fields() From 2987ad6da6cfec5308f32a7f28128e442bfae629 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 22 Sep 2019 10:32:17 +0200 Subject: [PATCH 17/72] remove unused self.day_start conf.day_start is safer (always up to date) --- src/hamster/edit_activity.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 4dcf0f292..b159fa5fa 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -56,8 +56,6 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): 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.day_start = conf.day_start - self.dayline = widgets.DayLine() self._gui.get_object("day_preview").add(self.dayline) From 235ab866b6dae6f9218f50cc8a0bfe794bc2e2fb Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 22 Sep 2019 13:27:49 +0200 Subject: [PATCH 18/72] update date entries --- data/edit_activity.ui | 4 ++-- src/hamster/edit_activity.py | 7 +++++-- src/hamster/widgets/__init__.py | 2 +- src/hamster/widgets/dates.py | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 2706f2e58..c1a00c37c 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -193,7 +193,7 @@ - + True True 2019 @@ -248,7 +248,7 @@ - + True True 2019 diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index b159fa5fa..fec42626d 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -63,6 +63,9 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.description_buffer = self.description_box.get_buffer() self.description_buffer.connect("changed", self.on_description_changed) + self.end_date = widgets.Calendar(widget=self.get_widget("end date")) + self.start_date = widgets.Calendar(widget=self.get_widget("start date")) + self.tags_entry = widgets.TagsEntry() self.get_widget("tags box").add(self.tags_entry) @@ -161,8 +164,6 @@ def on_cmdline_changed(self, widget): self.validate_fields() previous_description = self.fact.description fact = Fact.parse(self.cmdline.get_text()) - self.activity_entry.set_text(fact.activity) - self.category_entry.set_text(fact.category) if not fact.description: fact.description = previous_description self.fact = fact @@ -189,6 +190,8 @@ def update_cmdline(self, select=None): self.cmdline.select_region(0, time_len - 1) def update_fields(self): + self.start_date.date = self.fact.start_time + self.end_date.date = self.fact.end_time self.activity_entry.set_text(self.fact.activity) self.category_entry.set_text(self.fact.category) self.description_buffer.set_text(self.fact.description) diff --git a/src/hamster/widgets/__init__.py b/src/hamster/widgets/__init__.py index 58b7dbef6..86f09d4c3 100644 --- a/src/hamster/widgets/__init__.py +++ b/src/hamster/widgets/__init__.py @@ -29,7 +29,7 @@ from hamster.widgets.tags import Tag, TagBox, TagsEntry from hamster.widgets.reportchooserdialog import ReportChooserDialog from hamster.widgets.facttree import FactTree -from hamster.widgets.dates import RangePick +from hamster.widgets.dates import Calendar, RangePick # handy wrappers diff --git a/src/hamster/widgets/dates.py b/src/hamster/widgets/dates.py index 216b4a7b3..46347bf9e 100644 --- a/src/hamster/widgets/dates.py +++ b/src/hamster/widgets/dates.py @@ -28,6 +28,41 @@ from hamster.lib import stuff from hamster.lib.configuration import load_ui_file + +class Calendar(): + """Python date interface to a Gtk.Calendar. + + widget (Gtk.Calendar): the associated Gtk widget. + """ + def __init__(self, widget): + self.widget = widget + + @property + def date(self): + """Selected day, as datetime.date.""" + year, month, day = self.widget.get_date() + # months start at 0 in Gtk.Calendar and at 1 in python date + month += 1 + return dt.date(year=year, month=month, day=day) if day else None + + @date.setter + def date(self, value): + """Set date. + + value can be a python date or datetime. + """ + if value is None: + # unselect day + self.widget.select_day(0) + else: + year = value.year + # months start at 0 in Gtk.Calendar and at 1 in python date + month = value.month - 1 + day = value.day + self.widget.select_month(month, year) + self.widget.select_day(day) + + class RangePick(gtk.ToggleButton): """ a text entry widget with calendar popup""" __gsignals__ = { From 62ddfe49618754e2a9fcad750106d5ae41330282 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 22 Sep 2019 13:47:28 +0200 Subject: [PATCH 19/72] use TimeInput for start and end times --- data/edit_activity.ui | 18 ++++++++++++------ src/hamster/edit_activity.py | 7 +++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index c1a00c37c..5fbfebe71 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -181,10 +181,13 @@ - + True - False - True + False + vertical + + + False @@ -236,10 +239,13 @@ - + True - False - True + False + vertical + + + False diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index fec42626d..c3ceddff4 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -64,8 +64,15 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.description_buffer.connect("changed", self.on_description_changed) self.end_date = widgets.Calendar(widget=self.get_widget("end date")) + + 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")) + 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) From 51f05abdae5916dca25bc1e0b22a54dfd350d657 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 22 Sep 2019 21:48:54 +0200 Subject: [PATCH 20/72] cmdline update times --- src/hamster/edit_activity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index c3ceddff4..fabcd9970 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -197,6 +197,9 @@ def update_cmdline(self, select=None): self.cmdline.select_region(0, time_len - 1) def update_fields(self): + self.start_time.set_time(self.fact.start_time) + self.end_time.set_time(self.fact.end_time) + self.end_time.set_start_time(self.fact.start_time) self.start_date.date = self.fact.start_time self.end_date.date = self.fact.end_time self.activity_entry.set_text(self.fact.activity) From 8c6c473756f0166d6a9d9c14f2ee23acde37ed0b Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 24 Sep 2019 00:11:19 +0200 Subject: [PATCH 21/72] fix start_time suggestions --- src/hamster/lib/stuff.py | 7 ++++- src/hamster/widgets/timeinput.py | 45 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/hamster/lib/stuff.py b/src/hamster/lib/stuff.py index 02b287d8a..20c9d7049 100644 --- a/src/hamster/lib/stuff.py +++ b/src/hamster/lib/stuff.py @@ -61,7 +61,12 @@ def datetime_to_hamsterday(civil_date_time): def hamster_now(): # current datetime truncated to the minute - return dt.datetime.now().replace(second=0, microsecond=0) + return hamster_round(dt.datetime.now()) + + +def hamster_round(time): + """Round time or datetime.""" + return time.replace(second=0, microsecond=0) def hamster_today(): diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index deefb7e51..930d736ea 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -25,7 +25,7 @@ from gi.repository import Gtk as gtk from gi.repository import GObject as gobject -from hamster.lib.stuff import format_duration +from hamster.lib.stuff import format_duration, hamster_round class TimeInput(gtk.Entry): __gsignals__ = { @@ -87,11 +87,12 @@ def set_start_time(self, start_time): will start from start time and duration will be displayed in brackets """ - start_time = start_time or dt.time() - if isinstance(start_time, dt.time): # ensure that we operate with time - self.start_time = dt.time(start_time.hour, start_time.minute) - else: - self.start_time = dt.time(start_time.time().hour, start_time.time().minute) + if start_time is not None: + start_time = hamster_round(start_time) + if isinstance(start_time, dt.datetime): + # timeinput works on time only + start_time = start_time.time() + self.start_time = start_time def _on_text_changed(self, widget): self.news = True @@ -166,41 +167,39 @@ 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) - # will be going either 24 hours or from start time to start time + 12 hours - start_time = dt.datetime.combine(dt.date.today(), self.start_time) # we will be adding things - i_time = start_time # we will be adding things + # we will be adding things, need datetime + i_time_0 = dt.datetime.combine(dt.date.today(), self.start_time or dt.time()) - if self.start_time: - end_time = i_time + dt.timedelta(hours = 12) - i_time += dt.timedelta(minutes = 15) + if self.start_time is None: + # full 24 hours + i_time = i_time_0 + end_time = i_time_0 + dt.timedelta(days = 1) else: - end_time = i_time + dt.timedelta(days = 1) - + # from start time to start time + 12 hours + i_time = i_time_0 + dt.timedelta(minutes = 15) + end_time = i_time_0 + dt.timedelta(hours = 12) focus_time = dt.datetime.combine(dt.date.today(), self.figure_time(self.get_text())) hours = gtk.ListStore(gobject.TYPE_STRING) - i, focus_row = 0, None while i_time < end_time: row_text = self._format_time(i_time) - if self.start_time: - delta = (i_time - start_time).seconds / 60 - delta_text = format_duration(delta) + if self.start_time is not None: + delta_text = format_duration(i_time - i_time_0) row_text += " (%s)" % delta_text hours.append([row_text]) - if focus_time and i_time <= focus_time <= i_time + \ dt.timedelta(minutes = 30): focus_row = i - if self.start_time: - i_time += dt.timedelta(minutes = 15) - else: + if self.start_time is None: i_time += dt.timedelta(minutes = 30) + else: + i_time += dt.timedelta(minutes = 15) i += 1 @@ -216,7 +215,7 @@ def show_popup(self): #move popup under the widget alloc = self.get_allocation() w = alloc.width - if self.start_time: + if self.start_time is not None: w = w * 2 self.time_tree.set_size_request(w, alloc.height * 5) From e30410b1b54aa65f09137568a3d1bbb57c6060ca Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 24 Sep 2019 22:09:42 +0200 Subject: [PATCH 22/72] accept None --- src/hamster/lib/stuff.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hamster/lib/stuff.py b/src/hamster/lib/stuff.py index 20c9d7049..0b7474464 100644 --- a/src/hamster/lib/stuff.py +++ b/src/hamster/lib/stuff.py @@ -66,7 +66,10 @@ def hamster_now(): def hamster_round(time): """Round time or datetime.""" - return time.replace(second=0, microsecond=0) + if time is None: + return None + else: + return time.replace(second=0, microsecond=0) def hamster_today(): From 045e866b3bf01d00b8503e81489a6e2ea0e5c2f8 Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 24 Sep 2019 22:13:24 +0200 Subject: [PATCH 23/72] handle end_time=None --- src/hamster/widgets/timeinput.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 930d736ea..ca6da4bcf 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -74,11 +74,7 @@ def on_destroy(self, window): self.popup = None def set_time(self, time): - time = time or dt.time() - if isinstance(time, dt.time): # ensure that we operate with time and strip seconds - self.time = dt.time(time.hour, time.minute) - else: - self.time = dt.time(time.time().hour, time.time().minute) + self.time = hamster_round(time) self.set_text(self._format_time(time)) @@ -87,11 +83,10 @@ def set_start_time(self, start_time): will start from start time and duration will be displayed in brackets """ - if start_time is not None: - start_time = hamster_round(start_time) - if isinstance(start_time, dt.datetime): - # timeinput works on time only - start_time = start_time.time() + start_time = hamster_round(start_time) + if isinstance(start_time, dt.datetime): + # timeinput works on time only + start_time = start_time.time() self.start_time = start_time def _on_text_changed(self, widget): @@ -99,7 +94,7 @@ def _on_text_changed(self, widget): def figure_time(self, str_time): if not str_time: - return self.time + return None # strip everything non-numeric and consider hours to be first number # and minutes - second number @@ -117,7 +112,7 @@ def figure_time(self, str_time): minutes = int(numbers[1]) if (hours is None or minutes is None) or hours > 24 or minutes > 60: - return self.time #no can do + return None # no can do return dt.time(hours, minutes) @@ -179,7 +174,8 @@ def show_popup(self): i_time = i_time_0 + dt.timedelta(minutes = 15) end_time = i_time_0 + dt.timedelta(hours = 12) - focus_time = dt.datetime.combine(dt.date.today(), self.figure_time(self.get_text())) + time = self.figure_time(self.get_text()) + focus_time = dt.datetime.combine(dt.date.today(), time) if time else None hours = gtk.ListStore(gobject.TYPE_STRING) i, focus_row = 0, None From b176cc3aeb8817f2889c48ab8eaa3d512d9701d1 Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 26 Sep 2019 18:52:24 +0200 Subject: [PATCH 24/72] control date from dayline --- src/hamster/edit_activity.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index fabcd9970..b05ec819a 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -116,12 +116,10 @@ def on_description_changed(self, text): self.validate_fields() def on_prev_day_clicked(self, button): - self.date = self.date - dt.timedelta(days=1) - self.validate_fields() + self.increment_date(-1) def on_next_day_clicked(self, button): - self.date = self.date + dt.timedelta(days=1) - self.validate_fields() + self.increment_date(+1) def draw_preview(self, start_time, end_time=None): day_facts = runtime.storage.get_facts(self.date) @@ -131,6 +129,15 @@ def get_widget(self, name): """ skip one variable (huh) """ return self._gui.get_object(name) + def increment_date(self, days): + delta = dt.timedelta(days=days) + self.date += delta + if self.fact.start_time: + self.fact.start_time += delta + if self.fact.end_time: + self.fact.end_time += delta + self.update_fields() + def show(self): self.window.show() @@ -139,15 +146,6 @@ def figure_description(self): description = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0) return description.strip() - def localized_fact(self): - """Make sure fact has the correct start_time.""" - fact = Fact.parse(self.cmdline.get_text()) - if fact.start_time: - fact.date = self.date - else: - fact.start_time = hamster_now() - return fact - def on_save_button_clicked(self, button): fact = self.validate_fields() if self.fact_id: @@ -168,9 +166,8 @@ def on_category_changed(self, widget): def on_cmdline_changed(self, widget): if self.master_is_cmdline: - self.validate_fields() previous_description = self.fact.description - fact = Fact.parse(self.cmdline.get_text()) + fact = Fact.parse(self.cmdline.get_text(), date=self.date) if not fact.description: fact.description = previous_description self.fact = fact @@ -206,6 +203,7 @@ def update_fields(self): self.category_entry.set_text(self.fact.category) self.description_buffer.set_text(self.fact.description) self.tags_entry.set_tags(self.fact.tags) + self.validate_fields() def update_status(self, status, markup): """Set save button sensitivity and tooltip.""" @@ -230,7 +228,7 @@ def validate_fields(self): Return the consolidated fact if successful, or None. """ - fact = self.localized_fact() + fact = self.fact now = hamster_now() self.get_widget("button-next-day").set_sensitive(self.date < now.date()) From 94e4416376c8f6e5add698a471db0707bdb5eac3 Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 26 Sep 2019 21:02:58 +0200 Subject: [PATCH 25/72] comments --- src/hamster/edit_activity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index b05ec819a..207454205 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -185,6 +185,7 @@ def on_tags_changed(self, widget): self.update_cmdline() def update_cmdline(self, select=None): + """Update the cmdline entry content.""" stripped_fact = self.fact.copy(description=None) label = stripped_fact.serialized(prepend_date=False) with self.cmdline.handler_block(self.cmdline.checker): @@ -194,6 +195,7 @@ def update_cmdline(self, select=None): self.cmdline.select_region(0, time_len - 1) def update_fields(self): + """Update gui fields content.""" self.start_time.set_time(self.fact.start_time) self.end_time.set_time(self.fact.end_time) self.end_time.set_start_time(self.fact.start_time) From 88234f353286e783074655e980cd02429c2d878f Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 26 Sep 2019 21:51:39 +0200 Subject: [PATCH 26/72] forward failing attribute requests to widget --- src/hamster/widgets/dates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hamster/widgets/dates.py b/src/hamster/widgets/dates.py index 46347bf9e..5e948d366 100644 --- a/src/hamster/widgets/dates.py +++ b/src/hamster/widgets/dates.py @@ -62,6 +62,9 @@ def date(self, value): self.widget.select_month(month, year) self.widget.select_day(day) + def __getattr__(self, name): + return getattr(self.widget, name) + class RangePick(gtk.ToggleButton): """ a text entry widget with calendar popup""" From 6644b6836f5766ee1241a8b7af1b74ea80e79da7 Mon Sep 17 00:00:00 2001 From: ederag Date: Fri, 27 Sep 2019 09:39:05 +0200 Subject: [PATCH 27/72] start new fact now in gui --- src/hamster/edit_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 207454205..a2aeec3f9 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -92,7 +92,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.fact = base_fact.copy(start_time=hamster_now(), end_time=None) else: - self.fact = Fact() + self.fact = Fact(start_time=hamster_now()) original_fact = self.fact From f52b4294e2256a94de22b478ce9df250d869a4de Mon Sep 17 00:00:00 2001 From: ederag Date: Fri, 27 Sep 2019 09:50:57 +0200 Subject: [PATCH 28/72] update timeline from start_date --- src/hamster/edit_activity.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index a2aeec3f9..918d42c54 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -104,6 +104,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # 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.start_date.connect("day-selected", self.on_start_date_changed) 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) @@ -179,6 +180,20 @@ def on_cmdline_focus_in_event(self, widget, event): def on_cmdline_focus_out_event(self, widget, event): self.master_is_cmdline = False + def on_start_date_changed(self, widget): + if not self.master_is_cmdline: + previous_date = self.fact.start_time.date() + new_date = self.start_date.date + delta = new_date - previous_date + self.fact.start_time += delta + if self.fact.end_time: + # preserve fact duration + self.fact.end_time += delta + self.end_date.date = self.fact.end_time + self.date = self.fact.date + self.validate_fields() + self.update_cmdline() + def on_tags_changed(self, widget): if not self.master_is_cmdline: self.fact.tags = self.tags_entry.get_tags() From f6428085f92b85ad52a7b74948d2a0dc0e6c5c66 Mon Sep 17 00:00:00 2001 From: ederag Date: Fri, 27 Sep 2019 19:30:49 +0200 Subject: [PATCH 29/72] update end_date from gui entry --- src/hamster/edit_activity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 918d42c54..03a95e487 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -105,6 +105,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # not at init time when cmdline might not always be fully parsable. self.cmdline.connect("changed", self.on_cmdline_changed) self.start_date.connect("day-selected", self.on_start_date_changed) + self.end_date.connect("day-selected", self.on_end_date_changed) 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) @@ -180,6 +181,13 @@ def on_cmdline_focus_in_event(self, widget, event): def on_cmdline_focus_out_event(self, widget, event): self.master_is_cmdline = False + def on_end_date_changed(self, widget): + if self.fact.end_time and not self.master_is_cmdline: + time = self.fact.end_time.time() + self.fact.end_time = dt.datetime.combine(self.end_date.date, time) + self.validate_fields() + self.update_cmdline() + def on_start_date_changed(self, widget): if not self.master_is_cmdline: previous_date = self.fact.start_time.date() From dbd337bd4af0a4d213ced5565451871057231c5d Mon Sep 17 00:00:00 2001 From: ederag Date: Fri, 27 Sep 2019 19:55:09 +0200 Subject: [PATCH 30/72] change TimeInput.time to @property --- src/hamster/edit_activity.py | 4 ++-- src/hamster/preferences.py | 4 ++-- src/hamster/widgets/timeinput.py | 23 ++++++++++++----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 03a95e487..78c17d709 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -219,8 +219,8 @@ def update_cmdline(self, select=None): def update_fields(self): """Update gui fields content.""" - self.start_time.set_time(self.fact.start_time) - self.end_time.set_time(self.fact.end_time) + self.start_time.time = self.fact.start_time + self.end_time.time = self.fact.end_time self.end_time.set_start_time(self.fact.start_time) self.start_date.date = self.fact.start_time self.end_date.date = self.fact.end_time diff --git a/src/hamster/preferences.py b/src/hamster/preferences.py index adfe0d918..38ade3803 100755 --- a/src/hamster/preferences.py +++ b/src/hamster/preferences.py @@ -205,7 +205,7 @@ def load_config(self, *args): self.get_widget("notify_on_idle").set_active(conf.get("notify_on_idle")) self.get_widget("notify_on_idle").set_sensitive(conf.get("notify_interval") <=120) - self.day_start.set_time(conf.day_start) + self.day_start.time = conf.day_start self.tags = [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)] self.get_widget("autocomplete_tags").set_text(", ".join(self.tags)) @@ -587,7 +587,7 @@ def on_notify_interval_value_changed(self, scale): self.get_widget("notify_on_idle").set_sensitive(value <= 120) def on_day_start_changed(self, widget): - day_start = self.day_start.get_time() + day_start = self.day_start.time if day_start is None: return diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index ca6da4bcf..54d541124 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -38,7 +38,7 @@ def __init__(self, time = None, start_time = None): self.news = False self.set_width_chars(7) #7 is like 11:24pm - self.set_time(time) + self.time = time self.set_start_time(start_time) self.popup = gtk.Window(type = gtk.WindowType.POPUP) @@ -69,15 +69,21 @@ def __init__(self, time = None, start_time = None): self.show() self.connect("destroy", self.on_destroy) + @property + def time(self): + """Displayed time (as datetime.time, or None).""" + return self.figure_time(self.get_text()) + + @time.setter + def time(self, value): + time = hamster_round(value) + self.set_text(self._format_time(time)) + return time + def on_destroy(self, window): self.popup.destroy() self.popup = None - def set_time(self, time): - self.time = hamster_round(time) - - self.set_text(self._format_time(time)) - def set_start_time(self, start_time): """ set the start time. when start time is set, drop down list will start from start time and duration will be displayed in @@ -129,11 +135,6 @@ def _select_time(self, time_text): self.emit("time-entered") self.news = False - def get_time(self): - self.time = self.figure_time(self.get_text()) - self.set_text(self._format_time(self.time)) - return self.time - def _format_time(self, time): if time is None: return "" From 0f2fd46a7310d133ae9edd1d447b73313db05312 Mon Sep 17 00:00:00 2001 From: ederag Date: Fri, 27 Sep 2019 20:17:44 +0200 Subject: [PATCH 31/72] update start_time from gui entry --- src/hamster/edit_activity.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 78c17d709..5d045ae93 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -104,6 +104,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # 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.start_time.connect("changed", self.on_start_time_changed) self.start_date.connect("day-selected", self.on_start_date_changed) self.end_date.connect("day-selected", self.on_end_date_changed) self.activity_entry.connect("changed", self.on_activity_changed) @@ -202,6 +203,18 @@ def on_start_date_changed(self, widget): self.validate_fields() self.update_cmdline() + def on_start_time_changed(self, widget): + if not self.master_is_cmdline: + previous_time = self.fact.start_time.time() + new_time = self.start_time.time + if new_time: + date = self.fact.start_time.date() + self.fact.start_time = dt.datetime.combine(date, new_time) + else: + self.fact.start_time = None + self.validate_fields() + self.update_cmdline() + def on_tags_changed(self, widget): if not self.master_is_cmdline: self.fact.tags = self.tags_entry.get_tags() From f49b271191cff2474d2ae1d742f59854e0416dfb Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 28 Sep 2019 00:26:23 +0200 Subject: [PATCH 32/72] handle no start-time on cmdline --- src/hamster/edit_activity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 5d045ae93..be9d648c3 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -173,6 +173,8 @@ def on_cmdline_changed(self, widget): fact = Fact.parse(self.cmdline.get_text(), date=self.date) if not fact.description: fact.description = previous_description + if fact.start_time is None: + fact.start_time = hamster_now() self.fact = fact self.update_fields() From 949e841ec82c01172d6d37981c6ef9b903210d67 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 28 Sep 2019 10:39:26 +0200 Subject: [PATCH 33/72] fix time pre-selection --- src/hamster/edit_activity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index be9d648c3..081a02cda 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -229,8 +229,8 @@ def update_cmdline(self, select=None): with self.cmdline.handler_block(self.cmdline.checker): self.cmdline.set_text(label) if select: - time_len = len(label) - len(stripped_fact.serialized_name()) - self.cmdline.select_region(0, time_len - 1) + time_str = stripped_fact.serialized_time(prepend_date=False) + self.cmdline.select_region(0, len(time_str)) def update_fields(self): """Update gui fields content.""" From b6a3c0d1e8ec35f0633aecbd370251756ea97fb3 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 28 Sep 2019 22:11:46 +0200 Subject: [PATCH 34/72] use start_time.date() if available --- src/hamster/widgets/timeinput.py | 40 +++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 54d541124..55a0929b2 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -41,6 +41,7 @@ def __init__(self, time = None, start_time = None): self.time = time self.set_start_time(start_time) + self.popup = gtk.Window(type = gtk.WindowType.POPUP) time_box = gtk.ScrolledWindow() time_box.set_policy(gtk.PolicyType.NEVER, gtk.PolicyType.ALWAYS) @@ -71,8 +72,28 @@ def __init__(self, time = None, start_time = None): @property def time(self): - """Displayed time (as datetime.time, or None).""" - return self.figure_time(self.get_text()) + """Displayed time. + + None, + or time type, + or datetime if start_time() was given a datetime. + """ + time = self.figure_time(self.get_text()) + if self.start_date and time: + # recombine (since self.start_time contains only the time part) + start = dt.datetime.combine(self.start_date, self.start_time) + new = dt.datetime.combine(self.start_date, time) + if new < start: + # a bit hackish, valid only because + # duration can not be negative if start_time was given, + # and we accept that it can not exceed 24h. + # For longer durations, + # date will have to be changed subsequently. + return new + dt.timedelta(days=1) + else: + return new + else: + return time @time.setter def time(self, value): @@ -85,14 +106,20 @@ def on_destroy(self, window): self.popup = None def set_start_time(self, start_time): - """ set the start time. when start time is set, drop down list - will start from start time and duration will be displayed in - brackets + """ Set the start time. + + When start time is set, drop down list will start from start time, + and duration will be displayed in brackets. + + self.time will have the same type as start_time. """ start_time = hamster_round(start_time) if isinstance(start_time, dt.datetime): + self.start_date = start_time.date() # timeinput works on time only start_time = start_time.time() + else: + self.start_date = None self.start_time = start_time def _on_text_changed(self, widget): @@ -164,7 +191,8 @@ def show_popup(self): 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(dt.date.today(), self.start_time or dt.time()) + 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 From 80a70ae5058f49968e4659408f8ebad0768db58e Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 28 Sep 2019 22:12:17 +0200 Subject: [PATCH 35/72] update end_time from gui entry --- src/hamster/edit_activity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 081a02cda..b8f2a8f28 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -106,6 +106,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.cmdline.connect("changed", self.on_cmdline_changed) self.start_time.connect("changed", self.on_start_time_changed) self.start_date.connect("day-selected", self.on_start_date_changed) + self.end_time.connect("changed", self.on_end_time_changed) self.end_date.connect("day-selected", self.on_end_date_changed) self.activity_entry.connect("changed", self.on_activity_changed) self.category_entry.connect("changed", self.on_category_changed) @@ -191,6 +192,16 @@ def on_end_date_changed(self, widget): self.validate_fields() self.update_cmdline() + def on_end_time_changed(self, widget): + if not self.master_is_cmdline: + # self.end_time.start_time() was given a datetime, + # so self.end_time.time is a datetime too. + end = self.end_time.time + self.fact.end_time = end + self.end_date.date = end.date() if end else None + self.validate_fields() + self.update_cmdline() + def on_start_date_changed(self, widget): if not self.master_is_cmdline: previous_date = self.fact.start_time.date() From be7330543caf05ae221cd9e43f9ac4c3903e6be6 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 28 Sep 2019 22:24:05 +0200 Subject: [PATCH 36/72] update activity and category from gui entries --- src/hamster/edit_activity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index b8f2a8f28..a437969b1 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -161,11 +161,13 @@ def on_save_button_clicked(self, button): def on_activity_changed(self, widget): if not self.master_is_cmdline: self.fact.activity = self.activity_entry.get_text() + self.validate_fields() self.update_cmdline() def on_category_changed(self, widget): if not self.master_is_cmdline: self.fact.category = self.category_entry.get_text() + self.validate_fields() self.update_cmdline() def on_cmdline_changed(self, widget): From 147a9bc10cd33bedf9d6afcc5d005334b71d5abd Mon Sep 17 00:00:00 2001 From: ederag Date: Mon, 30 Sep 2019 17:13:09 +0200 Subject: [PATCH 37/72] rename ActivityEntry to CmdLineEntry --- src/hamster/edit_activity.py | 2 +- src/hamster/widgets/__init__.py | 2 +- src/hamster/widgets/activityentry.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index a437969b1..f9231185c 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -51,7 +51,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.activity_entry = self.get_widget('activity') self.category_entry = self.get_widget('category') - self.cmdline = widgets.ActivityEntry() + 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) diff --git a/src/hamster/widgets/__init__.py b/src/hamster/widgets/__init__.py index 86f09d4c3..b90ee3b8a 100644 --- a/src/hamster/widgets/__init__.py +++ b/src/hamster/widgets/__init__.py @@ -23,7 +23,7 @@ from gi.repository import Pango as pango # import our children -from hamster.widgets.activityentry import ActivityEntry +from hamster.widgets.activityentry import CmdLineEntry from hamster.widgets.timeinput import TimeInput from hamster.widgets.dayline import DayLine from hamster.widgets.tags import Tag, TagBox, TagsEntry diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 9fcc81a02..d2c27a7a9 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -199,7 +199,7 @@ def on_enter_frame(self, scene, context): -class ActivityEntry(gtk.Entry): +class CmdLineEntry(gtk.Entry): def __init__(self, updating=True, **kwargs): gtk.Entry.__init__(self) From 2c1d57d8a0bfcc2e2336f12fd73a1cc953446e47 Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 1 Oct 2019 00:09:56 +0200 Subject: [PATCH 38/72] add category completion --- data/edit_activity.ui | 5 +++ src/hamster/edit_activity.py | 2 +- src/hamster/widgets/__init__.py | 2 +- src/hamster/widgets/activityentry.py | 48 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 5fbfebe71..e98fad812 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -2,6 +2,9 @@ + + 0 + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -351,6 +354,8 @@ True True + gtk-clear + category completion True diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index f9231185c..10612847f 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -49,7 +49,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.fact_id = fact_id self.activity_entry = self.get_widget('activity') - self.category_entry = self.get_widget('category') + self.category_entry = widgets.CategoryEntry(widget=self.get_widget('category')) self.cmdline = widgets.CmdLineEntry() self.get_widget("command line box").add(self.cmdline) diff --git a/src/hamster/widgets/__init__.py b/src/hamster/widgets/__init__.py index b90ee3b8a..7c08d3ca7 100644 --- a/src/hamster/widgets/__init__.py +++ b/src/hamster/widgets/__init__.py @@ -23,7 +23,7 @@ from gi.repository import Pango as pango # import our children -from hamster.widgets.activityentry import CmdLineEntry +from hamster.widgets.activityentry import CategoryEntry, CmdLineEntry from hamster.widgets.timeinput import TimeInput from hamster.widgets.dayline import DayLine from hamster.widgets.tags import Tag, TagBox, TagsEntry diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index d2c27a7a9..294f5684b 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -39,6 +39,7 @@ from hamster.lib import Fact, looks_like_time from hamster.lib import stuff from hamster.lib import graphics +from hamster.lib.configuration import runtime def extract_search(text): @@ -462,3 +463,50 @@ def show_suggestions(self, text): self.popup.move(x, y) self.popup.resize(entry_alloc.width, tree_h) self.popup.show_all() + + +class CategoryEntry(): + """Category entry widget.""" + def __init__(self, widget=None, **kwds): + # widget and completion are already defined + # e.g. in the glade edit_activity.ui file + self.widget = widget + if not self.widget: + self.widget = gtk.Entry(**kwds) + + self.completion = self.widget.get_completion() + if not self.completion: + self.completion = gtk.EntryCompletion() + self.widget.set_completion(self.completion) + + self.model = gtk.ListStore(str) + self.completion.set_model(self.model) + self.completion.set_text_column(0) + self.completion.set_match_func(self.match_func, None) + + self.widget.connect("icon-release", self.on_icon_release) + self.widget.connect("focus-in-event", self.on_focus_in_event) + + def match_func(self, completion, key, iter, *user_data): + if not key.strip(): + # show all keys if entry is empty + return True + else: + # return if the entered string is anywhere in the first column data + return key.strip() in self.model.get_value(iter, 0) + + def on_focus_in_event(self, widget, event): + self.populate_completions() + + def on_icon_release(self, entry, icon_pos, event): + self.widget.grab_focus() + self.widget.set_text("") + self.emit("changed") + + def populate_completions(self): + self.model.clear() + for category in runtime.storage.get_categories(): + self.model.append([category['name']]) + + def __getattr__(self, name): + return getattr(self.widget, name) From e5a1bbfc288ef4151f25cd7878db2ad197710642 Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 2 Oct 2019 00:16:02 +0200 Subject: [PATCH 39/72] activity@category completion TreeStore did not work due to a Gtk limitation: https://gitlab.gnome.org/GNOME/gtk/issues/1541 --- data/edit_activity.ui | 5 ++ src/hamster/edit_activity.py | 3 +- src/hamster/widgets/__init__.py | 6 ++- src/hamster/widgets/activityentry.py | 76 ++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index e98fad812..fa71d44aa 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -2,6 +2,9 @@ + + 0 + 0 @@ -314,6 +317,8 @@ True True + gtk-clear + activity completion True diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 10612847f..659e00d33 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -48,8 +48,9 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # None if creating a new fact, instead of editing one self.fact_id = fact_id - self.activity_entry = self.get_widget('activity') 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) diff --git a/src/hamster/widgets/__init__.py b/src/hamster/widgets/__init__.py index 7c08d3ca7..6ec4d49b4 100644 --- a/src/hamster/widgets/__init__.py +++ b/src/hamster/widgets/__init__.py @@ -23,7 +23,11 @@ from gi.repository import Pango as pango # import our children -from hamster.widgets.activityentry import CategoryEntry, CmdLineEntry +from hamster.widgets.activityentry import ( + ActivityEntry, + CategoryEntry, + CmdLineEntry, + ) from hamster.widgets.timeinput import TimeInput from hamster.widgets.dayline import DayLine from hamster.widgets.tags import Tag, TagBox, TagsEntry diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 294f5684b..144e1169d 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -465,6 +465,82 @@ def show_suggestions(self, text): self.popup.show_all() +class ActivityEntry(): + """Category entry widget.""" + def __init__(self, widget=None, category_widget=None, **kwds): + # widget and completion are already defined + # e.g. in the glade edit_activity.ui file + self.widget = widget + if not self.widget: + self.widget = gtk.Entry(**kwds) + + self.category_widget = category_widget + + self.completion = self.widget.get_completion() + if not self.completion: + self.completion = gtk.EntryCompletion() + self.widget.set_completion(self.completion) + + # activity, category, text to display + self.activity_column = 0 + self.category_column = 1 + self.text_column = 2 + self.model = gtk.ListStore(str, str, str) + self.completion.set_model(self.model) + self.completion.set_text_column(2) + self.completion.set_match_func(self.match_func, None) + + self.connect("icon-release", self.on_icon_release) + self.connect("focus-in-event", self.on_focus_in_event) + self.completion.connect('match-selected', self.on_match_selected) + + def match_func(self, completion, key, iter, *user_data): + if not key.strip(): + # show all keys if entry is empty + return True + else: + # return if the entered string is anywhere in the first column data + stripped_key = key.strip() + activities = self.model.get_value(iter, self.activity_column).lower() + categories = self.model.get_value(iter, self.category_column).lower() + key_in_activity = stripped_key in activities + key_in_category = stripped_key in categories + return key_in_activity or key_in_category + + def on_focus_in_event(self, widget, event): + self.populate_completions() + + def on_icon_release(self, entry, icon_pos, event): + self.grab_focus() + self.set_text("") + self.emit("changed") + + def on_match_selected(self, entry, model, iter): + activity_name = model[iter][self.activity_column] + category_name = model[iter][self.category_column] + combined = model[iter][self.text_column] + if self.category_widget: + self.set_text(activity_name) + self.category_widget.set_text(category_name) + else: + self.set_text(combined) + return True # prevent the standard callback from overwriting text + + def populate_completions(self): + self.model.clear() + for category in runtime.storage.get_categories(): + category_name = category['name'] + category_id = category['id'] + c_iter = self.model.append(["", category_name, "@{}".format(category_name)]) + for activity in runtime.storage.get_category_activities(category_id): + activity_name = activity["name"] + text = "{}@{}".format(activity_name, category_name) + self.model.append([activity_name, category_name, text]) + + def __getattr__(self, name): + return getattr(self.widget, name) + + class CategoryEntry(): """Category entry widget.""" def __init__(self, widget=None, **kwds): From 61b7adc20586727d207a98270891ab565e70e38c Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 2 Oct 2019 21:40:26 +0200 Subject: [PATCH 40/72] fix description update from cmdline Box description is always up-to-date, except when the last character of a description is removed. Could be solved by discriminating again between empty string and None. --- src/hamster/edit_activity.py | 42 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 659e00d33..23c8f3838 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -117,9 +117,6 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.validate_fields() self.window.show_all() - def on_description_changed(self, text): - self.validate_fields() - def on_prev_day_clicked(self, button): self.increment_date(-1) @@ -173,12 +170,14 @@ def on_category_changed(self, widget): def on_cmdline_changed(self, widget): if self.master_is_cmdline: - previous_description = self.fact.description + previous_cmdline_fact = self.cmdline_fact fact = Fact.parse(self.cmdline.get_text(), date=self.date) - if not fact.description: - fact.description = previous_description if fact.start_time is None: fact.start_time = hamster_now() + self.cmdline_fact = fact.copy() + if fact.description == previous_cmdline_fact.description: + # no change to description here, keep the main one + fact.description = self.fact.description self.fact = fact self.update_fields() @@ -188,6 +187,12 @@ def on_cmdline_focus_in_event(self, widget, event): def on_cmdline_focus_out_event(self, widget, event): self.master_is_cmdline = False + def on_description_changed(self, text): + if not self.master_is_cmdline: + self.fact.description = self.figure_description() + self.validate_fields() + self.update_cmdline() + def on_end_date_changed(self, widget): if self.fact.end_time and not self.master_is_cmdline: time = self.fact.end_time.time() @@ -238,12 +243,12 @@ def on_tags_changed(self, widget): def update_cmdline(self, select=None): """Update the cmdline entry content.""" - stripped_fact = self.fact.copy(description=None) - label = stripped_fact.serialized(prepend_date=False) + self.cmdline_fact = self.fact.copy(description=None) + label = self.cmdline_fact.serialized(prepend_date=False) with self.cmdline.handler_block(self.cmdline.checker): self.cmdline.set_text(label) if select: - time_str = stripped_fact.serialized_time(prepend_date=False) + time_str = self.cmdline_fact.serialized_time(prepend_date=False) self.cmdline.select_region(0, len(time_str)) def update_fields(self): @@ -303,25 +308,6 @@ def validate_fields(self): self.update_status(status="wrong", markup="Missing activity") return None - description_box_content = self.figure_description() - if fact.description and description_box_content: - escaped_cmd = escape_pango(fact.description) - escaped_box = escape_pango(description_box_content) - markup = dedent("""\ - Duplicate description - command line: - '{}' - description box: - '''{}''' - """).format(escaped_cmd, escaped_box) - self.update_status(status="wrong", - markup=markup) - return None - - # Good to go, no description ambiguity - if description_box_content: - fact.description = description_box_content - if (fact.delta < dt.timedelta(0)) and fact.end_time: fact.end_time += dt.timedelta(days=1) markup = dedent("""\ From e70fd54d7b003d17048574ce374cde5ba08b989d Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 00:17:27 +0200 Subject: [PATCH 41/72] remove @category lines They were more disturbing than helpful. There is already a specific category entry. --- src/hamster/widgets/activityentry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 144e1169d..693c38837 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -531,7 +531,6 @@ def populate_completions(self): for category in runtime.storage.get_categories(): category_name = category['name'] category_id = category['id'] - c_iter = self.model.append(["", category_name, "@{}".format(category_name)]) for activity in runtime.storage.get_category_activities(category_id): activity_name = activity["name"] text = "{}@{}".format(activity_name, category_name) From 116b3845c773dd050af6f00185c4878d848b333e Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 12:15:31 +0200 Subject: [PATCH 42/72] use self.fact directly on save self.facts has already been validated. --- src/hamster/edit_activity.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 23c8f3838..0858e8750 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -149,11 +149,10 @@ def figure_description(self): return description.strip() def on_save_button_clicked(self, button): - fact = self.validate_fields() if self.fact_id: - runtime.storage.update_fact(self.fact_id, fact) + runtime.storage.update_fact(self.fact_id, self.fact) else: - runtime.storage.add_fact(fact) + runtime.storage.add_fact(self.fact) self.close_window() def on_activity_changed(self, widget): From 030bf049828b7ffbac39dc7342ee16a9a96dcf87 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 12:20:22 +0200 Subject: [PATCH 43/72] cleanup: move on_save_button_clicked --- src/hamster/edit_activity.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 0858e8750..1cd125b0d 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -148,13 +148,6 @@ def figure_description(self): description = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0) return description.strip() - def on_save_button_clicked(self, button): - if self.fact_id: - runtime.storage.update_fact(self.fact_id, self.fact) - else: - runtime.storage.add_fact(self.fact) - self.close_window() - def on_activity_changed(self, widget): if not self.master_is_cmdline: self.fact.activity = self.activity_entry.get_text() @@ -340,6 +333,13 @@ def on_cancel_clicked(self, button): def on_close(self, widget, event): self.close_window() + def on_save_button_clicked(self, button): + if self.fact_id: + runtime.storage.update_fact(self.fact_id, self.fact) + else: + runtime.storage.add_fact(self.fact) + self.close_window() + def on_window_key_pressed(self, tree, event_key): popups = self.cmdline.popup.get_property("visible"); From 20496ecfbc3186a5245d20249ddecd5662914b20 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 12:29:49 +0200 Subject: [PATCH 44/72] prevent enter from saving invalid fact --- src/hamster/edit_activity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 1cd125b0d..8c3d0b87e 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -355,7 +355,8 @@ def on_window_key_pressed(self, tree, event_key): return False if self.description_box.has_focus(): return False - self.on_save_button_clicked(None) + if self.validate_fields(): + self.on_save_button_clicked(None) def close_window(self): if not self.parent: From 2fd35134a7319b7f1f20f0173085e68d235ae985 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 13:29:35 +0200 Subject: [PATCH 45/72] copy the entered fact before any modification --- src/hamster/edit_activity.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 8c3d0b87e..071b6737f 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -79,7 +79,9 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): 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) @@ -162,11 +164,12 @@ def on_category_changed(self, widget): def on_cmdline_changed(self, widget): if self.master_is_cmdline: - previous_cmdline_fact = self.cmdline_fact fact = Fact.parse(self.cmdline.get_text(), date=self.date) + previous_cmdline_fact = self.cmdline_fact + # copy the entered fact before any modification + self.cmdline_fact = fact.copy() if fact.start_time is None: fact.start_time = hamster_now() - self.cmdline_fact = fact.copy() if fact.description == previous_cmdline_fact.description: # no change to description here, keep the main one fact.description = self.fact.description From ac40301a490bd2f0ba99be541c088159c3dd85f3 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 14:16:50 +0200 Subject: [PATCH 46/72] ensure no end date without time --- src/hamster/edit_activity.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 071b6737f..fc8752ac1 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -189,11 +189,17 @@ def on_description_changed(self, text): self.update_cmdline() def on_end_date_changed(self, widget): - if self.fact.end_time and not self.master_is_cmdline: - time = self.fact.end_time.time() - self.fact.end_time = dt.datetime.combine(self.end_date.date, time) - self.validate_fields() - self.update_cmdline() + if not self.master_is_cmdline: + if self.fact.end_time: + time = self.fact.end_time.time() + self.fact.end_time = dt.datetime.combine(self.end_date.date, time) + self.validate_fields() + self.update_cmdline() + elif self.end_date.date: + # No end time means on-going, hence date would be meaningless. + # And a default end date may be provided when end time is set, + # so there should never be a date without time. + self.end_date.date = None def on_end_time_changed(self, widget): if not self.master_is_cmdline: From 324756b47fa7310c75c169fd123ba6a65ee22f22 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 16:22:24 +0200 Subject: [PATCH 47/72] cleanup: remove unused previous_time --- src/hamster/edit_activity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index fc8752ac1..c7f7c022d 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -227,7 +227,8 @@ def on_start_date_changed(self, widget): def on_start_time_changed(self, widget): if not self.master_is_cmdline: - previous_time = self.fact.start_time.time() + # note: resist the temptation to preserve duration here; + # for instance, end time might be at the beginning of next fact. new_time = self.start_time.time if new_time: date = self.fact.start_time.date() From c04a2c348111696ccc9216b17f6c25c80c5f1d2b Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 16:43:18 +0200 Subject: [PATCH 48/72] Do not quit on Esc if any popup is visible --- src/hamster/edit_activity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index c7f7c022d..951635e0a 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -351,7 +351,10 @@ def on_save_button_clicked(self, button): self.close_window() def on_window_key_pressed(self, tree, event_key): - popups = self.cmdline.popup.get_property("visible"); + popups = (self.cmdline.popup.get_property("visible") + or self.start_time.popup.get_property("visible") + or self.end_time.popup.get_property("visible") + or self.tags_entry.popup.get_property("visible")) if (event_key.keyval == gdk.KEY_Escape or \ (event_key.keyval == gdk.KEY_w and event_key.state & gdk.ModifierType.CONTROL_MASK)): From 42fc93e84c8b96b4225fe2f5604feaf9911b8335 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 20:09:27 +0200 Subject: [PATCH 49/72] move keyrelease events to keypress handler Key press was handled before key release obviously, Now Esc keypress is correctly catched by the tags handler before the parent. --- src/hamster/widgets/tags.py | 49 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 40f131ef4..6fb5ab870 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -58,7 +58,6 @@ def __init__(self): self.connect("icon-press", self._on_icon_press) self.connect("key-press-event", self._on_key_press_event) - self.connect("key-release-event", self._on_key_release_event) self.connect("focus-out-event", self._on_focus_out_event) self._parent_click_watcher = None # bit lame but works @@ -181,30 +180,6 @@ def _on_icon_press(self, entry, icon_pos, event): self.populate_suggestions() self.show_popup() - def _on_key_release_event(self, entry, event): - if (event.keyval in (gdk.KEY_Return, gdk.KEY_KP_Enter)): - if self.popup.get_property("visible"): - if self.get_text(): - self.hide_popup() - return True - else: - if self.get_text(): - self.emit("tags-selected") - return False - elif (event.keyval == gdk.KEY_Escape): - if self.popup.get_property("visible"): - self.hide_popup() - return True - else: - return False - else: - self.populate_suggestions() - self.show_popup() - - if event.keyval not in (gdk.KEY_Delete, gdk.KEY_BackSpace): - self.complete_inline() - - def get_cursor_tag(self): #returns the tag on which the cursor is on right now if self.get_selection_bounds(): @@ -253,6 +228,30 @@ def _on_key_press_event(self, entry, event): else: return False + elif event.keyval in (gdk.KEY_Return, gdk.KEY_KP_Enter): + if self.popup.get_property("visible"): + if self.get_text(): + self.hide_popup() + return True + else: + if self.get_text(): + self.emit("tags-selected") + return False + + elif event.keyval == gdk.KEY_Escape: + if self.popup.get_property("visible"): + self.hide_popup() + return True + else: + return False + + else: + self.populate_suggestions() + self.show_popup() + + if event.keyval not in (gdk.KEY_Delete, gdk.KEY_BackSpace): + self.complete_inline() + return False From f05c700ae778ee77b8ce05e00305390d1e9a5760 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 20:16:53 +0200 Subject: [PATCH 50/72] grab focus on icon click --- src/hamster/widgets/tags.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 6fb5ab870..f167cf49e 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -169,6 +169,8 @@ def _on_focus_out_event(self, widget, event): self.hide_popup() def _on_icon_press(self, entry, icon_pos, event): + # otherwise Esc could not hide popup + self.grab_focus() # toggle popup if self.popup.get_visible(): # remove trailing comma is any From 2c78db4bf2501ccbbffc4a7532aa774fd61f5302 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 20:26:56 +0200 Subject: [PATCH 51/72] comments --- src/hamster/widgets/activityentry.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 693c38837..926ce4b54 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -466,9 +466,13 @@ def show_suggestions(self, text): class ActivityEntry(): - """Category entry widget.""" + """Activity entry widget. + + widget (gtk.Entry): the associated activity entry + category_widget (gtk.Entry): the associated category entry + """ def __init__(self, widget=None, category_widget=None, **kwds): - # widget and completion are already defined + # widget and completion may be defined already # e.g. in the glade edit_activity.ui file self.widget = widget if not self.widget: @@ -499,7 +503,8 @@ def match_func(self, completion, key, iter, *user_data): # show all keys if entry is empty return True else: - # return if the entered string is anywhere in the first column data + # return whether the entered string is + # anywhere in the first column data stripped_key = key.strip() activities = self.model.get_value(iter, self.activity_column).lower() categories = self.model.get_value(iter, self.category_column).lower() @@ -541,7 +546,10 @@ def __getattr__(self, name): class CategoryEntry(): - """Category entry widget.""" + """Category entry widget. + + widget (gtk.Entry): the associated category entry + """ def __init__(self, widget=None, **kwds): # widget and completion are already defined # e.g. in the glade edit_activity.ui file @@ -567,7 +575,8 @@ def match_func(self, completion, key, iter, *user_data): # show all keys if entry is empty return True else: - # return if the entered string is anywhere in the first column data + # return whether the entered string is + # anywhere in the first column data return key.strip() in self.model.get_value(iter, 0) def on_focus_in_event(self, widget, event): From 1b51b7769c9c24ab74ea66d2de997a2cf3d0336c Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 20:36:20 +0200 Subject: [PATCH 52/72] use first column for text/filter This will make future additions cleaner. --- src/hamster/widgets/activityentry.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 926ce4b54..49af16212 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -485,13 +485,14 @@ def __init__(self, widget=None, category_widget=None, **kwds): self.completion = gtk.EntryCompletion() self.widget.set_completion(self.completion) - # activity, category, text to display - self.activity_column = 0 - self.category_column = 1 - self.text_column = 2 + # text to display/filter on, activity, category + self.text_column = 0 + self.activity_column = 1 + self.category_column = 2 + self.model = gtk.ListStore(str, str, str) self.completion.set_model(self.model) - self.completion.set_text_column(2) + self.completion.set_text_column(self.text_column) self.completion.set_match_func(self.match_func, None) self.connect("icon-release", self.on_icon_release) @@ -539,7 +540,7 @@ def populate_completions(self): for activity in runtime.storage.get_category_activities(category_id): activity_name = activity["name"] text = "{}@{}".format(activity_name, category_name) - self.model.append([activity_name, category_name, text]) + self.model.append([text, activity_name, category_name]) def __getattr__(self, name): return getattr(self.widget, name) From 4a5fa5480debd9387505ba654ba7ac8d2215ab0f Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 21:49:03 +0200 Subject: [PATCH 53/72] cleanup: wrap long line --- src/hamster/widgets/tags.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index f167cf49e..6d9d54b3c 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -149,7 +149,8 @@ def refresh_activities(self): self.categories = None def populate_suggestions(self): - self.ac_tags = self.ac_tags or [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)] + self.ac_tags = self.ac_tags or [tag["name"] for tag in + runtime.storage.get_tags(only_autocomplete=True)] cursor_tag = self.get_cursor_tag() From 558a4b22aec9eb4b92ed554543a935d0b7bfe7cd Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 21:49:14 +0200 Subject: [PATCH 54/72] comment --- src/hamster/widgets/tags.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 6d9d54b3c..344bbc8ab 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -174,7 +174,7 @@ def _on_icon_press(self, entry, icon_pos, event): self.grab_focus() # toggle popup if self.popup.get_visible(): - # remove trailing comma is any + # remove trailing comma if any self.update_tagsline(add=False) self.hide_popup() else: @@ -213,6 +213,11 @@ def set_tags(self, tags): self.update_tagsline() def update_tagsline(self, add=False): + """Update tags line text. + + If add is True, prepare to add tags to the list: + a comma is appended and the popup is displayed. + """ text = ", ".join(self.tags) if add and text: text = "{}, ".format(text) From 888f209bd36218fbc0ff8a2239666b4d1ac76f12 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 5 Oct 2019 22:00:19 +0200 Subject: [PATCH 55/72] cleanup: remove no-op --- src/hamster/widgets/tags.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py index 344bbc8ab..90bb363b2 100644 --- a/src/hamster/widgets/tags.py +++ b/src/hamster/widgets/tags.py @@ -138,11 +138,6 @@ def show_popup(self): self.popup.resize(w, height) self.popup.show_all() - - - def complete_inline(self): - return - def refresh_activities(self): # scratch activities and categories so that they get repopulated on demand self.activities = None @@ -257,9 +252,6 @@ def _on_key_press_event(self, entry, event): self.populate_suggestions() self.show_popup() - if event.keyval not in (gdk.KEY_Delete, gdk.KEY_BackSpace): - self.complete_inline() - return False From a7e567ba7a48e530cd6ae5311f91e3aedaa5e396 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 11:41:10 +0200 Subject: [PATCH 56/72] use str in ListStore call --- src/hamster/widgets/timeinput.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 55a0929b2..e6981b5b7 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -205,7 +205,7 @@ def show_popup(self): time = self.figure_time(self.get_text()) focus_time = dt.datetime.combine(dt.date.today(), time) if time else None - hours = gtk.ListStore(gobject.TYPE_STRING) + hours = gtk.ListStore(str) i, focus_row = 0, None while i_time < end_time: From 7e8d0e7f15df1f8f211364c4c2b01efe94488349 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 11:50:31 +0200 Subject: [PATCH 57/72] match the parent Entry width --- src/hamster/widgets/timeinput.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index e6981b5b7..344815924 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -236,12 +236,9 @@ def show_popup(self): 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 - if self.start_time is not None: - w = w * 2 self.time_tree.set_size_request(w, alloc.height * 5) window = self.get_parent_window() From 7eec94ca068c481898f30ea7019e05544723c219 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 12:09:55 +0200 Subject: [PATCH 58/72] set no start time interval to 15 min --- src/hamster/widgets/timeinput.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 344815924..adc34a93a 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -197,10 +197,12 @@ def show_popup(self): 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 - i_time = i_time_0 + dt.timedelta(minutes = 15) + 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()) @@ -217,14 +219,10 @@ def show_popup(self): hours.append([row_text]) - if focus_time and i_time <= focus_time <= i_time + \ - dt.timedelta(minutes = 30): + if focus_time and i_time <= focus_time < i_time + interval: focus_row = i - if self.start_time is None: - i_time += dt.timedelta(minutes = 30) - else: - i_time += dt.timedelta(minutes = 15) + i_time += interval i += 1 From 3d2109b0844d2897df5767b55559cfc3fb7a609d Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 14:03:38 +0200 Subject: [PATCH 59/72] accept None in datetime_to_hamsterday --- src/hamster/lib/stuff.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hamster/lib/stuff.py b/src/hamster/lib/stuff.py index 0b7474464..632d4b89e 100644 --- a/src/hamster/lib/stuff.py +++ b/src/hamster/lib/stuff.py @@ -46,6 +46,9 @@ def datetime_to_hamsterday(civil_date_time): The hamster day start is taken into account. """ + if civil_date_time is None: + return None + # work around cyclic imports from hamster.lib.configuration import conf From 00df6b7564e95ef63efcf14ee56f600763f0f801 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 16:06:20 +0200 Subject: [PATCH 60/72] handle date changes with empty start_time --- src/hamster/edit_activity.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 951635e0a..444db9ddc 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -29,7 +29,8 @@ """ from hamster import widgets from hamster.lib.configuration import runtime, conf, load_ui_file -from hamster.lib.stuff import hamster_today, hamster_now, escape_pango +from hamster.lib.stuff import ( + hamsterday_time_to_datetime, hamster_today, hamster_now, escape_pango) from hamster.lib import Fact @@ -213,15 +214,16 @@ def on_end_time_changed(self, widget): def on_start_date_changed(self, widget): if not self.master_is_cmdline: - previous_date = self.fact.start_time.date() - new_date = self.start_date.date - delta = new_date - previous_date - self.fact.start_time += delta - if self.fact.end_time: - # preserve fact duration - self.fact.end_time += delta - self.end_date.date = self.fact.end_time - self.date = self.fact.date + if self.fact.start_time: + previous_date = self.fact.start_time.date() + new_date = self.start_date.date + delta = new_date - previous_date + self.fact.start_time += delta + if self.fact.end_time: + # preserve fact duration + self.fact.end_time += delta + self.end_date.date = self.fact.end_time + self.date = self.fact.date or hamster_today() self.validate_fields() self.update_cmdline() @@ -231,10 +233,18 @@ def on_start_time_changed(self, widget): # for instance, end time might be at the beginning of next fact. new_time = self.start_time.time if new_time: - date = self.fact.start_time.date() - self.fact.start_time = dt.datetime.combine(date, new_time) + if self.fact.start_time: + new_start_time = dt.datetime.combine(self.fact.start_time.date(), + new_time) + else: + # date not specified; result must fall in current hamster_day + new_start_time = hamsterday_time_to_datetime(hamster_today(), + new_time) else: - self.fact.start_time = None + new_start_time = None + self.fact.start_time = new_start_time + # let start_date extract date or handle None + self.start_date.date = new_start_time self.validate_fields() self.update_cmdline() From aea835224658136426107dcc5aef489553d4f70a Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 16:19:30 +0200 Subject: [PATCH 61/72] add clear icon to TimeInput --- src/hamster/widgets/timeinput.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index adc34a93a..f8a8409b9 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -60,6 +60,9 @@ def __init__(self, time = None, start_time = None): time_box.add(self.time_tree) self.popup.add(time_box) + self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "gtk-clear") + + self.connect("icon-release", self._on_icon_release) self.connect("button-press-event", self._on_button_press_event) self.connect("key-press-event", self._on_key_press_event) self.connect("focus-in-event", self._on_focus_in_event) @@ -180,6 +183,11 @@ def _on_focus_out_event(self, event, something): self.emit("time-entered") self.news = False + def _on_icon_release(self, entry, icon_pos, event): + self.grab_focus() + self.set_text("") + self.emit("changed") + def hide_popup(self): if self._parent_click_watcher and self.get_toplevel().handler_is_connected(self._parent_click_watcher): self.get_toplevel().disconnect(self._parent_click_watcher) From d57eb94a8f0c909433923b7ddf7ef6c2c9bfcada Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 19:26:20 +0200 Subject: [PATCH 62/72] move on_description_changed signal connection Has to be after the self.cmdline.grab_focus() that eventually sets self.master_is_cmdline --- src/hamster/edit_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 444db9ddc..63aa055d8 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -63,7 +63,6 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): self.description_box = self.get_widget('description') self.description_buffer = self.description_box.get_buffer() - self.description_buffer.connect("changed", self.on_description_changed) self.end_date = widgets.Calendar(widget=self.get_widget("end date")) @@ -108,6 +107,7 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): # 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.end_time.connect("changed", self.on_end_time_changed) From 2d18aea4edbf0c6b53c98c398d05da30c396ad2e Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Oct 2019 19:31:18 +0200 Subject: [PATCH 63/72] replace gtk.gdk references with gdk --- src/hamster/widgets/timeinput.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index f8a8409b9..795f3adc5 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -273,18 +273,18 @@ def _on_key_press_event(self, entry, event): i = model.get_path(iter)[0] - if event.keyval == gtk.gdk.KEY_Up: + if event.keyval == gdk.KEY_Up: i-=1 - elif event.keyval == gtk.gdk.KEY_Down: + elif event.keyval == gdk.KEY_Down: i+=1 - elif (event.keyval == gtk.gdk.KEY_Return or - event.keyval == gtk.gdk.KEY_KP_Enter): + elif (event.keyval == gdk.KEY_Return or + event.keyval == gdk.KEY_KP_Enter): if self.popup.get_property("visible"): self._select_time(self.time_tree.get_model()[i][0]) else: self._select_time(entry.get_text()) - elif (event.keyval == gtk.gdk.KEY_Escape): + elif (event.keyval == gdk.KEY_Escape): self.hide_popup() return @@ -295,7 +295,7 @@ def _on_key_press_event(self, entry, event): self.time_tree.scroll_to_cell(i, use_align = True, row_align = 0.4) # if popup is not visible, display it on up and down - if event.keyval in (gtk.gdk.KEY_Up, gtk.gdk.KEY_Down) and self.popup.props.visible == False: + if event.keyval in (gdk.KEY_Up, gdk.KEY_Down) and self.popup.props.visible == False: self.show_popup() return True From 55a3155e469d778d754a26b659b8d04bd9432d28 Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 20 Oct 2019 21:07:10 +0200 Subject: [PATCH 64/72] separate down and clear category icons --- data/edit_activity.ui | 3 ++- src/hamster/widgets/activityentry.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index fa71d44aa..8a92ebf42 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -359,7 +359,8 @@ True True - gtk-clear + edit-delete + go-down-symbolic category completion diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 49af16212..16251c407 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -586,7 +586,9 @@ def on_focus_in_event(self, widget, event): def on_icon_release(self, entry, icon_pos, event): self.widget.grab_focus() self.widget.set_text("") - self.emit("changed") + # do not emit changed on the primary (clear) button + if icon_pos == gtk.EntryIconPosition.SECONDARY: + self.emit("changed") def populate_completions(self): self.model.clear() From 577b5bc8da4633ca091f6d7719c0ba8de88af90d Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 20 Oct 2019 21:31:24 +0200 Subject: [PATCH 65/72] add clear action to category completion --- src/hamster/widgets/activityentry.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 16251c407..880697607 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -562,6 +562,8 @@ def __init__(self, widget=None, **kwds): if not self.completion: self.completion = gtk.EntryCompletion() self.widget.set_completion(self.completion) + self.completion.insert_action_markup(0, "Clear ({})".format(_("Unsorted"))) + self.unsorted_action_index = 0 self.model = gtk.ListStore(str) self.completion.set_model(self.model) @@ -570,6 +572,12 @@ def __init__(self, widget=None, **kwds): self.widget.connect("icon-release", self.on_icon_release) self.widget.connect("focus-in-event", self.on_focus_in_event) + self.completion.connect("action_activated", self.on_action_activated) + + def clear(self, notify): + self.widget.set_text("") + if notify: + self.emit("changed") def match_func(self, completion, key, iter, *user_data): if not key.strip(): @@ -580,15 +588,19 @@ def match_func(self, completion, key, iter, *user_data): # anywhere in the first column data return key.strip() in self.model.get_value(iter, 0) + def on_action_activated(self, completion, index): + if index == self.unsorted_action_index: + self.clear(notify=False) + def on_focus_in_event(self, widget, event): self.populate_completions() def on_icon_release(self, entry, icon_pos, event): self.widget.grab_focus() - self.widget.set_text("") + # do not emit changed on the primary (clear) button - if icon_pos == gtk.EntryIconPosition.SECONDARY: - self.emit("changed") + self.clear(icon_pos == gtk.EntryIconPosition.SECONDARY) + def populate_completions(self): self.model.clear() From fa5759e1fc06e23f519b17485e05795bc572a75a Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 20 Oct 2019 22:11:29 +0200 Subject: [PATCH 66/72] separate icons for TimeInput --- src/hamster/widgets/timeinput.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 795f3adc5..027afd447 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -60,7 +60,8 @@ def __init__(self, time = None, start_time = None): time_box.add(self.time_tree) self.popup.add(time_box) - self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "gtk-clear") + self.set_icon_from_icon_name(gtk.EntryIconPosition.PRIMARY, "edit-delete") + self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "go-down-symbolic") self.connect("icon-release", self._on_icon_release) self.connect("button-press-event", self._on_button_press_event) @@ -185,8 +186,12 @@ def _on_focus_out_event(self, event, something): def _on_icon_release(self, entry, icon_pos, event): self.grab_focus() - self.set_text("") - self.emit("changed") + if icon_pos == gtk.EntryIconPosition.PRIMARY: + # "clear" button + self.set_text("") + self.emit("changed") + else: + self.toggle_popup() def hide_popup(self): if self._parent_click_watcher and self.get_toplevel().handler_is_connected(self._parent_click_watcher): @@ -254,6 +259,11 @@ def show_popup(self): self.popup.resize(*self.time_tree.get_size_request()) self.popup.show_all() + def toggle_popup(self): + if self.popup.get_property("visible"): + self.hide_popup() + else: + self.show_popup() def _on_time_tree_button_press_event(self, tree, event): model, iter = tree.get_selection().get_selected() From c967463a3f97af88257eae1bd3a5ee33dfe4c786 Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 22 Oct 2019 01:55:16 +0200 Subject: [PATCH 67/72] filter activities on category --- data/edit_activity.ui | 17 +++---- src/hamster/storage/db.py | 4 ++ src/hamster/widgets/activityentry.py | 74 ++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 8a92ebf42..8abafcb86 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -292,7 +292,7 @@ True True - + True False vertical @@ -301,7 +301,7 @@ True False start - activity + category @@ -314,10 +314,10 @@ - + True True - gtk-clear + edit-clear-all-symbolic activity completion @@ -333,7 +333,7 @@ - + True False 4 @@ -343,7 +343,7 @@ True False start - category + activity @@ -356,11 +356,10 @@ - + True True - edit-delete - go-down-symbolic + edit-clear-all-symbolic category completion diff --git a/src/hamster/storage/db.py b/src/hamster/storage/db.py index 0048d18e6..feff76d0b 100644 --- a/src/hamster/storage/db.py +++ b/src/hamster/storage/db.py @@ -51,6 +51,8 @@ from hamster.lib.stuff import hamster_today, hamster_now +UNSORTED_ID = -1 + class Storage(storage.Storage): con = None # Connection will be created on demand @@ -344,6 +346,8 @@ def __get_activity_by_name(self, name, category_id = None, resurrect = True): def __get_category_id(self, name): """returns category by it's name""" + if not name: + return UNSORTED_ID query = """ SELECT id from categories diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 880697607..60c013a44 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -480,6 +480,8 @@ def __init__(self, widget=None, category_widget=None, **kwds): self.category_widget = category_widget + # internal list of actions added to the suggestions + self._action_list = [] self.completion = self.widget.get_completion() if not self.completion: self.completion = gtk.EntryCompletion() @@ -490,14 +492,48 @@ def __init__(self, widget=None, category_widget=None, **kwds): self.activity_column = 1 self.category_column = 2 + # whether the category choice limit the activity suggestions + self.filter_on_category = True if self.category_widget else False self.model = gtk.ListStore(str, str, str) self.completion.set_model(self.model) self.completion.set_text_column(self.text_column) self.completion.set_match_func(self.match_func, None) + # enable selection with up and down arrow + self.completion.set_inline_selection(True) + # It is not possible to change actions later dynamically; + # once actions are removed, + # they can not be added back (they are not visible). + # => nevermind, showing all actions. + self.add_action("show all", "Show all activities") + self.add_action("filter on category", "Filter on selected category") self.connect("icon-release", self.on_icon_release) self.connect("focus-in-event", self.on_focus_in_event) self.completion.connect('match-selected', self.on_match_selected) + self.completion.connect("action_activated", self.on_action_activated) + + def add_action(self, name, text): + """Add an action to the suggestions. + + name (str): unique label, use to retrieve the action index. + text (str): text used to display the action. + """ + markup = "{}".format(stuff.escape_pango(text)) + idx = len(self._action_list) + self.completion.insert_action_markup(idx, markup) + self._action_list.append(name) + + def clear(self, notify=True): + self.widget.set_text("") + if notify: + self.emit("changed") + + def clear_actions(self): + """Remove all actions from the suggestion list.""" + while self._action_list: + idx_last = len(self._action_list) - 1 + self.completion.delete_action(idx_last) + self._action_list.pop(idx_last) def match_func(self, completion, key, iter, *user_data): if not key.strip(): @@ -513,6 +549,17 @@ def match_func(self, completion, key, iter, *user_data): key_in_category = stripped_key in categories return key_in_activity or key_in_category + def on_action_activated(self, completion, index): + name = self._action_list[index] + if name == "clear": + self.clear(notify=False) + elif name == "show all": + self.filter_on_category = False + self.populate_completions() + elif name == "filter on category": + self.filter_on_category = True + self.populate_completions() + def on_focus_in_event(self, widget, event): self.populate_completions() @@ -527,21 +574,32 @@ def on_match_selected(self, entry, model, iter): combined = model[iter][self.text_column] if self.category_widget: self.set_text(activity_name) - self.category_widget.set_text(category_name) + if not self.filter_on_category: + self.category_widget.set_text(category_name) else: self.set_text(combined) return True # prevent the standard callback from overwriting text def populate_completions(self): self.model.clear() - for category in runtime.storage.get_categories(): - category_name = category['name'] - category_id = category['id'] - for activity in runtime.storage.get_category_activities(category_id): + if self.filter_on_category: + category_names = [self.category_widget.get_text()] + else: + category_names = [category['name'] + for category in runtime.storage.get_categories()] + for category_name in category_names: + category_id = runtime.storage.get_category_id(category_name) + activities = runtime.storage.get_category_activities(category_id) + for activity in activities: activity_name = activity["name"] text = "{}@{}".format(activity_name, category_name) self.model.append([text, activity_name, category_name]) + def remove_action(self, name): + idx = self._action_list.find(name) + self.completion.delete_action(idx) + del(self._action_list[idx]) + def __getattr__(self, name): return getattr(self.widget, name) @@ -574,7 +632,7 @@ def __init__(self, widget=None, **kwds): self.widget.connect("focus-in-event", self.on_focus_in_event) self.completion.connect("action_activated", self.on_action_activated) - def clear(self, notify): + def clear(self, notify=True): self.widget.set_text("") if notify: self.emit("changed") @@ -597,10 +655,8 @@ def on_focus_in_event(self, widget, event): def on_icon_release(self, entry, icon_pos, event): self.widget.grab_focus() - # do not emit changed on the primary (clear) button - self.clear(icon_pos == gtk.EntryIconPosition.SECONDARY) - + self.clear() def populate_completions(self): self.model.clear() From 6225d2791c6da848f1bbf9c20d828b244e8dcb78 Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 23 Oct 2019 00:23:42 +0200 Subject: [PATCH 68/72] remove unused functions --- src/hamster/widgets/activityentry.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py index 60c013a44..87555ee56 100644 --- a/src/hamster/widgets/activityentry.py +++ b/src/hamster/widgets/activityentry.py @@ -528,13 +528,6 @@ def clear(self, notify=True): if notify: self.emit("changed") - def clear_actions(self): - """Remove all actions from the suggestion list.""" - while self._action_list: - idx_last = len(self._action_list) - 1 - self.completion.delete_action(idx_last) - self._action_list.pop(idx_last) - def match_func(self, completion, key, iter, *user_data): if not key.strip(): # show all keys if entry is empty @@ -595,11 +588,6 @@ def populate_completions(self): text = "{}@{}".format(activity_name, category_name) self.model.append([text, activity_name, category_name]) - def remove_action(self, name): - idx = self._action_list.find(name) - self.completion.delete_action(idx) - del(self._action_list[idx]) - def __getattr__(self, name): return getattr(self.widget, name) From 8ebbc7abc34fa68261f299fbcfb7b7913bae0f5f Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 23 Oct 2019 00:26:47 +0200 Subject: [PATCH 69/72] use colorless icon --- src/hamster/widgets/timeinput.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 027afd447..903d3a200 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -60,7 +60,7 @@ def __init__(self, time = None, start_time = None): time_box.add(self.time_tree) self.popup.add(time_box) - self.set_icon_from_icon_name(gtk.EntryIconPosition.PRIMARY, "edit-delete") + self.set_icon_from_icon_name(gtk.EntryIconPosition.PRIMARY, "edit-clear-all-symbolic") self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "go-down-symbolic") self.connect("icon-release", self._on_icon_release) From 63417e8675c4fbebe8ace9cfeb9a7daefd08bfab Mon Sep 17 00:00:00 2001 From: ederag Date: Wed, 23 Oct 2019 18:07:27 +0200 Subject: [PATCH 70/72] remove go-down-symbolic icon --- src/hamster/widgets/timeinput.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py index 903d3a200..8b871e5bf 100644 --- a/src/hamster/widgets/timeinput.py +++ b/src/hamster/widgets/timeinput.py @@ -61,7 +61,6 @@ def __init__(self, time = None, start_time = None): self.popup.add(time_box) self.set_icon_from_icon_name(gtk.EntryIconPosition.PRIMARY, "edit-clear-all-symbolic") - self.set_icon_from_icon_name(gtk.EntryIconPosition.SECONDARY, "go-down-symbolic") self.connect("icon-release", self._on_icon_release) self.connect("button-press-event", self._on_button_press_event) @@ -186,12 +185,8 @@ def _on_focus_out_event(self, event, something): def _on_icon_release(self, entry, icon_pos, event): self.grab_focus() - if icon_pos == gtk.EntryIconPosition.PRIMARY: - # "clear" button - self.set_text("") - self.emit("changed") - else: - self.toggle_popup() + self.set_text("") + self.emit("changed") def hide_popup(self): if self._parent_click_watcher and self.get_toplevel().handler_is_connected(self._parent_click_watcher): From cc55b7e8e53f280083614f1ceccb6c858e85e124 Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 29 Oct 2019 13:47:11 +0100 Subject: [PATCH 71/72] hide calendars --- data/edit_activity.ui | 44 +++++++++++++++++++++++++++++------- src/hamster/edit_activity.py | 20 ++++++++++++++-- src/hamster/widgets/dates.py | 17 ++++++++++++-- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 8abafcb86..0fc2d7725 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -202,12 +202,26 @@ - + True True - 2019 - 8 - 15 + True + + + True + True + 2019 + 8 + 15 + + + + + True + False + start date + + False @@ -260,12 +274,26 @@ - + True True - 2019 - 8 - 15 + True + + + True + True + 2019 + 8 + 15 + + + + + True + False + end date + + False diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py index 63aa055d8..4f3d5120d 100644 --- a/src/hamster/edit_activity.py +++ b/src/hamster/edit_activity.py @@ -64,12 +64,14 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): 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")) + 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")) + 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) @@ -110,8 +112,12 @@ def __init__(self, parent=None, fact_id=None, base_fact=None): 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) @@ -202,6 +208,11 @@ def on_end_date_changed(self, widget): # so there should never be a date without time. self.end_date.date = None + def on_end_date_expander_activated(self, widget): + # state has not changed yet, toggle also start_date calendar visibility + previous_state = self.end_date.expander.get_expanded() + self.start_date.expander.set_expanded(not previous_state) + def on_end_time_changed(self, widget): if not self.master_is_cmdline: # self.end_time.start_time() was given a datetime, @@ -227,6 +238,11 @@ def on_start_date_changed(self, widget): self.validate_fields() self.update_cmdline() + def on_start_date_expander_activated(self, widget): + # state has not changed yet, toggle also end_date calendar visibility + previous_state = self.start_date.expander.get_expanded() + self.end_date.expander.set_expanded(not previous_state) + def on_start_time_changed(self, widget): if not self.master_is_cmdline: # note: resist the temptation to preserve duration here; diff --git a/src/hamster/widgets/dates.py b/src/hamster/widgets/dates.py index 5e948d366..5be7494e3 100644 --- a/src/hamster/widgets/dates.py +++ b/src/hamster/widgets/dates.py @@ -32,10 +32,16 @@ class Calendar(): """Python date interface to a Gtk.Calendar. - widget (Gtk.Calendar): the associated Gtk widget. + widget (Gtk.Calendar): + the associated Gtk widget. + expander (Gtk.expander): + An optional expander which contains the widget. + The expander label displays the date. """ - def __init__(self, widget): + def __init__(self, widget, expander=None): self.widget = widget + self.expander = expander + self.widget.connect("day-selected", self.on_date_changed) @property def date(self): @@ -62,6 +68,13 @@ def date(self, value): self.widget.select_month(month, year) self.widget.select_day(day) + def on_date_changed(self, widget): + if self.expander: + if self.date: + self.expander.set_label(self.date.strftime("%A %Y-%m-%d")) + else: + self.expander.set_label("") + def __getattr__(self, name): return getattr(self.widget, name) From 43c76d078ac0290c96ff5c16ea634ff7d1e5a366 Mon Sep 17 00:00:00 2001 From: ederag Date: Tue, 29 Oct 2019 18:39:06 +0100 Subject: [PATCH 72/72] set label_fill --- data/edit_activity.ui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/edit_activity.ui b/data/edit_activity.ui index 0fc2d7725..4174d2716 100644 --- a/data/edit_activity.ui +++ b/data/edit_activity.ui @@ -205,6 +205,7 @@ True True + True True @@ -277,6 +278,7 @@ True True + True True