diff --git a/setup.cfg b/setup.cfg index b6eb0475d9..af4550aa0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [flake8] 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 [yapf] diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 1aa08c8b00..e82ee95c0f 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -2,8 +2,6 @@ # License: GPLv3 Copyright: 2008, Kovid Goyal from __future__ import absolute_import, division, print_function, unicode_literals -from functools import partial - from PyQt5.Qt import ( QBrush, QCheckBox, QCoreApplication, QDialog, QGridLayout, QHBoxLayout, QIcon, QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap, QPushButton, @@ -40,14 +38,14 @@ class Configure(Dialog): h.addWidget(fdo) v = QVBoxLayout() 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.setToolTip(_('Move the selected field up')) v.addWidget(b), v.addStretch(10) self.mud = b = QToolButton(self) b.setIcon(QIcon(I('arrow-down.png'))) 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) h.addLayout(v) diff --git a/src/calibre/gui2/dialogs/confirm_delete_location.py b/src/calibre/gui2/dialogs/confirm_delete_location.py index b2849d41ea..3a82f399fb 100644 --- a/src/calibre/gui2/dialogs/confirm_delete_location.py +++ b/src/calibre/gui2/dialogs/confirm_delete_location.py @@ -4,8 +4,6 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' \ '2010, John Schember ' __docformat__ = 'restructuredtext en' -from functools import partial - from calibre.gui2.dialogs.confirm_delete_location_ui import Ui_Dialog from PyQt5.Qt import QDialog, Qt, QPixmap, QIcon @@ -20,9 +18,9 @@ class Dialog(QDialog, Ui_Dialog): self.msg.setText(msg) self.name = name self.buttonBox.setFocus(Qt.OtherFocusReason) - self.button_lib.clicked.connect(partial(self.set_loc, 'lib')) - self.button_device.clicked.connect(partial(self.set_loc, 'dev')) - self.button_both.clicked.connect(partial(self.set_loc, 'both')) + connect_lambda(self.button_lib.clicked, self, lambda self: self.set_loc('lib')) + connect_lambda(self.button_device.clicked, self, lambda self: self.set_loc('dev')) + connect_lambda(self.button_both.clicked, self, lambda self: self.set_loc('both')) def set_loc(self, loc): self.loc = loc diff --git a/src/calibre/gui2/dialogs/confirm_merge.py b/src/calibre/gui2/dialogs/confirm_merge.py index cd1a77d82a..1f50a6d0e0 100644 --- a/src/calibre/gui2/dialogs/confirm_merge.py +++ b/src/calibre/gui2/dialogs/confirm_merge.py @@ -6,7 +6,6 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -from functools import partial from PyQt5.Qt import ( QVBoxLayout, QSplitter, QWidget, QLabel, QCheckBox, QTextBrowser, Qt, ) @@ -116,7 +115,7 @@ class ChooseMerge(Dialog): l.addWidget(ans) prefs_key = ans.prefs_key = 'choose-merge-cb-' + name 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: ans.setToolTip(tt) setattr(self, name, ans) diff --git a/src/calibre/gui2/dialogs/custom_recipes.py b/src/calibre/gui2/dialogs/custom_recipes.py index 5a38cc3d8a..3ba3baef7d 100644 --- a/src/calibre/gui2/dialogs/custom_recipes.py +++ b/src/calibre/gui2/dialogs/custom_recipes.py @@ -561,7 +561,7 @@ class CustomRecipes(Dialog): d.l = QVBoxLayout() d.setLayout(d.l) 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.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal, d) diff --git a/src/calibre/gui2/dialogs/exim.py b/src/calibre/gui2/dialogs/exim.py index cf1e9e993d..282540836c 100644 --- a/src/calibre/gui2/dialogs/exim.py +++ b/src/calibre/gui2/dialogs/exim.py @@ -177,10 +177,10 @@ class EximDialog(Dialog): l.addWidget(la) l.addSpacing(20) 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) 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) self.setup_export_panel() diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index d1b9371774..01726101e6 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -494,7 +494,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.initialize_combos() 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.tag_editor_button.clicked.connect(self.tag_editor) 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)) self.casing_algorithm.setCurrentIndex(idx) 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: self.central_widget.removeTab(1) diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index dbe7f1f61c..e47da7fbd1 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -212,11 +212,11 @@ def create_date_tab(self, db): self.date_human = dh = a(QComboBox(w)) for val, text in [('today', _('Today')), ('yesterday', _('Yesterday')), ('thismonth', _('This month'))]: dh.addItem(text, val) - self.date_year.valueChanged.connect(lambda : self.sel_date.setChecked(True)) - self.date_month.currentIndexChanged.connect(lambda : self.sel_date.setChecked(True)) - self.date_day.valueChanged.connect(lambda : self.sel_date.setChecked(True)) - self.date_daysago.valueChanged.connect(lambda : self.sel_daysago.setChecked(True)) - self.date_human.currentIndexChanged.connect(lambda : self.sel_human.setChecked(True)) + connect_lambda(self.date_year.valueChanged, self, lambda self: self.sel_date.setChecked(True)) + connect_lambda(self.date_month.currentIndexChanged, self, lambda self: self.sel_date.setChecked(True)) + connect_lambda(self.date_day.valueChanged, self, lambda self: self.sel_date.setChecked(True)) + connect_lambda(self.date_daysago.valueChanged, self, lambda self: self.sel_daysago.setChecked(True)) + connect_lambda(self.date_human.currentIndexChanged, self, lambda self: self.sel_human.setChecked(True)) self.sel_date.setChecked(True) h.addStretch(10) diff --git a/src/calibre/gui2/dialogs/tag_editor.py b/src/calibre/gui2/dialogs/tag_editor.py index 5b9815d6eb..df6ff04234 100644 --- a/src/calibre/gui2/dialogs/tag_editor.py +++ b/src/calibre/gui2/dialogs/tag_editor.py @@ -1,8 +1,6 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -from functools import partial - from PyQt5.Qt import Qt, QDialog, QAbstractItemView from calibre.gui2.dialogs.tag_editor_ui import Ui_TagEditor @@ -67,19 +65,18 @@ class TagEditor(QDialog, Ui_TagEditor): if tag not in q: self.available_tags.addItem(tag) - self.apply_button.clicked.connect(lambda: self.apply_tags()) - self.unapply_button.clicked.connect(lambda: self.unapply_tags()) + connect_lambda(self.apply_button.clicked, self, lambda self: self.apply_tags()) + connect_lambda(self.unapply_button.clicked, self, lambda self: self.unapply_tags()) 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) # add the handlers for the filter input fields - self.available_filter_input.textChanged.connect(self.filter_tags) - self.applied_filter_input.textChanged.connect(partial(self.filter_tags, which='applied_tags')) + connect_lambda(self.available_filter_input.textChanged, self, lambda self, text: self.filter_tags(text)) + 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) - self.add_tag_input.textChanged.connect(partial(self.edit_box_changed, which="add_tag_input")) - self.available_filter_input.textChanged.connect(partial(self.edit_box_changed, which="available_filter_input")) - self.applied_filter_input.textChanged.connect(partial(self.edit_box_changed, which="applied_filter_input")) + for x in ('add_tag_input', 'available_filter_input', 'applied_filter_input'): + connect_lambda(getattr(self, x).textChanged, self, lambda self: self.edit_box_changed(x)) getattr(self, gprefs.get('tag_editor_last_filter', 'add_tag_input')).setFocus() if islinux: diff --git a/src/calibre/startup.py b/src/calibre/startup.py index ad899d0ca1..48b9d99a87 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -153,6 +153,24 @@ if not _run_once: __builtin__.__dict__['icu_upper'] = icu_upper __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: # Name all threads at the OS level created using the threading module, see # http://bugs.python.org/issue15500