-
Notifications
You must be signed in to change notification settings - Fork 28
Implemented bookmarking feature to add directories to places #352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0768324
297a6cc
c206366
4adb482
66e2402
7bb780b
adf87ac
f555f0c
06cf20e
f687b15
e250189
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # devices.py | ||
| # | ||
| # Copyright 2025 Jason Beetham | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| from gi.repository import GLib, GObject, Gtk | ||
|
|
||
| import json, os | ||
|
|
||
|
|
||
| class PortfolioBookmarks(GObject.GObject): | ||
| __gtype_name__ = "PortfolioBookmarks" | ||
|
|
||
| __gsignals__ = { | ||
| "add-bookmark": (GObject.SignalFlags.RUN_LAST, None, (str,)), | ||
| "remove-bookmark": (GObject.SignalFlags.RUN_LAST, None, (str,)), | ||
| } | ||
|
|
||
| def __init__(self): | ||
| GObject.GObject.__init__(self) | ||
|
|
||
| self.bookmarked = set() | ||
| self._portfolio_config_path = os.path.join( | ||
| GLib.get_user_config_dir(), "portfolio" | ||
| ) | ||
| self._bookmark_path = os.path.join(self._portfolio_config_path, "bookmarks") | ||
|
|
||
| try: | ||
| with open(self._bookmark_path, "r") as f: | ||
| data = f.read() | ||
| if not data is None: | ||
| bookmarked = json.loads(data) | ||
| for val in bookmarked: | ||
| if type(val) is not str: | ||
| bookmarked = [] | ||
| break | ||
|
|
||
| for path in bookmarked: | ||
| self._add_bookmark(path) | ||
|
|
||
| except (json.JSONDecodeError, OSError, IOError): | ||
| self.bookmarked = set() | ||
|
|
||
| def _add_bookmark(self, path): | ||
| if path not in self.bookmarked: | ||
| self.bookmarked.add(path) | ||
| self.emit("add-bookmark", path) | ||
|
|
||
| def _delete_bookmark(self, path): | ||
| if path in self.bookmarked: | ||
| self.bookmarked.remove(path) | ||
| self.emit("remove-bookmark", path) | ||
|
|
||
| def is_bookmarked(self, path): | ||
| return path in self.bookmarked | ||
|
|
||
| def _save_bookmarks(self): | ||
| os.makedirs(self._portfolio_config_path, exist_ok=True) | ||
| with open(self._bookmark_path, "w") as f: | ||
| paths = [bookmark for bookmark in self.bookmarked] | ||
| json.dump(paths, f) | ||
|
|
||
| def toggle_bookmark(self, button, path): | ||
| if self.is_bookmarked(path): | ||
| self._delete_bookmark(path) | ||
| else: | ||
| self._add_bookmark(path) | ||
| self._save_bookmarks() | ||
|
|
||
|
|
||
| class PortfolioBookmarkButton(Gtk.Button): | ||
| __gtype_name__ = "PortfolioBookmarkButton" | ||
|
|
||
| def __init__(self, bookmarks): | ||
| Gtk.Button.__init__(self) | ||
| self.connect("clicked", self._on_bookmark_toggle) | ||
| self._bookmarks = bookmarks | ||
| self.props.icon_name = "bookmark-filled-symbolic" | ||
|
|
||
| def _change_icon(self): | ||
| if self._bookmarks.is_bookmarked(self._path): | ||
| self.props.icon_name = "bookmark-filled-symbolic" | ||
| else: | ||
| self.props.icon_name = "bookmark-outline-symbolic" | ||
|
|
||
| def _on_bookmark_toggle(self, button): | ||
| self._bookmarks.toggle_bookmark(button, self._path) | ||
| self._change_icon() | ||
|
|
||
| @property | ||
| def path(self): | ||
| self._path | ||
|
|
||
| @path.setter | ||
| def path(self, path): | ||
| self._path = path | ||
| self._change_icon() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| from . import logger | ||
| from .place import PortfolioPlace | ||
| from .devices import PortfolioDevices | ||
| from .bookmarks import PortfolioBookmarks | ||
| from .translation import gettext as _ | ||
|
|
||
|
|
||
|
|
@@ -60,21 +61,23 @@ class PortfolioPlaces(Gtk.Stack): | |
| VIDEOS_PERMISSION = ["host", "home", "xdg-videos"] | ||
| TRASH_PERMISSION = ["host", "home"] | ||
|
|
||
| def __init__(self, **kargs): | ||
| def __init__(self, bookmarks, **kargs): | ||
| super().__init__(**kargs) | ||
| self._setup() | ||
| self._setup(bookmarks) | ||
|
|
||
| def _setup(self): | ||
| def _setup(self, bookmarks): | ||
| self.props.visible = True | ||
| self.props.transition_type = Gtk.StackTransitionType.CROSSFADE | ||
|
|
||
| self._bookmarks = bookmarks | ||
| self._permissions = None | ||
|
|
||
| self._devices = PortfolioDevices() | ||
| self._devices.connect("added", self._on_device_added) | ||
| self._devices.connect("removed", self._on_device_removed) | ||
| self._devices.connect("encrypted-added", self._on_encrypted_added) | ||
|
|
||
| self._bookmarks = PortfolioBookmarks() | ||
|
|
||
| # begin UI structure | ||
|
|
||
| self._groups_box = Gtk.Box() | ||
|
|
@@ -94,6 +97,10 @@ def _setup(self): | |
| self._devices_group.props.visible = True | ||
| self._devices_group.get_style_context().add_class("devices-group") | ||
|
|
||
| self._bookmarks_group = Adw.PreferencesGroup() | ||
| self._bookmarks_group.props.title = _("Bookmarks") | ||
| self._bookmarks_group.props.visible = True | ||
|
|
||
| # places | ||
|
|
||
| if self._has_permission_for(self.HOME_PERMISSION): | ||
|
|
@@ -166,10 +173,15 @@ def _setup(self): | |
|
|
||
| self._groups_box.append(self._places_group) | ||
| self._groups_box.append(self._devices_group) | ||
| self._groups_box.append(self._bookmarks_group) | ||
|
|
||
| self._places_listbox = utils.find_child_by_id(self._places_group, "listbox") | ||
| self._devices_listbox = utils.find_child_by_id(self._devices_group, "listbox") | ||
| self._bookmarks_listbox = utils.find_child_by_id( | ||
| self._bookmarks_group, "listbox" | ||
| ) | ||
|
|
||
| self._add_accessible_bookmarks() | ||
| # no places message | ||
|
|
||
| message = Gtk.Label() | ||
|
|
@@ -192,6 +204,9 @@ def _setup(self): | |
|
|
||
| self._update_visibility() | ||
|
|
||
| bookmarks.connect("add-bookmark", self._on_bookmark_added) | ||
| bookmarks.connect("remove-bookmark", self._on_bookmark_removed) | ||
|
|
||
| def _update_visibility(self): | ||
| self._update_stack_visibility() | ||
| self._update_places_group_visibility() | ||
|
|
@@ -214,6 +229,10 @@ def _update_device_group_visibility(self): | |
| visible = len(list(self._devices_listbox)) >= 1 | ||
| self._devices_group.props.visible = visible | ||
|
|
||
| def _update_bookmarks_group_visibility(self): | ||
| visible = len(list(self._bookmarks_listbox)) >= 1 | ||
| self._bookmarks_group.props.visible = visible | ||
|
|
||
| def _get_permissions(self): | ||
| if self._permissions is not None: | ||
| return self._permissions | ||
|
|
@@ -384,3 +403,37 @@ def _on_encrypted_eject_finished(self, encrypted, success): | |
| self._on_device_removed(None, encrypted) | ||
| else: | ||
| self.emit("failed", None) | ||
|
|
||
| def _find_place_by_path(self, listbox, path): | ||
| for place in listbox: | ||
| if place.path == path: | ||
| return place | ||
| return None | ||
|
|
||
| def _on_bookmark_removed(self, bookmark, path): | ||
| place = self._find_place_by_path(self._bookmarks_listbox, path) | ||
| if place is not None: | ||
| self._bookmarks_group.remove(place) | ||
|
|
||
| def _on_bookmark_added(self, bookmark, path): | ||
| if self._find_place_by_path(self._bookmarks_listbox, path) is None: | ||
| self._add_bookmark_place(path) | ||
|
|
||
| def _add_accessible_bookmarks(self): | ||
| for path in self._bookmarks.bookmarked: | ||
| not_added = self._find_place_by_path(self._bookmarks_listbox, path) is None | ||
| if not_added and os.path.isdir( | ||
| path | ||
| ): # Might be a bookmarked path on a unmounted device | ||
| self._add_bookmark_place(path) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need to check if it's in widget? If you're worried about duplicates, that's something you can deal with in the mode (e.g., using a set instead of a list). Same thing for available or unavailable bookmarks. So this ideally look like: for path in self._bookmarks.bookmarked:
self._add_bookmark_place(path)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One may connect a device which has paths stored in their bookmarks, the check was to not repeat add to the collection. As on start up it can be come disjointed from the stored list as there may not be bookmarks accessible at the time of opening. |
||
|
|
||
| def _add_bookmark_place(self, path): | ||
| name = os.path.basename(path) | ||
| place = self._add_place( | ||
| self._bookmarks_group, | ||
| "bookmark-filled-symbolic", | ||
| name, | ||
| path, | ||
| ) | ||
| place.remove_bookmark.props.visible = True | ||
| place.remove_bookmark.connect("clicked", self._on_bookmark_removed, path) | ||
Uh oh!
There was an error while loading. Please reload this page.