Fix reference cycles due to lambda/partial slots in various dialogs

This commit is contained in:
Kovid Goyal 2018-07-24 07:50:50 +05:30
parent feec3e3024
commit 859917cce9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 44 additions and 32 deletions

View File

@ -1,6 +1,6 @@
[flake8] [flake8]
max-line-length = 160 max-line-length = 160
builtins = _,dynamic_property,__,P,I,lopen,icu_lower,icu_upper,icu_title,ngettext builtins = _,dynamic_property,__,P,I,lopen,icu_lower,icu_upper,icu_title,ngettext,connect_lambda
ignore = E12,E203,E22,E231,E241,E401,E402,E731,W391,E722,E741,W504 ignore = E12,E203,E22,E231,E241,E401,E402,E731,W391,E722,E741,W504
[yapf] [yapf]

View File

@ -2,8 +2,6 @@
# License: GPLv3 Copyright: 2008, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2008, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
from functools import partial
from PyQt5.Qt import ( from PyQt5.Qt import (
QBrush, QCheckBox, QCoreApplication, QDialog, QGridLayout, QHBoxLayout, QIcon, QBrush, QCheckBox, QCoreApplication, QDialog, QGridLayout, QHBoxLayout, QIcon,
QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap, QPushButton, QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap, QPushButton,
@ -40,14 +38,14 @@ class Configure(Dialog):
h.addWidget(fdo) h.addWidget(fdo)
v = QVBoxLayout() v = QVBoxLayout()
self.mub = b = QToolButton(self) self.mub = b = QToolButton(self)
b.clicked.connect(partial(move_field_up, fdo, self.model)) connect_lambda(b.clicked, self, lambda self: move_field_up(fdo, self.model))
b.setIcon(QIcon(I('arrow-up.png'))) b.setIcon(QIcon(I('arrow-up.png')))
b.setToolTip(_('Move the selected field up')) b.setToolTip(_('Move the selected field up'))
v.addWidget(b), v.addStretch(10) v.addWidget(b), v.addStretch(10)
self.mud = b = QToolButton(self) self.mud = b = QToolButton(self)
b.setIcon(QIcon(I('arrow-down.png'))) b.setIcon(QIcon(I('arrow-down.png')))
b.setToolTip(_('Move the selected field down')) b.setToolTip(_('Move the selected field down'))
b.clicked.connect(partial(move_field_down, fdo, self.model)) connect_lambda(b.clicked, self, lambda self: move_field_down(fdo, self.model))
v.addWidget(b) v.addWidget(b)
h.addLayout(v) h.addLayout(v)

View File

@ -4,8 +4,6 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' \
'2010, John Schember <john@nachtimwald.com>' '2010, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from functools import partial
from calibre.gui2.dialogs.confirm_delete_location_ui import Ui_Dialog from calibre.gui2.dialogs.confirm_delete_location_ui import Ui_Dialog
from PyQt5.Qt import QDialog, Qt, QPixmap, QIcon from PyQt5.Qt import QDialog, Qt, QPixmap, QIcon
@ -20,9 +18,9 @@ class Dialog(QDialog, Ui_Dialog):
self.msg.setText(msg) self.msg.setText(msg)
self.name = name self.name = name
self.buttonBox.setFocus(Qt.OtherFocusReason) self.buttonBox.setFocus(Qt.OtherFocusReason)
self.button_lib.clicked.connect(partial(self.set_loc, 'lib')) connect_lambda(self.button_lib.clicked, self, lambda self: self.set_loc('lib'))
self.button_device.clicked.connect(partial(self.set_loc, 'dev')) connect_lambda(self.button_device.clicked, self, lambda self: self.set_loc('dev'))
self.button_both.clicked.connect(partial(self.set_loc, 'both')) connect_lambda(self.button_both.clicked, self, lambda self: self.set_loc('both'))
def set_loc(self, loc): def set_loc(self, loc):
self.loc = loc self.loc = loc

View File

@ -6,7 +6,6 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
from functools import partial
from PyQt5.Qt import ( from PyQt5.Qt import (
QVBoxLayout, QSplitter, QWidget, QLabel, QCheckBox, QTextBrowser, Qt, QVBoxLayout, QSplitter, QWidget, QLabel, QCheckBox, QTextBrowser, Qt,
) )
@ -116,7 +115,7 @@ class ChooseMerge(Dialog):
l.addWidget(ans) l.addWidget(ans)
prefs_key = ans.prefs_key = 'choose-merge-cb-' + name prefs_key = ans.prefs_key = 'choose-merge-cb-' + name
ans.setChecked(gprefs.get(prefs_key, True)) ans.setChecked(gprefs.get(prefs_key, True))
ans.stateChanged.connect(partial(self.state_changed, ans), type=Qt.QueuedConnection) connect_lambda(ans.stateChanged, self, lambda self, state: self.state_changed(ans, state), type=Qt.QueuedConnection)
if tt: if tt:
ans.setToolTip(tt) ans.setToolTip(tt)
setattr(self, name, ans) setattr(self, name, ans)

View File

@ -561,7 +561,7 @@ class CustomRecipes(Dialog):
d.l = QVBoxLayout() d.l = QVBoxLayout()
d.setLayout(d.l) d.setLayout(d.l)
d.list = QListWidget(d) d.list = QListWidget(d)
d.list.doubleClicked.connect(lambda x: d.accept()) connect_lambda(d.list.doubleClicked, d, lambda d: d.accept())
d.l.addWidget(d.list) d.l.addWidget(d.list)
d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
Qt.Horizontal, d) Qt.Horizontal, d)

View File

@ -177,10 +177,10 @@ class EximDialog(Dialog):
l.addWidget(la) l.addWidget(la)
l.addSpacing(20) l.addSpacing(20)
self.exp_button = b = QPushButton(_('&Export all your calibre data')) self.exp_button = b = QPushButton(_('&Export all your calibre data'))
b.clicked.connect(partial(self.show_panel, 'export')) connect_lambda(b.clicked, self, lambda self: self.show_panel('export'))
l.addWidget(b), l.addSpacing(20) l.addWidget(b), l.addSpacing(20)
self.imp_button = b = QPushButton(_('&Import previously exported data')) self.imp_button = b = QPushButton(_('&Import previously exported data'))
b.clicked.connect(partial(self.show_panel, 'import')) connect_lambda(b.clicked, self, lambda self: self.show_panel('import'))
l.addWidget(b), l.addStretch(20) l.addWidget(b), l.addStretch(20)
self.setup_export_panel() self.setup_export_panel()

View File

@ -494,7 +494,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.initialize_combos() self.initialize_combos()
self.series.currentIndexChanged[int].connect(self.series_changed) self.series.currentIndexChanged[int].connect(self.series_changed)
self.rating.currentIndexChanged.connect(lambda:self.apply_rating.setChecked(True)) connect_lambda(self.rating.currentIndexChanged, self, lambda self:self.apply_rating.setChecked(True))
self.series.editTextChanged.connect(self.series_changed) self.series.editTextChanged.connect(self.series_changed)
self.tag_editor_button.clicked.connect(self.tag_editor) self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed) self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
@ -525,7 +525,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
idx = max(0, self.casing_map.index(prevca)) idx = max(0, self.casing_map.index(prevca))
self.casing_algorithm.setCurrentIndex(idx) self.casing_algorithm.setCurrentIndex(idx)
self.casing_algorithm.setEnabled(False) self.casing_algorithm.setEnabled(False)
self.change_title_to_title_case.toggled.connect(lambda : self.casing_algorithm.setEnabled(self.change_title_to_title_case.isChecked())) connect_lambda(
self.change_title_to_title_case.toggled, self,
lambda self: self.casing_algorithm.setEnabled(self.change_title_to_title_case.isChecked()))
if len(self.db.custom_field_keys(include_composites=False)) == 0: if len(self.db.custom_field_keys(include_composites=False)) == 0:
self.central_widget.removeTab(1) self.central_widget.removeTab(1)

View File

@ -212,11 +212,11 @@ def create_date_tab(self, db):
self.date_human = dh = a(QComboBox(w)) self.date_human = dh = a(QComboBox(w))
for val, text in [('today', _('Today')), ('yesterday', _('Yesterday')), ('thismonth', _('This month'))]: for val, text in [('today', _('Today')), ('yesterday', _('Yesterday')), ('thismonth', _('This month'))]:
dh.addItem(text, val) dh.addItem(text, val)
self.date_year.valueChanged.connect(lambda : self.sel_date.setChecked(True)) connect_lambda(self.date_year.valueChanged, self, lambda self: self.sel_date.setChecked(True))
self.date_month.currentIndexChanged.connect(lambda : self.sel_date.setChecked(True)) connect_lambda(self.date_month.currentIndexChanged, self, lambda self: self.sel_date.setChecked(True))
self.date_day.valueChanged.connect(lambda : self.sel_date.setChecked(True)) connect_lambda(self.date_day.valueChanged, self, lambda self: self.sel_date.setChecked(True))
self.date_daysago.valueChanged.connect(lambda : self.sel_daysago.setChecked(True)) connect_lambda(self.date_daysago.valueChanged, self, lambda self: self.sel_daysago.setChecked(True))
self.date_human.currentIndexChanged.connect(lambda : self.sel_human.setChecked(True)) connect_lambda(self.date_human.currentIndexChanged, self, lambda self: self.sel_human.setChecked(True))
self.sel_date.setChecked(True) self.sel_date.setChecked(True)
h.addStretch(10) h.addStretch(10)

View File

@ -1,8 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
from functools import partial
from PyQt5.Qt import Qt, QDialog, QAbstractItemView from PyQt5.Qt import Qt, QDialog, QAbstractItemView
from calibre.gui2.dialogs.tag_editor_ui import Ui_TagEditor from calibre.gui2.dialogs.tag_editor_ui import Ui_TagEditor
@ -67,19 +65,18 @@ class TagEditor(QDialog, Ui_TagEditor):
if tag not in q: if tag not in q:
self.available_tags.addItem(tag) self.available_tags.addItem(tag)
self.apply_button.clicked.connect(lambda: self.apply_tags()) connect_lambda(self.apply_button.clicked, self, lambda self: self.apply_tags())
self.unapply_button.clicked.connect(lambda: self.unapply_tags()) connect_lambda(self.unapply_button.clicked, self, lambda self: self.unapply_tags())
self.add_tag_button.clicked.connect(self.add_tag) self.add_tag_button.clicked.connect(self.add_tag)
self.delete_button.clicked.connect(lambda: self.delete_tags()) connect_lambda(self.delete_button.clicked, self, lambda self: self.delete_tags())
self.add_tag_input.returnPressed[()].connect(self.add_tag) self.add_tag_input.returnPressed[()].connect(self.add_tag)
# add the handlers for the filter input fields # add the handlers for the filter input fields
self.available_filter_input.textChanged.connect(self.filter_tags) connect_lambda(self.available_filter_input.textChanged, self, lambda self, text: self.filter_tags(text))
self.applied_filter_input.textChanged.connect(partial(self.filter_tags, which='applied_tags')) connect_lambda(self.applied_filter_input.textChanged, self, lambda self, text: self.filter_tags(text, which='applied_tags'))
# Restore the focus to the last input box used (typed into) # Restore the focus to the last input box used (typed into)
self.add_tag_input.textChanged.connect(partial(self.edit_box_changed, which="add_tag_input")) for x in ('add_tag_input', 'available_filter_input', 'applied_filter_input'):
self.available_filter_input.textChanged.connect(partial(self.edit_box_changed, which="available_filter_input")) connect_lambda(getattr(self, x).textChanged, self, lambda self: self.edit_box_changed(x))
self.applied_filter_input.textChanged.connect(partial(self.edit_box_changed, which="applied_filter_input"))
getattr(self, gprefs.get('tag_editor_last_filter', 'add_tag_input')).setFocus() getattr(self, gprefs.get('tag_editor_last_filter', 'add_tag_input')).setFocus()
if islinux: if islinux:

View File

@ -153,6 +153,24 @@ if not _run_once:
__builtin__.__dict__['icu_upper'] = icu_upper __builtin__.__dict__['icu_upper'] = icu_upper
__builtin__.__dict__['icu_title'] = title_case __builtin__.__dict__['icu_title'] = title_case
def connect_lambda(bound_signal, self, func, **kw):
import weakref
r = weakref.ref(self)
del self
num_args = func.__code__.co_argcount - 1
if num_args < 0:
raise TypeError('lambda must take at least one argument')
def slot(*args):
ctx = r()
if ctx is not None:
if len(args) != num_args:
args = args[:num_args]
func(ctx, *args)
bound_signal.connect(slot, **kw)
__builtin__.__dict__['connect_lambda'] = connect_lambda
if islinux: if islinux:
# Name all threads at the OS level created using the threading module, see # Name all threads at the OS level created using the threading module, see
# http://bugs.python.org/issue15500 # http://bugs.python.org/issue15500