[triggers] Type annotations, linting

This commit is contained in:
wurstsalat
2022-11-29 19:36:05 +01:00
parent 220e670211
commit 067c233b4b
4 changed files with 193 additions and 86 deletions

View File

@@ -1 +1 @@
from .triggers import Triggers
from .triggers import Triggers # type: ignore

View File

@@ -14,6 +14,11 @@
# 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
from pathlib import Path
from gi.repository import Gtk
@@ -26,7 +31,10 @@ from gajim.common.helpers import play_sound_file
from gajim.plugins.plugins_i18n import _
from gajim.plugins.helpers import get_builder
EVENTS = {
if TYPE_CHECKING:
from ..triggers import Triggers
EVENTS: dict[str, Any] = {
'message_received': [],
}
@@ -39,7 +47,7 @@ RECIPIENT_TYPES = [
class ConfigDialog(Gtk.ApplicationWindow):
def __init__(self, plugin, transient):
def __init__(self, plugin: Triggers, transient: Gtk.Window) -> None:
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_show_menubar(False)
@@ -51,41 +59,61 @@ class ConfigDialog(Gtk.ApplicationWindow):
self.set_destroy_with_parent(True)
ui_path = Path(__file__).parent
self._ui = get_builder(ui_path.resolve() / 'config.ui')
self._ui = get_builder(str(ui_path.resolve() / 'config.ui'))
self._plugin = plugin
self.add(self._ui.box)
self.show_all()
self._active_num = ''
self._active_num = -1
self._config: dict[int, Any] = {}
self._initialize()
self._ui.connect_signals(self)
self.connect('destroy', self._on_destroy)
def _on_destroy(self, *args):
def _on_destroy(self, *args: Any) -> None:
for num in list(self._plugin.config.keys()):
del self._plugin.config[num]
for num in self._config:
self._plugin.config[str(num)] = self._config[num]
def _initialize(self):
def _initialize(self) -> None:
# Fill window
for widget in (
'conditions_treeview', 'config_box', 'event_combobox',
'recipient_type_combobox', 'recipient_list_entry',
'delete_button', 'online_cb', 'away_cb', 'xa_cb', 'dnd_cb',
'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
'disable_popup_cb',
'tab_opened_cb', 'not_tab_opened_cb', 'has_focus_cb',
'not_has_focus_cb', 'filechooser', 'sound_file_box',
'up_button', 'down_button', 'run_command_cb',
'command_entry', 'one_shot_cb'):
widgets = [
'conditions_treeview',
'config_box',
'event_combobox',
'recipient_type_combobox',
'recipient_list_entry',
'delete_button',
'online_cb',
'away_cb',
'xa_cb',
'dnd_cb',
'use_sound_cb',
'disable_sound_cb',
'use_popup_cb',
'disable_popup_cb',
'tab_opened_cb',
'not_tab_opened_cb',
'has_focus_cb',
'not_has_focus_cb',
'filechooser',
'sound_file_box',
'up_button',
'down_button',
'run_command_cb',
'command_entry',
'one_shot_cb'
]
for widget in widgets:
self._ui.__dict__[widget] = self._ui.get_object(widget)
self._config = {}
for num in self._plugin.config:
for num in self._plugin.config.keys():
self._config[int(num)] = self._plugin.config[num]
if not self._ui.conditions_treeview.get_column(0):
@@ -141,7 +169,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.filechooser.add_filter(filter_)
self._ui.filechooser.set_filter(filter_)
def _initiate_rule_state(self):
def _initiate_rule_state(self) -> None:
"""
Set values for all widgets
"""
@@ -243,7 +271,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
value = False
self._ui.one_shot_cb.set_active(value)
def _set_treeview_string(self):
def _set_treeview_string(self) -> None:
selection = self._ui.conditions_treeview.get_selection()
(model, iter_) = selection.get_selected()
if not iter_:
@@ -274,17 +302,22 @@ class ConfigDialog(Gtk.ApplicationWindow):
'recipient': recipient,
'status': status}
def _on_conditions_treeview_cursor_changed(self, widget):
def _on_conditions_treeview_cursor_changed(self,
widget: Gtk.TreeView
) -> None:
(model, iter_) = widget.get_selection().get_selected()
if not iter_:
self._active_num = ''
self._active_num = -1
return
self._active_num = model[iter_][0]
if self._active_num == 0:
self._ui.up_button.set_sensitive(False)
else:
self._ui.up_button.set_sensitive(True)
_max = widget.get_model().iter_n_children(None)
model = widget.get_model()
assert model is not None
_max = model.iter_n_children(None)
if self._active_num == _max - 1:
self._ui.down_button.set_sensitive(False)
else:
@@ -293,7 +326,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.config_box.set_sensitive(True)
self._ui.delete_button.set_sensitive(True)
def _on_new_button_clicked(self, _widget):
def _on_new_button_clicked(self, _button: Gtk.Button) -> None:
model = self._ui.conditions_treeview.get_model()
num = self._ui.conditions_treeview.get_model().iter_n_children(None)
self._config[num] = {
@@ -317,7 +350,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._set_treeview_string()
self._ui.config_box.set_sensitive(True)
def _on_delete_button_clicked(self, widget):
def _on_delete_button_clicked(self, button: Gtk.Button) -> None:
selection = self._ui.conditions_treeview.get_selection()
(model, iter_) = selection.get_selected()
if not iter_:
@@ -332,13 +365,13 @@ class ConfigDialog(Gtk.ApplicationWindow):
iter2 = model.iter_next(iter2)
model.remove(iter_)
del self._config[num]
self._active_num = ''
widget.set_sensitive(False)
self._active_num = -1
button.set_sensitive(False)
self._ui.up_button.set_sensitive(False)
self._ui.down_button.set_sensitive(False)
self._ui.config_box.set_sensitive(False)
def _on_up_button_clicked(self, _widget):
def _on_up_button_clicked(self, _button: Gtk.Button) -> None:
selection = self._ui.conditions_treeview.get_selection()
(model, iter_) = selection.get_selected()
if not iter_:
@@ -355,7 +388,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._on_conditions_treeview_cursor_changed(
self._ui.conditions_treeview)
def _on_down_button_clicked(self, _widget):
def _on_down_button_clicked(self, _button: Gtk.Button) -> None:
selection = self._ui.conditions_treeview.get_selection()
(model, iter_) = selection.get_selected()
if not iter_:
@@ -370,10 +403,10 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._on_conditions_treeview_cursor_changed(
self._ui.conditions_treeview)
def _on_event_combobox_changed(self, widget):
def _on_event_combobox_changed(self, combo: Gtk.ComboBox) -> None:
if self._active_num < 0:
return
active = widget.get_active()
active = combo.get_active()
if active == -1:
return
event = list(EVENTS.keys())[active]
@@ -383,7 +416,10 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.__dict__[widget].set_state(False)
self._set_treeview_string()
def _on_recipient_type_combobox_changed(self, widget):
def _on_recipient_type_combobox_changed(self,
widget: Gtk.ComboBox
) -> None:
if self._active_num < 0:
return
recipient_type = RECIPIENT_TYPES[widget.get_active()]
@@ -394,7 +430,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.recipient_list_entry.set_sensitive(True)
self._set_treeview_string()
def _on_recipient_list_entry_changed(self, widget):
def _on_recipient_list_entry_changed(self, widget: Gtk.Entry) -> None:
if self._active_num < 0:
return
recipients = widget.get_text()
@@ -402,7 +438,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._config[self._active_num]['recipients'] = recipients
self._set_treeview_string()
def _set_status_config(self):
def _set_status_config(self) -> None:
if self._active_num < 0:
return
status = ''
@@ -414,7 +450,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._config[self._active_num]['status'] = status
self._set_treeview_string()
def _on_status_radiobutton_toggled(self, _widget):
def _on_status_radiobutton_toggled(self, _widget: Gtk.RadioButton) -> None:
if self._active_num < 0:
return
if self._ui.all_status_rb.get_active():
@@ -432,13 +468,13 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._set_treeview_string()
def _on_status_cb_toggled(self, _widget):
def _on_status_cb_toggled(self, _widget: Gtk.CheckButton) -> None:
if self._active_num < 0:
return
self._set_status_config()
# tab_opened OR (not xor) not_tab_opened must be active
def _on_tab_opened_cb_toggled(self, widget):
def _on_tab_opened_cb_toggled(self, widget: Gtk.CheckButton) -> None:
if self._active_num < 0:
return
if widget.get_active():
@@ -454,7 +490,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.not_tab_opened_cb.set_active(True)
self._config[self._active_num]['tab_opened'] = 'no'
def _on_not_tab_opened_cb_toggled(self, widget):
def _on_not_tab_opened_cb_toggled(self, widget: Gtk.CheckButton) -> None:
if self._active_num < 0:
return
if widget.get_active():
@@ -467,7 +503,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._config[self._active_num]['tab_opened'] = 'yes'
# has_focus OR (not xor) not_has_focus must be active
def _on_has_focus_cb_toggled(self, widget):
def _on_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None:
if self._active_num < 0:
return
if widget.get_active():
@@ -479,7 +515,7 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.not_has_focus_cb.set_active(True)
self._config[self._active_num]['has_focus'] = 'no'
def _on_not_has_focus_cb_toggled(self, widget):
def _on_not_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None:
if self._active_num < 0:
return
if widget.get_active():
@@ -491,7 +527,12 @@ class ConfigDialog(Gtk.ApplicationWindow):
self._ui.has_focus_cb.set_active(True)
self._config[self._active_num]['has_focus'] = 'yes'
def _on_use_it_toggled(self, widget, opposite_widget, option):
def _on_use_it_toggled(self,
widget: Gtk.CheckButton,
opposite_widget: Gtk.CheckButton,
option: str
) -> None:
if widget.get_active():
if opposite_widget.get_active():
opposite_widget.set_active(False)
@@ -501,7 +542,12 @@ class ConfigDialog(Gtk.ApplicationWindow):
else:
self._config[self._active_num][option] = ''
def _on_disable_it_toggled(self, widget, opposite_widget, option):
def _on_disable_it_toggled(self,
widget: Gtk.CheckButton,
opposite_widget: Gtk.CheckButton,
option: str
) -> None:
if widget.get_active():
if opposite_widget.get_active():
opposite_widget.set_active(False)
@@ -511,38 +557,38 @@ class ConfigDialog(Gtk.ApplicationWindow):
else:
self._config[self._active_num][option] = ''
def _on_use_sound_cb_toggled(self, widget):
def _on_use_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._on_use_it_toggled(widget, self._ui.disable_sound_cb, 'sound')
if widget.get_active():
self._ui.sound_file_box.set_sensitive(True)
else:
self._ui.sound_file_box.set_sensitive(False)
def _on_sound_file_set(self, widget):
def _on_sound_file_set(self, widget: Gtk.FileChooserButton) -> None:
self._config[self._active_num]['sound_file'] = widget.get_filename()
def _on_play_button_clicked(self, _widget):
def _on_play_button_clicked(self, _button: Gtk.Button) -> None:
play_sound_file(self._ui.filechooser.get_filename())
def _on_disable_sound_cb_toggled(self, widget):
def _on_disable_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._on_disable_it_toggled(widget, self._ui.use_sound_cb, 'sound')
def _on_use_popup_cb_toggled(self, widget):
def _on_use_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._on_use_it_toggled(widget, self._ui.disable_popup_cb, 'popup')
def _on_disable_popup_cb_toggled(self, widget):
def _on_disable_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._on_disable_it_toggled(widget, self._ui.use_popup_cb, 'popup')
def _on_run_command_cb_toggled(self, widget):
def _on_run_command_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._config[self._active_num]['run_command'] = widget.get_active()
if widget.get_active():
self._ui.command_entry.set_sensitive(True)
else:
self._ui.command_entry.set_sensitive(False)
def _on_command_entry_changed(self, widget):
def _on_command_entry_changed(self, widget: Gtk.Entry) -> None:
self._config[self._active_num]['command'] = widget.get_text()
def _on_one_shot_cb_toggled(self, widget):
def _on_one_shot_cb_toggled(self, widget: Gtk.CheckButton) -> None:
self._config[self._active_num]['one_shot'] = widget.get_active()
self._ui.command_entry.set_sensitive(widget.get_active())

View File

@@ -14,16 +14,19 @@
#
# 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 Callable
from typing import cast
from typing import Union
import logging
from functools import partial
from nbxmpp.protocol import JID
from gajim.common import app
from gajim.common import ged
from gajim.common.const import PROPAGATE_EVENT
@@ -31,24 +34,26 @@ from gajim.common.const import STOP_EVENT
from gajim.common.events import Notification
from gajim.common.events import GcMessageReceived
from gajim.common.events import MessageReceived
from gajim.common.events import PresenceReceived
from gajim.common.helpers import exec_command
from gajim.common.helpers import play_sound_file
from gajim.plugins import GajimPlugin
from gajim.plugins.plugins_i18n import _
from triggers.gtk.config import ConfigDialog
from triggers.util import log_result
from triggers.util import RuleResult
from triggers.gtk.config import ConfigDialog
log = logging.getLogger('gajim.p.triggers')
MessageEventsT = Union[GcMessageReceived, MessageReceived]
ProcessableEventsT = Union[MessageEventsT, Notification, PresenceReceived]
RuleT = dict[str, Any]
class Triggers(GajimPlugin):
def init(self):
def init(self) -> None:
self.description = _(
'Configure Gajims behaviour with triggers for each contact')
self.config_dialog = partial(ConfigDialog, self)
@@ -77,7 +82,7 @@ class Triggers(GajimPlugin):
log.info('Result: %s', result)
return self._excecute_message_rules(result)
def _on_presence_received(self, event):
def _on_presence_received(self, event: PresenceReceived) -> None:
# TODO
return
@@ -89,14 +94,19 @@ class Triggers(GajimPlugin):
check_func = self._check_rule_apply_status_changed
self._check_all(event, check_func, self._apply_rule)
def _check_all(self, event, check_func, apply_func) -> RuleResult:
def _check_all(self,
event: ProcessableEventsT,
check_func: Callable[..., bool],
apply_func: Callable[..., Any]
) -> RuleResult:
result = RuleResult()
rules_num = [int(item) for item in self.config.keys()]
rules_num.sort()
to_remove = []
to_remove: list[int] = []
for num in rules_num:
rule = self.config[str(num)]
rule = cast(RuleT, self.config[str(num)])
if check_func(event, rule):
apply_func(result, rule)
if 'one_shot' in rule and rule['one_shot']:
@@ -108,7 +118,8 @@ class Triggers(GajimPlugin):
if num + decal in to_remove:
num2 = num
while str(num2 + 1) in self.config:
self.config[str(num2)] = self.config[str(num2 + 1)].copy()
copy = self.config[str(num2 + 1)].copy() # type: ignore
self.config[str(num2)] = copy
num2 += 1
del self.config[str(num2)]
decal += 1
@@ -120,43 +131,64 @@ class Triggers(GajimPlugin):
@log_result
def _check_rule_apply_msg_received(self,
event: MessageEventsT,
rule: RuleT) -> bool:
rule: RuleT
) -> bool:
return self._check_rule_all('message_received', event, rule)
@log_result
def _check_rule_apply_connected(self, event, rule: RuleT) -> bool:
def _check_rule_apply_connected(self,
event: PresenceReceived,
rule: RuleT
) -> bool:
return self._check_rule_all('contact_connected', event, rule)
@log_result
def _check_rule_apply_disconnected(self, event, rule: RuleT) -> bool:
def _check_rule_apply_disconnected(self,
event: PresenceReceived,
rule: RuleT
) -> bool:
return self._check_rule_all('contact_disconnected', event, rule)
@log_result
def _check_rule_apply_status_changed(self, event, rule: RuleT) -> bool:
def _check_rule_apply_status_changed(self,
event: PresenceReceived,
rule: RuleT
) -> bool:
return self._check_rule_all('contact_status_change', event, rule)
@log_result
def _check_rule_apply_notification(self,
event: Notification,
rule: RuleT) -> bool:
rule: RuleT
) -> bool:
# Check notification type
notif_type = ''
if event.type == 'incoming-message':
notif_type = 'message_received'
if event.type == 'pres':
# TODO:
if (event.base_event.old_show < 2 and
event.base_event.new_show > 1):
notif_type = 'contact_connected'
elif (event.base_event.old_show > 1 and
event.base_event.new_show < 2):
notif_type = 'contact_disconnected'
else:
notif_type = 'contact_status_change'
# if event.type == 'pres':
# # TODO:
# if (event.base_event.old_show < 2 and
# event.base_event.new_show > 1):
# notif_type = 'contact_connected'
# elif (event.base_event.old_show > 1 and
# event.base_event.new_show < 2):
# notif_type = 'contact_disconnected'
# else:
# notif_type = 'contact_status_change'
return self._check_rule_all(notif_type, event, rule)
def _check_rule_all(self, notif_type: str, event: Any, rule: RuleT) -> bool:
def _check_rule_all(self,
notif_type: str,
event: ProcessableEventsT,
rule: RuleT
) -> bool:
# Check notification type
if rule['event'] != notif_type:
return False
@@ -181,7 +213,11 @@ class Triggers(GajimPlugin):
return True
@log_result
def _check_rule_recipients(self, event, rule: RuleT) -> bool:
def _check_rule_recipients(self,
event: ProcessableEventsT,
rule: RuleT
) -> bool:
rule_recipients = [t.strip() for t in rule['recipients'].split(',')]
if rule['recipient_type'] == 'groupchat':
if event.jid in rule_recipients:
@@ -207,7 +243,11 @@ class Triggers(GajimPlugin):
return True
@log_result
def _check_rule_status(self, event, rule: RuleT) -> bool:
def _check_rule_status(self,
event: ProcessableEventsT,
rule: RuleT
) -> bool:
rule_statuses = rule['status'].split()
client = app.get_client(event.account)
if rule['status'] != 'all' and client.status not in rule_statuses:
@@ -216,10 +256,15 @@ class Triggers(GajimPlugin):
return True
@log_result
def _check_rule_tab_opened(self, event, rule: RuleT) -> bool:
def _check_rule_tab_opened(self,
event: ProcessableEventsT,
rule: RuleT
) -> bool:
if rule['tab_opened'] == 'both':
return True
tab_opened = False
assert isinstance(event.jid, JID)
if app.window.chat_exists(event.account, event.jid):
tab_opened = True
if tab_opened and rule['tab_opened'] == 'no':
@@ -230,12 +275,17 @@ class Triggers(GajimPlugin):
return True
@log_result
def _check_rule_has_focus(self, event, rule: RuleT) -> bool:
def _check_rule_has_focus(self,
event: ProcessableEventsT,
rule: RuleT
) -> bool:
if rule['has_focus'] == 'both':
return True
if rule['tab_opened'] == 'no':
# Does not apply in this case
return True
assert isinstance(event.jid, JID)
chat_active = app.window.is_chat_active(event.account, event.jid)
if chat_active and rule['has_focus'] == 'no':
return False
@@ -263,7 +313,9 @@ class Triggers(GajimPlugin):
def _excecute_notification_rules(self,
result: RuleResult,
event: Notification) -> bool:
event: Notification
) -> bool:
if result.sound is False:
event.sound = None

View File

@@ -12,20 +12,29 @@
# 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 Callable
from typing import Optional
from typing import TYPE_CHECKING
import logging
from dataclasses import dataclass
if TYPE_CHECKING:
from .triggers import ProcessableEventsT
from .triggers import RuleT
log = logging.getLogger('gajim.p.triggers')
def log_result(func):
def wrapper(self, event, rule):
res = func(self, event, rule)
log.info(f'{event.name} -> {func.__name__} -> {res}')
return res
return wrapper
def log_result(func: Callable[..., Any]) -> Callable[..., bool]:
def wrapper(self: Any, event: ProcessableEventsT, rule: RuleT):
res = func(self, event, rule)
log.info(f'{event.name} -> {func.__name__} -> {res}')
return res
return wrapper
@dataclass