Files
deluge/deluge/plugins/Notifications/deluge_notifications/gtkui.py
DjLegolas ec0bcc11f5 Upgrade codebase with pyupgrade (>=py3.6)
Added pyupgrade utility with manual stage to pre-commit and run on all
files.

Ref: https://github.com/asottile/pyupgrade
Closes: deluge-torrent/deluge#326
2021-12-29 21:51:07 +00:00

742 lines
29 KiB
Python

#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import logging
from os.path import basename
from gi import require_version
from gi.repository import Gtk
from twisted.internet import defer
import deluge.common
import deluge.component as component
import deluge.configmanager
from deluge.plugins.pluginbase import Gtk3PluginBase
from deluge.ui.client import client
from .common import CustomNotifications, get_resource
# Relative imports
log = logging.getLogger(__name__)
try:
import pygame
SOUND_AVAILABLE = True
except ImportError:
SOUND_AVAILABLE = False
try:
require_version('Notify', '0.7')
from gi.repository import GLib, Notify
except (ValueError, ImportError):
POPUP_AVAILABLE = False
else:
POPUP_AVAILABLE = not deluge.common.windows_check()
DEFAULT_PREFS = {
# BLINK
'blink_enabled': False,
# FLASH
'flash_enabled': False,
# POPUP
'popup_enabled': False,
# SOUND
'sound_enabled': False,
'sound_path': '',
'custom_sounds': {},
# Subscriptions
'subscriptions': {'popup': [], 'blink': [], 'sound': []},
}
RECIPIENT_FIELD, RECIPIENT_EDIT = list(range(2))
(
SUB_EVENT,
SUB_EVENT_DOC,
SUB_NOT_EMAIL,
SUB_NOT_POPUP,
SUB_NOT_BLINK,
SUB_NOT_SOUND,
) = list(range(6))
SND_EVENT, SND_EVENT_DOC, SND_NAME, SND_PATH = list(range(4))
class GtkUiNotifications(CustomNotifications):
def __init__(self, plugin_name=None):
CustomNotifications.__init__(self, plugin_name)
def enable(self):
CustomNotifications.enable(self)
self.register_custom_blink_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_blink
)
self.register_custom_sound_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_sound
)
self.register_custom_popup_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_popup
)
def disable(self):
self.deregister_custom_blink_notification('TorrentFinishedEvent')
self.deregister_custom_sound_notification('TorrentFinishedEvent')
self.deregister_custom_popup_notification('TorrentFinishedEvent')
CustomNotifications.disable(self)
def register_custom_popup_notification(self, eventtype, handler):
"""This is used to register popup notifications for custom event types.
:param event: the event name
:param type: string
:param handler: function, to be called when `:param:event` is emitted
Your handler should return a tuple of (popup_title, popup_contents).
"""
self._register_custom_provider('popup', eventtype, handler)
def deregister_custom_popup_notification(self, eventtype):
self._deregister_custom_provider('popup', eventtype)
def register_custom_blink_notification(self, eventtype, handler):
"""This is used to register blink notifications for custom event types.
:param event: str, the event name
:param handler: function, to be called when `:param:event` is emitted
Your handler should return `True` or `False` to blink or not the
trayicon.
"""
self._register_custom_provider('blink', eventtype, handler)
def deregister_custom_blink_notification(self, eventtype):
self._deregister_custom_provider('blink', eventtype)
def register_custom_sound_notification(self, eventtype, handler):
"""This is used to register sound notifications for custom event types.
:param event: the event name
:type event: string
:param handler: function to be called when `:param:event` is emitted
Your handler should return either '' to use the sound defined on the
notification preferences, the path to a sound file, which will then be
played or None, where no sound will be played at all.
"""
self._register_custom_provider('sound', eventtype, handler)
def deregister_custom_sound_notification(self, eventtype):
self._deregister_custom_provider('sound', eventtype)
def handle_custom_popup_notification(self, result, eventtype):
title, message = result
return defer.maybeDeferred(self.__popup, title, message)
def handle_custom_blink_notification(self, result, eventtype):
if result:
return defer.maybeDeferred(self.__blink)
return defer.succeed(
'Will not blink. The returned value from the custom '
'handler was: %s' % result
)
def handle_custom_sound_notification(self, result, eventtype):
if isinstance(result, ''.__class__):
if not result and eventtype in self.config['custom_sounds']:
return defer.maybeDeferred(
self.__play_sound, self.config['custom_sounds'][eventtype]
)
return defer.maybeDeferred(self.__play_sound, result)
return defer.succeed(
'Will not play sound. The returned value from the '
'custom handler was: %s' % result
)
def __blink(self):
self.systray.blink(True)
return defer.succeed(_('Notification Blink shown'))
def __popup(self, title='', message=''):
if not self.config['popup_enabled']:
return defer.succeed(_('Popup notification is not enabled.'))
if not POPUP_AVAILABLE:
err_msg = _('libnotify is not installed')
log.warning(err_msg)
return defer.fail(ImportError(err_msg))
if Notify.init('Deluge'):
self.note = Notify.Notification.new(title, message, 'deluge-panel')
self.note.set_hint('desktop-entry', GLib.Variant.new_string('deluge'))
if not self.note.show():
err_msg = _('Failed to popup notification')
log.warning(err_msg)
return defer.fail(Exception(err_msg))
return defer.succeed(_('Notification popup shown'))
def __play_sound(self, sound_path=''):
if not self.config['sound_enabled']:
return defer.succeed(_('Sound notification not enabled'))
if not SOUND_AVAILABLE:
err_msg = _('pygame is not installed')
log.warning(err_msg)
return defer.fail(ImportError(err_msg))
pygame.init()
try:
if not sound_path:
sound_path = self.config['sound_path']
alert_sound = pygame.mixer.music
alert_sound.load(sound_path)
alert_sound.play()
except pygame.error as ex:
err_msg = _('Sound notification failed %s') % ex
log.warning(err_msg)
return defer.fail(ex)
else:
msg = _('Sound notification Success')
log.info(msg)
return defer.succeed(msg)
def _on_torrent_finished_event_blink(self, torrent_id):
return True # Yes, Blink
def _on_torrent_finished_event_sound(self, torrent_id):
# Since there's no custom sound hardcoded, just return ''
return ''
def _on_torrent_finished_event_popup(self, torrent_id):
d = client.core.get_torrent_status(torrent_id, ['name', 'file_progress'])
d.addCallback(self._on_torrent_finished_event_got_torrent_status)
d.addErrback(self._on_torrent_finished_event_torrent_status_failure)
return d
def _on_torrent_finished_event_torrent_status_failure(self, failure):
log.debug('Failed to get torrent status to be able to show the popup')
def _on_torrent_finished_event_got_torrent_status(self, torrent_status):
log.debug(
'Handler for TorrentFinishedEvent GTKUI called. ' 'Got Torrent Status'
)
title = _('Finished Torrent')
torrent_status['num_files'] = torrent_status['file_progress'].count(1.0)
message = (
_(
'The torrent "%(name)s" including %(num_files)i file(s) '
'has finished downloading.'
)
% torrent_status
)
return title, message
class GtkUI(Gtk3PluginBase, GtkUiNotifications):
def __init__(self, plugin_name):
Gtk3PluginBase.__init__(self, plugin_name)
GtkUiNotifications.__init__(self)
def enable(self):
self.config = deluge.configmanager.ConfigManager(
'notifications-gtk.conf', DEFAULT_PREFS
)
self.builder = Gtk.Builder()
self.builder.add_from_file(get_resource('config.ui'))
self.builder.get_object('smtp_port').set_value(25)
self.prefs = self.builder.get_object('prefs_box')
self.prefs.show_all()
self.build_recipients_model_populate_treeview()
self.build_sounds_model_populate_treeview()
self.build_notifications_model_populate_treeview()
client.notifications.get_handled_events().addCallback(
self.popuplate_what_needs_handled_events
)
self.builder.connect_signals(
{
'on_add_button_clicked': (
self.on_add_button_clicked,
self.recipients_treeview,
),
'on_delete_button_clicked': (
self.on_delete_button_clicked,
self.recipients_treeview,
),
'on_enabled_toggled': self.on_enabled_toggled,
'on_sound_enabled_toggled': self.on_sound_enabled_toggled,
'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked,
'on_sounds_revert_button_clicked': self.on_sounds_revert_button_clicked,
'on_sound_path_update_preview': self.on_sound_path_update_preview,
}
)
component.get('Preferences').add_page(_('Notifications'), self.prefs)
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs
)
if not POPUP_AVAILABLE:
self.builder.get_object('popup_enabled').set_property('sensitive', False)
if not SOUND_AVAILABLE:
# for widget_name in ('sound_enabled', 'sound_path', 'sounds_page', 'sounds_page_label'):
# self.builder.get_object(widget_name).set_property('sensitive', False)
self.builder.get_object('sound_enabled').set_property('sensitive', False)
self.builder.get_object('sound_path').set_property('sensitive', False)
self.builder.get_object('sounds_page').set_property('sensitive', False)
self.builder.get_object('sounds_page_label').set_property(
'sensitive', False
)
self.systray = component.get('SystemTray')
if not hasattr(self.systray, 'tray'):
# Tray is not beeing used
self.builder.get_object('blink_enabled').set_property('sensitive', False)
GtkUiNotifications.enable(self)
def disable(self):
GtkUiNotifications.disable(self)
component.get('Preferences').remove_page(_('Notifications'))
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs
)
def build_recipients_model_populate_treeview(self):
# SMTP Recipients treeview/model
self.recipients_treeview = self.builder.get_object('smtp_recipients')
treeview_selection = self.recipients_treeview.get_selection()
treeview_selection.connect(
'changed', self.on_recipients_treeview_selection_changed
)
self.recipients_model = Gtk.ListStore(str, bool)
renderer = Gtk.CellRendererText()
renderer.connect('edited', self.on_cell_edited, self.recipients_model)
renderer.recipient = RECIPIENT_FIELD
column = Gtk.TreeViewColumn(
'Recipients', renderer, text=RECIPIENT_FIELD, editable=RECIPIENT_EDIT
)
column.set_expand(True)
self.recipients_treeview.append_column(column)
self.recipients_treeview.set_model(self.recipients_model)
def build_sounds_model_populate_treeview(self):
# Sound customisation treeview/model
self.sounds_treeview = self.builder.get_object('sounds_treeview')
sounds_selection = self.sounds_treeview.get_selection()
sounds_selection.connect('changed', self.on_sounds_treeview_selection_changed)
self.sounds_treeview.set_tooltip_column(SND_EVENT_DOC)
self.sounds_model = Gtk.ListStore(str, str, str, str)
renderer = Gtk.CellRendererText()
renderer.event = SND_EVENT
column = Gtk.TreeViewColumn('Event', renderer, text=SND_EVENT)
column.set_expand(True)
self.sounds_treeview.append_column(column)
renderer = Gtk.CellRendererText()
renderer.event_doc = SND_EVENT_DOC
column = Gtk.TreeViewColumn('Doc', renderer, text=SND_EVENT_DOC)
column.set_property('visible', False)
self.sounds_treeview.append_column(column)
renderer = Gtk.CellRendererText()
renderer.sound_name = SND_NAME
column = Gtk.TreeViewColumn('Name', renderer, text=SND_NAME)
self.sounds_treeview.append_column(column)
renderer = Gtk.CellRendererText()
renderer.sound_path = SND_PATH
column = Gtk.TreeViewColumn('Path', renderer, text=SND_PATH)
column.set_property('visible', False)
self.sounds_treeview.append_column(column)
self.sounds_treeview.set_model(self.sounds_model)
def build_notifications_model_populate_treeview(self):
# Notification Subscriptions treeview/model
self.subscriptions_treeview = self.builder.get_object('subscriptions_treeview')
subscriptions_selection = self.subscriptions_treeview.get_selection()
subscriptions_selection.connect(
'changed', self.on_subscriptions_treeview_selection_changed
)
self.subscriptions_treeview.set_tooltip_column(SUB_EVENT_DOC)
self.subscriptions_model = Gtk.ListStore(str, str, bool, bool, bool, bool)
renderer = Gtk.CellRendererText()
setattr(renderer, 'event', SUB_EVENT)
column = Gtk.TreeViewColumn('Event', renderer, text=SUB_EVENT)
column.set_expand(True)
self.subscriptions_treeview.append_column(column)
renderer = Gtk.CellRendererText()
setattr(renderer, 'event_doc', SUB_EVENT)
column = Gtk.TreeViewColumn('Doc', renderer, text=SUB_EVENT_DOC)
column.set_property('visible', False)
self.subscriptions_treeview.append_column(column)
renderer = Gtk.CellRendererToggle()
renderer.set_property('activatable', True)
renderer.connect('toggled', self._on_email_col_toggled)
column = Gtk.TreeViewColumn('Email', renderer, active=SUB_NOT_EMAIL)
column.set_clickable(True)
self.subscriptions_treeview.append_column(column)
renderer = Gtk.CellRendererToggle()
renderer.set_property('activatable', True)
renderer.connect('toggled', self._on_popup_col_toggled)
column = Gtk.TreeViewColumn('Popup', renderer, active=SUB_NOT_POPUP)
column.set_clickable(True)
self.subscriptions_treeview.append_column(column)
renderer = Gtk.CellRendererToggle()
renderer.set_property('activatable', True)
renderer.connect('toggled', self._on_blink_col_toggled)
column = Gtk.TreeViewColumn('Blink', renderer, active=SUB_NOT_BLINK)
column.set_clickable(True)
self.subscriptions_treeview.append_column(column)
renderer = Gtk.CellRendererToggle()
renderer.set_property('activatable', True)
renderer.connect('toggled', self._on_sound_col_toggled)
column = Gtk.TreeViewColumn('Sound', renderer, active=SUB_NOT_SOUND)
column.set_clickable(True)
self.subscriptions_treeview.append_column(column)
self.subscriptions_treeview.set_model(self.subscriptions_model)
def popuplate_what_needs_handled_events(
self, handled_events, email_subscriptions=None
):
if email_subscriptions is None:
email_subscriptions = []
self.populate_subscriptions(handled_events, email_subscriptions)
self.populate_sounds(handled_events)
def populate_sounds(self, handled_events):
self.sounds_model.clear()
for event_name, event_doc in handled_events:
if event_name in self.config['custom_sounds']:
snd_path = self.config['custom_sounds'][event_name]
else:
snd_path = self.config['sound_path']
if snd_path:
self.sounds_model.set(
self.sounds_model.append(),
SND_EVENT,
event_name,
SND_EVENT_DOC,
event_doc,
SND_NAME,
basename(snd_path),
SND_PATH,
snd_path,
)
def populate_subscriptions(self, handled_events, email_subscriptions=None):
if email_subscriptions is None:
email_subscriptions = []
subscriptions_dict = self.config['subscriptions']
self.subscriptions_model.clear()
# self.handled_events = handled_events
for event_name, event_doc in handled_events:
self.subscriptions_model.set(
self.subscriptions_model.append(),
SUB_EVENT,
event_name,
SUB_EVENT_DOC,
event_doc,
SUB_NOT_EMAIL,
event_name in email_subscriptions,
SUB_NOT_POPUP,
event_name in subscriptions_dict['popup'],
SUB_NOT_BLINK,
event_name in subscriptions_dict['blink'],
SUB_NOT_SOUND,
event_name in subscriptions_dict['sound'],
)
def on_apply_prefs(self):
log.debug('applying prefs for Notifications')
current_popup_subscriptions = []
current_blink_subscriptions = []
current_sound_subscriptions = []
current_email_subscriptions = []
for event, doc, email, popup, blink, sound in self.subscriptions_model:
if email:
current_email_subscriptions.append(event)
if popup:
current_popup_subscriptions.append(event)
if blink:
current_blink_subscriptions.append(event)
if sound:
current_sound_subscriptions.append(event)
old_sound_file = self.config['sound_path']
new_sound_file = self.builder.get_object('sound_path').get_filename()
log.debug(
'Old Default sound file: %s New one: %s', old_sound_file, new_sound_file
)
custom_sounds = {}
for event_name, event_doc, filename, filepath in self.sounds_model:
log.debug('Custom sound for event "%s": %s', event_name, filename)
if filepath == old_sound_file:
continue
custom_sounds[event_name] = filepath
self.config.config.update(
{
'popup_enabled': self.builder.get_object('popup_enabled').get_active(),
'blink_enabled': self.builder.get_object('blink_enabled').get_active(),
'sound_enabled': self.builder.get_object('sound_enabled').get_active(),
'sound_path': new_sound_file,
'subscriptions': {
'popup': current_popup_subscriptions,
'blink': current_blink_subscriptions,
'sound': current_sound_subscriptions,
},
'custom_sounds': custom_sounds,
}
)
self.config.save()
core_config = {
'smtp_enabled': self.builder.get_object('smtp_enabled').get_active(),
'smtp_host': self.builder.get_object('smtp_host').get_text(),
'smtp_port': self.builder.get_object('smtp_port').get_value(),
'smtp_user': self.builder.get_object('smtp_user').get_text(),
'smtp_pass': self.builder.get_object('smtp_pass').get_text(),
'smtp_from': self.builder.get_object('smtp_from').get_text(),
'smtp_tls': self.builder.get_object('smtp_tls').get_active(),
'smtp_recipients': [
dest[0] for dest in self.recipients_model if dest[0] != 'USER@HOST'
],
'subscriptions': {'email': current_email_subscriptions},
}
client.notifications.set_config(core_config)
client.notifications.get_config().addCallback(self.cb_get_config)
def on_show_prefs(self):
client.notifications.get_config().addCallback(self.cb_get_config)
def cb_get_config(self, core_config):
"""Callback for on show_prefs."""
self.builder.get_object('smtp_host').set_text(core_config['smtp_host'])
self.builder.get_object('smtp_port').set_value(core_config['smtp_port'])
self.builder.get_object('smtp_user').set_text(core_config['smtp_user'])
self.builder.get_object('smtp_pass').set_text(core_config['smtp_pass'])
self.builder.get_object('smtp_from').set_text(core_config['smtp_from'])
self.builder.get_object('smtp_tls').set_active(core_config['smtp_tls'])
self.recipients_model.clear()
for recipient in core_config['smtp_recipients']:
self.recipients_model.set(
self.recipients_model.append(),
RECIPIENT_FIELD,
recipient,
RECIPIENT_EDIT,
False,
)
self.builder.get_object('smtp_enabled').set_active(core_config['smtp_enabled'])
self.builder.get_object('sound_enabled').set_active(
self.config['sound_enabled']
)
self.builder.get_object('popup_enabled').set_active(
self.config['popup_enabled']
)
self.builder.get_object('blink_enabled').set_active(
self.config['blink_enabled']
)
if self.config['sound_path']:
sound_path = self.config['sound_path']
else:
sound_path = deluge.common.get_default_download_dir()
self.builder.get_object('sound_path').set_filename(sound_path)
# Force toggle
self.on_enabled_toggled(self.builder.get_object('smtp_enabled'))
self.on_sound_enabled_toggled(self.builder.get_object('sound_enabled'))
client.notifications.get_handled_events().addCallback(
self.popuplate_what_needs_handled_events,
core_config['subscriptions']['email'],
)
def on_sound_path_update_preview(self, filechooser):
client.notifications.get_handled_events().addCallback(self.populate_sounds)
def on_add_button_clicked(self, widget, treeview):
model = treeview.get_model()
model.set(model.append(), RECIPIENT_FIELD, 'USER@HOST', RECIPIENT_EDIT, True)
def on_delete_button_clicked(self, widget, treeview):
selection = treeview.get_selection()
model, selected_iter = selection.get_selected()
if selected_iter:
model.remove(selected_iter)
def on_cell_edited(self, cell, path_string, new_text, model):
str_iter = model.get_iter_from_string(path_string)
model.set(str_iter, RECIPIENT_FIELD, new_text)
def on_recipients_treeview_selection_changed(self, selection):
model, selected_connection_iter = selection.get_selected()
if selected_connection_iter:
self.builder.get_object('delete_button').set_property('sensitive', True)
else:
self.builder.get_object('delete_button').set_property('sensitive', False)
def on_subscriptions_treeview_selection_changed(self, selection):
model, selected_connection_iter = selection.get_selected()
if selected_connection_iter:
self.builder.get_object('delete_button').set_property('sensitive', True)
else:
self.builder.get_object('delete_button').set_property('sensitive', False)
def on_sounds_treeview_selection_changed(self, selection):
model, selected_iter = selection.get_selected()
if selected_iter:
self.builder.get_object('sounds_edit_button').set_property(
'sensitive', True
)
path = model.get(selected_iter, SND_PATH)[0]
log.debug('Sound selection changed: %s', path)
if path != self.config['sound_path']:
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', True
)
else:
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', False
)
else:
self.builder.get_object('sounds_edit_button').set_property(
'sensitive', False
)
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', False
)
def on_sounds_revert_button_clicked(self, widget):
log.debug('on_sounds_revert_button_clicked')
selection = self.sounds_treeview.get_selection()
model, selected_iter = selection.get_selected()
if selected_iter:
log.debug('on_sounds_revert_button_clicked: got iter')
model.set(
selected_iter,
SND_PATH,
self.config['sound_path'],
SND_NAME,
basename(self.config['sound_path']),
)
def on_sounds_edit_button_clicked(self, widget):
log.debug('on_sounds_edit_button_clicked')
selection = self.sounds_treeview.get_selection()
model, selected_iter = selection.get_selected()
if selected_iter:
path = model.get(selected_iter, SND_PATH)[0]
dialog = Gtk.FileChooserDialog(
title=_('Choose Sound File'),
buttons=(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN,
Gtk.ResponseType.OK,
),
)
dialog.set_filename(path)
def update_model(response):
if response == Gtk.ResponseType.OK:
new_filename = dialog.get_filename()
dialog.destroy()
log.debug(new_filename)
model.set(
selected_iter,
SND_PATH,
new_filename,
SND_NAME,
basename(new_filename),
)
d = defer.maybeDeferred(dialog.run)
d.addCallback(update_model)
log.debug('dialog should have been shown')
def on_enabled_toggled(self, widget):
for widget_name in (
'smtp_host',
'smtp_port',
'smtp_user',
'smtp_pass',
'smtp_pass',
'smtp_tls',
'smtp_from',
'smtp_recipients',
):
self.builder.get_object(widget_name).set_property(
'sensitive', widget.get_active()
)
def on_sound_enabled_toggled(self, widget):
if widget.get_active():
self.builder.get_object('sound_path').set_property('sensitive', True)
self.builder.get_object('sounds_page').set_property('sensitive', True)
self.builder.get_object('sounds_page_label').set_property('sensitive', True)
else:
self.builder.get_object('sound_path').set_property('sensitive', False)
self.builder.get_object('sounds_page').set_property('sensitive', False)
self.builder.get_object('sounds_page_label').set_property(
'sensitive', False
)
# for widget_name in ('sounds_path', 'sounds_page', 'sounds_page_label'):
# self.builder.get_object(widget_name).set_property('sensitive',
# widget.get_active())
def _on_email_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_EMAIL] = not self.subscriptions_model[
path
][SUB_NOT_EMAIL]
return
def _on_popup_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_POPUP] = not self.subscriptions_model[
path
][SUB_NOT_POPUP]
return
def _on_blink_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_BLINK] = not self.subscriptions_model[
path
][SUB_NOT_BLINK]
return
def _on_sound_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_SOUND] = not self.subscriptions_model[
path
][SUB_NOT_SOUND]
return