diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4f9a2249ef..7e681d299f 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -12,17 +12,27 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: python-version: "3.10" - - name: Install dependencies + + # Note: we could try and cache the pre-commit environment + # See the pre-commit docs at https://pre-commit.com/#github-actions-example + - name: Run pre-commit hooks run: | python -m pip install --upgrade pip + pip install pre-commit + pre-commit run --all-files + + - name: Install dependencies + run: | sudo apt-get update sudo apt install -y libgirepository1.0-dev gir1.2-gtk-4.0 libgtksourceview-5-dev pip install --user -e git+https://github.com/getting-things-gnome/liblarch.git#egg=liblarch pip install --user pytest pycairo PyGObject caldav lxml - name: Run unit tests with Pytest + run: | ./run-tests diff --git a/.github/workflows/weekly_flatpak.yml b/.github/workflows/weekly_flatpak.yml index 396ea269aa..5f7444de6a 100644 --- a/.github/workflows/weekly_flatpak.yml +++ b/.github/workflows/weekly_flatpak.yml @@ -19,4 +19,3 @@ jobs: bundle: org.gnome.GTG.Devel.flatpak manifest-path: build-aux/org.gnome.GTG.Devel.json cache-key: flatpak-builder-${{ github.sha }} - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..fd16ba2dc3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files diff --git a/GTG/backends/unmaintained/README.txt b/GTG/backends/unmaintained/README.txt index d7b2941736..cb8cb0ea73 100644 --- a/GTG/backends/unmaintained/README.txt +++ b/GTG/backends/unmaintained/README.txt @@ -5,7 +5,7 @@ and support them. If you are interested in taking over one of these, let us know in the bug tracker! ---- +--- Evolution synchronization service has dependencies: * python3-evolution @@ -27,4 +27,3 @@ version of Tweety library. Remember the Milk synchronization service is shipped with a library for RTM api. It has an external dependency: * python3-dateutil - diff --git a/GTG/core/base_store.py b/GTG/core/base_store.py index 0807f37a71..49a7bf4b98 100644 --- a/GTG/core/base_store.py +++ b/GTG/core/base_store.py @@ -105,7 +105,7 @@ def remove(self, item_id: UUID) -> None: """Remove an existing item from the store.""" item = self.lookup[item_id] - + try: parent = item.parent diff --git a/GTG/core/datastore.py b/GTG/core/datastore.py index b4a14b419d..483f80ee57 100644 --- a/GTG/core/datastore.py +++ b/GTG/core/datastore.py @@ -236,13 +236,13 @@ def count_tasks(count: dict, tasklist: list): 'closed': {'all': 0, 'untagged': 0}, } - count_tasks(self.task_count['open'], + count_tasks(self.task_count['open'], self.tasks.filter(Filter.ACTIVE)) - count_tasks(self.task_count['closed'], + count_tasks(self.task_count['closed'], self.tasks.filter(Filter.CLOSED)) - count_tasks(self.task_count['actionable'], + count_tasks(self.task_count['actionable'], self.tasks.filter(Filter.ACTIONABLE)) @@ -259,7 +259,7 @@ def refresh_tag_stats(self) -> None: def notify_tag_change(self, tag) -> None: """Notify tasks that this tag has changed.""" - + for task in self.tasks.lookup.values(): if tag in task.tags: task.notify('icons') @@ -280,14 +280,14 @@ def do_first_run_versioning(self, filepath: str) -> None: """If there is an old file around needing versioning, convert it, then rename the old file.""" old_path = self.find_old_path(DATA_DIR) - + if old_path is not None: log.warning('Found old file: %r. Running versioning code.', old_path) tree = versioning.convert(old_path, self) self.load_data(tree) self.save(filepath) os.rename(old_path, old_path + '.imported') - + else: self.first_run(self.data_path) @@ -297,13 +297,13 @@ def find_old_path(self, datadir: str) -> None | str: # used by which version? path = os.path.join(datadir, 'gtg_tasks.xml') - + if os.path.isfile(path): return path - + # used by (at least) 0.3.1-4 path = os.path.join(datadir, 'projects.xml') - + if os.path.isfile(path): return self.find_old_uuid_path(path) @@ -315,7 +315,7 @@ def find_old_uuid_path(self, path: str) -> None | str: with open(path, 'r') as stream: xml_tree = et.parse(stream) - + for backend in xml_tree.findall('backend'): module = backend.get('module') if module == 'backend_localfile': diff --git a/GTG/core/filters.py b/GTG/core/filters.py index 4207f4a635..0b1c056fcb 100644 --- a/GTG/core/filters.py +++ b/GTG/core/filters.py @@ -26,9 +26,9 @@ def unwrap(row, expected_type): """Find an item in TreeRow widget (sometimes nested).""" - + item = row.get_item() - + while type(item) is not expected_type: item = item.get_item() @@ -58,7 +58,7 @@ def do_match(self, item) -> bool: else: return True - + class TaskPaneFilter(Gtk.Filter): __gtype_name__ = 'TaskPaneFilter' @@ -90,13 +90,13 @@ def get_children(children: set) -> None: if tag.children: get_children(tag.children) - + return result def match_tags(self, task: Task) -> bool: """Match selected tags to task tags.""" - + all_tags = self.expand_tags() return len(all_tags.intersection(set(task.tags))) >= len(self.tags) @@ -108,7 +108,7 @@ def do_match(self, item) -> bool: show = task.status is Status.ACTIVE elif self.pane == 'workview': show = task.is_actionable - + if self.expand: item.set_expanded(True) self.expand = False @@ -144,7 +144,7 @@ def __init__(self, ds, pane): def set_query(self, query: str) -> None: self.query = query - + try: self.checks = search.parse_search_query(query) except search.InvalidQuery: @@ -153,7 +153,7 @@ def set_query(self, query: str) -> None: def match_tags(self, task: Task) -> bool: """Match selected tags to task tags.""" - + return len(self.tags.intersection(set(task.tags))) >= len(self.tags) @@ -179,4 +179,4 @@ def do_match(self, item) -> bool: return search.search_filter(task, self.checks) else: - return False \ No newline at end of file + return False diff --git a/GTG/core/firstrun_tasks.py b/GTG/core/firstrun_tasks.py index 42bbaba83e..c9610525e5 100644 --- a/GTG/core/firstrun_tasks.py +++ b/GTG/core/firstrun_tasks.py @@ -410,5 +410,5 @@ def generate() -> etree.Element: tag_tag = etree.SubElement(taglist, 'tag') tag_tag.set('id', tid) tag_tag.set('name', tag) - + return etree.ElementTree(root) diff --git a/GTG/core/info.py.in b/GTG/core/info.py.in index 7b82ce63dc..9586e3264c 100644 --- a/GTG/core/info.py.in +++ b/GTG/core/info.py.in @@ -58,7 +58,7 @@ AUTHORS_RELEASE_CONTRIBUTORS = """ • François Schmidts • Sebastian Grabowski • Fridolin Weisser -• Tommy Priest +• Tommy Priest • Laurent Combe • Smitty • Tiziana Sellitto diff --git a/GTG/core/plugins/api.py b/GTG/core/plugins/api.py index 7fe7f3d095..01e898e522 100644 --- a/GTG/core/plugins/api.py +++ b/GTG/core/plugins/api.py @@ -51,7 +51,7 @@ def __init__(self, self.app = app self.ds = app.ds self.browser = app.browser - + self.selection_changed_callback_listeners = [] if taskeditor: @@ -60,7 +60,7 @@ def __init__(self, else: self.__ui = self.browser self.__task_id = None - + for pane in self.browser.panes.values(): pane.task_selection.connect('selection-changed', self.__selection_changed, pane) diff --git a/GTG/core/saved_searches.py b/GTG/core/saved_searches.py index 10220c3c28..675d645930 100644 --- a/GTG/core/saved_searches.py +++ b/GTG/core/saved_searches.py @@ -172,7 +172,7 @@ def new(self, name: str, query: str, parent: UUID = None) -> SavedSearch: self.model.append(search) return search - + def add(self, item, parent_id: UUID = None) -> None: """Add a tag to the tagstore.""" diff --git a/GTG/core/sorters.py b/GTG/core/sorters.py index f2e8fb1178..0eb5464f4c 100644 --- a/GTG/core/sorters.py +++ b/GTG/core/sorters.py @@ -23,9 +23,9 @@ def unwrap(row, expected_type): """Find an item in TreeRow widget (sometimes nested).""" - + item = row - + while type(item) is not expected_type: item = item.get_item() @@ -40,7 +40,7 @@ def __init__(self): def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -63,7 +63,7 @@ def __init__(self): def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -86,7 +86,7 @@ def __init__(self): def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -109,7 +109,7 @@ def __init__(self): def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -133,14 +133,14 @@ def __init__(self): def get_first_letter(self, tags) -> str: """Get first letter of the first tag in a set of tags.""" - + # Fastest way to get the first item # on a set in Python for t in tags: return t.name[0] def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -170,7 +170,7 @@ def __init__(self): def do_compare(self, a, b) -> Gtk.Ordering: - + a = unwrap(a, Task) b = unwrap(b, Task) @@ -183,4 +183,3 @@ def do_compare(self, a, b) -> Gtk.Ordering: return Gtk.Ordering.SMALLER else: return Gtk.Ordering.EQUAL - diff --git a/GTG/core/tags.py b/GTG/core/tags.py index 71f0afb68d..a4e62698c1 100644 --- a/GTG/core/tags.py +++ b/GTG/core/tags.py @@ -129,7 +129,7 @@ def has_icon(self) -> bool: return self._icon - + @GObject.Property(type=int, default=0) def task_count_open(self) -> int: @@ -174,8 +174,8 @@ def get_ancestors(self) -> List['Tag']: def __hash__(self): return id(self) - - + + class TagStore(BaseStore): """A tree of tags.""" @@ -219,7 +219,7 @@ def __str__(self) -> str: def get_all_tag_names(self) -> List[str]: """Return all tag names.""" return list(self.lookup_names.keys()) - + def find(self, name: str) -> Tag: """Get a tag by name.""" @@ -340,7 +340,7 @@ def rand_color() -> str: r = random.randint(0, 255) g = random.randint(0, 255) b = random.randint(0, 255) - + return f'#{r:02x}{g:02x}{b:02x}' color = rand_color() diff --git a/GTG/core/tasks.py b/GTG/core/tasks.py index 8abb5f6bb9..2166606aa2 100644 --- a/GTG/core/tasks.py +++ b/GTG/core/tasks.py @@ -108,7 +108,7 @@ def __init__(self, id: UUID, title: str) -> None: self._is_recurring = False self.recurring_term = None self.recurring_updated_date = datetime.datetime.now() - + self.attributes = {} self.duplicate_cb = NotImplemented @@ -162,7 +162,7 @@ def set_status(self, status: Status, propagated: bool = False) -> None: if self.status == Status.ACTIVE: for t in self.tags: t.task_count_open -= 1 - + if self.is_actionable: for t in self.tags: t.task_count_actionable -= 1 @@ -184,10 +184,10 @@ def set_status(self, status: Status, propagated: bool = False) -> None: # 1- It is recurring. # 2- It has no parent or no recurring parent. # 3- It was directly marked as done (not by propagation from its parent). - if (self._is_recurring and not propagated and + if (self._is_recurring and not propagated and not self.is_parent_recurring()): self.duplicate_cb(self) - + else: self.date_closed = Date.no_date() @@ -197,7 +197,7 @@ def set_status(self, status: Status, propagated: bool = False) -> None: if status == Status.ACTIVE: for t in self.tags: t.task_count_open += 1 - + if self.is_actionable: for t in self.tags: t.task_count_actionable += 1 @@ -206,7 +206,7 @@ def set_status(self, status: Status, propagated: bool = False) -> None: for t in self.tags: t.task_count_closed += 1 - + for child in self.children: child.set_status(status, propagated=True) @@ -220,7 +220,7 @@ def date_due(self) -> Date: def date_due(self, value: Date) -> None: self._date_due = value self.has_date_due = bool(value) - + if value: self.date_due_str = self._date_due.to_readable_string() else: @@ -228,9 +228,9 @@ def date_due(self, value: Date) -> None: for tag in self.tags: if self.is_actionable: - tag.task_count_actionable += 1 + tag.task_count_actionable += 1 else: - tag.task_count_actionable -= 1 + tag.task_count_actionable -= 1 if not value or value.is_fuzzy(): return @@ -329,9 +329,9 @@ def add_tag(self, tag: Tag) -> None: if self.status == Status.ACTIVE: tag.task_count_open += 1 - else: + else: tag.task_count_closed += 1 - + if self.is_actionable: tag.task_count_actionable += 1 else: @@ -347,9 +347,9 @@ def remove_tag(self, tag_name: str) -> None: if self.status == Status.ACTIVE: t.task_count_open -= 1 - else: + else: t.task_count_closed -= 1 - + if self.is_actionable: t.task_count_actionable -= 1 @@ -388,7 +388,7 @@ def set_recurring(self, recurring: bool, recurring_term: str = None, newtask=Fal when creating a new task, the due date is calculated from either the current date or the start date, while we get the next occurrence of a task not from the current date but from the due date itself. - + However when we are retrieving the task from the XML files, we should only set the the recurring_term. @@ -460,7 +460,7 @@ def is_valid_term(): if self._is_recurring: child.date_due = newdate - + self.notify('is_recurring') @@ -490,9 +490,9 @@ def inherit_recursion(self): def is_parent_recurring(self): """Determine if the parent task is recurring.""" - - return (self.parent and - self.parent.status == Status.ACTIVE + + return (self.parent and + self.parent.status == Status.ACTIVE and self.parent._is_recurring) @@ -514,7 +514,7 @@ def get_next_occurrence(self): """ today = datetime.date.today() - + if today <= self.date_due: try: nextdate = self.date_due.parse_from_date(self.recurring_term, newtask=False) @@ -533,7 +533,7 @@ def get_next_occurrence(self): while next_date < datetime.date.today(): next_date = next_date.parse_from_date(self.recurring_term, newtask=False) - + return next_date except Exception: @@ -543,8 +543,8 @@ def get_next_occurrence(self): # Bind Properties # # Since PyGobject doesn't support bind_property_full() yet - # we can't do complex binds. These props below serve as a - # workaround so that we can use them with the regular + # we can't do complex binds. These props below serve as a + # workaround so that we can use them with the regular # bind_property(). # ----------------------------------------------------------------------- @@ -630,7 +630,7 @@ def row_css(self) -> str: @GObject.Property(type=str) def tag_colors(self) -> str: - return ','.join(t.color for t in self.tags + return ','.join(t.color for t in self.tags if t.color and not t.icon) @@ -642,7 +642,7 @@ def show_tag_colors(self) -> str: @property def tag_names(self) -> list[str]: return [ t.name for t in self.tags ] - + def set_attribute(self, att_name, att_value, namespace="") -> None: """Set an arbitrary attribute.""" @@ -730,14 +730,14 @@ def get(self, tid: UUID) -> Task: def duplicate_for_recurrent(self, task: Task) -> Task: """Duplicate a task for the next ocurrence.""" - + new_task = self.new(task.title) new_task.tags = task.tags new_task.content = task.content new_task.date_added = task.date_added new_task.date_due = task.get_next_occurrence() - # Only goes through for the first task + # Only goes through for the first task if task.parent and task.parent.is_active: self.parent(new_task.id, task.parent.id) @@ -747,7 +747,7 @@ def duplicate_for_recurrent(self, task: Task) -> Task: log.debug("Duplicated task %s as task %s", task.id, new_task.id) return new_task - + def new(self, title: str = None, parent: UUID = None) -> Task: """Create a new task and add it to the store.""" @@ -901,7 +901,7 @@ def add(self, item: Any, parent_id: UUID = None) -> None: if not parent_id: self.model.append(item) - + item.duplicate_cb = self.duplicate_for_recurrent self.notify('task_count_all') self.notify('task_count_no_tags') @@ -1027,7 +1027,7 @@ def task_count_all(self) -> str: @GObject.Property(type=str) def task_count_no_tags(self) -> str: i = 0 - + for task in self.lookup.values(): if not task.tags: i += 1 diff --git a/GTG/gtk/application.py b/GTG/gtk/application.py index 43503210b0..bf854b0bdb 100644 --- a/GTG/gtk/application.py +++ b/GTG/gtk/application.py @@ -119,7 +119,7 @@ def do_startup(self): self.config_core = CoreConfig() self.config = self.config_core.get_subconfig('browser') - self.config_plugins = self.config_core.get_subconfig('plugins') + self.config_plugins = self.config_core.get_subconfig('plugins') self.clipboard = clipboard.TaskClipboard(self.ds) @@ -150,7 +150,7 @@ def do_activate(self): try: self.init_shared() self.browser.present() - self.browser.restore_editor_windows() + self.browser.restore_editor_windows() log.debug("Application activation finished") except Exception as e: @@ -337,7 +337,7 @@ def new_subtask(self, param, action): def add_parent(self, param, action): """Callback to add a parent to a task""" - + self.browser.on_add_parent() @@ -365,7 +365,7 @@ def dismiss(self, param, action): self.browser.on_dismiss_task() finally: self.browser.get_pane().refresh() - + def reopen(self, param, action): """Callback to mark task as open.""" @@ -455,7 +455,7 @@ def purge_old_tasks(self, widget=None): today = Date.today() max_days = self.config.get('autoclean_days') closed_tasks = self.ds.tasks.filter(Filter.CLOSED) - + to_remove = [t for t in closed_tasks if (today - t.date_closed).days > max_days] @@ -549,7 +549,7 @@ def open_task(self, task, new=False): If a Task editor is already opened for a given task, we present it. Otherwise, we create a new one. """ - + if task.id in self.open_tasks: editor = self.open_tasks[task.id] editor.present() diff --git a/GTG/gtk/browser/adaptive_button.py b/GTG/gtk/browser/adaptive_button.py index db6fbac53e..7340dc2ee4 100644 --- a/GTG/gtk/browser/adaptive_button.py +++ b/GTG/gtk/browser/adaptive_button.py @@ -293,4 +293,3 @@ def do_set_child_property(self, child: Gtk.Widget, property_id: int, # We don't have any child properties anyway log.debug("Unimplemented child=%r, property_id=%r, value=%r, pspec=%r", child, property_id, value, pspec) - diff --git a/GTG/gtk/browser/delete_task.py b/GTG/gtk/browser/delete_task.py index bd6337b012..af83e07242 100644 --- a/GTG/gtk/browser/delete_task.py +++ b/GTG/gtk/browser/delete_task.py @@ -58,7 +58,7 @@ def recursive_list_tasks(self, tasklist, root): tasklist.append(root) [self.recursive_list_tasks(tasklist, t) - for t in root.children + for t in root.children if t not in tasklist] diff --git a/GTG/gtk/browser/main_window.py b/GTG/gtk/browser/main_window.py index 568e21506b..6520792ea4 100644 --- a/GTG/gtk/browser/main_window.py +++ b/GTG/gtk/browser/main_window.py @@ -491,7 +491,7 @@ def on_search(self, data): def on_save_search(self, action, param): query = self.search_entry.get_text() - name = re.sub(r'!(?=\w)+', '', query) + name = re.sub(r'!(?=\w)+', '', query) self.app.ds.saved_searches.new(name, query) @@ -773,9 +773,9 @@ def on_quickadd_activate(self, widget) -> None: tasks = self.get_pane().get_selection() for t in tasks: self.app.open_task(t) - + return - + tags = self.sidebar.selected_tags(names_only=True) data = quick_add.parse(text) @@ -975,20 +975,20 @@ def on_add_subtask(self, widget=None): def on_add_parent(self, widget=None): selection = self.get_pane().get_selection() - + if not selection: return - + parent = selection[0].parent # Check all tasks have the same parent if any(t.parent != parent for t in selection): return - + if parent: if parent.status == Status.ACTIVE: new_parent = self.app.ds.tasks.new(parent=parent.id) - + for task in selection: self.app.ds.tasks.refresh_lookup_cache() self.app.ds.tasks.unparent(task.id, parent.id) @@ -999,7 +999,7 @@ def on_add_parent(self, widget=None): for task in selection: self.app.ds.tasks.refresh_lookup_cache() self.app.ds.tasks.parent(task.id, new_parent.id) - + self.app.open_task(new_parent) self.get_pane().refresh() @@ -1210,43 +1210,43 @@ def on_sort_start(self, action, params) -> None: """Callback when changing task sorting.""" self.get_pane().set_sorter('Start') - + def on_sort_due(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Due') - + def on_sort_added(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Added') - + def on_sort_title(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Title') - + def on_sort_modified(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Modified') - + def on_sort_added(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Added') - + def on_sort_tags(self, action, params) -> None: """Callback when changing task sorting.""" - + self.get_pane().set_sorter('Tags') - + def close_all_task_editors(self, task_id): """ Including editors of subtasks """ @@ -1301,12 +1301,12 @@ def on_pane_switch(self, obj, pspec): # so their subtasks "exist" when switching # to actionable self.stack_switcher.get_stack().get_first_child().get_first_child().emit('expand-all') - + self.get_pane().set_filter_tags(set(self.sidebar.selected_tags())) self.sidebar.change_pane(current_pane) self.get_pane().sort_btn.set_popover(None) self.get_pane().sort_btn.set_popover(self.sort_menu) - + if search_query := self.search_entry.get_text(): self.get_pane().set_search_query(search_query) @@ -1358,24 +1358,24 @@ def get_selected_pane(self): def get_pane(self): """Get the selected pane.""" - + return self.stack_switcher.get_stack().get_visible_child().get_first_child() - - + + @GObject.Property(type=bool, default=True) def is_pane_open(self) -> bool: return self.get_selected_pane() == 'active' - + @GObject.Property(type=bool, default=False) def is_pane_actionable(self) -> bool: return self.get_selected_pane() == 'workview' - - + + @GObject.Property(type=bool, default=False) def is_pane_closed(self) -> bool: return self.get_selected_pane() == 'closed' - + def get_selected_tree(self, refresh: bool = False): return self.req.get_tasks_tree(name=self.get_selected_pane(), diff --git a/GTG/gtk/browser/quick_add.py b/GTG/gtk/browser/quick_add.py index c8bfa3f93d..b0c9c28ec9 100644 --- a/GTG/gtk/browser/quick_add.py +++ b/GTG/gtk/browser/quick_add.py @@ -113,4 +113,3 @@ def parse(text: str) -> Dict: result['title'] = text return result - diff --git a/GTG/gtk/browser/sidebar.py b/GTG/gtk/browser/sidebar.py index 78c166a2cd..f987ffed04 100644 --- a/GTG/gtk/browser/sidebar.py +++ b/GTG/gtk/browser/sidebar.py @@ -45,14 +45,14 @@ class SearchBox(Gtk.Box): def unwrap(row, expected_type): """Find an item in TreeRow widget (sometimes nested).""" - + item = row.get_item() - + while type(item) is not expected_type: item = item.get_item() return item - + # Shorthands BIND_FLAGS = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE @@ -61,7 +61,7 @@ def unwrap(row, expected_type): class Sidebar(Gtk.ScrolledWindow): """The sidebar widget""" - + def __init__(self, app, ds: Datastore, browser): super(Sidebar, self).__init__() @@ -97,7 +97,7 @@ def __init__(self, app, ds: Datastore, browser): self.general_box.select_row(self.general_box.get_row_at_index(0)) self.box_handle = self.general_box.connect('row-selected', self.on_general_box_selected) - + # ------------------------------------------------------------------------------- # Saved Searches Section # ------------------------------------------------------------------------------- @@ -144,7 +144,7 @@ def __init__(self, app, ds: Datastore, browser): self.filtered_tags = Gtk.FilterListModel() self.filtered_tags.set_model(ds.tags.tree_model) self.filtered_tags.set_filter(self.tags_filter) - + self.tag_selection = Gtk.MultiSelection.new(self.filtered_tags) self.tag_handle = self.tag_selection.connect('selection-changed', self.on_tag_selected) @@ -199,21 +199,21 @@ def __init__(self, app, ds: Datastore, browser): def refresh_tags(self) -> None: """Refresh tags list.""" - + self.filtered_tags.items_changed(0, 0, 0) self.tags_filter.changed(Gtk.FilterChange.DIFFERENT) def change_pane(self, pane: str) -> None: """Change pane for the tag list.""" - + self.tags_filter.pane = pane self.tags_filter.changed(Gtk.FilterChange.DIFFERENT) - + def on_tag_RMB_click(self, gesture, sequence) -> None: """Callback when right-clicking on a tag.""" - + menu = TagContextMenu(self.ds, self.app, gesture.get_widget().tag) menu.set_parent(gesture.get_widget()) menu.set_halign(Gtk.Align.START) @@ -306,7 +306,7 @@ def tags_setup_cb(self, factory, listitem, user_data=None) -> None: # Drag ... - source = Gtk.DragSource() + source = Gtk.DragSource() source.connect('prepare', self.prepare) source.connect('drag-begin', self.drag_begin) source.connect('drag-end', self.drag_end) @@ -376,11 +376,11 @@ def tags_bind_cb(self, signallistitem, listitem, user_data=None) -> None: item.bind_property('task_count_actionable', actionable_count_label, 'label', BIND_FLAGS), item.bind_property('task_count_closed', closed_count_label, 'label', BIND_FLAGS), ] - + self.browser.bind_property('is_pane_open', open_count_label, 'visible', BIND_FLAGS) self.browser.bind_property('is_pane_actionable', actionable_count_label, 'visible', BIND_FLAGS) self.browser.bind_property('is_pane_closed', closed_count_label, 'visible', BIND_FLAGS) - + if item.parent: parent = item @@ -404,11 +404,11 @@ def tags_unbind_cb(self, signallistitem, listitem, user_data=None) -> None: """Clean up bindings made in tags_bind_cb""" for binding in listitem.bindings: binding.unbind() - + def unselect_tags(self) -> None: """Clear tags selection""" - + with signal_block(self.tag_selection, self.tag_handle): self.tag_selection.unselect_all() @@ -440,7 +440,7 @@ def on_general_box_selected(self, listbox, user_data=None): self.app.browser.get_pane().set_filter_tags() elif index == 1: self.app.browser.get_pane().set_filter_notags() - + def on_search_selected(self, model, position, user_data=None): """Callback when selecting a saved search""" @@ -459,15 +459,15 @@ def selected_tags(self, names_only: bool = False) -> list: selection = self.tag_selection.get_selection() result, iterator, _ = Gtk.BitsetIter.init_first(selection) selected = [] - + while iterator.is_valid(): val = iterator.get_value() item = unwrap(self.tag_selection.get_item(val), Tag) selected.append(item.name if names_only else item) iterator.next() - + return selected - + def on_tag_selected(self, model, position, n_items, user_data=None): """Callback when selecting one or more tags""" @@ -483,14 +483,14 @@ def on_tag_reveal(self, event) -> None: """Callback for clicking on the tags title button (revealer).""" self.revealer.set_reveal_child(not self.revealer.get_reveal_child()) - + def on_search_reveal(self, event) -> None: """Callback for clicking on the search title button (revealer).""" self.searches_revealer.set_reveal_child(not self.searches_revealer.get_reveal_child()) - - + + def searches_setup_cb(self, factory, listitem, user_data=None) -> None: """Setup for a row in the saved searches listview""" @@ -573,14 +573,14 @@ def drag_end(self, source, drag, unused): def check_parent(self, value, target) -> bool: """Check to parenting a parent to its own children""" - + item = target while item.parent: if item.parent == value: return False - + item = item.parent - + return True @@ -593,12 +593,12 @@ def drag_drop(self, target, value, x, y): if value.parent: self.ds.tags.unparent(value.id, value.parent.id) - + self.ds.tags.parent(value.id, dropped.id) self.ds.refresh_tag_stats() self.ds.tags.tree_model.emit('items-changed', 0, 0, 0) self.refresh_tags() - + def drop_enter(self, target, x, y, user_data=None): @@ -639,11 +639,11 @@ def multi_task_drag_drop(self, target, tasklist, x, y): def notify_task(self, task: Task) -> None: """Notify that tasks props have changed.""" - + task.notify('row_css') task.notify('icons') task.notify('tag_colors') - + def on_toplevel_tag_drop(self, drop_target, tag, x, y): if tag.parent: @@ -656,7 +656,7 @@ def on_toplevel_tag_drop(self, drop_target, tag, x, y): expander.activate_action('listitem.toggle-expand') except RuntimeError: pass - + self.ds.tags.tree_model.emit('items-changed', 0, 0, 0) return True else: diff --git a/GTG/gtk/browser/sidebar_context_menu.py b/GTG/gtk/browser/sidebar_context_menu.py index c7f356a440..3e8289a62c 100644 --- a/GTG/gtk/browser/sidebar_context_menu.py +++ b/GTG/gtk/browser/sidebar_context_menu.py @@ -18,8 +18,8 @@ """ sidebar_context_menu: -Implements a context (pop-up) menu for the tag or saved search item in the -sidebar. It is supposed to be a generic sidebar context for all kind of item +Implements a context (pop-up) menu for the tag or saved search item in the +sidebar. It is supposed to be a generic sidebar context for all kind of item displayed there. Also, it is supposed to handle more complex menus (with non-std widgets, like a color picker). @@ -44,7 +44,7 @@ def __init__(self, ds, app, tag): actions = [ ("edit_tag", self.on_mi_cc_activate), ("generate_tag_color", self.on_mi_ctag_activate), - ("delete_tag", lambda w, a, p: + ("delete_tag", lambda w, a, p: self.app.browser.on_delete_tag_activate([self.tag])) ] @@ -92,7 +92,7 @@ def __init__(self, ds, app, search): actions = [ ("edit_search", lambda w, a, p: app.open_search_editor(search)), - ("delete_search", lambda w, a, p: + ("delete_search", lambda w, a, p: ds.saved_searches.remove(self.search.id)) ] diff --git a/GTG/gtk/browser/tag_editor.py b/GTG/gtk/browser/tag_editor.py index 4cdd51bfcd..cc921ff742 100644 --- a/GTG/gtk/browser/tag_editor.py +++ b/GTG/gtk/browser/tag_editor.py @@ -245,7 +245,7 @@ def _apply(self): for t in self.app.ds.tasks.lookup.values(): t.rename_tag(self.tag.name, self.tag_name) - + self.tag.name = self.tag_name self.app.ds.refresh_task_count() @@ -274,7 +274,7 @@ def _random_color(self, widget: GObject.Object): c.red, c.green, c.blue, c.alpha = 1.0, 1.0, 1.0, 1.0 c.parse('#' + color) self.tag_rgba = c - + @Gtk.Template.Callback('activate_color') def _activate_color(self, widget: GObject.Object): """ diff --git a/GTG/gtk/browser/tag_pill.py b/GTG/gtk/browser/tag_pill.py index d2bf752240..f786e5a225 100644 --- a/GTG/gtk/browser/tag_pill.py +++ b/GTG/gtk/browser/tag_pill.py @@ -23,9 +23,9 @@ class TagPill(Gtk.DrawingArea): """Color pill widget for tags.""" - + __gtype_name__ = 'TagPill' - + default_color = '#666666' def __init__(self, radius: int = 5): @@ -53,7 +53,7 @@ def set_colors(self, value) -> None: for color in value.split(','): rgba = Gdk.RGBA() valid = rgba.parse(color) - + if valid: self.colors.append(rgba) else: @@ -68,13 +68,13 @@ def set_colors(self, value) -> None: self.queue_draw() - def draw_rect(self, context, x: int, w: int, h: int, + def draw_rect(self, context, x: int, w: int, h: int, color: Gdk.RGBA = None) -> None: """Draw a single color rectangle.""" - + y = 0 # No change in Y axis r = self.radius - + if color: context.set_source_rgba(color.red, color.green, color.blue) @@ -117,7 +117,7 @@ def draw_rect(self, context, x: int, w: int, h: int, def do_draw_function(self, area, context, w, h, user_data=None): """Drawing callback.""" - + for i, color in enumerate(self.colors): x = i * (16 + 6) self.draw_rect(context, x, 16, h, color) diff --git a/GTG/gtk/browser/task_pane.py b/GTG/gtk/browser/task_pane.py index 25d436b09d..2108bf7fc0 100644 --- a/GTG/gtk/browser/task_pane.py +++ b/GTG/gtk/browser/task_pane.py @@ -41,7 +41,7 @@ def __init__(self, config, is_actionable=False): self.expander.set_margin_end(6) self.expander.add_css_class('arrow-only-expander') - self.check = Gtk.CheckButton() + self.check = Gtk.CheckButton() self.check.set_margin_end(6) self.append(self.expander) @@ -64,7 +64,7 @@ def set_has_children(self, value) -> bool: self.expander.set_visible(value) if value: - widget = self.expander + widget = self.expander else: widget = self.check @@ -84,7 +84,7 @@ def set_has_children(self, value) -> bool: widget.set_margin_start(indent + (21 * depth)) else: widget.set_margin_start(indent) - + @GObject.Property(type=bool, default=True) def is_active(self) -> None: @@ -97,7 +97,7 @@ def set_is_active(self, value) -> bool: self.remove_css_class('closed-task') else: self.add_css_class('closed-task') - + @GObject.Property(type=str) def row_css(self) -> None: @@ -115,7 +115,7 @@ def set_row_css(self, value) -> None: return except AttributeError: return - + val = str.encode(value) self.provider = Gtk.CssProvider() @@ -125,9 +125,9 @@ def set_row_css(self, value) -> None: def unwrap(row, expected_type): """Find an item in TreeRow widget (sometimes nested).""" - + item = row.get_item() - + while type(item) is not expected_type: item = item.get_item() @@ -136,7 +136,7 @@ def unwrap(row, expected_type): class TaskPane(Gtk.ScrolledWindow): """The task pane widget""" - + def __init__(self, browser, pane): super(TaskPane, self).__init__() @@ -148,7 +148,7 @@ def __init__(self, browser, pane): self.set_vexpand(True) self.set_hexpand(True) - + # ------------------------------------------------------------------------------- # Title # ------------------------------------------------------------------------------- @@ -159,17 +159,17 @@ def __init__(self, browser, pane): title_box.set_margin_bottom(32) title_box.set_margin_start(24) title_box.set_margin_end(24) - + self.title = Gtk.Label() self.title.set_halign(Gtk.Align.START) self.title.set_hexpand(True) self.title.add_css_class('title-1') title_box.append(self.title) - + self.sort_btn = Gtk.MenuButton() self.sort_btn.set_icon_name('view-more-symbolic') self.sort_btn.add_css_class('flat') - + title_box.append(self.sort_btn) @@ -178,14 +178,14 @@ def __init__(self, browser, pane): # ------------------------------------------------------------------------------- self.search_filter = SearchTaskFilter(self.ds, pane) - self.task_filter = TaskPaneFilter(self.app.ds, pane) + self.task_filter = TaskPaneFilter(self.app.ds, pane) self.filtered = Gtk.FilterListModel() self.filtered.set_model(self.app.ds.tasks.tree_model) self.filtered.set_filter(self.task_filter) self.sort_model = Gtk.TreeListRowSorter() - + self.main_sorter = Gtk.SortListModel() self.main_sorter.set_model(self.filtered) self.main_sorter.set_sorter(self.sort_model) @@ -234,7 +234,7 @@ def set_title(self) -> None: self.title.set_text(_('Actionable Tasks')) if self.pane == 'closed': self.title.set_text(_('All Closed Tasks')) - + else: tags = ', '.join('@' + t.name for t in self.task_filter.tags) @@ -244,7 +244,7 @@ def set_title(self) -> None: self.title.set_text(_('{0} (Actionable)'.format(tags))) if self.pane == 'closed': self.title.set_text(_('{0} (Closed)'.format(tags))) - + def set_search_query(self, query) -> None: """Change tasks filter.""" @@ -299,7 +299,7 @@ def set_filter_notags(self, tags=[]) -> None: def refresh(self): """Refresh the task filter""" - + self.task_filter.changed(Gtk.FilterChange.DIFFERENT) self.main_sorter.items_changed(0,0,0) @@ -327,13 +327,13 @@ def set_sorter(self, method=None) -> None: def on_listview_activated(self, listview, position, user_data = None): """Callback when double clicking on a row.""" - + self.app.browser.on_edit_active_task() def on_key_released(self, controller, keyval, keycode, state): """Callback when a key is released. """ - + is_enter = keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter) is_left = keyval == Gdk.KEY_Left is_right = keyval == Gdk.KEY_Right @@ -348,14 +348,14 @@ def on_key_released(self, controller, keyval, keycode, state): def select_last(self) -> None: """Select last position in the task list.""" - + position = self.app.ds.tasks.tree_model.get_n_items() self.task_selection.select_item(position - 1, True) - + def select_task(self, task: Task) -> None: """Select a task in the list.""" - + position = None for i in range(self.main_sorter.get_n_items()): @@ -375,7 +375,7 @@ def get_selection(self, indices: bool = False) -> list: selection = self.task_selection.get_selection() result, iterator, _ = Gtk.BitsetIter.init_first(selection) selected = [] - + while iterator.is_valid(): val = iterator.get_value() @@ -383,7 +383,7 @@ def get_selection(self, indices: bool = False) -> list: selected.append(val) else: selected.append(unwrap(self.task_selection.get_item(val), Task)) - + iterator.next() return selected @@ -394,12 +394,12 @@ def expand_selected(self, expand) -> None: selection = self.task_selection.get_selection() result, iterator, _ = Gtk.BitsetIter.init_first(selection) - + while iterator.is_valid(): val = iterator.get_value() row = self.task_selection.get_item(val) row.set_expanded(expand) - + iterator.next() @@ -412,12 +412,12 @@ def get_selected_number(self) -> int: def on_checkbox_toggled(self, button, task=None): """Callback when clicking a checkbox.""" - + if task.status == Status.DISMISSED: task.toggle_dismiss() else: task.toggle_active() - + task.notify('is_active') self.task_filter.changed(Gtk.FilterChange.DIFFERENT) @@ -426,18 +426,18 @@ def task_setup_cb(self, factory, listitem, user_data=None): """Setup widgets for rows""" box = TaskBox(self.app.config, self.pane == 'workview') - label = Gtk.Label() - separator = Gtk.Separator() - icons = Gtk.Label() + label = Gtk.Label() + separator = Gtk.Separator() + icons = Gtk.Label() color = TagPill() - due = Gtk.Label() - due_icon = Gtk.Image.new_from_icon_name('alarm-symbolic') - start = Gtk.Label() - start_icon = Gtk.Image.new_from_icon_name('media-playback-start-symbolic') - recurring_icon = Gtk.Label() + due = Gtk.Label() + due_icon = Gtk.Image.new_from_icon_name('alarm-symbolic') + start = Gtk.Label() + start_icon = Gtk.Image.new_from_icon_name('media-playback-start-symbolic') + recurring_icon = Gtk.Label() color.set_size_request(16, 16) - + color.set_vexpand(False) color.set_valign(Gtk.Align.CENTER) @@ -471,7 +471,7 @@ def on_notify_visibility(obj, gparamstring): start.set_margin_end(12) # DnD stuff - source = Gtk.DragSource() + source = Gtk.DragSource() source.connect('prepare', self.drag_prepare) source.connect('drag-begin', self.drag_begin) source.connect('drag-end', self.drag_end) @@ -572,7 +572,7 @@ def drag_prepare(self, source, x, y): """Callback to prepare for the DnD operation""" selection = self.get_selection() - + if len(selection) > 1: data = Gio.ListStore() data.splice(0, 0, selection) @@ -626,14 +626,14 @@ def drop_enter(self, target, x, y, user_data=None): def check_parent(self, value, target) -> bool: """Check to parenting a parent to its own children""" - + item = target while item.parent: if item.parent == value: return False - + item = item.parent - + return True @@ -647,7 +647,7 @@ def drag_drop(self, target, task, x, y): if task.parent: self.ds.tasks.unparent(task.id, task.parent.id) - + self.ds.tasks.parent(task.id, dropped.id) self.refresh() self.emit('collapse-all') @@ -657,7 +657,7 @@ def drag_drop(self, target, task, x, y): def on_task_RMB_click(self, gesture, sequence) -> None: """Callback when right-clicking on an open task.""" - widget = gesture.get_widget() + widget = gesture.get_widget() task = widget.task if self.get_selected_number() <= 1: @@ -674,7 +674,7 @@ def on_task_RMB_click(self, gesture, sequence) -> None: rect = Gdk.Rectangle() rect.x = x rect.y = y - + menu.set_pointing_to(rect) menu.popup() diff --git a/GTG/gtk/data/main_window.ui b/GTG/gtk/data/main_window.ui index b12dbd511b..251b8a0b08 100644 --- a/GTG/gtk/data/main_window.ui +++ b/GTG/gtk/data/main_window.ui @@ -221,7 +221,7 @@ Sort by Modified Date win.sort_by_modified - + Sort by Tags win.sort_by_tags diff --git a/GTG/gtk/data/style.css b/GTG/gtk/data/style.css index 951c688ba3..a6101f1f4a 100644 --- a/GTG/gtk/data/style.css +++ b/GTG/gtk/data/style.css @@ -10,8 +10,8 @@ border-top: 1px solid rgb(213, 208, 204); } -/* - We have to get move the padding into the boxes +/* + We have to get move the padding into the boxes so the background color of the rows reaches all the way to the edges */ @@ -21,7 +21,7 @@ .task-list row > box { padding: 15px 16px; -} +} /* -------------------------------------------------------------------------------- * ERROR HANDLER @@ -87,10 +87,10 @@ textview check { .hide { opacity: 0.2; } .color-pill { - background: #999; - padding: 0; - min-height: 16px; - min-width: 16px; + background: #999; + padding: 0; + min-height: 16px; + min-width: 16px; border: none; } @@ -104,4 +104,4 @@ textview check { .closed-task { opacity: 0.5; -} \ No newline at end of file +} diff --git a/GTG/gtk/editor/editor.py b/GTG/gtk/editor/editor.py index 87092c969a..bb19ddf23a 100644 --- a/GTG/gtk/editor/editor.py +++ b/GTG/gtk/editor/editor.py @@ -301,7 +301,7 @@ def sync_tag_store(self, widget=None): self.tag_store.clear() used = set() - + for used_tag in self.task.tags: # First parameter marks the tag as used self.tag_store.append((True, used_tag.name)) @@ -310,7 +310,7 @@ def sync_tag_store(self, widget=None): for tag_name in self.ds.tags.lookup_names.keys(): if tag_name not in used: self.tag_store.append((False, tag_name)) - + def sync_repeat_button(self, object=None, pspec=None): if self.recurring_menu.is_task_recurring: @@ -568,7 +568,7 @@ def reload_editor(self): textview = self.textview task_text = task.content task_title = task.title - + textview.set_text(f"{task_title}\n") if task_text: @@ -709,7 +709,7 @@ def open_subtask(self, tid): task = self.ds.tasks.lookup[tid] self.app.open_task(task) - + if task.parent: self.app.close_task(task.parent.id) @@ -718,19 +718,19 @@ def new_subtask(self, title=None, tid=None): self.app.ds.tasks.parent(self.task.id, tid) return self.app.ds.tasks.lookup[tid] - + elif title and not tid: t = self.app.ds.tasks.new(title, self.task.id) tid = t.id self.app.ds.tasks.refresh_lookup_cache() return t - + elif title and tid: t = self.app.ds.tasks.new(title, self.task.id) t.id = tid self.app.ds.tasks.refresh_lookup_cache() - + return t def remove_subtask(self, tid): @@ -875,7 +875,7 @@ def close(self, action=None, param=None): def is_new(self) -> bool: - return (self.task.title == DEFAULT_TITLE + return (self.task.title == DEFAULT_TITLE and self.textview.get_text() == '') diff --git a/GTG/gtk/editor/taskview.py b/GTG/gtk/editor/taskview.py index a1574739b5..23dfb3f279 100644 --- a/GTG/gtk/editor/taskview.py +++ b/GTG/gtk/editor/taskview.py @@ -239,7 +239,7 @@ def process(self,mask_current_word:bool=False) -> None: cur_pos = cursor_iter.get_line_offset() text = TaskView.mask_current_word(text,cur_pos) - self.detect_tag(text, start) + self.detect_tag(text, start) start.forward_line() diff --git a/GTG/gtk/errorhandler.py b/GTG/gtk/errorhandler.py index 3d837cd9f3..7afa18df30 100644 --- a/GTG/gtk/errorhandler.py +++ b/GTG/gtk/errorhandler.py @@ -268,4 +268,3 @@ def restore_excepthook() -> bool: return False sys.excepthook = original_excepthook original_excepthook = None - diff --git a/GTG/gtk/general_preferences.py b/GTG/gtk/general_preferences.py index d6e06e6f36..6b26a5e3a0 100644 --- a/GTG/gtk/general_preferences.py +++ b/GTG/gtk/general_preferences.py @@ -178,4 +178,4 @@ def on_dark_mode_toggled(self, widget, state): """Toggle darkmode.""" self.config.set("dark_mode", state) - self.app.toggle_darkmode(state) \ No newline at end of file + self.app.toggle_darkmode(state) diff --git a/GTG/gtk/tag_completion.py b/GTG/gtk/tag_completion.py index febe71c561..f9ee28cfb0 100644 --- a/GTG/gtk/tag_completion.py +++ b/GTG/gtk/tag_completion.py @@ -111,7 +111,7 @@ def _on_tag_added(self, tag): def _try_delete(self, name): """ Delete an item if it is in the list """ - + for row in self.tags: if row[0] == name: self.tags.remove(row.iter) diff --git a/GTG/plugins/export/export_templates/template_pocketmod.tex b/GTG/plugins/export/export_templates/template_pocketmod.tex index 592c323027..9d88ac80a4 100644 --- a/GTG/plugins/export/export_templates/template_pocketmod.tex +++ b/GTG/plugins/export/export_templates/template_pocketmod.tex @@ -28,7 +28,7 @@ #end if #if $task.has_due_date - \textit{Due: $task.due_date}\\ + \textit{Due: $task.due_date}\\ #end if #if $task.has_subtasks diff --git a/GTG/plugins/export/export_templates/template_sexy.html b/GTG/plugins/export/export_templates/template_sexy.html index 97e53d8ae8..2d11c35a6b 100644 --- a/GTG/plugins/export/export_templates/template_sexy.html +++ b/GTG/plugins/export/export_templates/template_sexy.html @@ -31,12 +31,12 @@