diff --git a/data/entry_row.ui b/data/entry_row.ui
new file mode 100644
index 0000000000000000000000000000000000000000..0462dfa1f9a9e87b03dbfdb5f16e6aae8958acdb
--- /dev/null
+++ b/data/entry_row.ui
@@ -0,0 +1,119 @@
+
+
+
+
+
+
diff --git a/data/passwordsafe.gresource.xml b/data/passwordsafe.gresource.xml
index eaacb4cc95b6b5c92946d700d9a824498bde2278..52ae13ce9498197b1590f22083e5c8ed41db716a 100644
--- a/data/passwordsafe.gresource.xml
+++ b/data/passwordsafe.gresource.xml
@@ -11,6 +11,7 @@
created_database.ui
database_settings_dialog.ui
entry_page.ui
+ entry_row.ui
group_page.ui
unlock_database.ui
unlocked_database.ui
diff --git a/data/unlocked_database.ui b/data/unlocked_database.ui
index 93341558fc6853f49b9e18e8c3dd64fd8b0bec24..8bf4c4606913d056989d715dbe45a99a831120f3 100644
--- a/data/unlocked_database.ui
+++ b/data/unlocked_database.ui
@@ -267,166 +267,6 @@
-
- 1
- True
- False
- baseline
-
-
- 60
- True
- False
-
-
- True
- False
- 10
- vertical
-
-
- True
- True
- False
- center
- center
- True
- True
-
-
- False
- True
- 0
-
-
-
-
- False
- True
- 0
-
-
-
-
- 32
- 32
- True
- False
- False
- False
- False
- center
- center
- 10
- 10
- none
-
-
- True
- False
- 16
- application-x-executable-symbolic
-
-
-
-
-
- False
- False
- 1
-
-
-
-
- True
- False
- vertical
-
-
- name_label
- True
- False
- start
- end
- False
- True
- Title not specified
- end
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- True
- vertical
-
-
- entry_subtitle_label
- True
- False
- start
- start
- False
- True
- No username specified
- end
-
-
- False
- True
- 0
-
-
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 2
-
-
-
-
- ListBoxRowButton
- True
- True
- True
- end
- center
- 5
- 8
-
-
- True
- False
- edit-copy-symbolic
-
-
-
-
- False
- False
- end
- 3
-
-
-
-
-
True
False
@@ -440,7 +280,6 @@
- 1
True
False
baseline
@@ -450,30 +289,18 @@
True
False
-
- True
- False
- 10
-
-
- True
- True
- False
- center
- center
- True
- True
-
-
- False
- True
- 0
-
-
+
+ True
+ False
+ center
+ center
+ True
+ True
+ 8
False
- True
+ False
0
@@ -509,7 +336,6 @@
- 1
True
False
True
@@ -598,6 +424,9 @@
+
+ 1
+
@@ -1195,7 +1024,7 @@
2
-
+
True
False
diff --git a/passwordsafe/color_widget.py b/passwordsafe/color_widget.py
index 343846649cc784a46a75e225da9f0320ebeaab6c..51b1f5cab9442945afea1f7617b47e0f10c6ae40 100644
--- a/passwordsafe/color_widget.py
+++ b/passwordsafe/color_widget.py
@@ -82,8 +82,7 @@ class ColorEntryRow(Gtk.ListBoxRow): # pylint: disable=too-few-public-methods
self._scrolled_page: ScrolledPage = scrolled_page
self._entry_uuid: UUID = entry_uuid
- self._selected_color: str = self._db_manager.get_entry_color_from_entry_uuid(
- entry_uuid)
+ self._selected_color: str = self._db_manager.get_entry_color(entry_uuid)
for color in Color:
active: bool = (self._selected_color == color.value)
diff --git a/passwordsafe/database_manager.py b/passwordsafe/database_manager.py
index a8f5502546ed52ba287adfbf6e8b0196c554076a..87e92444f199b6c22e29e9fcb2564e71d48f7b6f 100644
--- a/passwordsafe/database_manager.py
+++ b/passwordsafe/database_manager.py
@@ -182,6 +182,8 @@ class DatabaseManager(GObject.GObject):
def get_entry_name(self, data: Union[Entry, UUID]) -> str:
"""Get entry name from an uuid or an entry
+ Passing in an Entry is more performant than passing in a UUID
+ as we avoid having to look up the entry.
:param data: UUID or Entry
:returns: entry name or an empty string if it does not exist
:rtype: str
@@ -201,6 +203,8 @@ class DatabaseManager(GObject.GObject):
def get_entry_username(self, data: Union[Entry, UUID]) -> str:
"""Get an entry username from an entry or an uuid
+ Passing in an Entry is more performant than passing in a UUID
+ as we avoid having to look up the entry.
:param data: entry or uuid
:returns: entry username or an empty string if it does not exist
:rtype: str
@@ -220,6 +224,8 @@ class DatabaseManager(GObject.GObject):
def get_entry_password(self, data: Union[Entry, UUID]) -> str:
"""Get an entry password from an entry or an uuid
+ Passing in an Entry is more performant than passing in a UUID
+ as we avoid having to look up the entry.
:param data: entry or uuid
:returns: entry password or an empty string if it does not exist
:rtype: str
@@ -239,6 +245,8 @@ class DatabaseManager(GObject.GObject):
def get_entry_url(self, data: Union[Entry, UUID]) -> str:
"""Get an entry url from an entry or an uuid
+ Passing in an Entry is more performant than passing in a UUID
+ as we avoid having to look up the entry.
:param data: UUID or Entry
:returns: entry url or an empty string if it does not exist
:rtype: str
@@ -255,9 +263,27 @@ class DatabaseManager(GObject.GObject):
return entry.url or ""
- # Return the belonging color for an entry uuid
- def get_entry_color_from_entry_uuid(self, uuid):
- entry = self.db.find_entries(uuid=uuid, first=True)
+ def get_entry_color(self, data: Union[Entry, UUID]) -> Union[str, Color]:
+ """Get an entry color from an entry or an uuid
+
+ Passing in an Entry is more performant than passing in a UUID
+ as we avoid having to look up the entry.
+ :param data: UUID or Entry
+ :returns: entry color as str or Color.NONE.value
+ :rtype: str
+ """
+ if isinstance(data, UUID):
+ entry: Entry = self.db.find_entries(uuid=data, first=True)
+ if not entry:
+ logging.warning(
+ "Trying to look up a non-existing UUID %s, this should "
+ "never happen",
+ data,
+ )
+ return Color.NONE.value
+ else:
+ entry = data
+
if entry.get_custom_property("color_prop_LcljUMJZ9X") is None:
return Color.NONE.value
else:
@@ -611,19 +637,6 @@ class DatabaseManager(GObject.GObject):
# Read Database
#
- def get_groups_in_root(self):
- return self.db.root_group.subgroups
-
- def get_groups_in_folder(self, uuid):
- """Return list of all subgroups in a group"""
- folder = self.get_group(uuid)
- return folder.subgroups
-
- def get_entries_in_folder(self, uuid):
- """Return list of all entries in a group"""
- parent_group = self.get_group(uuid)
- return parent_group.entries
-
# Return the root group of the database instance
def get_root_group(self):
return self.db.root_group
diff --git a/passwordsafe/entry_page.py b/passwordsafe/entry_page.py
index bdfaaaa979736007a26b00cd757af78c397e0020..c7c4205be66a4ec8ff121d9434e0b3920f76d9a5 100644
--- a/passwordsafe/entry_page.py
+++ b/passwordsafe/entry_page.py
@@ -67,33 +67,23 @@ class EntryPage:
builder.add_from_resource("/org/gnome/PasswordSafe/entry_page.ui")
- entry_uuid = self.unlocked_database.current_element.uuid
+ cur_entry = self.unlocked_database.current_element
+ entry_uuid = cur_entry.uuid
scrolled_page = self.unlocked_database.get_current_page()
-
- if self.unlocked_database.database_manager.has_entry_name(entry_uuid) is True or add_all is True:
+ entry_name = self.unlocked_database.database_manager.get_entry_name(cur_entry)
+ if entry_name or add_all:
if scrolled_page.name_property_row is NotImplemented:
+ # Create the name_property_row
scrolled_page.name_property_row = builder.get_object("name_property_row")
scrolled_page.name_property_value_entry = builder.get_object("name_property_value_entry")
scrolled_page.name_property_value_entry.set_buffer(HistoryEntryBuffer([]))
- value = self.unlocked_database.database_manager.get_entry_name(entry_uuid)
- if self.unlocked_database.database_manager.has_entry_name(entry_uuid) is True:
- scrolled_page.name_property_value_entry.set_text(value)
- else:
- scrolled_page.name_property_value_entry.set_text("")
-
- scrolled_page.name_property_value_entry.connect("changed", self.on_property_value_entry_changed, "name")
- properties_list_box.add(scrolled_page.name_property_row)
- scrolled_page.name_property_value_entry.grab_focus()
- elif scrolled_page.name_property_row:
- value = self.unlocked_database.database_manager.get_entry_name(
- entry_uuid)
- if self.unlocked_database.database_manager.has_entry_name(entry_uuid) is True:
- scrolled_page.name_property_value_entry.set_text(value)
- else:
- scrolled_page.name_property_value_entry.set_text("")
- scrolled_page.name_property_value_entry.connect("changed", self.on_property_value_entry_changed, "name")
- properties_list_box.add(scrolled_page.name_property_row)
+ scrolled_page.name_property_value_entry.set_text(entry_name)
+ scrolled_page.name_property_value_entry.connect(
+ "changed", self.on_property_value_entry_changed, "name"
+ )
+ properties_list_box.add(scrolled_page.name_property_row)
+ scrolled_page.name_property_value_entry.grab_focus()
if self.unlocked_database.database_manager.has_entry_username(entry_uuid) is True or add_all is True:
if scrolled_page.username_property_row is NotImplemented:
diff --git a/passwordsafe/entry_row.py b/passwordsafe/entry_row.py
index 713bfee994ed71635fa4289968f350f78e186666..0d6b5df8885e02fdccf692122cda04ac0f922251 100644
--- a/passwordsafe/entry_row.py
+++ b/passwordsafe/entry_row.py
@@ -1,77 +1,67 @@
# SPDX-License-Identifier: GPL-3.0-only
from __future__ import annotations
+import typing
from gettext import gettext as _
from uuid import UUID
from typing import Optional
from gi.repository import Gtk
+
import passwordsafe.config_manager
import passwordsafe.icon
from passwordsafe.color_widget import Color
+if typing.TYPE_CHECKING:
+ from pykeepass.entry import Entry
+ from passwordsafe.unlocked_database import UnlockedDatabase
+
class EntryRow(Gtk.ListBoxRow):
- unlocked_database = NotImplemented
- database_manager = NotImplemented
- entry_uuid = NotImplemented
- icon = NotImplemented
- label = NotImplemented
- password = NotImplemented
- changed = False
+ builder = Gtk.Builder()
selection_checkbox = NotImplemented
- checkbox_box = NotImplemented
- color = NotImplemented
type = "EntryRow"
- targets = NotImplemented
-
- def __init__(self, unlocked_database, dbm, entry):
+ def __init__(self, database: UnlockedDatabase, entry: Entry) -> None:
Gtk.ListBoxRow.__init__(self)
self.set_name("EntryRow")
- self.unlocked_database = unlocked_database
- self.database_manager = dbm
+ self.unlocked_database = database
+ self.db_manager = database.database_manager
self.entry_uuid = entry.uuid
- self.icon: Optional[int] = dbm.get_icon(entry)
- self.label = dbm.get_entry_name(entry)
- self.password = dbm.get_entry_password(entry)
- self.color = dbm.get_entry_color_from_entry_uuid(self.entry_uuid)
+ self.icon: Optional[int] = self.db_manager.get_icon(entry)
+ self.label: str = entry.title or ""
+ self.color = self.db_manager.get_entry_color(entry)
+ self.username: str = entry.username or ""
+ if self.username.startswith("{REF:U"):
+ # Loopup reference and put in the "real" username
+ uuid = UUID(self.unlocked_database.reference_to_hex_uuid(self.username))
+ self.username = self.db_manager.get_entry_username(uuid)
self.assemble_entry_row()
def assemble_entry_row(self):
- builder = Gtk.Builder()
- builder.add_from_resource(
- "/org/gnome/PasswordSafe/unlocked_database.ui")
- entry_event_box = builder.get_object("entry_event_box")
+ self.builder.add_from_resource("/org/gnome/PasswordSafe/entry_row.ui")
+ entry_event_box = self.builder.get_object("entry_event_box")
entry_event_box.connect("button-press-event", self.unlocked_database.on_entry_row_button_pressed)
- entry_icon = builder.get_object("entry_icon")
- entry_name_label = builder.get_object("entry_name_label")
- entry_subtitle_label = builder.get_object("entry_subtitle_label")
- entry_copy_button = builder.get_object("entry_copy_button")
- entry_color_button = builder.get_object("entry_color_button")
+ entry_icon = self.builder.get_object("entry_icon")
+ entry_name_label = self.builder.get_object("entry_name_label")
+ entry_subtitle_label = self.builder.get_object("entry_subtitle_label")
+ entry_copy_button = self.builder.get_object("entry_copy_button")
+ entry_color_button = self.builder.get_object("entry_color_button")
# Icon
icon_name: str = passwordsafe.icon.get_icon_name(self.icon)
entry_icon.set_from_icon_name(icon_name, 20)
# Title/Name
- if self.database_manager.has_entry_name(self.entry_uuid) and self.label:
+ if self.label:
entry_name_label.set_text(self.label)
else:
entry_name_label.set_markup("" + _("Title not specified") + "")
# Subtitle
- subtitle = self.database_manager.get_entry_username(self.entry_uuid)
- if (self.database_manager.has_entry_username(self.entry_uuid) and subtitle):
- username = self.database_manager.get_entry_username(self.entry_uuid)
- if username.startswith("{REF:U"):
- uuid = UUID(
- self.unlocked_database.reference_to_hex_uuid(username))
- username = self.database_manager.get_entry_username(uuid)
- entry_subtitle_label.set_text(username)
- else:
- entry_subtitle_label.set_text(username)
+ if self.username:
+ entry_subtitle_label.set_text(self.username)
else:
entry_subtitle_label.set_markup("" + _("No username specified") + "")
@@ -82,21 +72,17 @@ class EntryRow(Gtk.ListBoxRow):
image = entry_color_button.get_children()[0]
image_style = image.get_style_context()
if self.color != Color.NONE.value:
+ image_style.remove_class("DarkIcon")
image_style.add_class("BrightIcon")
- else:
- image_style.add_class("DarkIcon")
self.add(entry_event_box)
- self.show_all()
+ self.show()
# Selection Mode Checkboxes
- self.checkbox_box = builder.get_object("entry_checkbox_box")
- self.selection_checkbox = builder.get_object("selection_checkbox_entry")
+ self.selection_checkbox = self.builder.get_object("selection_checkbox_entry")
self.selection_checkbox.connect("toggled", self.on_selection_checkbox_toggled)
if self.unlocked_database.selection_ui.selection_mode_active is True:
- self.checkbox_box.show_all()
- else:
- self.checkbox_box.hide()
+ self.selection_checkbox.show()
def get_uuid(self):
return self.entry_uuid
@@ -107,19 +93,9 @@ class EntryRow(Gtk.ListBoxRow):
def set_label(self, label):
self.label = label
- def update_password(self):
- self.password = self.database_manager.get_entry_password(
- self.entry_uuid)
-
def get_type(self):
return self.type
- def set_changed(self, boolean):
- self.changed = boolean
-
- def get_changed(self):
- return self.changed
-
def on_selection_checkbox_toggled(self, _widget):
if self.selection_checkbox.get_active() is True:
if self not in self.unlocked_database.selection_ui.entries_selected:
@@ -143,7 +119,9 @@ class EntryRow(Gtk.ListBoxRow):
# self.unlocked_database.selection_ui.cut_mode is True
def on_entry_copy_button_clicked(self, _button):
- self.unlocked_database.send_to_clipboard(self.database_manager.get_entry_password(self.entry_uuid))
+ self.unlocked_database.send_to_clipboard(
+ self.db_manager.get_entry_password(self.entry_uuid)
+ )
def update_color(self, color):
self.color = color
diff --git a/passwordsafe/group_row.py b/passwordsafe/group_row.py
index 9e8854e793defd8850a79524e65c7d0584d8bf58..54dcc1ee5c240571e189d1702e85acf4997418c4 100644
--- a/passwordsafe/group_row.py
+++ b/passwordsafe/group_row.py
@@ -8,7 +8,6 @@ class GroupRow(Gtk.ListBoxRow):
group_uuid = NotImplemented
label = NotImplemented
selection_checkbox = NotImplemented
- checkbox_box = NotImplemented
edit_button = NotImplemented
type = "GroupRow"
@@ -38,16 +37,13 @@ class GroupRow(Gtk.ListBoxRow):
group_name_label.set_markup("" + _("No group title specified") + "")
self.add(group_event_box)
- self.show_all()
+ self.show()
# Selection Mode Checkboxes
- self.checkbox_box = builder.get_object("group_checkbox_box")
self.selection_checkbox = builder.get_object("selection_checkbox_group")
self.selection_checkbox.connect("toggled", self.on_selection_checkbox_toggled)
if self.unlocked_database.selection_ui.selection_mode_active is True:
- self.checkbox_box.show_all()
- else:
- self.checkbox_box.hide()
+ self.selection_checkbox.show()
# Edit Button
self.edit_button = builder.get_object("group_edit_button")
diff --git a/passwordsafe/pathbar.py b/passwordsafe/pathbar.py
index 8914d24d63388e4f17e57a2436ce807007719610..63448d8858828222630f5fb66df400b3e4b7eaee 100644
--- a/passwordsafe/pathbar.py
+++ b/passwordsafe/pathbar.py
@@ -216,16 +216,16 @@ class Pathbar(Gtk.HBox):
if self.database_manager.check_is_root_group(update_group) is True:
update_group = self.database_manager.get_root_group()
- page_name = update_group.uuid
- self.unlocked_database.schedule_stack_page_for_destroy(page_name)
+ page_uuid = update_group.uuid
+ self.unlocked_database.schedule_stack_page_for_destroy(page_uuid)
self.page_update_queried()
else:
if self.check_is_edit_page_from_group() is False:
return
- edit_page = self.unlocked_database.current_element
- self.unlocked_database.schedule_stack_page_for_destroy(edit_page.uuid)
+ edited_uuid = self.unlocked_database.current_element.uuid
+ self.unlocked_database.schedule_stack_page_for_destroy(edited_uuid)
def page_update_queried(self):
"""Marks the curent page as not dirty"""
@@ -233,7 +233,12 @@ class Pathbar(Gtk.HBox):
page.is_dirty = False
def check_values_of_edit_page(self, parent_group: Group) -> bool:
- """Check all values of the group/entry - if all are blank we delete the entry/group and return true"""
+ """Check all values of the current group/entry which we finished editing
+
+ If all are blank we delete the entry/group and return True.
+ It also schedules the parent page for destruction if the
+ Entry/Group has been deleted.
+ """
current_elt = self.unlocked_database.current_element
notes = self.database_manager.get_notes(current_elt)
icon = self.database_manager.get_icon(current_elt)
diff --git a/passwordsafe/search.py b/passwordsafe/search.py
index 46df8c1f747b79f283b0ab2cf5c9b75f8c412a51..86cc87cc5bec8a800f6cec7181237b0bfa24d6cc 100644
--- a/passwordsafe/search.py
+++ b/passwordsafe/search.py
@@ -188,7 +188,12 @@ class Search:
search_height += entry_row_height
if skip is False:
- row = EntryRow(self.unlocked_database, self.unlocked_database.database_manager, self.unlocked_database.database_manager.get_entry_object_from_uuid(uuid))
+ row = EntryRow(
+ self.unlocked_database,
+ self.unlocked_database.database_manager.get_entry_object_from_uuid(
+ uuid
+ ),
+ )
self.search_list_box.add(row)
self.cached_rows.append(row)
else:
diff --git a/passwordsafe/selection_ui.py b/passwordsafe/selection_ui.py
index cce88e794c88015c3e3ee0bc5dabd87b2f28e1ff..d930493b16032442c2f8749bcbb409b39863329b 100644
--- a/passwordsafe/selection_ui.py
+++ b/passwordsafe/selection_ui.py
@@ -82,8 +82,8 @@ class SelectionUI:
list_box = stack_page.get_children()[0].get_children()[0].get_children()[0].get_children()[0]
for row in list_box:
row.show()
- if hasattr(row, "checkbox_box") is True:
- row.checkbox_box.hide()
+ if hasattr(row, "selection_checkbox"):
+ row.selection_checkbox.hide()
row.selection_checkbox.set_active(False)
if hasattr(row, "edit_button") is True:
row.edit_button.show_all()
@@ -127,8 +127,8 @@ class SelectionUI:
if stack_page.check_is_edit_page() is False:
list_box = stack_page.get_children()[0].get_children()[0].get_children()[0].get_children()[0]
for row in list_box:
- if hasattr(row, "checkbox_box") is True:
- row.checkbox_box.show()
+ if hasattr(row, "selection_checkbox"):
+ row.selection_checkbox.show()
if hasattr(row, "edit_button") is True:
row.edit_button.hide()
diff --git a/passwordsafe/unlocked_database.py b/passwordsafe/unlocked_database.py
index 1d9d1c84503fb830eb6cb1e0f1c63e13a5382bc1..dc814ec3f7aa89e27f71b04ed71ac0bd46f4e8eb 100644
--- a/passwordsafe/unlocked_database.py
+++ b/passwordsafe/unlocked_database.py
@@ -218,12 +218,10 @@ class UnlockedDatabase(GObject.GObject):
def show_page_of_new_directory(self, edit_group, new_entry):
# First, remove stack pages which should not exist because they are scheduled for remove
- self.destroy_scheduled_stack_page()
+ self.destroy_current_page_if_scheduled()
# Creation of group edit page
if edit_group is True:
- self.destroy_scheduled_stack_page()
-
builder = Gtk.Builder()
builder.add_from_resource("/org/gnome/PasswordSafe/group_page.ui")
scrolled_window = ScrolledPage(True)
@@ -387,13 +385,17 @@ class UnlockedDatabase(GObject.GObject):
"""
return self._stack.get_children()
- def schedule_stack_page_for_destroy(self, page_name):
- self.scheduled_page_destroy.append(page_name)
+ def schedule_stack_page_for_destroy(self, page_uuid: UUID) -> None:
+ """Add page to the list of pages to be destroyed"""
+ logging.debug("Scheduling page %s for destruction")
+ self.scheduled_page_destroy.append(page_uuid)
- def destroy_scheduled_stack_page(self):
+ def destroy_current_page_if_scheduled(self) -> None:
+ """If the current_element is in self.scheduled_page_destroy, destroy it"""
page_uuid = self.current_element.uuid
-
+ logging.debug("Test if we should destroy page %s", page_uuid)
if page_uuid in self.scheduled_page_destroy:
+ logging.debug("Yes, destroying page %s", page_uuid)
stack_page_name = self._stack.get_child_by_name(page_uuid.urn)
if stack_page_name is not None:
stack_page_name.destroy()
@@ -410,17 +412,13 @@ class UnlockedDatabase(GObject.GObject):
add_loading_indicator_thread = threading.Thread(target=self.add_loading_indicator_thread, args=(list_box, overlay))
add_loading_indicator_thread.start()
- if self.current_element.is_root_group:
- groups = self.database_manager.get_groups_in_root()
- else:
- groups = self.database_manager.get_groups_in_folder(self.current_element.uuid)
-
+ groups = self.current_element.subgroups
GLib.idle_add(self.group_instance_creation, list_box, sorted_list, groups)
self.insert_entries_into_listbox(list_box, overlay)
def insert_entries_into_listbox(self, list_box, overlay):
- entries = self.database_manager.get_entries_in_folder(self.current_element.uuid)
+ entries = self.current_element.entries
sorted_list = []
GLib.idle_add(self.entry_instance_creation, list_box, sorted_list, entries, overlay)
@@ -440,7 +438,7 @@ class UnlockedDatabase(GObject.GObject):
def entry_instance_creation(self, list_box, sorted_list, entries, overlay):
for entry in entries:
- entry_row = EntryRow(self, self.database_manager, entry)
+ entry_row = EntryRow(self, entry)
sorted_list.append(entry_row)
if self.list_box_sorting == "A-Z":