[pgp] Port to Gtk4 and add type annotations

This commit is contained in:
Philipp Hörist
2025-01-28 23:22:05 +01:00
parent 6fe2da67c3
commit 3a5816259c
11 changed files with 1323 additions and 285 deletions

View File

@@ -1,42 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk" version="4.0"/>
<object class="GtkListStore" id="liststore">
<columns>
<!-- column-name keyid -->
<column type="gchararray"/>
<!-- column-name contactname -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkBox" id="box">
<property name="width_request">500</property>
<property name="height_request">300</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<property name="focusable">1</property>
<property name="vexpand">1</property>
<property name="hexpand">1</property>
<property name="child">
<object class="GtkTreeView" id="keys_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focusable">1</property>
<property name="model">liststore</property>
<property name="search_column">1</property>
<signal name="cursor-changed" handler="_on_row_changed" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Key ID</property>
<property name="title" translatable="1">Key ID</property>
<property name="sort_order">descending</property>
<child>
<object class="GtkCellRendererText"/>
@@ -48,7 +37,7 @@
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Contact Name</property>
<property name="title" translatable="1">Contact Name</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText"/>
@@ -59,13 +48,24 @@
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkBox" id="button_box">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">Cancel</property>
</object>
</child>
<child>
<object class="GtkButton" id="ok_button">
<property name="label">OK</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</interface>

View File

@@ -14,89 +14,111 @@
# You should have received a copy of the GNU General Public License
# along with PGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import cast
from typing import TYPE_CHECKING
from pathlib import Path
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gtk
from gajim.common import app
from gajim.gtk.util import SignalManager
from gajim.gtk.widgets import GajimAppWindow
from gajim.plugins.helpers import get_builder
from gajim.plugins.plugins_i18n import _
from pgp.gtk.key import ChooseGPGKeyDialog
from ..modules.pgp_legacy import PGPLegacy
from .key import ChooseGPGKeyDialog
if TYPE_CHECKING:
from ..plugin import PGPPlugin
class PGPConfigDialog(Gtk.ApplicationWindow):
def __init__(self, plugin, parent):
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_show_menubar(False)
self.set_title(_("PGP Configuration"))
self.set_transient_for(parent)
self.set_resizable(True)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_destroy_with_parent(True)
class ConfigBuilder(Gtk.Builder):
config_box: Gtk.Box
sidebar: Gtk.StackSidebar
stack: Gtk.Stack
class PGPConfigDialog(GajimAppWindow):
def __init__(self, plugin: PGPPlugin, transient: Gtk.Window) -> None:
GajimAppWindow.__init__(
self,
name="PGPConfigDialog",
title=_("PGP Configuration"),
default_width=600,
default_height=500,
transient_for=transient,
modal=True,
)
ui_path = Path(__file__).parent
self._ui = get_builder(ui_path.resolve() / "config.ui")
self._ui = cast(
ConfigBuilder, get_builder(str(ui_path.resolve() / "config.ui"))
)
self.add(self._ui.config_box)
self._ui.connect_signals(self)
self.set_child(self._ui.config_box)
self._plugin = plugin
for account in app.settings.get_active_accounts():
page = Page(plugin, account)
module = cast(
PGPLegacy,
app.get_client(account).get_module("PGPLegacy"), # pyright: ignore
)
page = Page(module)
self._ui.stack.add_titled(page, account, app.get_account_label(account))
self.show_all()
self.show()
def _cleanup(self) -> None:
del self._plugin
class Page(Gtk.Box):
def __init__(self, plugin, account):
class Page(Gtk.Box, SignalManager):
def __init__(self, module: PGPLegacy) -> None:
SignalManager.__init__(self)
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self._client = app.get_client(account)
self._plugin = plugin
self._module = module
self._label = Gtk.Label()
self._button = Gtk.Button(label=_("Assign Key"))
self._button.get_style_context().add_class("suggested-action")
self._button.add_css_class("suggested-action")
self._button.set_halign(Gtk.Align.CENTER)
self._button.set_margin_top(18)
self._button.connect("clicked", self._on_assign)
self._connect(self._button, "clicked", self._on_assign)
self._load_key()
self.add(self._label)
self.add(self._button)
self.show_all()
self.append(self._label)
self.append(self._button)
def _on_assign(self, _button):
backend = self._client.get_module("PGPLegacy").pgp_backend
secret_keys = backend.get_keys(secret=True)
dialog = ChooseGPGKeyDialog(secret_keys, self.get_toplevel())
dialog.connect("response", self._on_response)
def _on_assign(self, _button: Gtk.Button) -> None:
secret_keys = self._module.pgp_backend.get_keys(secret=True)
ChooseGPGKeyDialog(
secret_keys, cast(Gtk.Window, self.get_root()), self._on_response
)
def _load_key(self):
key_data = self._client.get_module("PGPLegacy").get_own_key_data()
def _load_key(self) -> None:
key_data = self._module.get_own_key_data()
if key_data is None:
self._set_key(None)
else:
self._set_key((key_data["key_id"], key_data["key_user"]))
def _on_response(self, dialog, response):
if response != Gtk.ResponseType.OK:
return
if dialog.selected_key is None:
self._client.get_module("PGPLegacy").set_own_key_data(None)
def _on_response(self, key: tuple[str, str] | None) -> None:
if key is None:
self._module.set_own_key_data(None)
self._set_key(None)
else:
self._client.get_module("PGPLegacy").set_own_key_data(dialog.selected_key)
self._set_key(dialog.selected_key)
self._module.set_own_key_data(key)
self._set_key(key)
def _set_key(self, key_data):
def _set_key(self, key_data: tuple[str, str] | None) -> None:
if key_data is None:
self._label.set_text(_("No key assigned"))
else:
@@ -104,3 +126,9 @@ class Page(Gtk.Box):
self._label.set_markup(
"<b><tt>%s</tt> %s</b>" % (key_id, GLib.markup_escape_text(key_user))
)
def do_unroot(self) -> None:
Gtk.Box.do_unroot(self)
self._disconnect_all()
del self._module
app.check_finalize(self)

View File

@@ -1,41 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk" version="4.0"/>
<object class="GtkBox" id="config_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkStackSidebar" id="sidebar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="width_request">400</property>
<property name="height_request">350</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="border_width">18</property>
<property name="hexpand">1</property>
<property name="transition_type">crossfade</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@@ -14,27 +14,60 @@
# You should have received a copy of the GNU General Public License
# along with PGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import Any
from typing import cast
from typing import TYPE_CHECKING
from collections.abc import Callable
from pathlib import Path
from gi.repository import GLib
from gi.repository import Gtk
from nbxmpp import JID
from gajim.common import app
from gajim.gtk.widgets import GajimAppWindow
from gajim.plugins.helpers import get_builder
from gajim.plugins.plugins_i18n import _
from ..modules.pgp_legacy import PGPLegacy
class KeyDialog(Gtk.Dialog):
def __init__(self, plugin, account, jid, transient):
super().__init__(title=_("Assign key for %s") % jid, destroy_with_parent=True)
if TYPE_CHECKING:
from ..plugin import PGPPlugin
self.set_transient_for(transient)
self.set_resizable(True)
self.set_default_size(450, -1)
class ChooseKeyBuilder(Gtk.Builder):
liststore: Gtk.ListStore
box: Gtk.Box
keys_treeview: Gtk.TreeView
cancel_button: Gtk.Button
ok_button: Gtk.Button
class KeyDialog(GajimAppWindow):
def __init__(
self, plugin: PGPPlugin, account: str, jid: JID, transient: Gtk.Window
) -> None:
GajimAppWindow.__init__(
self,
name="PGPKeyDialog",
title=_("Assign key for %s") % jid,
default_width=450,
transient_for=transient,
modal=True,
)
self.window.set_resizable(True)
self._plugin = plugin
self._jid = jid
self._client = app.get_client(account)
self._jid = str(jid)
self._module = cast(
PGPLegacy,
app.get_client(account).get_module("PGPLegacy"), # pyright: ignore
)
self._label = Gtk.Label()
@@ -42,45 +75,43 @@ class KeyDialog(Gtk.Dialog):
self._assign_button.get_style_context().add_class("suggested-action")
self._assign_button.set_halign(Gtk.Align.CENTER)
self._assign_button.set_margin_top(18)
self._assign_button.connect("clicked", self._choose_key)
self._connect(self._assign_button, "clicked", self._choose_key)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.set_border_width(18)
box.add(self._label)
box.add(self._assign_button)
box.append(self._label)
box.append(self._assign_button)
area = self.get_content_area()
area.pack_start(box, True, True, 0)
self.set_child(box)
self._load_key()
self.show_all()
self.show()
def _choose_key(self, *args):
backend = self._client.get_module("PGPLegacy").pgp_backend
dialog = ChooseGPGKeyDialog(backend.get_keys(), self)
dialog.connect("response", self._on_response)
def _cleanup(self) -> None:
del self._plugin
del self._module
def _load_key(self):
key_data = self._client.get_module("PGPLegacy").get_contact_key_data(self._jid)
def _choose_key(self, _button: Gtk.Button) -> None:
ChooseGPGKeyDialog(
self._module.pgp_backend.get_keys(), self.window, self._on_response
)
def _load_key(self) -> None:
key_data = self._module.get_contact_key_data(self._jid)
if key_data is None:
self._set_key(None)
else:
self._set_key(key_data.values())
key_id, key_user = key_data.values()
self._set_key((key_id, key_user))
def _on_response(self, dialog, response):
if response != Gtk.ResponseType.OK:
return
if dialog.selected_key is None:
self._client.get_module("PGPLegacy").set_contact_key_data(self._jid, None)
def _on_response(self, key: tuple[str, str] | None) -> None:
if key is None:
self._module.set_contact_key_data(self._jid, None)
self._set_key(None)
else:
self._client.get_module("PGPLegacy").set_contact_key_data(
self._jid, dialog.selected_key
)
self._set_key(dialog.selected_key)
self._module.set_contact_key_data(self._jid, key)
self._set_key(key)
def _set_key(self, key_data):
def _set_key(self, key_data: tuple[str, str] | None) -> None:
if key_data is None:
self._label.set_text(_("No key assigned"))
else:
@@ -90,49 +121,56 @@ class KeyDialog(Gtk.Dialog):
)
class ChooseGPGKeyDialog(Gtk.Dialog):
def __init__(self, secret_keys, transient_for):
Gtk.Dialog.__init__(
self, title=_("Assign PGP Key"), transient_for=transient_for
class ChooseGPGKeyDialog(GajimAppWindow):
def __init__(
self,
secret_keys: dict[str, str],
transient: Gtk.Window,
callback: Callable[[tuple[str, str] | None], None],
) -> None:
GajimAppWindow.__init__(
self,
name="PGPChooseKeyDialog",
title=_("Assign PGP Key"),
default_width=450,
default_height=400,
transient_for=transient,
modal=True,
)
secret_keys[_("None")] = _("None")
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(True)
self.set_default_size(500, 300)
self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
self.add_button(_("OK"), Gtk.ResponseType.OK)
self.window.set_resizable(True)
self._callback = callback
self._selected_key = None
ui_path = Path(__file__).parent
self._ui = get_builder(ui_path.resolve() / "choose_key.ui")
self._ui = cast(
ChooseKeyBuilder, get_builder(str(ui_path.resolve() / "choose_key.ui"))
)
self._ui.keys_treeview = self._ui.keys_treeview
self._connect(self._ui.cancel_button, "clicked", self._on_cancel)
self._connect(self._ui.ok_button, "clicked", self._on_ok)
self._connect(self._ui.keys_treeview, "cursor-changed", self._on_row_changed)
model = self._ui.keys_treeview.get_model()
model = cast(Gtk.ListStore, self._ui.keys_treeview.get_model())
model.set_sort_func(1, self._sort)
model = self._ui.keys_treeview.get_model()
for key_id in secret_keys.keys():
model.append((key_id, secret_keys[key_id]))
self.get_content_area().add(self._ui.box)
self.set_child(self._ui.box)
self.show()
self._ui.connect_signals(self)
self.connect_after("response", self._on_response)
self.show_all()
@property
def selected_key(self):
return self._selected_key
def _cleanup(self) -> None:
del self._callback
@staticmethod
def _sort(model, iter1, iter2, _data):
def _sort(
model: Gtk.TreeModel, iter1: Gtk.TreeIter, iter2: Gtk.TreeIter, _data: Any
) -> int:
value1 = model[iter1][1]
value2 = model[iter2][1]
if value1 == _("None"):
@@ -143,10 +181,14 @@ class ChooseGPGKeyDialog(Gtk.Dialog):
return -1
return 1
def _on_response(self, _dialog, _response):
self.destroy()
def _on_cancel(self, _button: Gtk.Button) -> None:
self.close()
def _on_row_changed(self, treeview):
def _on_ok(self, _button: Gtk.Button) -> None:
self._callback(self._selected_key)
self.close()
def _on_row_changed(self, treeview: Gtk.TreeView) -> None:
selection = treeview.get_selection()
model, iter_ = selection.get_selected()
if iter_ is None: