Commit 504b14b6 authored by Philipp Hörist's avatar Philipp Hörist
Browse files

refactor: Move PluginsWindow into gtk folder

parent a21509a8
......@@ -143,7 +143,7 @@ def __str__(self):
'ManageSounds': 'gajim.gui.manage_sounds',
'PasswordDialog': 'gajim.gui.password_dialog',
'PEPConfig': 'gajim.gui.pep_config',
'PluginsWindow': 'gajim.plugins.gui',
'PluginsWindow': 'gajim.gui.plugins',
'Preferences': 'gajim.gui.preferences',
'ProfileWindow': 'gajim.gui.profile',
'RemoveAccount': 'gajim.gui.remove_account',
......
# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import Any
from typing import TYPE_CHECKING
import os
from enum import IntEnum
from enum import unique
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import Gdk
from gajim.common import app
from gajim.common import ged
from gajim.common.events import PluginAdded
from gajim.common.events import PluginRemoved
from gajim.common.exceptions import PluginsystemError
from gajim.common.helpers import open_uri
from gajim.common.ged import EventHelper
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins.helpers import GajimPluginActivateException
from gajim.plugins.plugins_i18n import _
from gajim.gui.dialogs import WarningDialog
from gajim.gui.dialogs import DialogButton
from gajim.gui.dialogs import ConfirmationDialog
from gajim.gui.filechoosers import ArchiveChooserDialog
from gajim.gui.builder import get_builder
from gajim.gui.util import load_icon_pixbuf
if TYPE_CHECKING:
from gajim.plugins.gajimplugin import GajimPlugin
@unique
class Column(IntEnum):
PLUGIN = 0
NAME = 1
ACTIVE = 2
ACTIVATABLE = 3
ICON = 4
class PluginsWindow(Gtk.ApplicationWindow, EventHelper):
def __init__(self) -> None:
Gtk.ApplicationWindow.__init__(self)
EventHelper.__init__(self)
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(650, 500)
self.set_show_menubar(False)
self.set_title(_('Plugins'))
self._ui = get_builder('plugins_window.ui')
self.add(self._ui.plugins_notebook)
# Disable 'Install from ZIP' for Flatpak installs
if app.is_flatpak():
self._ui.install_plugin_button.set_tooltip_text(
_('Click to view Gajim\'s wiki page on how to install plugins '
'in Flatpak.'))
self.installed_plugins_model = Gtk.ListStore(
object,
str,
bool,
bool,
GdkPixbuf.Pixbuf
)
self._ui.installed_plugins_treeview.set_model(
self.installed_plugins_model)
renderer = Gtk.CellRendererText()
col = Gtk.TreeViewColumn(_('Plugin')) # , renderer, text=Column.NAME)
cell = Gtk.CellRendererPixbuf()
col.pack_start(cell, False)
col.add_attribute(cell, 'pixbuf', Column.ICON)
col.pack_start(renderer, True)
col.add_attribute(renderer, 'text', Column.NAME)
col.set_property('expand', True)
self._ui.installed_plugins_treeview.append_column(col)
renderer = Gtk.CellRendererToggle()
renderer.connect('toggled', self._installed_plugin_toggled)
col = Gtk.TreeViewColumn(_('Active'), renderer, active=Column.ACTIVE,
activatable=Column.ACTIVATABLE)
self._ui.installed_plugins_treeview.append_column(col)
# connect signal for selection change
selection = self._ui.installed_plugins_treeview.get_selection()
selection.connect(
'changed', self._selection_changed)
selection.set_mode(Gtk.SelectionMode.SINGLE)
self._clear_installed_plugin_info()
self._fill_installed_plugins_model()
root_iter = self.installed_plugins_model.get_iter_first()
if root_iter:
selection.select_iter(root_iter)
self.connect('destroy', self._on_destroy)
self.connect('key-press-event', self._on_key_press)
self._ui.connect_signals(self)
self._ui.plugins_notebook.set_current_page(0)
# Adding GUI extension point for Plugins that want to hook
# the Plugin Window
app.plugin_manager.gui_extension_point('plugin_window', self)
self.register_events([
('plugin-removed', ged.GUI1, self._on_plugin_removed),
('plugin-added', ged.GUI1, self._on_plugin_added),
])
self.show_all()
def get_notebook(self) -> Gtk.Notebook:
# Used by plugins
return self._ui.plugins_notebook
def _on_key_press(self, _widget: Gtk.Widget, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def _on_destroy(self, *args: Any) -> None:
self.unregister_events()
app.plugin_manager.remove_gui_extension_point('plugin_window', self)
def _selection_changed(self,
treeview_selection: Gtk.TreeSelection
) -> None:
model, iter_ = treeview_selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
self._display_installed_plugin_info(plugin)
else:
self._clear_installed_plugin_info()
def _display_installed_plugin_info(self, plugin: GajimPlugin) -> None:
self._ui.plugin_name_label.set_text(plugin.name)
self._ui.plugin_version_label.set_text(plugin.version)
self._ui.plugin_authors_label.set_text(plugin.authors)
markup = f'<a href="{plugin.homepage}">{plugin.homepage}</a>'
self._ui.plugin_homepage_linkbutton.set_markup(markup)
if plugin.available_text:
text = _('Warning: %s') % plugin.available_text
self._ui.available_text_label.set_text(text)
self._ui.available_text.show()
# Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=710888
self._ui.available_text.queue_resize()
else:
self._ui.available_text.hide()
self._ui.description.set_text(plugin.description)
self._ui.uninstall_plugin_button.set_sensitive(True)
self._ui.configure_plugin_button.set_sensitive(
plugin.config_dialog is not None and plugin.active)
def _clear_installed_plugin_info(self) -> None:
self._ui.plugin_name_label.set_text('')
self._ui.plugin_version_label.set_text('')
self._ui.plugin_authors_label.set_text('')
self._ui.plugin_homepage_linkbutton.set_markup('')
self._ui.description.set_text('')
self._ui.uninstall_plugin_button.set_sensitive(False)
self._ui.configure_plugin_button.set_sensitive(False)
def _fill_installed_plugins_model(self) -> None:
pm = app.plugin_manager
self.installed_plugins_model.clear()
self.installed_plugins_model.set_sort_column_id(1,
Gtk.SortType.ASCENDING)
for plugin in pm.plugins:
icon = self._get_plugin_icon(plugin)
self.installed_plugins_model.append(
[plugin,
plugin.name,
plugin.active and plugin.activatable,
plugin.activatable,
icon])
def _get_plugin_icon(self, plugin: GajimPlugin) -> GdkPixbuf.Pixbuf:
icon_file = os.path.join(
plugin.__path__, os.path.split(plugin.__path__)[1]) + '.png'
if os.path.isfile(icon_file):
icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16)
else:
icon = load_icon_pixbuf('applications-utilities')
assert icon is not None
return icon
def _installed_plugin_toggled(self,
_cell: Gtk.CellRendererToggle,
path: str
) -> None:
is_active = self.installed_plugins_model[path][Column.ACTIVE]
plugin = self.installed_plugins_model[path][Column.PLUGIN]
if is_active:
app.plugin_manager.deactivate_plugin(plugin)
else:
try:
app.plugin_manager.activate_plugin(plugin)
except GajimPluginActivateException as e:
WarningDialog(_('Plugin failed'), str(e),
transient_for=self)
return
self._ui.configure_plugin_button.set_sensitive(
plugin.config_dialog is not None and not is_active)
self.installed_plugins_model[path][Column.ACTIVE] = not is_active
def _on_configure_plugin(self, _button: Gtk.Button) -> None:
selection = self._ui.installed_plugins_treeview.get_selection()
model, iter_ = selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
if isinstance(plugin.config_dialog, GajimPluginConfigDialog):
plugin.config_dialog.run(self)
else:
plugin.config_dialog(self)
else:
# No plugin selected. this should never be reached. As configure
# plugin button should only be clickable when plugin is selected.
# XXX: maybe throw exception here?
pass
def _on_uninstall_plugin(self, _button: Gtk.ToolButton) -> None:
selection = self._ui.installed_plugins_treeview.get_selection()
model, iter_ = selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
try:
app.plugin_manager.uninstall_plugin(plugin)
except PluginsystemError as error:
WarningDialog(_('Unable to properly remove the plugin'),
str(error), self)
return
def _on_plugin_removed(self, event: PluginRemoved) -> None:
for row in self.installed_plugins_model:
if row[Column.PLUGIN] == event.plugin:
self.installed_plugins_model.remove(row.iter)
break
def _on_plugin_added(self, event: PluginAdded) -> None:
icon = self._get_plugin_icon(event.plugin)
self.installed_plugins_model.append([event.plugin,
event.plugin.name,
False,
event.plugin.activatable,
icon])
def _on_install_plugin(self, _button: Gtk.ToolButton) -> None:
if app.is_flatpak():
open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/flathub')
return
def _show_warn_dialog() -> None:
text = _('Archive is malformed')
dialog = WarningDialog(text, '', transient_for=self)
dialog.set_modal(False)
dialog.popup()
def _on_plugin_exists(zip_filename: str) -> None:
def _on_yes():
plugin = app.plugin_manager.install_from_zip(zip_filename,
overwrite=True)
if not plugin:
_show_warn_dialog()
return
ConfirmationDialog(
_('Overwrite Plugin?'),
_('Plugin already exists'),
_('Do you want to overwrite the currently installed version?'),
[DialogButton.make('Cancel'),
DialogButton.make('Remove',
text=_('_Overwrite'),
callback=_on_yes)],
transient_for=self).show()
def _try_install(zip_filename: str) -> None:
try:
plugin = app.plugin_manager.install_from_zip(zip_filename)
except PluginsystemError as er_type:
error_text = str(er_type)
if error_text == _('Plugin already exists'):
_on_plugin_exists(zip_filename)
return
WarningDialog(error_text, f'"{zip_filename}"', self)
return
if not plugin:
_show_warn_dialog()
return
ArchiveChooserDialog(_try_install, transient_for=self)
......@@ -24,314 +24,17 @@
from __future__ import annotations
from typing import Any
from typing import Optional
from typing import TYPE_CHECKING
import os
from enum import IntEnum
from enum import unique
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import Gdk
from gajim.common import app
from gajim.common import ged
from gajim.common.events import PluginAdded
from gajim.common.events import PluginRemoved
from gajim.common.exceptions import PluginsystemError
from gajim.common.helpers import open_uri
from gajim.common.ged import EventHelper
from gajim.plugins.helpers import GajimPluginActivateException
from gajim.plugins.plugins_i18n import _
from gajim.gui.dialogs import WarningDialog
from gajim.gui.dialogs import DialogButton
from gajim.gui.dialogs import ConfirmationDialog
from gajim.gui.filechoosers import ArchiveChooserDialog
from gajim.gui.builder import get_builder
from gajim.gui.util import load_icon_pixbuf
if TYPE_CHECKING:
from gajim.plugins.gajimplugin import GajimPlugin
@unique
class Column(IntEnum):
PLUGIN = 0
NAME = 1
ACTIVE = 2
ACTIVATABLE = 3
ICON = 4
class PluginsWindow(Gtk.ApplicationWindow, EventHelper):
def __init__(self) -> None:
Gtk.ApplicationWindow.__init__(self)
EventHelper.__init__(self)
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(650, 500)
self.set_show_menubar(False)
self.set_title(_('Plugins'))
self._ui = get_builder('plugins_window.ui')
self.add(self._ui.plugins_notebook)
# Disable 'Install from ZIP' for Flatpak installs
if app.is_flatpak():
self._ui.install_plugin_button.set_tooltip_text(
_('Click to view Gajim\'s wiki page on how to install plugins '
'in Flatpak.'))
self.installed_plugins_model = Gtk.ListStore(
object,
str,
bool,
bool,
GdkPixbuf.Pixbuf
)
self._ui.installed_plugins_treeview.set_model(
self.installed_plugins_model)
renderer = Gtk.CellRendererText()
col = Gtk.TreeViewColumn(_('Plugin')) # , renderer, text=Column.NAME)
cell = Gtk.CellRendererPixbuf()
col.pack_start(cell, False)
col.add_attribute(cell, 'pixbuf', Column.ICON)
col.pack_start(renderer, True)
col.add_attribute(renderer, 'text', Column.NAME)
col.set_property('expand', True)
self._ui.installed_plugins_treeview.append_column(col)
renderer = Gtk.CellRendererToggle()
renderer.connect('toggled', self._installed_plugin_toggled)
col = Gtk.TreeViewColumn(_('Active'), renderer, active=Column.ACTIVE,
activatable=Column.ACTIVATABLE)
self._ui.installed_plugins_treeview.append_column(col)
# connect signal for selection change
selection = self._ui.installed_plugins_treeview.get_selection()
selection.connect(
'changed', self._selection_changed)
selection.set_mode(Gtk.SelectionMode.SINGLE)
self._clear_installed_plugin_info()
self._fill_installed_plugins_model()
root_iter = self.installed_plugins_model.get_iter_first()
if root_iter:
selection.select_iter(root_iter)
self.connect('destroy', self._on_destroy)
self.connect('key-press-event', self._on_key_press)
self._ui.connect_signals(self)
self._ui.plugins_notebook.set_current_page(0)
# Adding GUI extension point for Plugins that want to hook
# the Plugin Window
app.plugin_manager.gui_extension_point('plugin_window', self)
self.register_events([
('plugin-removed', ged.GUI1, self._on_plugin_removed),
('plugin-added', ged.GUI1, self._on_plugin_added),
])
self.show_all()
def get_notebook(self) -> Gtk.Notebook:
# Used by plugins
return self._ui.plugins_notebook
def _on_key_press(self, _widget: Gtk.Widget, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def _on_destroy(self, *args: Any) -> None:
self.unregister_events()
app.plugin_manager.remove_gui_extension_point('plugin_window', self)
def _selection_changed(self,
treeview_selection: Gtk.TreeSelection
) -> None:
model, iter_ = treeview_selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
self._display_installed_plugin_info(plugin)
else:
self._clear_installed_plugin_info()
def _display_installed_plugin_info(self, plugin: GajimPlugin) -> None:
self._ui.plugin_name_label.set_text(plugin.name)
self._ui.plugin_version_label.set_text(plugin.version)
self._ui.plugin_authors_label.set_text(plugin.authors)
markup = f'<a href="{plugin.homepage}">{plugin.homepage}</a>'
self._ui.plugin_homepage_linkbutton.set_markup(markup)
if plugin.available_text:
text = _('Warning: %s') % plugin.available_text
self._ui.available_text_label.set_text(text)
self._ui.available_text.show()
# Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=710888
self._ui.available_text.queue_resize()
else:
self._ui.available_text.hide()
self._ui.description.set_text(plugin.description)
self._ui.uninstall_plugin_button.set_sensitive(True)
self._ui.configure_plugin_button.set_sensitive(
plugin.config_dialog is not None and plugin.active)
def _clear_installed_plugin_info(self) -> None:
self._ui.plugin_name_label.set_text('')
self._ui.plugin_version_label.set_text('')
self._ui.plugin_authors_label.set_text('')
self._ui.plugin_homepage_linkbutton.set_markup('')
self._ui.description.set_text('')
self._ui.uninstall_plugin_button.set_sensitive(False)
self._ui.configure_plugin_button.set_sensitive(False)
def _fill_installed_plugins_model(self) -> None:
pm = app.plugin_manager
self.installed_plugins_model.clear()
self.installed_plugins_model.set_sort_column_id(1,
Gtk.SortType.ASCENDING)
for plugin in pm.plugins:
icon = self._get_plugin_icon(plugin)
self.installed_plugins_model.append(
[plugin,
plugin.name,
plugin.active and plugin.activatable,
plugin.activatable,
icon])
def _get_plugin_icon(self, plugin: GajimPlugin) -> GdkPixbuf.Pixbuf:
icon_file = os.path.join(
plugin.__path__, os.path.split(plugin.__path__)[1]) + '.png'
if os.path.isfile(icon_file):
icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16)
else:
icon = load_icon_pixbuf('applications-utilities')
assert icon is not None
return icon
def _installed_plugin_toggled(self,
_cell: Gtk.CellRendererToggle,
path: str
) -> None:
is_active = self.installed_plugins_model[path][Column.ACTIVE]
plugin = self.installed_plugins_model[path][Column.PLUGIN]
if is_active:
app.plugin_manager.deactivate_plugin(plugin)
else:
try:
app.plugin_manager.activate_plugin(plugin)
except GajimPluginActivateException as e:
WarningDialog(_('Plugin failed'), str(e),
transient_for=self)
return
self._ui.configure_plugin_button.set_sensitive(
plugin.config_dialog is not None and not is_active)
self.installed_plugins_model[path][Column.ACTIVE] = not is_active
def _on_configure_plugin(self, _button: Gtk.Button) -> None:
selection = self._ui.installed_plugins_treeview.get_selection()
model, iter_ = selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
if isinstance(plugin.config_dialog, GajimPluginConfigDialog):
plugin.config_dialog.run(self)
else:
plugin.config_dialog(self)
else:
# No plugin selected. this should never be reached. As configure
# plugin button should only be clickable when plugin is selected.
# XXX: maybe throw exception here?
pass
def _on_uninstall_plugin(self, _button: Gtk.ToolButton) -> None:
selection = self._ui.installed_plugins_treeview.get_selection()
model, iter_ = selection.get_selected()
if iter_:
plugin = model.get_value(iter_, Column.PLUGIN)
try:
app.plugin_manager.uninstall_plugin(plugin)
except PluginsystemError as error:
WarningDialog(_('Unable to properly remove the plugin'),
str(error), self)
return
def _on_plugin_removed(self, event: PluginRemoved) -> None:
for row in self.installed_plugins_model:
if row[Column.PLUGIN] == event.plugin:
self.installed_plugins_model.remove(row.iter)
break
def _on_plugin_added(self, event: PluginAdded) -> None:
icon = self._get_plugin_icon(event.plugin)
self.installed_plugins_model.append([event.plugin,
event.plugin.name,
False,
event.plugin.activatable,
icon])
def _on_install_plugin(self, _button: Gtk.ToolButton) -> None:
if app.is_flatpak():
open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/flathub')
return
def _show_warn_dialog() -> None:
text = _('Archive is malformed')
dialog = WarningDialog(text, '', transient_for=self)
dialog.set_modal(False)
dialog.popup()
def _on_plugin_exists(zip_filename: str) -> None: