diff --git a/build.py b/build.py index 07e69733..ff959400 100644 --- a/build.py +++ b/build.py @@ -30,12 +30,8 @@ def parse_args(): dest="clean", help="Clean build folder before building", ) - parser.add_option( - "--doc", action="store_true", dest="doc", help="Build only the help file" - ) - parser.add_option( - "--loc", action="store_true", dest="loc", help="Build only localization" - ) + parser.add_option("--doc", action="store_true", dest="doc", help="Build only the help file") + parser.add_option("--loc", action="store_true", dest="loc", help="Build only localization") parser.add_option( "--updatepot", action="store_true", @@ -96,9 +92,7 @@ def build_localizations(): locale_dest = op.join("build", "locale") if op.exists(locale_dest): shutil.rmtree(locale_dest) - shutil.copytree( - "locale", locale_dest, ignore=shutil.ignore_patterns("*.po", "*.pot") - ) + shutil.copytree("locale", locale_dest, ignore=shutil.ignore_patterns("*.po", "*.pot")) def build_updatepot(): @@ -165,9 +159,7 @@ def build_normal(): print("Building localizations") build_localizations() print("Building Qt stuff") - print_and_do( - "pyrcc5 {0} > {1}".format(op.join("qt", "dg.qrc"), op.join("qt", "dg_rc.py")) - ) + print_and_do("pyrcc5 {0} > {1}".format(op.join("qt", "dg.qrc"), op.join("qt", "dg_rc.py"))) fix_qt_resource_file(op.join("qt", "dg_rc.py")) build_help() diff --git a/core/app.py b/core/app.py index 20e31a3b..0e7656e9 100644 --- a/core/app.py +++ b/core/app.py @@ -132,9 +132,7 @@ class DupeGuru(Broadcaster): logging.debug("Debug mode enabled") Broadcaster.__init__(self) self.view = view - self.appdata = desktop.special_folder_path( - desktop.SpecialFolder.AppData, appname=self.NAME - ) + self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME) if not op.exists(self.appdata): os.makedirs(self.appdata) self.app_mode = AppMode.Standard @@ -182,17 +180,13 @@ class DupeGuru(Broadcaster): def _get_picture_cache_path(self): cache_type = self.options["picture_cache_type"] - cache_name = ( - "cached_pictures.shelve" if cache_type == "shelve" else "cached_pictures.db" - ) + cache_name = "cached_pictures.shelve" if cache_type == "shelve" else "cached_pictures.db" return op.join(self.appdata, cache_name) def _get_dupe_sort_key(self, dupe, get_group, key, delta): if self.app_mode in (AppMode.Music, AppMode.Picture): if key == "folder_path": - dupe_folder_path = getattr( - dupe, "display_folder_path", dupe.folder_path - ) + dupe_folder_path = getattr(dupe, "display_folder_path", dupe.folder_path) return str(dupe_folder_path).lower() if self.app_mode == AppMode.Picture: if delta and key == "dimensions": @@ -220,9 +214,7 @@ class DupeGuru(Broadcaster): def _get_group_sort_key(self, group, key): if self.app_mode in (AppMode.Music, AppMode.Picture): if key == "folder_path": - dupe_folder_path = getattr( - group.ref, "display_folder_path", group.ref.folder_path - ) + dupe_folder_path = getattr(group.ref, "display_folder_path", group.ref.folder_path) return str(dupe_folder_path).lower() if key == "percentage": return group.percentage @@ -235,9 +227,7 @@ class DupeGuru(Broadcaster): def _do_delete(self, j, link_deleted, use_hardlinks, direct_deletion): def op(dupe): j.add_progress() - return self._do_delete_dupe( - dupe, link_deleted, use_hardlinks, direct_deletion - ) + return self._do_delete_dupe(dupe, link_deleted, use_hardlinks, direct_deletion) j.start_job(self.results.mark_count) self.results.perform_on_marked(op, True) @@ -277,11 +267,7 @@ class DupeGuru(Broadcaster): return None def _get_export_data(self): - columns = [ - col - for col in self.result_table.columns.ordered_columns - if col.visible and col.name != "marked" - ] + columns = [col for col in self.result_table.columns.ordered_columns if col.visible and col.name != "marked"] colnames = [col.display for col in columns] rows = [] for group_id, group in enumerate(self.results.groups): @@ -293,11 +279,7 @@ class DupeGuru(Broadcaster): return colnames, rows def _results_changed(self): - self.selected_dupes = [ - d - for d in self.selected_dupes - if self.results.get_group_of_duplicate(d) is not None - ] + self.selected_dupes = [d for d in self.selected_dupes if self.results.get_group_of_duplicate(d) is not None] self.notify("results_changed") def _start_job(self, jobid, func, args=()): @@ -332,9 +314,7 @@ class DupeGuru(Broadcaster): msg = { JobType.Copy: tr("All marked files were copied successfully."), JobType.Move: tr("All marked files were moved successfully."), - JobType.Delete: tr( - "All marked files were successfully sent to Trash." - ), + JobType.Delete: tr("All marked files were successfully sent to Trash."), }[jobid] self.view.show_message(msg) @@ -401,15 +381,12 @@ class DupeGuru(Broadcaster): self.view.show_message(tr("'{}' does not exist.").format(d)) def add_selected_to_ignore_list(self): - """Adds :attr:`selected_dupes` to :attr:`ignore_list`. - """ + """Adds :attr:`selected_dupes` to :attr:`ignore_list`.""" dupes = self.without_ref(self.selected_dupes) if not dupes: self.view.show_message(MSG_NO_SELECTED_DUPES) return - msg = tr( - "All selected %d matches are going to be ignored in all subsequent scans. Continue?" - ) + msg = tr("All selected %d matches are going to be ignored in all subsequent scans. Continue?") if not self.view.ask_yes_no(msg % len(dupes)): return for dupe in dupes: @@ -483,16 +460,17 @@ class DupeGuru(Broadcaster): self.view.show_message(MSG_NO_MARKED_DUPES) return destination = self.view.select_dest_folder( - tr("Select a directory to copy marked files to") if copy - else tr("Select a directory to move marked files to")) + tr("Select a directory to copy marked files to") + if copy + else tr("Select a directory to move marked files to") + ) if destination: desttype = self.options["copymove_dest_type"] jobid = JobType.Copy if copy else JobType.Move self._start_job(jobid, do) def delete_marked(self): - """Start an async job to send marked duplicates to the trash. - """ + """Start an async job to send marked duplicates to the trash.""" if not self.results.mark_count: self.view.show_message(MSG_NO_MARKED_DUPES) return @@ -523,9 +501,7 @@ class DupeGuru(Broadcaster): The columns and their order in the resulting CSV file is determined in the same way as in :meth:`export_to_xhtml`. """ - dest_file = self.view.select_dest_file( - tr("Select a destination for your exported CSV"), "csv" - ) + dest_file = self.view.select_dest_file(tr("Select a destination for your exported CSV"), "csv") if dest_file: colnames, rows = self._get_export_data() try: @@ -542,9 +518,7 @@ class DupeGuru(Broadcaster): try: return dupe.get_display_info(group, delta) except Exception as e: - logging.warning( - "Exception (type: %s) on GetDisplayInfo for %s: %s", - type(e), str(dupe.path), str(e)) + logging.warning("Exception (type: %s) on GetDisplayInfo for %s: %s", type(e), str(dupe.path), str(e)) return empty_data() def invoke_custom_command(self): @@ -556,9 +530,7 @@ class DupeGuru(Broadcaster): """ cmd = self.view.get_default("CustomCommand") if not cmd: - msg = tr( - "You have no custom command set up. Set it up in your preferences." - ) + msg = tr("You have no custom command set up. Set it up in your preferences.") self.view.show_message(msg) return if not self.selected_dupes: @@ -634,9 +606,7 @@ class DupeGuru(Broadcaster): if not self.result_table.power_marker: if changed_groups: self.selected_dupes = [ - d - for d in self.selected_dupes - if self.results.get_group_of_duplicate(d).ref is d + d for d in self.selected_dupes if self.results.get_group_of_duplicate(d).ref is d ] self.notify("results_changed") else: @@ -648,20 +618,17 @@ class DupeGuru(Broadcaster): self.notify("results_changed_but_keep_selection") def mark_all(self): - """Set all dupes in the results as marked. - """ + """Set all dupes in the results as marked.""" self.results.mark_all() self.notify("marking_changed") def mark_none(self): - """Set all dupes in the results as unmarked. - """ + """Set all dupes in the results as unmarked.""" self.results.mark_none() self.notify("marking_changed") def mark_invert(self): - """Invert the marked state of all dupes in the results. - """ + """Invert the marked state of all dupes in the results.""" self.results.mark_invert() self.notify("marking_changed") @@ -679,8 +646,7 @@ class DupeGuru(Broadcaster): self.notify("marking_changed") def open_selected(self): - """Open :attr:`selected_dupes` with their associated application. - """ + """Open :attr:`selected_dupes` with their associated application.""" if len(self.selected_dupes) > 10: if not self.view.ask_yes_no(MSG_MANY_FILES_TO_OPEN): return @@ -688,8 +654,7 @@ class DupeGuru(Broadcaster): desktop.open_path(dupe.path) def purge_ignore_list(self): - """Remove files that don't exist from :attr:`ignore_list`. - """ + """Remove files that don't exist from :attr:`ignore_list`.""" self.ignore_list.Filter(lambda f, s: op.exists(f) and op.exists(s)) self.ignore_list_dialog.refresh() @@ -719,8 +684,7 @@ class DupeGuru(Broadcaster): self.notify("results_changed_but_keep_selection") def remove_marked(self): - """Removed marked duplicates from the results (without touching the files themselves). - """ + """Removed marked duplicates from the results (without touching the files themselves).""" if not self.results.mark_count: self.view.show_message(MSG_NO_MARKED_DUPES) return @@ -731,8 +695,7 @@ class DupeGuru(Broadcaster): self._results_changed() def remove_selected(self): - """Removed :attr:`selected_dupes` from the results (without touching the files themselves). - """ + """Removed :attr:`selected_dupes` from the results (without touching the files themselves).""" dupes = self.without_ref(self.selected_dupes) if not dupes: self.view.show_message(MSG_NO_SELECTED_DUPES) @@ -773,9 +736,7 @@ class DupeGuru(Broadcaster): if count: self.results.refresh_required = True self._results_changed() - msg = tr("{} duplicate groups were changed by the re-prioritization.").format( - count - ) + msg = tr("{} duplicate groups were changed by the re-prioritization.").format(count) self.view.show_message(msg) def reveal_selected(self): @@ -819,9 +780,7 @@ class DupeGuru(Broadcaster): """ scanner = self.SCANNER_CLASS() if not self.directories.has_any_file(): - self.view.show_message( - tr("The selected directories contain no scannable file.") - ) + self.view.show_message(tr("The selected directories contain no scannable file.")) return # Send relevant options down to the scanner instance for k, v in self.options.items(): @@ -836,13 +795,9 @@ class DupeGuru(Broadcaster): def do(j): j.set_progress(0, tr("Collecting files to scan")) if scanner.scan_type == ScanType.Folders: - files = list( - self.directories.get_folders(folderclass=se.fs.Folder, j=j) - ) + files = list(self.directories.get_folders(folderclass=se.fs.Folder, j=j)) else: - files = list( - self.directories.get_files(fileclasses=self.fileclasses, j=j) - ) + files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j)) if self.options["ignore_hardlink_matches"]: files = self._remove_hardlink_dupes(files) logging.info("Scanning %d files" % len(files)) @@ -864,13 +819,8 @@ class DupeGuru(Broadcaster): self.notify("marking_changed") def without_ref(self, dupes): - """Returns ``dupes`` with all reference elements removed. - """ - return [ - dupe - for dupe in dupes - if self.results.get_group_of_duplicate(dupe).ref is not dupe - ] + """Returns ``dupes`` with all reference elements removed.""" + return [dupe for dupe in dupes if self.results.get_group_of_duplicate(dupe).ref is not dupe] def get_default(self, key, fallback_value=None): result = nonone(self.view.get_default(key), fallback_value) diff --git a/core/directories.py b/core/directories.py index 9b4293b0..afbd8758 100644 --- a/core/directories.py +++ b/core/directories.py @@ -109,8 +109,7 @@ class Directories: # print(f"len of files: {len(files)} {files}") for f in files: if not self._exclude_list.is_excluded(root, f): - found_files.append(fs.get_file(rootPath + f, - fileclasses=fileclasses)) + found_files.append(fs.get_file(rootPath + f, fileclasses=fileclasses)) found_files = [f for f in found_files if f is not None] # In some cases, directories can be considered as files by dupeGuru, which is # why we have this line below. In fact, there only one case: Bundle files under diff --git a/core/exclude.py b/core/exclude.py index 314a1c6e..e790a3a7 100644 --- a/core/exclude.py +++ b/core/exclude.py @@ -4,6 +4,7 @@ from .markable import Markable from xml.etree import ElementTree as ET + # TODO: perhaps use regex module for better Unicode support? https://pypi.org/project/regex/ # also https://pypi.org/project/re2/ # TODO update the Result list with newly added regexes if possible @@ -15,13 +16,14 @@ from hscommon.util import FileOrPath from hscommon.plat import ISWINDOWS import time -default_regexes = [r"^thumbs\.db$", # Obsolete after WindowsXP - r"^desktop\.ini$", # Windows metadata - r"^\.DS_Store$", # MacOS metadata - r"^\.Trash\-.*", # Linux trash directories - r"^\$Recycle\.Bin$", # Windows - r"^\..*", # Hidden files on Unix-like - ] +default_regexes = [ + r"^thumbs\.db$", # Obsolete after WindowsXP + r"^desktop\.ini$", # Windows metadata + r"^\.DS_Store$", # MacOS metadata + r"^\.Trash\-.*", # Linux trash directories + r"^\$Recycle\.Bin$", # Windows + r"^\..*", # Hidden files on Unix-like +] # These are too broad forbidden_regexes = [r".*", r"\/.*", r".*\/.*", r".*\\\\.*", r".*\..*"] @@ -34,6 +36,7 @@ def timer(func): end = time.perf_counter_ns() print(f"DEBUG: func {func.__name__!r} took {end - start} ns.") return value + return wrapper_timer @@ -45,11 +48,13 @@ def memoize(func): if args not in func.cache: func.cache[args] = func(*args) return func.cache[args] + return _memoize class AlreadyThereException(Exception): """Expression already in the list""" + def __init__(self, arg="Expression is already in excluded list."): super().__init__(arg) @@ -148,7 +153,7 @@ class ExcludeList(Markable): try: return re.compile(expr) except Exception as e: - raise(e) + raise (e) # @timer # @memoize # probably not worth memoizing this one if we memoize the above @@ -169,10 +174,8 @@ class ExcludeList(Markable): def build_compiled_caches(self, union=False): if not union: - self._cached_compiled_files =\ - [x for x in self._excluded_compiled if not has_sep(x.pattern)] - self._cached_compiled_paths =\ - [x for x in self._excluded_compiled if has_sep(x.pattern)] + self._cached_compiled_files = [x for x in self._excluded_compiled if not has_sep(x.pattern)] + self._cached_compiled_paths = [x for x in self._excluded_compiled if has_sep(x.pattern)] self._dirty = False return @@ -185,20 +188,17 @@ class ExcludeList(Markable): else: # HACK returned as a tuple to get a free iterator and keep interface # the same regardless of whether the client asked for union or not - self._cached_compiled_union_all =\ - (re.compile('|'.join(marked_count)),) + self._cached_compiled_union_all = (re.compile("|".join(marked_count)),) files_marked = [x for x in marked_count if not has_sep(x)] if not files_marked: self._cached_compiled_union_files = tuple() else: - self._cached_compiled_union_files =\ - (re.compile('|'.join(files_marked)),) + self._cached_compiled_union_files = (re.compile("|".join(files_marked)),) paths_marked = [x for x in marked_count if has_sep(x)] if not paths_marked: self._cached_compiled_union_paths = tuple() else: - self._cached_compiled_union_paths =\ - (re.compile('|'.join(paths_marked)),) + self._cached_compiled_union_paths = (re.compile("|".join(paths_marked)),) self._dirty = False @property @@ -218,16 +218,14 @@ class ExcludeList(Markable): one item (one Pattern in the union case).""" if self._dirty: self.build_compiled_caches(self._use_union) - return self._cached_compiled_union_files if self._use_union\ - else self._cached_compiled_files + return self._cached_compiled_union_files if self._use_union else self._cached_compiled_files @property def compiled_paths(self): """Returns patterns with only separators in them, for more precise filtering.""" if self._dirty: self.build_compiled_caches(self._use_union) - return self._cached_compiled_union_paths if self._use_union\ - else self._cached_compiled_paths + return self._cached_compiled_union_paths if self._use_union else self._cached_compiled_paths # ---Public def add(self, regex, forced=False): @@ -295,8 +293,7 @@ class ExcludeList(Markable): was_marked = self.is_marked(regex) is_compilable, exception, compiled = self.compile_re(newregex) # We overwrite the found entry - self._excluded[self._excluded.index(item)] =\ - [newregex, is_compilable, exception, compiled] + self._excluded[self._excluded.index(item)] = [newregex, is_compilable, exception, compiled] self._remove_compiled(regex) break if not found: @@ -343,8 +340,10 @@ class ExcludeList(Markable): # "forced" avoids compilation exceptions and adds anyway self.add(regex_string, forced=True) except AlreadyThereException: - logging.error(f"Regex \"{regex_string}\" \ -loaded from XML was already present in the list.") + logging.error( + f'Regex "{regex_string}" \ +loaded from XML was already present in the list.' + ) continue if exclude_item.get("marked") == "y": marked.add(regex_string) @@ -369,6 +368,7 @@ loaded from XML was already present in the list.") class ExcludeDict(ExcludeList): """Exclusion list holding a set of regular expressions as keys, the compiled Pattern, compilation error and compilable boolean as values.""" + # Implemntation around a dictionary instead of a list, which implies # to keep the index of each string-key as its sub-element and keep it updated # whenever insert/remove is done. @@ -435,12 +435,7 @@ class ExcludeDict(ExcludeList): # and other indices should be pushed by one for value in self._excluded.values(): value["index"] += 1 - self._excluded[regex] = { - "index": 0, - "compilable": iscompilable, - "error": exception, - "compiled": compiled - } + self._excluded[regex] = {"index": 0, "compilable": iscompilable, "error": exception, "compiled": compiled} def has_entry(self, regex): if regex in self._excluded.keys(): @@ -468,10 +463,10 @@ class ExcludeDict(ExcludeList): previous = self._excluded.pop(regex) iscompilable, error, compiled = self.compile_re(newregex) self._excluded[newregex] = { - "index": previous.get('index'), + "index": previous.get("index"), "compilable": iscompilable, "error": error, - "compiled": compiled + "compiled": compiled, } self._remove_compiled(regex) if iscompilable: @@ -511,8 +506,12 @@ def ordered_keys(_dict): if ISWINDOWS: + def has_sep(regexp): - return '\\' + sep in regexp + return "\\" + sep in regexp + + else: + def has_sep(regexp): return sep in regexp diff --git a/core/export.py b/core/export.py index dac500b0..b8db8dc8 100644 --- a/core/export.py +++ b/core/export.py @@ -131,15 +131,11 @@ def export_to_xhtml(colnames, rows): indented = "indented" filename = row[1] cells = "".join(CELL_TEMPLATE.format(value=value) for value in row[2:]) - rendered_rows.append( - ROW_TEMPLATE.format(indented=indented, filename=filename, cells=cells) - ) + rendered_rows.append(ROW_TEMPLATE.format(indented=indented, filename=filename, cells=cells)) previous_group_id = row[0] rendered_rows = "".join(rendered_rows) # The main template can't use format because the css code uses {} - content = MAIN_TEMPLATE.replace("$colheaders", colheaders).replace( - "$rows", rendered_rows - ) + content = MAIN_TEMPLATE.replace("$colheaders", colheaders).replace("$rows", rendered_rows) folder = mkdtemp() destpath = op.join(folder, "export.htm") fp = open(destpath, "wt", encoding="utf-8") diff --git a/core/fs.py b/core/fs.py index 1f765d09..c95f9374 100644 --- a/core/fs.py +++ b/core/fs.py @@ -79,16 +79,9 @@ class OperationError(FSError): class File: - """Represents a file and holds metadata to be used for scanning. - """ + """Represents a file and holds metadata to be used for scanning.""" - INITIAL_INFO = { - "size": 0, - "mtime": 0, - "md5": b"", - "md5partial": b"", - "md5samples": b"" - } + INITIAL_INFO = {"size": 0, "mtime": 0, "md5": b"", "md5partial": b"", "md5samples": b""} # Slots for File make us save quite a bit of memory. In a memory test I've made with a lot of # files, I saved 35% memory usage with "unread" files (no _read_info() call) and gains become # even greater when we take into account read attributes (70%!). Yeah, it's worth it. @@ -108,9 +101,7 @@ class File: try: self._read_info(attrname) except Exception as e: - logging.warning( - "An error '%s' was raised while decoding '%s'", e, repr(self.path) - ) + logging.warning("An error '%s' was raised while decoding '%s'", e, repr(self.path)) result = object.__getattribute__(self, attrname) if result is NOT_SET: result = self.INITIAL_INFO[attrname] @@ -192,8 +183,7 @@ class File: # --- Public @classmethod def can_handle(cls, path): - """Returns whether this file wrapper class can handle ``path``. - """ + """Returns whether this file wrapper class can handle ``path``.""" return not path.islink() and path.isfile() def rename(self, newname): @@ -211,8 +201,7 @@ class File: self.path = destpath def get_display_info(self, group, delta): - """Returns a display-ready dict of dupe's data. - """ + """Returns a display-ready dict of dupe's data.""" raise NotImplementedError() # --- Properties @@ -271,9 +260,7 @@ class Folder(File): @property def subfolders(self): if self._subfolders is None: - subfolders = [ - p for p in self.path.listdir() if not p.islink() and p.isdir() - ] + subfolders = [p for p in self.path.listdir() if not p.islink() and p.isdir()] self._subfolders = [self.__class__(p) for p in subfolders] return self._subfolders diff --git a/core/gui/deletion_options.py b/core/gui/deletion_options.py index 72b7815a..ec6093b9 100644 --- a/core/gui/deletion_options.py +++ b/core/gui/deletion_options.py @@ -29,8 +29,7 @@ class DeletionOptionsView: """ def update_msg(self, msg: str): - """Update the dialog's prompt with ``str``. - """ + """Update the dialog's prompt with ``str``.""" def show(self): """Show the dialog in a modal fashion. @@ -39,8 +38,7 @@ class DeletionOptionsView: """ def set_hardlink_option_enabled(self, is_enabled: bool): - """Enable or disable the widget controlling :attr:`DeletionOptions.use_hardlinks`. - """ + """Enable or disable the widget controlling :attr:`DeletionOptions.use_hardlinks`.""" class DeletionOptions(GUIObject): @@ -75,8 +73,7 @@ class DeletionOptions(GUIObject): return self.view.show() def supports_links(self): - """Returns whether our platform supports symlinks. - """ + """Returns whether our platform supports symlinks.""" # When on a platform that doesn't implement it, calling os.symlink() (with the wrong number # of arguments) raises NotImplementedError, which allows us to gracefully check for the # feature. diff --git a/core/gui/details_panel.py b/core/gui/details_panel.py index 4720923d..7cecefb2 100644 --- a/core/gui/details_panel.py +++ b/core/gui/details_panel.py @@ -32,9 +32,7 @@ class DetailsPanel(GUIObject, DupeGuruGUIObject): # we don't want the two sides of the table to display the stats for the same file ref = group.ref if group is not None and group.ref is not dupe else None data2 = self.app.get_display_info(ref, group, False) - columns = self.app.result_table.COLUMNS[ - 1: - ] # first column is the 'marked' column + columns = self.app.result_table.COLUMNS[1:] # first column is the 'marked' column self._table = [(c.display, data1[c.name], data2[c.name]) for c in columns] # --- Public diff --git a/core/gui/directory_tree.py b/core/gui/directory_tree.py index 48bd4898..977faf8a 100644 --- a/core/gui/directory_tree.py +++ b/core/gui/directory_tree.py @@ -36,9 +36,7 @@ class DirectoryNode(Node): self._loaded = True def update_all_states(self): - self._state = STATE_ORDER.index( - self._tree.app.directories.get_state(self._directory_path) - ) + self._state = STATE_ORDER.index(self._tree.app.directories.get_state(self._directory_path)) for node in self: node.update_all_states() diff --git a/core/gui/exclude_list_dialog.py b/core/gui/exclude_list_dialog.py index b07a5d99..fd2029c3 100644 --- a/core/gui/exclude_list_dialog.py +++ b/core/gui/exclude_list_dialog.py @@ -50,7 +50,7 @@ class ExcludeListDialogCore: try: self.exclude_list.add(regex) except Exception as e: - raise(e) + raise (e) self.exclude_list.mark(regex) self.exclude_list_table.add(regex) diff --git a/core/gui/exclude_list_table.py b/core/gui/exclude_list_table.py index 01f56762..d02d2b7f 100644 --- a/core/gui/exclude_list_table.py +++ b/core/gui/exclude_list_table.py @@ -6,14 +6,12 @@ from .base import DupeGuruGUIObject from hscommon.gui.table import GUITable, Row from hscommon.gui.column import Column, Columns from hscommon.trans import trget + tr = trget("ui") class ExcludeListTable(GUITable, DupeGuruGUIObject): - COLUMNS = [ - Column("marked", ""), - Column("regex", tr("Regular Expressions")) - ] + COLUMNS = [Column("marked", ""), Column("regex", tr("Regular Expressions"))] def __init__(self, exclude_list_dialog, app): GUITable.__init__(self) diff --git a/core/gui/ignore_list_dialog.py b/core/gui/ignore_list_dialog.py index cd3a0996..f2c4f87b 100644 --- a/core/gui/ignore_list_dialog.py +++ b/core/gui/ignore_list_dialog.py @@ -22,9 +22,7 @@ class IgnoreListDialog: def clear(self): if not self.ignore_list: return - msg = tr( - "Do you really want to remove all %d items from the ignore list?" - ) % len(self.ignore_list) + msg = tr("Do you really want to remove all %d items from the ignore list?") % len(self.ignore_list) if self.app.view.ask_yes_no(msg): self.ignore_list.Clear() self.refresh() diff --git a/core/gui/result_table.py b/core/gui/result_table.py index 11123e7b..c004a471 100644 --- a/core/gui/result_table.py +++ b/core/gui/result_table.py @@ -45,9 +45,7 @@ class DupeRow(Row): return False ref_info = self._group.ref.get_display_info(group=self._group, delta=False) for key, value in dupe_info.items(): - if (key not in self._delta_columns) and ( - ref_info[key].lower() != value.lower() - ): + if (key not in self._delta_columns) and (ref_info[key].lower() != value.lower()): self._delta_columns.add(key) return column_name in self._delta_columns diff --git a/core/pe/cache_shelve.py b/core/pe/cache_shelve.py index fab735de..2aaeed91 100644 --- a/core/pe/cache_shelve.py +++ b/core/pe/cache_shelve.py @@ -33,8 +33,7 @@ CacheRow = namedtuple("CacheRow", "id path blocks mtime") class ShelveCache: - """A class to cache picture blocks in a shelve backend. - """ + """A class to cache picture blocks in a shelve backend.""" def __init__(self, db=None, readonly=False): self.istmp = db is None @@ -81,9 +80,7 @@ class ShelveCache: self.shelve[wrap_id(rowid)] = wrap_path(path_str) def _compute_maxid(self): - return max( - (unwrap_id(k) for k in self.shelve if k.startswith("id:")), default=1 - ) + return max((unwrap_id(k) for k in self.shelve if k.startswith("id:")), default=1) def _get_new_id(self): self.maxid += 1 diff --git a/core/pe/cache_sqlite.py b/core/pe/cache_sqlite.py index c8426477..b0dce06b 100644 --- a/core/pe/cache_sqlite.py +++ b/core/pe/cache_sqlite.py @@ -13,8 +13,7 @@ from .cache import string_to_colors, colors_to_string class SqliteCache: - """A class to cache picture blocks in a sqlite backend. - """ + """A class to cache picture blocks in a sqlite backend.""" def __init__(self, db=":memory:", readonly=False): # readonly is not used in the sqlite version of the cache @@ -71,18 +70,14 @@ class SqliteCache: except sqlite.OperationalError: logging.warning("Picture cache could not set value for key %r", path_str) except sqlite.DatabaseError as e: - logging.warning( - "DatabaseError while setting value for key %r: %s", path_str, str(e) - ) + logging.warning("DatabaseError while setting value for key %r: %s", path_str, str(e)) def _create_con(self, second_try=False): def create_tables(): logging.debug("Creating picture cache tables.") self.con.execute("drop table if exists pictures") self.con.execute("drop index if exists idx_path") - self.con.execute( - "create table pictures(path TEXT, mtime INTEGER, blocks TEXT)" - ) + self.con.execute("create table pictures(path TEXT, mtime INTEGER, blocks TEXT)") self.con.execute("create index idx_path on pictures (path)") self.con = sqlite.connect(self.dbname, isolation_level=None) @@ -93,9 +88,7 @@ class SqliteCache: except sqlite.DatabaseError as e: # corrupted db if second_try: raise # Something really strange is happening - logging.warning( - "Could not create picture cache because of an error: %s", str(e) - ) + logging.warning("Could not create picture cache because of an error: %s", str(e)) self.con.close() os.remove(self.dbname) self._create_con(second_try=True) @@ -125,9 +118,7 @@ class SqliteCache: raise ValueError(path) def get_multiple(self, rowids): - sql = "select rowid, blocks from pictures where rowid in (%s)" % ",".join( - map(str, rowids) - ) + sql = "select rowid, blocks from pictures where rowid in (%s)" % ",".join(map(str, rowids)) cur = self.con.execute(sql) return ((rowid, string_to_colors(blocks)) for rowid, blocks in cur) @@ -148,7 +139,5 @@ class SqliteCache: continue todelete.append(rowid) if todelete: - sql = "delete from pictures where rowid in (%s)" % ",".join( - map(str, todelete) - ) + sql = "delete from pictures where rowid in (%s)" % ",".join(map(str, todelete)) self.con.execute(sql) diff --git a/core/pe/exif.py b/core/pe/exif.py index c156e709..e2687164 100644 --- a/core/pe/exif.py +++ b/core/pe/exif.py @@ -256,9 +256,7 @@ class TIFF_file: for j in range(count): if type in {5, 10}: # The type is either 5 or 10 - value_j = Fraction( - self.s2n(offset, 4, signed), self.s2n(offset + 4, 4, signed) - ) + value_j = Fraction(self.s2n(offset, 4, signed), self.s2n(offset + 4, 4, signed)) else: # Not a fraction value_j = self.s2n(offset, typelen, signed) @@ -296,9 +294,7 @@ def get_fields(fp): logging.debug("Exif header length: %d bytes", length) data = fp.read(length - 8) data_format = data[0] - logging.debug( - "%s format", {INTEL_ENDIAN: "Intel", MOTOROLA_ENDIAN: "Motorola"}[data_format] - ) + logging.debug("%s format", {INTEL_ENDIAN: "Intel", MOTOROLA_ENDIAN: "Motorola"}[data_format]) T = TIFF_file(data) # There may be more than one IFD per file, but we only read the first one because others are # most likely thumbnails. diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index 953d39ab..0b26a233 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -95,9 +95,7 @@ def prepare_pictures(pictures, cache_path, with_dimensions, j=job.nulljob): picture.unicode_path, picture.size, ) - if ( - picture.size < 10 * 1024 * 1024 - ): # We're really running out of memory + if picture.size < 10 * 1024 * 1024: # We're really running out of memory raise except MemoryError: logging.warning("Ran out of memory while preparing pictures") @@ -106,9 +104,7 @@ def prepare_pictures(pictures, cache_path, with_dimensions, j=job.nulljob): def get_chunks(pictures): - min_chunk_count = ( - multiprocessing.cpu_count() * 2 - ) # have enough chunks to feed all subprocesses + min_chunk_count = multiprocessing.cpu_count() * 2 # have enough chunks to feed all subprocesses chunk_count = len(pictures) // DEFAULT_CHUNK_SIZE chunk_count = max(min_chunk_count, chunk_count) chunk_size = (len(pictures) // chunk_count) + 1 @@ -185,9 +181,7 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljo j.set_progress(comparison_count, progress_msg) j = j.start_subjob([3, 7]) - pictures = prepare_pictures( - pictures, cache_path, with_dimensions=not match_scaled, j=j - ) + pictures = prepare_pictures(pictures, cache_path, with_dimensions=not match_scaled, j=j) j = j.start_subjob([9, 1], tr("Preparing for matching")) cache = get_cache(cache_path) id2picture = {} @@ -231,12 +225,8 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljo chunks, pictures, ) # some wiggle room for the next statements - logging.warning( - "Ran out of memory when scanning! We had %d matches.", len(matches) - ) - del matches[ - -len(matches) // 3 : - ] # some wiggle room to ensure we don't run out of memory again. + logging.warning("Ran out of memory when scanning! We had %d matches.", len(matches)) + del matches[-len(matches) // 3 :] # some wiggle room to ensure we don't run out of memory again. pool.close() result = [] myiter = j.iter_with_progress( diff --git a/core/scanner.py b/core/scanner.py index c6813c89..4d1835d2 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -87,11 +87,7 @@ class Scanner: if self.size_threshold: files = [f for f in files if f.size >= self.size_threshold] if self.scan_type in {ScanType.Contents, ScanType.Folders}: - return engine.getmatches_by_contents( - files, - bigsize=self.big_file_size_threshold, - j=j - ) + return engine.getmatches_by_contents(files, bigsize=self.big_file_size_threshold, j=j) else: j = j.start_subjob([2, 8]) kw = {} @@ -165,27 +161,13 @@ class Scanner: toremove.add(p) else: last_parent_path = p - matches = [ - m - for m in matches - if m.first.path not in toremove or m.second.path not in toremove - ] + matches = [m for m in matches if m.first.path not in toremove or m.second.path not in toremove] if not self.mix_file_kind: - matches = [ - m - for m in matches - if get_file_ext(m.first.name) == get_file_ext(m.second.name) - ] - matches = [ - m for m in matches if m.first.path.exists() and m.second.path.exists() - ] + matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)] + matches = [m for m in matches if m.first.path.exists() and m.second.path.exists()] matches = [m for m in matches if not (m.first.is_ref and m.second.is_ref)] if ignore_list: - matches = [ - m - for m in matches - if not ignore_list.AreIgnored(str(m.first.path), str(m.second.path)) - ] + matches = [m for m in matches if not ignore_list.AreIgnored(str(m.first.path), str(m.second.path))] logging.info("Grouping matches") groups = engine.get_groups(matches) if self.scan_type in { @@ -194,9 +176,7 @@ class Scanner: ScanType.FieldsNoOrder, ScanType.Tag, }: - matched_files = dedupe( - [m.first for m in matches] + [m.second for m in matches] - ) + matched_files = dedupe([m.first for m in matches] + [m.second for m in matches]) self.discarded_file_count = len(matched_files) - sum(len(g) for g in groups) else: # Ticket #195 diff --git a/core/tests/app_test.py b/core/tests/app_test.py index 0b963974..a339d4bc 100644 --- a/core/tests/app_test.py +++ b/core/tests/app_test.py @@ -29,9 +29,7 @@ def add_fake_files_to_directories(directories, files): class TestCaseDupeGuru: def test_apply_filter_calls_results_apply_filter(self, monkeypatch): dgapp = TestApp().app - monkeypatch.setattr( - dgapp.results, "apply_filter", log_calls(dgapp.results.apply_filter) - ) + monkeypatch.setattr(dgapp.results, "apply_filter", log_calls(dgapp.results.apply_filter)) dgapp.apply_filter("foo") eq_(2, len(dgapp.results.apply_filter.calls)) call = dgapp.results.apply_filter.calls[0] @@ -41,15 +39,11 @@ class TestCaseDupeGuru: def test_apply_filter_escapes_regexp(self, monkeypatch): dgapp = TestApp().app - monkeypatch.setattr( - dgapp.results, "apply_filter", log_calls(dgapp.results.apply_filter) - ) + monkeypatch.setattr(dgapp.results, "apply_filter", log_calls(dgapp.results.apply_filter)) dgapp.apply_filter("()[]\\.|+?^abc") call = dgapp.results.apply_filter.calls[1] eq_("\\(\\)\\[\\]\\\\\\.\\|\\+\\?\\^abc", call["filter_str"]) - dgapp.apply_filter( - "(*)" - ) # In "simple mode", we want the * to behave as a wilcard + dgapp.apply_filter("(*)") # In "simple mode", we want the * to behave as a wilcard call = dgapp.results.apply_filter.calls[3] eq_(r"\(.*\)", call["filter_str"]) dgapp.options["escape_filter_regexp"] = False @@ -70,9 +64,7 @@ class TestCaseDupeGuru: ) # XXX This monkeypatch is temporary. will be fixed in a better monkeypatcher. monkeypatch.setattr(app, "smart_copy", hscommon.conflict.smart_copy) - monkeypatch.setattr( - os, "makedirs", lambda path: None - ) # We don't want the test to create that fake directory + monkeypatch.setattr(os, "makedirs", lambda path: None) # We don't want the test to create that fake directory dgapp = TestApp().app dgapp.directories.add_path(p) [f] = dgapp.directories.get_files() @@ -320,9 +312,7 @@ class TestCaseDupeGuruWithResults: assert groups[0].ref is objects[1] assert groups[1].ref is objects[4] - def test_makeSelectedReference_by_selecting_two_dupes_in_the_same_group( - self, do_setup - ): + def test_makeSelectedReference_by_selecting_two_dupes_in_the_same_group(self, do_setup): app = self.app objects = self.objects groups = self.groups @@ -404,9 +394,7 @@ class TestCaseDupeGuruWithResults: # results table. app = self.app app.JOB = Job(1, lambda *args, **kw: False) # Cancels the task - add_fake_files_to_directories( - app.directories, self.objects - ) # We want the scan to at least start + add_fake_files_to_directories(app.directories, self.objects) # We want the scan to at least start app.start_scanning() # will be cancelled immediately eq_(len(app.result_table), 0) diff --git a/core/tests/base.py b/core/tests/base.py index c4746168..03c0bfac 100644 --- a/core/tests/base.py +++ b/core/tests/base.py @@ -140,9 +140,7 @@ def GetTestGroups(): matches = engine.getmatches(objects) # we should have 5 matches groups = engine.get_groups(matches) # We should have 2 groups for g in groups: - g.prioritize( - lambda x: objects.index(x) - ) # We want the dupes to be in the same order as the list is + g.prioritize(lambda x: objects.index(x)) # We want the dupes to be in the same order as the list is groups.sort(key=len, reverse=True) # We want the group with 3 members to be first. return (objects, matches, groups) diff --git a/core/tests/block_test.py b/core/tests/block_test.py index 9a3a3591..39226664 100644 --- a/core/tests/block_test.py +++ b/core/tests/block_test.py @@ -14,9 +14,7 @@ except ImportError: skip("Can't import the block module, probably hasn't been compiled.") -def my_avgdiff( - first, second, limit=768, min_iter=3 -): # this is so I don't have to re-write every call +def my_avgdiff(first, second, limit=768, min_iter=3): # this is so I don't have to re-write every call return avgdiff(first, second, limit, min_iter) diff --git a/core/tests/directories_test.py b/core/tests/directories_test.py index 7d99cc20..01fd92d5 100644 --- a/core/tests/directories_test.py +++ b/core/tests/directories_test.py @@ -254,7 +254,12 @@ def test_invalid_path(): def test_set_state_on_invalid_path(): d = Directories() try: - d.set_state(Path("foobar",), DirectoryState.Normal) + d.set_state( + Path( + "foobar", + ), + DirectoryState.Normal, + ) except LookupError: assert False @@ -345,15 +350,17 @@ def test_default_path_state_override(tmpdir): eq_(len(list(d.get_files())), 2) -class TestExcludeList(): +class TestExcludeList: def setup_method(self, method): self.d = Directories(exclude_list=ExcludeList(union_regex=False)) def get_files_and_expect_num_result(self, num_result): """Calls get_files(), get the filenames only, print for debugging. num_result is how many files are expected as a result.""" - print(f"EXCLUDED REGEX: paths {self.d._exclude_list.compiled_paths} \ -files: {self.d._exclude_list.compiled_files} all: {self.d._exclude_list.compiled}") + print( + f"EXCLUDED REGEX: paths {self.d._exclude_list.compiled_paths} \ +files: {self.d._exclude_list.compiled_files} all: {self.d._exclude_list.compiled}" + ) files = list(self.d.get_files()) files = [file.name for file in files] print(f"FINAL FILES {files}") diff --git a/core/tests/exclude_test.py b/core/tests/exclude_test.py index c2f1f03e..38568e49 100644 --- a/core/tests/exclude_test.py +++ b/core/tests/exclude_test.py @@ -5,6 +5,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html import io + # import os.path as op from xml.etree import ElementTree as ET @@ -104,7 +105,7 @@ class TestCaseListEmpty: regex1 = r"one" regex2 = r"two" self.exclude_list.add(regex1) - assert(regex1 in self.exclude_list) + assert regex1 in self.exclude_list self.exclude_list.add(regex2) self.exclude_list.mark(regex1) self.exclude_list.mark(regex2) @@ -113,17 +114,17 @@ class TestCaseListEmpty: compiled_files = [x for x in self.exclude_list.compiled_files] eq_(len(compiled_files), 2) self.exclude_list.remove(regex2) - assert(regex2 not in self.exclude_list) + assert regex2 not in self.exclude_list eq_(len(self.exclude_list), 1) def test_add_duplicate(self): self.exclude_list.add(r"one") - eq_(1 , len(self.exclude_list)) + eq_(1, len(self.exclude_list)) try: self.exclude_list.add(r"one") except Exception: pass - eq_(1 , len(self.exclude_list)) + eq_(1, len(self.exclude_list)) def test_add_not_compilable(self): # Trying to add a non-valid regex should not work and raise exception @@ -230,13 +231,14 @@ class TestCaseListEmpty: if compiled_re.pattern == re: found = True if not found: - raise(Exception(f"Default RE {re} not found in compiled list.")) + raise (Exception(f"Default RE {re} not found in compiled list.")) continue eq_(len(default_regexes), len(self.exclude_list.compiled)) class TestCaseListEmptyUnion(TestCaseListEmpty): """Same but with union regex""" + def setup_method(self, method): self.app = DupeGuru() self.app.exclude_list = ExcludeList(union_regex=True) @@ -246,7 +248,7 @@ class TestCaseListEmptyUnion(TestCaseListEmpty): regex1 = r"one" regex2 = r"two" self.exclude_list.add(regex1) - assert(regex1 in self.exclude_list) + assert regex1 in self.exclude_list self.exclude_list.add(regex2) self.exclude_list.mark(regex1) self.exclude_list.mark(regex2) @@ -256,7 +258,7 @@ class TestCaseListEmptyUnion(TestCaseListEmpty): eq_(len(compiled_files), 1) # Two patterns joined together into one assert "|" in compiled_files[0].pattern self.exclude_list.remove(regex2) - assert(regex2 not in self.exclude_list) + assert regex2 not in self.exclude_list eq_(len(self.exclude_list), 1) def test_rename_regex_file_to_path(self): @@ -296,14 +298,15 @@ class TestCaseListEmptyUnion(TestCaseListEmpty): compiled = [x for x in self.exclude_list.compiled] assert regex not in compiled # Need to escape both to get the same strings after compilation - compiled_escaped = set([x.encode('unicode-escape').decode() for x in compiled[0].pattern.split("|")]) - default_escaped = set([x.encode('unicode-escape').decode() for x in default_regexes]) + compiled_escaped = set([x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")]) + default_escaped = set([x.encode("unicode-escape").decode() for x in default_regexes]) assert compiled_escaped == default_escaped eq_(len(default_regexes), len(compiled[0].pattern.split("|"))) class TestCaseDictEmpty(TestCaseListEmpty): """Same, but with dictionary implementation""" + def setup_method(self, method): self.app = DupeGuru() self.app.exclude_list = ExcludeDict(union_regex=False) @@ -312,6 +315,7 @@ class TestCaseDictEmpty(TestCaseListEmpty): class TestCaseDictEmptyUnion(TestCaseDictEmpty): """Same, but with union regex""" + def setup_method(self, method): self.app = DupeGuru() self.app.exclude_list = ExcludeDict(union_regex=True) @@ -321,7 +325,7 @@ class TestCaseDictEmptyUnion(TestCaseDictEmpty): regex1 = r"one" regex2 = r"two" self.exclude_list.add(regex1) - assert(regex1 in self.exclude_list) + assert regex1 in self.exclude_list self.exclude_list.add(regex2) self.exclude_list.mark(regex1) self.exclude_list.mark(regex2) @@ -331,7 +335,7 @@ class TestCaseDictEmptyUnion(TestCaseDictEmpty): # two patterns joined into one eq_(len(compiled_files), 1) self.exclude_list.remove(regex2) - assert(regex2 not in self.exclude_list) + assert regex2 not in self.exclude_list eq_(len(self.exclude_list), 1) def test_rename_regex_file_to_path(self): @@ -371,8 +375,8 @@ class TestCaseDictEmptyUnion(TestCaseDictEmpty): compiled = [x for x in self.exclude_list.compiled] assert regex not in compiled # Need to escape both to get the same strings after compilation - compiled_escaped = set([x.encode('unicode-escape').decode() for x in compiled[0].pattern.split("|")]) - default_escaped = set([x.encode('unicode-escape').decode() for x in default_regexes]) + compiled_escaped = set([x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")]) + default_escaped = set([x.encode("unicode-escape").decode() for x in default_regexes]) assert compiled_escaped == default_escaped eq_(len(default_regexes), len(compiled[0].pattern.split("|"))) @@ -382,8 +386,9 @@ def split_union(pattern_object): return [x for x in pattern_object.pattern.split("|")] -class TestCaseCompiledList(): +class TestCaseCompiledList: """Test consistency between union or and separate versions.""" + def setup_method(self, method): self.e_separate = ExcludeList(union_regex=False) self.e_separate.restore_defaults() @@ -431,6 +436,7 @@ class TestCaseCompiledList(): class TestCaseCompiledDict(TestCaseCompiledList): """Test the dictionary version""" + def setup_method(self, method): self.e_separate = ExcludeDict(union_regex=False) self.e_separate.restore_defaults() diff --git a/core/tests/ignore_test.py b/core/tests/ignore_test.py index 97678ac8..073b027a 100644 --- a/core/tests/ignore_test.py +++ b/core/tests/ignore_test.py @@ -73,9 +73,7 @@ def test_save_to_xml(): eq_(len(root), 2) eq_(len([c for c in root if c.tag == "file"]), 2) f1, f2 = root[:] - subchildren = [c for c in f1 if c.tag == "file"] + [ - c for c in f2 if c.tag == "file" - ] + subchildren = [c for c in f1 if c.tag == "file"] + [c for c in f2 if c.tag == "file"] eq_(len(subchildren), 3) @@ -96,9 +94,7 @@ def test_SaveThenLoad(): def test_LoadXML_with_empty_file_tags(): f = io.BytesIO() - f.write( - b'' - ) + f.write(b'') f.seek(0) il = IgnoreList() il.load_from_xml(f) diff --git a/core/tests/results_test.py b/core/tests/results_test.py index fb7c5f0c..9c43a256 100644 --- a/core/tests/results_test.py +++ b/core/tests/results_test.py @@ -117,9 +117,7 @@ class TestCaseResultsWithSomeGroups: assert d is g.ref def test_sort_groups(self): - self.results.make_ref( - self.objects[1] - ) # We want to make the 1024 sized object to go ref. + self.results.make_ref(self.objects[1]) # We want to make the 1024 sized object to go ref. g1, g2 = self.groups self.results.sort_groups("size") assert self.results.groups[0] is g2 @@ -129,9 +127,7 @@ class TestCaseResultsWithSomeGroups: assert self.results.groups[1] is g2 def test_set_groups_when_sorted(self): - self.results.make_ref( - self.objects[1] - ) # We want to make the 1024 sized object to go ref. + self.results.make_ref(self.objects[1]) # We want to make the 1024 sized object to go ref. self.results.sort_groups("size") objects, matches, groups = GetTestGroups() g1, g2 = groups @@ -601,9 +597,7 @@ class TestCaseResultsXML: matches = engine.getmatches(objects) # we should have 5 matches groups = engine.get_groups(matches) # We should have 2 groups for g in groups: - g.prioritize( - lambda x: objects.index(x) - ) # We want the dupes to be in the same order as the list is + g.prioritize(lambda x: objects.index(x)) # We want the dupes to be in the same order as the list is app = DupeGuru() results = Results(app) results.groups = groups @@ -807,9 +801,7 @@ class TestCaseResultsFilter: # Now the stats should display *2* markable dupes (instead of 1) expected = "0 / 2 (0.00 B / 2.00 B) duplicates marked. filter: foo" eq_(expected, self.results.stat_line) - self.results.apply_filter( - None - ) # Now let's make sure our unfiltered results aren't fucked up + self.results.apply_filter(None) # Now let's make sure our unfiltered results aren't fucked up expected = "0 / 3 (0.00 B / 3.00 B) duplicates marked." eq_(expected, self.results.stat_line) diff --git a/core/tests/scanner_test.py b/core/tests/scanner_test.py index f4ad1dee..c715d23d 100644 --- a/core/tests/scanner_test.py +++ b/core/tests/scanner_test.py @@ -150,8 +150,7 @@ def test_big_file_partial_hashes(fake_fileexists): bigsize = 100 * 1024 * 1024 # 100MB s.big_file_size_threshold = bigsize - f = [no("bigfoo", bigsize), no("bigbar", bigsize), - no("smallfoo", smallsize), no("smallbar", smallsize)] + f = [no("bigfoo", bigsize), no("bigbar", bigsize), no("smallfoo", smallsize), no("smallbar", smallsize)] f[0].md5 = f[0].md5partial = f[0].md5samples = "foobar" f[1].md5 = f[1].md5partial = f[1].md5samples = "foobar" f[2].md5 = f[2].md5partial = "bleh" @@ -193,10 +192,8 @@ def test_content_scan_doesnt_put_md5_in_words_at_the_end(fake_fileexists): s = Scanner() s.scan_type = ScanType.Contents f = [no("foo"), no("bar")] - f[0].md5 = f[0].md5partial = f[0].md5samples =\ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" - f[1].md5 = f[1].md5partial = f[1].md5samples =\ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + f[0].md5 = f[0].md5partial = f[0].md5samples = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + f[1].md5 = f[1].md5partial = f[1].md5samples = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" r = s.get_dupe_groups(f) # FIXME looks like we are missing something here? r[0] diff --git a/hscommon/build_ext.py b/hscommon/build_ext.py index b499ca4d..ce8b5c71 100644 --- a/hscommon/build_ext.py +++ b/hscommon/build_ext.py @@ -11,9 +11,7 @@ from setuptools import setup, Extension def get_parser(): parser = argparse.ArgumentParser(description="Build an arbitrary Python extension.") - parser.add_argument( - "source_files", nargs="+", help="List of source files to compile" - ) + parser.add_argument("source_files", nargs="+", help="List of source files to compile") parser.add_argument("name", nargs=1, help="Name of the resulting extension") return parser @@ -23,7 +21,8 @@ def main(): print("Building {}...".format(args.name[0])) ext = Extension(args.name[0], args.source_files) setup( - script_args=["build_ext", "--inplace"], ext_modules=[ext], + script_args=["build_ext", "--inplace"], + ext_modules=[ext], ) diff --git a/hscommon/conflict.py b/hscommon/conflict.py index 9c14b013..a661e7b9 100644 --- a/hscommon/conflict.py +++ b/hscommon/conflict.py @@ -48,15 +48,13 @@ def get_unconflicted_name(name): def is_conflicted(name): - """Returns whether ``name`` is prepended with a bracketed number. - """ + """Returns whether ``name`` is prepended with a bracketed number.""" return re_conflict.match(name) is not None @pathify def _smart_move_or_copy(operation, source_path: Path, dest_path: Path): - """Use move() or copy() to move and copy file with the conflict management. - """ + """Use move() or copy() to move and copy file with the conflict management.""" if dest_path.isdir() and not source_path.isdir(): dest_path = dest_path[source_path.name] if dest_path.exists(): @@ -68,14 +66,12 @@ def _smart_move_or_copy(operation, source_path: Path, dest_path: Path): def smart_move(source_path, dest_path): - """Same as :func:`smart_copy`, but it moves files instead. - """ + """Same as :func:`smart_copy`, but it moves files instead.""" _smart_move_or_copy(shutil.move, source_path, dest_path) def smart_copy(source_path, dest_path): - """Copies ``source_path`` to ``dest_path``, recursively and with conflict resolution. - """ + """Copies ``source_path`` to ``dest_path``, recursively and with conflict resolution.""" try: _smart_move_or_copy(shutil.copy, source_path, dest_path) except IOError as e: diff --git a/hscommon/desktop.py b/hscommon/desktop.py index acdbe78e..0defad28 100644 --- a/hscommon/desktop.py +++ b/hscommon/desktop.py @@ -16,20 +16,17 @@ class SpecialFolder: def open_url(url): - """Open ``url`` with the default browser. - """ + """Open ``url`` with the default browser.""" _open_url(url) def open_path(path): - """Open ``path`` with its associated application. - """ + """Open ``path`` with its associated application.""" _open_path(str(path)) def reveal_path(path): - """Open the folder containing ``path`` with the default file browser. - """ + """Open the folder containing ``path`` with the default file browser.""" _reveal_path(str(path)) diff --git a/hscommon/geometry.py b/hscommon/geometry.py index c5816698..adcb98e8 100644 --- a/hscommon/geometry.py +++ b/hscommon/geometry.py @@ -149,8 +149,7 @@ class Rect: return l1, l2, l3, l4 def scaled_rect(self, dx, dy): - """Returns a rect that has the same borders at self, but grown/shrunk by dx/dy on each side. - """ + """Returns a rect that has the same borders at self, but grown/shrunk by dx/dy on each side.""" x, y, w, h = self x -= dx y -= dy @@ -159,8 +158,7 @@ class Rect: return Rect(x, y, w, h) def united(self, other): - """Returns the bounding rectangle of this rectangle and `other`. - """ + """Returns the bounding rectangle of this rectangle and `other`.""" # ul=upper left lr=lower right ulcorner1, lrcorner1 = self.corners() ulcorner2, lrcorner2 = other.corners() diff --git a/hscommon/gui/column.py b/hscommon/gui/column.py index 230a5b65..7b6c7787 100644 --- a/hscommon/gui/column.py +++ b/hscommon/gui/column.py @@ -80,8 +80,7 @@ class PrefAccessInterface: """ def set_default(self, key, value): - """Set the value ``value`` for ``key`` in the currently running app's preference store. - """ + """Set the value ``value`` for ``key`` in the currently running app's preference store.""" class Columns(GUIObject): @@ -140,33 +139,27 @@ class Columns(GUIObject): # --- Public def column_by_index(self, index): - """Return the :class:`Column` having the :attr:`~Column.logical_index` ``index``. - """ + """Return the :class:`Column` having the :attr:`~Column.logical_index` ``index``.""" return self.column_list[index] def column_by_name(self, name): - """Return the :class:`Column` having the :attr:`~Column.name` ``name``. - """ + """Return the :class:`Column` having the :attr:`~Column.name` ``name``.""" return self.coldata[name] def columns_count(self): - """Returns the number of columns in our set. - """ + """Returns the number of columns in our set.""" return len(self.column_list) def column_display(self, colname): - """Returns display name for column named ``colname``, or ``''`` if there's none. - """ + """Returns display name for column named ``colname``, or ``''`` if there's none.""" return self._get_colname_attr(colname, "display", "") def column_is_visible(self, colname): - """Returns visibility for column named ``colname``, or ``True`` if there's none. - """ + """Returns visibility for column named ``colname``, or ``True`` if there's none.""" return self._get_colname_attr(colname, "visible", True) def column_width(self, colname): - """Returns width for column named ``colname``, or ``0`` if there's none. - """ + """Returns width for column named ``colname``, or ``0`` if there's none.""" return self._get_colname_attr(colname, "width", 0) def columns_to_right(self, colname): @@ -177,11 +170,7 @@ class Columns(GUIObject): """ column = self.coldata[colname] index = column.ordered_index - return [ - col.name - for col in self.column_list - if (col.visible and col.ordered_index > index) - ] + return [col.name for col in self.column_list if (col.visible and col.ordered_index > index)] def menu_items(self): """Returns a list of items convenient for quick visibility menu generation. @@ -207,8 +196,7 @@ class Columns(GUIObject): self.set_column_order(colnames) def reset_to_defaults(self): - """Reset all columns' width and visibility to their default values. - """ + """Reset all columns' width and visibility to their default values.""" self.set_column_order([col.name for col in self.column_list]) for col in self._optional_columns(): col.visible = col.default_visible @@ -216,13 +204,11 @@ class Columns(GUIObject): self.view.restore_columns() def resize_column(self, colname, newwidth): - """Set column ``colname``'s width to ``newwidth``. - """ + """Set column ``colname``'s width to ``newwidth``.""" self._set_colname_attr(colname, "width", newwidth) def restore_columns(self): - """Restore's column persistent attributes from the last :meth:`save_columns`. - """ + """Restore's column persistent attributes from the last :meth:`save_columns`.""" if not (self.prefaccess and self.savename and self.coldata): if (not self.savename) and (self.coldata): # This is a table that will not have its coldata saved/restored. we should @@ -241,8 +227,7 @@ class Columns(GUIObject): self.view.restore_columns() def save_columns(self): - """Save column attributes in persistent storage for restoration in :meth:`restore_columns`. - """ + """Save column attributes in persistent storage for restoration in :meth:`restore_columns`.""" if not (self.prefaccess and self.savename and self.coldata): return for col in self.column_list: @@ -263,15 +248,13 @@ class Columns(GUIObject): col.ordered_index = i def set_column_visible(self, colname, visible): - """Set the visibility of column ``colname``. - """ + """Set the visibility of column ``colname``.""" self.table.save_edits() # the table on the GUI side will stop editing when the columns change self._set_colname_attr(colname, "visible", visible) self.view.set_column_visible(colname, visible) def set_default_width(self, colname, width): - """Set the default width or column ``colname``. - """ + """Set the default width or column ``colname``.""" self._set_colname_attr(colname, "default_width", width) def toggle_menu_item(self, index): @@ -289,14 +272,10 @@ class Columns(GUIObject): # --- Properties @property def ordered_columns(self): - """List of :class:`Column` in visible order. - """ - return [ - col for col in sorted(self.column_list, key=lambda col: col.ordered_index) - ] + """List of :class:`Column` in visible order.""" + return [col for col in sorted(self.column_list, key=lambda col: col.ordered_index)] @property def colnames(self): - """List of column names in visible order. - """ + """List of column names in visible order.""" return [col.name for col in self.ordered_columns] diff --git a/hscommon/gui/progress_window.py b/hscommon/gui/progress_window.py index be6e7f05..f1c07143 100644 --- a/hscommon/gui/progress_window.py +++ b/hscommon/gui/progress_window.py @@ -21,12 +21,10 @@ class ProgressWindowView: """ def show(self): - """Show the dialog. - """ + """Show the dialog.""" def close(self): - """Close the dialog. - """ + """Close the dialog.""" def set_progress(self, progress): """Set the progress of the progress bar to ``progress``. @@ -76,8 +74,7 @@ class ProgressWindow(GUIObject, ThreadedJobPerformer): self.jobid = None def cancel(self): - """Call for a user-initiated job cancellation. - """ + """Call for a user-initiated job cancellation.""" # The UI is sometimes a bit buggy and calls cancel() on self.view.close(). We just want to # make sure that this doesn't lead us to think that the user acually cancelled the task, so # we verify that the job is still running. diff --git a/hscommon/gui/selectable_list.py b/hscommon/gui/selectable_list.py index 712801cd..58ad3a61 100644 --- a/hscommon/gui/selectable_list.py +++ b/hscommon/gui/selectable_list.py @@ -27,9 +27,7 @@ class Selectable(Sequence): self._selected_indexes = [] if not self._selected_indexes: return - self._selected_indexes = [ - index for index in self._selected_indexes if index < len(self) - ] + self._selected_indexes = [index for index in self._selected_indexes if index < len(self)] if not self._selected_indexes: self._selected_indexes = [len(self) - 1] diff --git a/hscommon/gui/text_field.py b/hscommon/gui/text_field.py index ae0c7c68..8d82b3d4 100644 --- a/hscommon/gui/text_field.py +++ b/hscommon/gui/text_field.py @@ -71,8 +71,7 @@ class TextField(GUIObject): # --- Public def refresh(self): - """Triggers a view :meth:`~TextFieldView.refresh`. - """ + """Triggers a view :meth:`~TextFieldView.refresh`.""" self.view.refresh() @property diff --git a/hscommon/gui/tree.py b/hscommon/gui/tree.py index 1a40d683..ea6d6562 100644 --- a/hscommon/gui/tree.py +++ b/hscommon/gui/tree.py @@ -55,8 +55,7 @@ class Node(MutableSequence): # --- Public def clear(self): - """Clears the node of all its children. - """ + """Clears the node of all its children.""" del self[:] def find(self, predicate, include_self=True): @@ -103,14 +102,12 @@ class Node(MutableSequence): @property def children_count(self): - """Same as ``len(self)``. - """ + """Same as ``len(self)``.""" return len(self) @property def name(self): - """Name for the node, supplied on init. - """ + """Name for the node, supplied on init.""" return self._name @property diff --git a/hscommon/jobprogress/job.py b/hscommon/jobprogress/job.py index bc63dc20..16e0991f 100644 --- a/hscommon/jobprogress/job.py +++ b/hscommon/jobprogress/job.py @@ -56,8 +56,7 @@ class Job: # ---Private def _subjob_callback(self, progress, desc=""): - """This is the callback passed to children jobs. - """ + """This is the callback passed to children jobs.""" self.set_progress(progress, desc) return True # if JobCancelled has to be raised, it will be at the highest level diff --git a/hscommon/loc.py b/hscommon/loc.py index 062173e2..dcbaff49 100644 --- a/hscommon/loc.py +++ b/hscommon/loc.py @@ -154,9 +154,7 @@ def strings2pot(target, dest): def allstrings2pot(lprojpath, dest, excludes=None): allstrings = files_with_ext(lprojpath, ".strings") if excludes: - allstrings = [ - p for p in allstrings if op.splitext(op.basename(p))[0] not in excludes - ] + allstrings = [p for p in allstrings if op.splitext(op.basename(p))[0] not in excludes] for strings_path in allstrings: strings2pot(strings_path, dest) @@ -195,11 +193,7 @@ def generate_cocoa_strings_from_code(code_folder, dest_folder): # genstrings produces utf-16 files with comments. After having generated the files, we convert # them to utf-8 and remove the comments. ensure_empty_folder(dest_folder) - print_and_do( - 'genstrings -o "{}" `find "{}" -name *.m | xargs`'.format( - dest_folder, code_folder - ) - ) + print_and_do('genstrings -o "{}" `find "{}" -name *.m | xargs`'.format(dest_folder, code_folder)) for stringsfile in os.listdir(dest_folder): stringspath = op.join(dest_folder, stringsfile) with open(stringspath, "rt", encoding="utf-16") as fp: @@ -214,9 +208,7 @@ def generate_cocoa_strings_from_code(code_folder, dest_folder): def generate_cocoa_strings_from_xib(xib_folder): - xibs = [ - op.join(xib_folder, fn) for fn in os.listdir(xib_folder) if fn.endswith(".xib") - ] + xibs = [op.join(xib_folder, fn) for fn in os.listdir(xib_folder) if fn.endswith(".xib")] for xib in xibs: dest = xib.replace(".xib", ".strings") print_and_do("ibtool {} --generate-strings-file {}".format(xib, dest)) @@ -234,10 +226,6 @@ def localize_stringsfile(stringsfile, dest_root_folder): def localize_all_stringsfiles(src_folder, dest_root_folder): - stringsfiles = [ - op.join(src_folder, fn) - for fn in os.listdir(src_folder) - if fn.endswith(".strings") - ] + stringsfiles = [op.join(src_folder, fn) for fn in os.listdir(src_folder) if fn.endswith(".strings")] for path in stringsfiles: localize_stringsfile(path, dest_root_folder) diff --git a/hscommon/notify.py b/hscommon/notify.py index 96dbe4e9..d5d55c89 100644 --- a/hscommon/notify.py +++ b/hscommon/notify.py @@ -16,8 +16,7 @@ from collections import defaultdict class Broadcaster: - """Broadcasts messages that are received by all listeners. - """ + """Broadcasts messages that are received by all listeners.""" def __init__(self): self.listeners = set() @@ -39,8 +38,7 @@ class Broadcaster: class Listener: - """A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected. - """ + """A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected.""" def __init__(self, broadcaster): self.broadcaster = broadcaster @@ -57,13 +55,11 @@ class Listener: self._bound_notifications[message].append(func) def connect(self): - """Connects the listener to its broadcaster. - """ + """Connects the listener to its broadcaster.""" self.broadcaster.add_listener(self) def disconnect(self): - """Disconnects the listener from its broadcaster. - """ + """Disconnects the listener from its broadcaster.""" self.broadcaster.remove_listener(self) def dispatch(self, msg): diff --git a/hscommon/path.py b/hscommon/path.py index e19386ed..23d41f1e 100644 --- a/hscommon/path.py +++ b/hscommon/path.py @@ -85,9 +85,7 @@ class Path(tuple): def __getitem__(self, key): if isinstance(key, slice): if isinstance(key.start, Path): - equal_elems = list( - takewhile(lambda pair: pair[0] == pair[1], zip(self, key.start)) - ) + equal_elems = list(takewhile(lambda pair: pair[0] == pair[1], zip(self, key.start))) key = slice(len(equal_elems), key.stop, key.step) if isinstance(key.stop, Path): equal_elems = list( @@ -226,9 +224,7 @@ def pathify(f): Calling ``foo('/bar', 0)`` will convert ``'/bar'`` to ``Path('/bar')``. """ sig = signature(f) - pindexes = { - i for i, p in enumerate(sig.parameters.values()) if p.annotation is Path - } + pindexes = {i for i, p in enumerate(sig.parameters.values()) if p.annotation is Path} pkeys = {k: v for k, v in sig.parameters.items() if v.annotation is Path} def path_or_none(p): @@ -236,9 +232,7 @@ def pathify(f): @wraps(f) def wrapped(*args, **kwargs): - args = tuple( - (path_or_none(a) if i in pindexes else a) for i, a in enumerate(args) - ) + args = tuple((path_or_none(a) if i in pindexes else a) for i, a in enumerate(args)) kwargs = {k: (path_or_none(v) if k in pkeys else v) for k, v in kwargs.items()} return f(*args, **kwargs) @@ -246,8 +240,7 @@ def pathify(f): def log_io_error(func): - """ Catches OSError, IOError and WindowsError and log them - """ + """Catches OSError, IOError and WindowsError and log them""" @wraps(func) def wrapper(path, *args, **kwargs): diff --git a/hscommon/pygettext.py b/hscommon/pygettext.py index 8e72c793..2207d0f2 100644 --- a/hscommon/pygettext.py +++ b/hscommon/pygettext.py @@ -110,22 +110,14 @@ def _visit_pyfiles(list, dirname, names): # get extension for python source files if "_py_ext" not in globals(): global _py_ext - _py_ext = [ - triple[0] for triple in imp.get_suffixes() if triple[2] == imp.PY_SOURCE - ][0] + _py_ext = [triple[0] for triple in imp.get_suffixes() if triple[2] == imp.PY_SOURCE][0] # don't recurse into CVS directories if "CVS" in names: names.remove("CVS") # add all *.py files to list - list.extend( - [ - os.path.join(dirname, file) - for file in names - if os.path.splitext(file)[1] == _py_ext - ] - ) + list.extend([os.path.join(dirname, file) for file in names if os.path.splitext(file)[1] == _py_ext]) def _get_modpkg_path(dotted_name, pathlist=None): @@ -406,8 +398,7 @@ def main(source_files, outpath, keywords=None): eater(*_token) except tokenize.TokenError as e: print( - "%s: %s, line %d, column %d" - % (e.args[0], filename, e.args[1][0], e.args[1][1]), + "%s: %s, line %d, column %d" % (e.args[0], filename, e.args[1][0], e.args[1][1]), file=sys.stderr, ) finally: diff --git a/hscommon/sphinxgen.py b/hscommon/sphinxgen.py index a3165097..225f1eb7 100644 --- a/hscommon/sphinxgen.py +++ b/hscommon/sphinxgen.py @@ -22,9 +22,7 @@ def tixgen(tixurl): """This is a filter *generator*. tixurl is a url pattern for the tix with a {0} placeholder for the tix # """ - urlpattern = tixurl.format( - "\\1" - ) # will be replaced buy the content of the first group in re + urlpattern = tixurl.format("\\1") # will be replaced buy the content of the first group in re R = re.compile(r"#(\d+)") repl = "`#\\1 <{}>`__".format(urlpattern) return lambda text: R.sub(repl, text) @@ -61,9 +59,7 @@ def gen( # The format of the changelog descriptions is in markdown, but since we only use bulled list # and links, it's not worth depending on the markdown package. A simple regexp suffice. description = re.sub(r"\[(.*?)\]\((.*?)\)", "`\\1 <\\2>`__", description) - rendered = CHANGELOG_FORMAT.format( - version=log["version"], date=log["date_str"], description=description - ) + rendered = CHANGELOG_FORMAT.format(version=log["version"], date=log["date_str"], description=description) rendered_logs.append(rendered) confrepl["version"] = changelog[0]["version"] changelog_out = op.join(basepath, "changelog.rst") @@ -75,6 +71,4 @@ def gen( try: sphinx_build([basepath, destpath]) except SystemExit: - print( - "Sphinx called sys.exit(), but we're cancelling it because we don't actually want to exit" - ) + print("Sphinx called sys.exit(), but we're cancelling it because we don't actually want to exit") diff --git a/hscommon/sqlite.py b/hscommon/sqlite.py index 0e1910eb..53f6ba82 100644 --- a/hscommon/sqlite.py +++ b/hscommon/sqlite.py @@ -31,8 +31,8 @@ class FakeCursor(list): class _ActualThread(threading.Thread): - """ We can't use this class directly because thread object are not automatically freed when - nothing refers to it, making it hang the application if not explicitely closed. + """We can't use this class directly because thread object are not automatically freed when + nothing refers to it, making it hang the application if not explicitely closed. """ def __init__(self, dbname, autocommit): diff --git a/hscommon/tests/conflict_test.py b/hscommon/tests/conflict_test.py index 617a9f62..1ad444df 100644 --- a/hscommon/tests/conflict_test.py +++ b/hscommon/tests/conflict_test.py @@ -80,9 +80,7 @@ class TestCase_move_copy: assert self.path["baz"].exists() assert not self.path["foo"].exists() - def test_copy_no_conflict( - self, do_setup - ): # No need to duplicate the rest of the tests... Let's just test on move + def test_copy_no_conflict(self, do_setup): # No need to duplicate the rest of the tests... Let's just test on move smart_copy(self.path + "foo", self.path + "baz") assert self.path["baz"].exists() assert self.path["foo"].exists() diff --git a/hscommon/tests/notify_test.py b/hscommon/tests/notify_test.py index 09ce8f43..7450ea5e 100644 --- a/hscommon/tests/notify_test.py +++ b/hscommon/tests/notify_test.py @@ -128,9 +128,7 @@ def test_repeater_with_repeated_notifications(): r.connect() listener.connect() b.notify("hello") - b.notify( - "foo" - ) # if the repeater repeated this notif, we'd get a crash on HelloListener + b.notify("foo") # if the repeater repeated this notif, we'd get a crash on HelloListener eq_(r.hello_count, 1) eq_(listener.hello_count, 1) eq_(r.foo_count, 1) diff --git a/hscommon/tests/path_test.py b/hscommon/tests/path_test.py index b87a7e81..35e64656 100644 --- a/hscommon/tests/path_test.py +++ b/hscommon/tests/path_test.py @@ -87,8 +87,7 @@ def test_filename(force_ossep): def test_deal_with_empty_components(force_ossep): - """Keep ONLY a leading space, which means we want a leading slash. - """ + """Keep ONLY a leading space, which means we want a leading slash.""" eq_("foo//bar", str(Path(("foo", "", "bar")))) eq_("/foo/bar", str(Path(("", "foo", "bar")))) eq_("foo/bar", str(Path("foo/bar/"))) @@ -154,8 +153,7 @@ def test_path_slice(force_ossep): def test_add_with_root_path(force_ossep): - """if I perform /a/b/c + /d/e/f, I want /a/b/c/d/e/f, not /a/b/c//d/e/f - """ + """if I perform /a/b/c + /d/e/f, I want /a/b/c/d/e/f, not /a/b/c//d/e/f""" eq_("/foo/bar", str(Path("/foo") + Path("/bar"))) @@ -166,8 +164,7 @@ def test_create_with_tuple_that_have_slash_inside(force_ossep, monkeypatch): def test_auto_decode_os_sep(force_ossep, monkeypatch): - """Path should decode any either / or os.sep, but always encode in os.sep. - """ + """Path should decode any either / or os.sep, but always encode in os.sep.""" eq_(("foo\\bar", "bleh"), Path("foo\\bar/bleh")) monkeypatch.setattr(os, "sep", "\\") eq_(("foo", "bar/bleh"), Path("foo\\bar/bleh")) diff --git a/hscommon/tests/selectable_list_test.py b/hscommon/tests/selectable_list_test.py index c464c2df..f9ac5242 100644 --- a/hscommon/tests/selectable_list_test.py +++ b/hscommon/tests/selectable_list_test.py @@ -44,9 +44,7 @@ def test_guicalls(): # A GUISelectableList appropriately calls its view. sl = GUISelectableList(["foo", "bar"]) sl.view = CallLogger() - sl.view.check_gui_calls( - ["refresh"] - ) # Upon setting the view, we get a call to refresh() + sl.view.check_gui_calls(["refresh"]) # Upon setting the view, we get a call to refresh() sl[1] = "baz" sl.view.check_gui_calls(["refresh"]) sl.append("foo") diff --git a/hscommon/tests/tree_test.py b/hscommon/tests/tree_test.py index 1f0b28e7..addd059c 100644 --- a/hscommon/tests/tree_test.py +++ b/hscommon/tests/tree_test.py @@ -105,9 +105,7 @@ def test_findall_dont_include_self(): # When calling findall with include_self=False, the node itself is never evaluated. t = tree_with_some_nodes() del t._name # so that if the predicate is called on `t`, we crash - r = t.findall( - lambda n: not n.name.startswith("sub"), include_self=False - ) # no crash + r = t.findall(lambda n: not n.name.startswith("sub"), include_self=False) # no crash eq_(set(r), set([t[0], t[1], t[2]])) diff --git a/hscommon/tests/util_test.py b/hscommon/tests/util_test.py index eae80f7c..b3d6c4ec 100644 --- a/hscommon/tests/util_test.py +++ b/hscommon/tests/util_test.py @@ -105,9 +105,7 @@ def test_iterconsume(): # We just want to make sure that we return *all* items and that we're not mistakenly skipping # one. eq_(list(range(2500)), list(iterconsume(list(range(2500))))) - eq_( - list(reversed(range(2500))), list(iterconsume(list(range(2500)), reverse=False)) - ) + eq_(list(reversed(range(2500))), list(iterconsume(list(range(2500)), reverse=False))) # --- String diff --git a/hscommon/testutil.py b/hscommon/testutil.py index 8e9b776b..92255d45 100644 --- a/hscommon/testutil.py +++ b/hscommon/testutil.py @@ -86,9 +86,7 @@ class CallLogger: eq_(set(self.calls), set(expected)) self.clear_calls() - def check_gui_calls_partial( - self, expected=None, not_expected=None, verify_order=False - ): + def check_gui_calls_partial(self, expected=None, not_expected=None, verify_order=False): """Checks that the expected calls have been made to 'self', then clears the log. `expected` is an iterable of strings representing method names. Order doesn't matter. @@ -99,25 +97,17 @@ class CallLogger: __tracebackhide__ = True if expected is not None: not_called = set(expected) - set(self.calls) - assert not not_called, "These calls haven't been made: {0}".format( - not_called - ) + assert not not_called, "These calls haven't been made: {0}".format(not_called) if verify_order: max_index = 0 for call in expected: index = self.calls.index(call) if index < max_index: - raise AssertionError( - "The call {0} hasn't been made in the correct order".format( - call - ) - ) + raise AssertionError("The call {0} hasn't been made in the correct order".format(call)) max_index = index if not_expected is not None: called = set(not_expected) & set(self.calls) - assert not called, "These calls shouldn't have been made: {0}".format( - called - ) + assert not called, "These calls shouldn't have been made: {0}".format(called) self.clear_calls() @@ -193,27 +183,25 @@ def jointhreads(): def _unify_args(func, args, kwargs, args_to_ignore=None): - """ Unify args and kwargs in the same dictionary. + """Unify args and kwargs in the same dictionary. - The result is kwargs with args added to it. func.func_code.co_varnames is used to determine - under what key each elements of arg will be mapped in kwargs. + The result is kwargs with args added to it. func.func_code.co_varnames is used to determine + under what key each elements of arg will be mapped in kwargs. - if you want some arguments not to be in the results, supply a list of arg names in - args_to_ignore. + if you want some arguments not to be in the results, supply a list of arg names in + args_to_ignore. - if f is a function that takes *args, func_code.co_varnames is empty, so args will be put - under 'args' in kwargs. + if f is a function that takes *args, func_code.co_varnames is empty, so args will be put + under 'args' in kwargs. - def foo(bar, baz) - _unifyArgs(foo, (42,), {'baz': 23}) --> {'bar': 42, 'baz': 23} - _unifyArgs(foo, (42,), {'baz': 23}, ['bar']) --> {'baz': 23} + def foo(bar, baz) + _unifyArgs(foo, (42,), {'baz': 23}) --> {'bar': 42, 'baz': 23} + _unifyArgs(foo, (42,), {'baz': 23}, ['bar']) --> {'baz': 23} """ result = kwargs.copy() if hasattr(func, "__code__"): # built-in functions don't have func_code args = list(args) - if ( - getattr(func, "__self__", None) is not None - ): # bound method, we have to add self to args list + if getattr(func, "__self__", None) is not None: # bound method, we have to add self to args list args = [func.__self__] + args defaults = list(func.__defaults__) if func.__defaults__ is not None else [] arg_count = func.__code__.co_argcount @@ -234,11 +222,11 @@ def _unify_args(func, args, kwargs, args_to_ignore=None): def log_calls(func): - """ Logs all func calls' arguments under func.calls. + """Logs all func calls' arguments under func.calls. - func.calls is a list of _unify_args() result (dict). + func.calls is a list of _unify_args() result (dict). - Mostly used for unit testing. + Mostly used for unit testing. """ def wrapper(*args, **kwargs): diff --git a/hscommon/trans.py b/hscommon/trans.py index b5c0299c..01d6b83c 100644 --- a/hscommon/trans.py +++ b/hscommon/trans.py @@ -110,9 +110,7 @@ def install_gettext_trans(base_folder, lang): if not lang: return lambda s: s try: - return gettext.translation( - domain, localedir=base_folder, languages=[lang] - ).gettext + return gettext.translation(domain, localedir=base_folder, languages=[lang]).gettext except IOError: return lambda s: s diff --git a/hscommon/util.py b/hscommon/util.py index c6676091..beca96ee 100644 --- a/hscommon/util.py +++ b/hscommon/util.py @@ -19,8 +19,7 @@ from .path import Path, pathify, log_io_error def nonone(value, replace_value): - """Returns ``value`` if ``value`` is not ``None``. Returns ``replace_value`` otherwise. - """ + """Returns ``value`` if ``value`` is not ``None``. Returns ``replace_value`` otherwise.""" if value is None: return replace_value else: @@ -28,8 +27,7 @@ def nonone(value, replace_value): def tryint(value, default=0): - """Tries to convert ``value`` to in ``int`` and returns ``default`` if it fails. - """ + """Tries to convert ``value`` to in ``int`` and returns ``default`` if it fails.""" try: return int(value) except (TypeError, ValueError): @@ -37,8 +35,7 @@ def tryint(value, default=0): def minmax(value, min_value, max_value): - """Returns `value` or one of the min/max bounds if `value` is not between them. - """ + """Returns `value` or one of the min/max bounds if `value` is not between them.""" return min(max(value, min_value), max_value) @@ -75,8 +72,7 @@ def flatten(iterables, start_with=None): def first(iterable): - """Returns the first item of ``iterable``. - """ + """Returns the first item of ``iterable``.""" try: return next(iter(iterable)) except StopIteration: @@ -84,14 +80,12 @@ def first(iterable): def stripfalse(seq): - """Returns a sequence with all false elements stripped out of seq. - """ + """Returns a sequence with all false elements stripped out of seq.""" return [x for x in seq if x] def extract(predicate, iterable): - """Separates the wheat from the shaft (`predicate` defines what's the wheat), and returns both. - """ + """Separates the wheat from the shaft (`predicate` defines what's the wheat), and returns both.""" wheat = [] shaft = [] for item in iterable: @@ -103,8 +97,7 @@ def extract(predicate, iterable): def allsame(iterable): - """Returns whether all elements of 'iterable' are the same. - """ + """Returns whether all elements of 'iterable' are the same.""" it = iter(iterable) try: first_item = next(it) @@ -152,14 +145,12 @@ def iterconsume(seq, reverse=True): def escape(s, to_escape, escape_with="\\"): - """Returns ``s`` with characters in ``to_escape`` all prepended with ``escape_with``. - """ + """Returns ``s`` with characters in ``to_escape`` all prepended with ``escape_with``.""" return "".join((escape_with + c if c in to_escape else c) for c in s) def get_file_ext(filename): - """Returns the lowercase extension part of filename, without the dot. - """ + """Returns the lowercase extension part of filename, without the dot.""" pos = filename.rfind(".") if pos > -1: return filename[pos + 1 :].lower() @@ -168,8 +159,7 @@ def get_file_ext(filename): def rem_file_ext(filename): - """Returns the filename without extension. - """ + """Returns the filename without extension.""" pos = filename.rfind(".") if pos > -1: return filename[:pos] @@ -217,8 +207,7 @@ def format_time(seconds, with_hours=True): def format_time_decimal(seconds): - """Transforms seconds in a strings like '3.4 minutes'. - """ + """Transforms seconds in a strings like '3.4 minutes'.""" minus = seconds < 0 if minus: seconds *= -1 @@ -320,8 +309,7 @@ ONE_DAY = timedelta(1) def iterdaterange(start, end): - """Yields every day between ``start`` and ``end``. - """ + """Yields every day between ``start`` and ``end``.""" date = start while date <= end: yield date @@ -365,8 +353,7 @@ def find_in_path(name, paths=None): @log_io_error @pathify def delete_if_empty(path: Path, files_to_delete=[]): - """Deletes the directory at 'path' if it is empty or if it only contains files_to_delete. - """ + """Deletes the directory at 'path' if it is empty or if it only contains files_to_delete.""" if not path.exists() or not path.isdir(): return contents = path.listdir() @@ -411,8 +398,7 @@ def ensure_file(path): def delete_files_with_pattern(folder_path, pattern, recursive=True): - """Delete all files (or folders) in `folder_path` that match the glob `pattern`. - """ + """Delete all files (or folders) in `folder_path` that match the glob `pattern`.""" to_delete = glob.glob(op.join(folder_path, pattern)) for fn in to_delete: if op.isdir(fn): diff --git a/package.py b/package.py index 711e5cec..aae91406 100644 --- a/package.py +++ b/package.py @@ -82,11 +82,7 @@ def package_debian_distribution(distribution): copy(op.join(debskel, fn), op.join(debdest, fn)) filereplace(op.join(debskel, "control"), op.join(debdest, "control"), **debopts) filereplace(op.join(debskel, "Makefile"), op.join(destpath, "Makefile"), **debopts) - filereplace( - op.join(debskel, "dupeguru.desktop"), - op.join(debdest, "dupeguru.desktop"), - **debopts - ) + filereplace(op.join(debskel, "dupeguru.desktop"), op.join(debdest, "dupeguru.desktop"), **debopts) changelogpath = op.join("help", "changelog") changelog_dest = op.join(debdest, "changelog") project_name = debopts["pkgname"] @@ -128,11 +124,7 @@ def package_arch(): copy_files_to_package(srcpath, packages, with_so=True) shutil.copy(op.join("images", "dgse_logo_128.png"), srcpath) debopts = json.load(open(op.join("pkg", "arch", "dupeguru.json"))) - filereplace( - op.join("pkg", "arch", "dupeguru.desktop"), - op.join(srcpath, "dupeguru.desktop"), - **debopts - ) + filereplace(op.join("pkg", "arch", "dupeguru.desktop"), op.join(srcpath, "dupeguru.desktop"), **debopts) def package_source_txz(): @@ -173,11 +165,7 @@ def package_windows(): version_info = version_template.read() version_template.close() version_info_file = open("win_version_info.txt", "w") - version_info_file.write( - version_info.format( - version_array[0], version_array[1], version_array[2], bits - ) - ) + version_info_file.write(version_info.format(version_array[0], version_array[1], version_array[2], bits)) version_info_file.close() except Exception: print("Error creating version info file, exiting...") @@ -195,9 +183,7 @@ def package_windows(): "--add-data=build/locale;locale", "--add-data=build/help;help", "--version-file=win_version_info.txt", - "--paths=C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\{0}".format( - arch - ), + "--paths=C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\{0}".format(arch), "run.py", ] ) diff --git a/pkg/debian/build_pe_modules.py b/pkg/debian/build_pe_modules.py index 3afd5a9b..92717cb7 100644 --- a/pkg/debian/build_pe_modules.py +++ b/pkg/debian/build_pe_modules.py @@ -6,19 +6,19 @@ import importlib from setuptools import setup, Extension -sys.path.insert(1, op.abspath('src')) +sys.path.insert(1, op.abspath("src")) from hscommon.build import move_all exts = [ - Extension("_block", [op.join('modules', 'block.c'), op.join('modules', 'common.c')]), - Extension("_cache", [op.join('modules', 'cache.c'), op.join('modules', 'common.c')]), - Extension("_block_qt", [op.join('modules', 'block_qt.c')]), + Extension("_block", [op.join("modules", "block.c"), op.join("modules", "common.c")]), + Extension("_cache", [op.join("modules", "cache.c"), op.join("modules", "common.c")]), + Extension("_block_qt", [op.join("modules", "block_qt.c")]), ] setup( - script_args = ['build_ext', '--inplace'], - ext_modules = exts, + script_args=["build_ext", "--inplace"], + ext_modules=exts, ) -move_all('_block_qt*', op.join('src', 'qt', 'pe')) -move_all('_cache*', op.join('src', 'core/pe')) -move_all('_block*', op.join('src', 'core/pe')) +move_all("_block_qt*", op.join("src", "qt", "pe")) +move_all("_cache*", op.join("src", "core/pe")) +move_all("_block*", op.join("src", "core/pe")) diff --git a/qt/app.py b/qt/app.py index 3653482c..a68f5be0 100644 --- a/qt/app.py +++ b/qt/app.py @@ -65,18 +65,10 @@ class DupeGuru(QObject): self.recentResults.mustOpenItem.connect(self.model.load_from) self.resultWindow = None if self.use_tabs: - self.main_window = ( - TabBarWindow(self) - if not self.prefs.tabs_default_pos - else TabWindow(self) - ) + self.main_window = TabBarWindow(self) if not self.prefs.tabs_default_pos else TabWindow(self) parent_window = self.main_window - self.directories_dialog = self.main_window.createPage( - "DirectoriesDialog", app=self - ) - self.main_window.addTab( - self.directories_dialog, tr("Directories"), switch=False - ) + self.directories_dialog = self.main_window.createPage("DirectoriesDialog", app=self) + self.main_window.addTab(self.directories_dialog, tr("Directories"), switch=False) self.actionDirectoriesWindow.setEnabled(False) else: # floating windows only self.main_window = None @@ -84,9 +76,7 @@ class DupeGuru(QObject): parent_window = self.directories_dialog self.progress_window = ProgressWindow(parent_window, self.model.progress_window) - self.problemDialog = ProblemDialog( - parent=parent_window, model=self.model.problem_dialog - ) + self.problemDialog = ProblemDialog(parent=parent_window, model=self.model.problem_dialog) if self.use_tabs: self.ignoreListDialog = self.main_window.createPage( "IgnoreListDialog", @@ -101,16 +91,10 @@ class DupeGuru(QObject): model=self.model.exclude_list_dialog, ) else: - self.ignoreListDialog = IgnoreListDialog( - parent=parent_window, model=self.model.ignore_list_dialog - ) - self.excludeDialog = ExcludeListDialog( - app=self, parent=parent_window, model=self.model.exclude_list_dialog - ) + self.ignoreListDialog = IgnoreListDialog(parent=parent_window, model=self.model.ignore_list_dialog) + self.excludeDialog = ExcludeListDialog(app=self, parent=parent_window, model=self.model.exclude_list_dialog) - self.deletionOptions = DeletionOptions( - parent=parent_window, model=self.model.deletion_options - ) + self.deletionOptions = DeletionOptions(parent=parent_window, model=self.model.deletion_options) self.about_box = AboutBox(parent_window, self) parent_window.show() @@ -174,25 +158,19 @@ class DupeGuru(QObject): self.model.options["mix_file_kind"] = self.prefs.mix_file_kind self.model.options["escape_filter_regexp"] = not self.prefs.use_regexp self.model.options["clean_empty_dirs"] = self.prefs.remove_empty_folders - self.model.options[ - "ignore_hardlink_matches" - ] = self.prefs.ignore_hardlink_matches + self.model.options["ignore_hardlink_matches"] = self.prefs.ignore_hardlink_matches self.model.options["copymove_dest_type"] = self.prefs.destination_type self.model.options["scan_type"] = self.prefs.get_scan_type(self.model.app_mode) self.model.options["min_match_percentage"] = self.prefs.filter_hardness self.model.options["word_weighting"] = self.prefs.word_weighting self.model.options["match_similar_words"] = self.prefs.match_similar - threshold = ( - self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0 - ) - self.model.options["size_threshold"] = ( - threshold * 1024 - ) # threshold is in KB. The scanner wants bytes - big_file_size_threshold = ( - self.prefs.big_file_size_threshold if self.prefs.big_file_partial_hashes else 0 - ) + threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0 + self.model.options["size_threshold"] = threshold * 1024 # threshold is in KB. The scanner wants bytes + big_file_size_threshold = self.prefs.big_file_size_threshold if self.prefs.big_file_partial_hashes else 0 self.model.options["big_file_size_threshold"] = ( - big_file_size_threshold * 1024 * 1024 + big_file_size_threshold + * 1024 + * 1024 # threshold is in MiB. The scanner wants bytes ) scanned_tags = set() @@ -259,9 +237,7 @@ class DupeGuru(QObject): if self.resultWindow is not None: if self.use_tabs: if self.main_window.indexOfWidget(self.resultWindow) < 0: - self.main_window.addTab( - self.resultWindow, tr("Results"), switch=True - ) + self.main_window.addTab(self.resultWindow, tr("Results"), switch=True) return self.main_window.showTab(self.resultWindow) else: @@ -318,9 +294,7 @@ class DupeGuru(QObject): def excludeListTriggered(self): if self.use_tabs: - self.showTriggeredTabbedDialog( - self.excludeListDialog, tr("Exclusion Filters") - ) + self.showTriggeredTabbedDialog(self.excludeListDialog, tr("Exclusion Filters")) else: # floating windows self.model.exclude_list_dialog.show() @@ -328,9 +302,7 @@ class DupeGuru(QObject): """Add tab for dialog, name the tab with desc_string, then show it.""" index = self.main_window.indexOfWidget(dialog) # Create the tab if it doesn't exist already - if ( - index < 0 - ): # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)): + if index < 0: # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)): index = self.main_window.addTab(dialog, desc_string, switch=True) # Show the tab for that widget self.main_window.setCurrentIndex(index) @@ -402,13 +374,9 @@ class DupeGuru(QObject): if self.resultWindow is not None: self.resultWindow.close() # This is better for tabs, as it takes care of duplicate items in menu bar - self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent( - None - ) + self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent(None) if self.use_tabs: - self.resultWindow = self.main_window.createPage( - "ResultWindow", parent=self.main_window, app=self - ) + self.resultWindow = self.main_window.createPage("ResultWindow", parent=self.main_window, app=self) else: # We don't use a tab widget, regular floating QMainWindow self.resultWindow = ResultWindow(self.directories_dialog, self) self.directories_dialog._updateActionsState() @@ -426,9 +394,7 @@ class DupeGuru(QObject): def select_dest_file(self, prompt, extension): files = tr("{} file (*.{})").format(extension.upper(), extension) - destination, chosen_filter = QFileDialog.getSaveFileName( - self.resultWindow, prompt, "", files - ) + destination, chosen_filter = QFileDialog.getSaveFileName(self.resultWindow, prompt, "", files) if not destination.endswith(".{}".format(extension)): destination = "{}.{}".format(destination, extension) return destination diff --git a/qt/deletion_options.py b/qt/deletion_options.py index 5b72ef58..ab879adb 100644 --- a/qt/deletion_options.py +++ b/qt/deletion_options.py @@ -42,9 +42,7 @@ class DeletionOptions(QDialog): self.linkMessageLabel = QLabel(text) self.linkMessageLabel.setWordWrap(True) self.verticalLayout.addWidget(self.linkMessageLabel) - self.linkTypeRadio = RadioBox( - items=[tr("Symlink"), tr("Hardlink")], spread=False - ) + self.linkTypeRadio = RadioBox(items=[tr("Symlink"), tr("Hardlink")], spread=False) self.verticalLayout.addWidget(self.linkTypeRadio) if not self.model.supports_links(): self.linkCheckbox.setEnabled(False) diff --git a/qt/details_dialog.py b/qt/details_dialog.py index 3c31fa55..efb7dfd7 100644 --- a/qt/details_dialog.py +++ b/qt/details_dialog.py @@ -31,8 +31,7 @@ class DetailsDialog(QDockWidget): self.model.view = self self.app.willSavePrefs.connect(self.appWillSavePrefs) # self.setAttribute(Qt.WA_DeleteOnClose) - parent.addDockWidget( - area if self._wasDocked else Qt.BottomDockWidgetArea, self) + parent.addDockWidget(area if self._wasDocked else Qt.BottomDockWidgetArea, self) def _setupUi(self): # Virtual pass diff --git a/qt/details_table.py b/qt/details_table.py index faa0e012..56d51bda 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -34,9 +34,11 @@ class DetailsModel(QAbstractTableModel): row = index.row() ignored_fields = ["Dupe Count"] - if (self.model.row(row)[0] in ignored_fields - or self.model.row(row)[1] == "---" - or self.model.row(row)[2] == "---"): + if ( + self.model.row(row)[0] in ignored_fields + or self.model.row(row)[1] == "---" + or self.model.row(row)[2] == "---" + ): if role != Qt.DisplayRole: return None return self.model.row(row)[column] @@ -52,17 +54,9 @@ class DetailsModel(QAbstractTableModel): return None # QVariant() def headerData(self, section, orientation, role): - if ( - orientation == Qt.Horizontal - and role == Qt.DisplayRole - and section < len(HEADER) - ): + if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER): return HEADER[section] - elif ( - orientation == Qt.Vertical - and role == Qt.DisplayRole - and section < self.model.row_count() - ): + elif orientation == Qt.Vertical and role == Qt.DisplayRole and section < self.model.row_count(): # Read "Attribute" cell for horizontal header return self.model.row(section)[0] return None diff --git a/qt/directories_dialog.py b/qt/directories_dialog.py index 320328bf..0c3e2fbc 100644 --- a/qt/directories_dialog.py +++ b/qt/directories_dialog.py @@ -45,9 +45,7 @@ class DirectoriesDialog(QMainWindow): self.recentFolders = Recent(self.app, "recentFolders") self._setupUi() self._updateScanTypeList() - self.directoriesModel = DirectoriesModel( - self.app.model.directory_tree, view=self.treeView - ) + self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) self._setupColumns() @@ -170,9 +168,7 @@ class DirectoriesDialog(QMainWindow): label = QLabel(tr("Application Mode:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) - self.appModeRadioBox = RadioBox( - self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False - ) + self.appModeRadioBox = RadioBox(self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False) hl.addWidget(self.appModeRadioBox) self.verticalLayout.addLayout(hl) hl = QHBoxLayout() @@ -181,27 +177,21 @@ class DirectoriesDialog(QMainWindow): label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) self.scanTypeComboBox = QComboBox(self) - self.scanTypeComboBox.setSizePolicy( - QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - ) + self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setMaximumWidth(400) hl.addWidget(self.scanTypeComboBox) self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget) self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(self.showPreferencesButton) self.verticalLayout.addLayout(hl) - self.promptLabel = QLabel( - tr('Select folders to scan and press "Scan".'), self.centralwidget - ) + self.promptLabel = QLabel(tr('Select folders to scan and press "Scan".'), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setAcceptDrops(True) triggers = ( - QAbstractItemView.DoubleClicked - | QAbstractItemView.EditKeyPressed - | QAbstractItemView.SelectedClicked + QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked ) self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) @@ -267,9 +257,7 @@ class DirectoriesDialog(QMainWindow): def _updateScanTypeList(self): try: - self.scanTypeComboBox.currentIndexChanged[int].disconnect( - self.scanTypeChanged - ) + self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged) except TypeError: # Not connected, ignore pass @@ -299,9 +287,7 @@ class DirectoriesDialog(QMainWindow): def addFolderTriggered(self): title = tr("Select a folder to add to the scanning list") flags = QFileDialog.ShowDirsOnly - dirpath = str( - QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags) - ) + dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)) if not dirpath: return self.lastAddedFolder = dirpath @@ -362,9 +348,7 @@ class DirectoriesDialog(QMainWindow): def scanTypeChanged(self, index): scan_options = self.app.model.SCANNER_CLASS.get_scan_options() - self.app.prefs.set_scan_type( - self.app.model.app_mode, scan_options[index].scan_type - ) + self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type) self.app._update_options() def selectionChanged(self, selected, deselected): diff --git a/qt/directories_model.py b/qt/directories_model.py index 9a32852e..03e2352e 100644 --- a/qt/directories_model.py +++ b/qt/directories_model.py @@ -44,9 +44,7 @@ class DirectoriesDelegate(QStyledItemDelegate): # On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to # fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright. cboption.state |= QStyle.State_Enabled - QApplication.style().drawComplexControl( - QStyle.CC_ComboBox, cboption, painter - ) + QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter) painter.setBrush(option.palette.text()) rect = QRect(option.rect) rect.setLeft(rect.left() + 4) @@ -75,9 +73,7 @@ class DirectoriesModel(TreeModel): self.view = view self.view.setModel(self) - self.view.selectionModel().selectionChanged[ - (QItemSelection, QItemSelection) - ].connect(self.selectionChanged) + self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged) def _createNode(self, ref, row): return RefNode(self, None, ref, row) @@ -155,10 +151,7 @@ class DirectoriesModel(TreeModel): # --- Events def selectionChanged(self, selected, deselected): - newNodes = [ - modelIndex.internalPointer().ref - for modelIndex in self.view.selectionModel().selectedRows() - ] + newNodes = [modelIndex.internalPointer().ref for modelIndex in self.view.selectionModel().selectedRows()] self.model.selected_nodes = newNodes # --- Signals diff --git a/qt/exclude_list_dialog.py b/qt/exclude_list_dialog.py index 4246087e..4b2f690d 100644 --- a/qt/exclude_list_dialog.py +++ b/qt/exclude_list_dialog.py @@ -5,13 +5,22 @@ import re from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtWidgets import ( - QPushButton, QLineEdit, QVBoxLayout, QGridLayout, QDialog, - QTableView, QAbstractItemView, QSpacerItem, QSizePolicy, QHeaderView + QPushButton, + QLineEdit, + QVBoxLayout, + QGridLayout, + QDialog, + QTableView, + QAbstractItemView, + QSpacerItem, + QSizePolicy, + QHeaderView, ) from .exclude_list_table import ExcludeListTable from core.exclude import AlreadyThereException from hscommon.trans import trget + tr = trget("ui") @@ -51,9 +60,7 @@ class ExcludeListDialog(QDialog): self.testLine = QLineEdit() self.tableView = QTableView() triggers = ( - QAbstractItemView.DoubleClicked - | QAbstractItemView.EditKeyPressed - | QAbstractItemView.SelectedClicked + QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked ) self.tableView.setEditTriggers(triggers) self.tableView.setSelectionMode(QTableView.ExtendedSelection) @@ -150,7 +157,9 @@ class ExcludeListDialog(QDialog): self.table.refresh() def display_help_message(self): - self.app.show_message(tr("""\ + self.app.show_message( + tr( + """\ These (case sensitive) python regular expressions will filter out files during scans.
\ Directores will also have their default state set to Excluded \ in the Directories tab if their name happens to match one of the selected regular expressions.
\ @@ -163,4 +172,6 @@ You can test the regular expression with the "test string" button after pasting C:\\\\User\\My Pictures\\test.png

Matching regular expressions will be highlighted.
\ If there is at least one highlight, the path or filename tested will be ignored during scans.

\ -Directories and files starting with a period '.' are filtered out by default.

""")) +Directories and files starting with a period '.' are filtered out by default.

""" + ) + ) diff --git a/qt/exclude_list_table.py b/qt/exclude_list_table.py index b58e2579..b14b2ae0 100644 --- a/qt/exclude_list_table.py +++ b/qt/exclude_list_table.py @@ -8,15 +8,14 @@ from PyQt5.QtGui import QFont, QFontMetrics, QIcon, QColor from qtlib.column import Column from qtlib.table import Table from hscommon.trans import trget + tr = trget("ui") class ExcludeListTable(Table): """Model for exclude list""" - COLUMNS = [ - Column("marked", defaultWidth=15), - Column("regex", defaultWidth=230) - ] + + COLUMNS = [Column("marked", defaultWidth=15), Column("regex", defaultWidth=230)] def __init__(self, app, view, **kwargs): model = app.model.exclude_list_dialog.exclude_list_table # pointer to GUITable diff --git a/qt/ignore_list_dialog.py b/qt/ignore_list_dialog.py index f9cd9649..fbb54081 100644 --- a/qt/ignore_list_dialog.py +++ b/qt/ignore_list_dialog.py @@ -56,9 +56,7 @@ class IgnoreListDialog(QDialog): self.clearButton = QPushButton(tr("Clear")) self.closeButton = QPushButton(tr("Close")) self.verticalLayout.addLayout( - horizontalWrap( - [self.removeSelectedButton, self.clearButton, None, self.closeButton] - ) + horizontalWrap([self.removeSelectedButton, self.clearButton, None, self.closeButton]) ) # --- model --> view diff --git a/qt/ignore_list_table.py b/qt/ignore_list_table.py index 46a9c175..2688f8eb 100644 --- a/qt/ignore_list_table.py +++ b/qt/ignore_list_table.py @@ -10,7 +10,7 @@ from qtlib.table import Table class IgnoreListTable(Table): - """ Ignore list model""" + """Ignore list model""" COLUMNS = [ Column("path1", defaultWidth=230), diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index 462e5682..8afa462e 100644 --- a/qt/me/preferences_dialog.py +++ b/qt/me/preferences_dialog.py @@ -59,13 +59,9 @@ class PreferencesDialog(PreferencesDialogBase): self.widgetsVLayout.addWidget(self.matchSimilarBox) self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind")) self.widgetsVLayout.addWidget(self.mixFileKindBox) - self._setupAddCheckbox( - "useRegexpBox", tr("Use regular expressions when filtering") - ) + self._setupAddCheckbox("useRegexpBox", tr("Use regular expressions when filtering")) self.widgetsVLayout.addWidget(self.useRegexpBox) - self._setupAddCheckbox( - "removeEmptyFoldersBox", tr("Remove empty folders on delete or move") - ) + self._setupAddCheckbox("removeEmptyFoldersBox", tr("Remove empty folders on delete or move")) self.widgetsVLayout.addWidget(self.removeEmptyFoldersBox) self._setupAddCheckbox( "ignoreHardlinkMatches", diff --git a/qt/pe/details_dialog.py b/qt/pe/details_dialog.py index d75fc1cb..9e06f789 100644 --- a/qt/pe/details_dialog.py +++ b/qt/pe/details_dialog.py @@ -5,14 +5,13 @@ # http://www.gnu.org/licenses/gpl-3.0.html from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot -from PyQt5.QtWidgets import ( - QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame) +from PyQt5.QtWidgets import QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame from PyQt5.QtGui import QResizeEvent from hscommon.trans import trget from ..details_dialog import DetailsDialog as DetailsDialogBase from ..details_table import DetailsTable -from .image_viewer import ( - ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController) +from .image_viewer import ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController + tr = trget("ui") @@ -70,8 +69,7 @@ class DetailsDialog(DetailsDialogBase): self.splitter.addWidget(self.tableView) self.splitter.setStretchFactor(1, 1) # Late population needed here for connections to the toolbar - self.vController.setupViewers( - self.selectedImageViewer, self.referenceImageViewer) + self.vController.setupViewers(self.selectedImageViewer, self.referenceImageViewer) # self.setCentralWidget(self.splitter) # only as QMainWindow self.setWidget(self.splitter) # only as QDockWidget @@ -103,11 +101,11 @@ class DetailsDialog(DetailsDialogBase): # Give the splitter a maximum height to reach. This is assuming that # all rows below their headers have the same height self.tableView.setMaximumHeight( - self.tableView.rowHeight(1) - * self.tableModel.model.row_count() + self.tableView.rowHeight(1) * self.tableModel.model.row_count() + self.tableView.verticalHeader().sectionSize(0) # looks like the handle is taken into account by the splitter - + self.splitter.handle(1).size().height()) + + self.splitter.handle(1).size().height() + ) DetailsDialogBase.show(self) self.ensure_same_sizes() self._update() @@ -138,6 +136,7 @@ class DetailsDialog(DetailsDialogBase): class EmittingFrame(QFrame): """Emits a signal whenever is resized""" + resized = pyqtSignal(QResizeEvent) def resizeEvent(self, event): diff --git a/qt/pe/image_viewer.py b/qt/pe/image_viewer.py index ef379361..c07c9fe3 100644 --- a/qt/pe/image_viewer.py +++ b/qt/pe/image_viewer.py @@ -2,15 +2,24 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import ( - QObject, Qt, QSize, QRectF, QPointF, QPoint, pyqtSlot, pyqtSignal, QEvent) +from PyQt5.QtCore import QObject, Qt, QSize, QRectF, QPointF, QPoint, pyqtSlot, pyqtSignal, QEvent from PyQt5.QtGui import QPixmap, QPainter, QPalette, QCursor, QIcon, QKeySequence from PyQt5.QtWidgets import ( - QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, - QToolBar, QToolButton, QAction, QWidget, QScrollArea, - QApplication, QAbstractScrollArea, QStyle) + QGraphicsView, + QGraphicsScene, + QGraphicsPixmapItem, + QToolBar, + QToolButton, + QAction, + QWidget, + QScrollArea, + QApplication, + QAbstractScrollArea, + QStyle, +) from hscommon.trans import trget from hscommon.plat import ISLINUX + tr = trget("ui") MAX_SCALE = 12.0 @@ -50,8 +59,7 @@ class ViewerToolBar(QToolBar): "actionZoomIn", QKeySequence.ZoomIn, QIcon.fromTheme("zoom-in") - if ISLINUX - and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "zoom_in")), tr("Increase zoom"), controller.zoomIn, @@ -60,8 +68,7 @@ class ViewerToolBar(QToolBar): "actionZoomOut", QKeySequence.ZoomOut, QIcon.fromTheme("zoom-out") - if ISLINUX - and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "zoom_out")), tr("Decrease zoom"), controller.zoomOut, @@ -70,8 +77,7 @@ class ViewerToolBar(QToolBar): "actionNormalSize", tr("Ctrl+/"), QIcon.fromTheme("zoom-original") - if ISLINUX - and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "zoom_original")), tr("Normal size"), controller.zoomNormalSize, @@ -80,12 +86,11 @@ class ViewerToolBar(QToolBar): "actionBestFit", tr("Ctrl+*"), QIcon.fromTheme("zoom-best-fit") - if ISLINUX - and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "zoom_best_fit")), tr("Best fit"), controller.zoomBestFit, - ) + ), ] # TODO try with QWidgetAction() instead in order to have # the popup menu work in the toolbar (if resized below minimum height) @@ -95,13 +100,12 @@ class ViewerToolBar(QToolBar): self.buttonImgSwap = QToolButton(self) self.buttonImgSwap.setToolButtonStyle(Qt.ToolButtonIconOnly) self.buttonImgSwap.setIcon( - QIcon.fromTheme('view-refresh', - self.style().standardIcon(QStyle.SP_BrowserReload)) - if ISLINUX - and not self.parent.app.prefs.details_dialog_override_theme_icons - else QIcon(QPixmap(":/" + "exchange"))) - self.buttonImgSwap.setText('Swap images') - self.buttonImgSwap.setToolTip('Swap images') + QIcon.fromTheme("view-refresh", self.style().standardIcon(QStyle.SP_BrowserReload)) + if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons + else QIcon(QPixmap(":/" + "exchange")) + ) + self.buttonImgSwap.setText("Swap images") + self.buttonImgSwap.setToolTip("Swap images") self.buttonImgSwap.pressed.connect(self.controller.swapImages) self.buttonImgSwap.released.connect(self.controller.swapImages) @@ -207,11 +211,11 @@ class BaseController(QObject): # than the ReferenceImageViewer by one pixel, which distorts the # scaled down pixmap for the reference, hence we'll reuse its size here. selected_size = self._updateImage( - self.selectedPixmap, self.scaledSelectedPixmap, - self.selectedViewer, None, same_group) + self.selectedPixmap, self.scaledSelectedPixmap, self.selectedViewer, None, same_group + ) self._updateImage( - self.referencePixmap, self.scaledReferencePixmap, - self.referenceViewer, selected_size, same_group) + self.referencePixmap, self.scaledReferencePixmap, self.referenceViewer, selected_size, same_group + ) if ignore_update: self.selectedViewer.ignore_signal = False @@ -229,12 +233,10 @@ class BaseController(QObject): return target_size # zoomed in state, expand # only if not same_group, we need full update - scaledpixmap = pixmap.scaled( - target_size, Qt.KeepAspectRatioByExpanding, Qt.FastTransformation) + scaledpixmap = pixmap.scaled(target_size, Qt.KeepAspectRatioByExpanding, Qt.FastTransformation) else: # best fit, keep ratio always - scaledpixmap = pixmap.scaled( - target_size, Qt.KeepAspectRatio, Qt.FastTransformation) + scaledpixmap = pixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.FastTransformation) viewer.setImage(scaledpixmap) return target_size @@ -347,12 +349,8 @@ class BaseController(QObject): self.selectedViewer.resetCenter() self.referenceViewer.resetCenter() - target_size = self._updateImage( - self.selectedPixmap, self.scaledSelectedPixmap, - self.selectedViewer, None, True) - self._updateImage( - self.referencePixmap, self.scaledReferencePixmap, - self.referenceViewer, target_size, True) + target_size = self._updateImage(self.selectedPixmap, self.scaledSelectedPixmap, self.selectedViewer, None, True) + self._updateImage(self.referencePixmap, self.scaledReferencePixmap, self.referenceViewer, target_size, True) self.centerViews() self.parent.verticalToolBar.buttonZoomIn.setEnabled(False) @@ -402,6 +400,7 @@ class BaseController(QObject): class QWidgetController(BaseController): """Specialized version for QWidget-based viewers.""" + def __init__(self, parent): super().__init__(parent) @@ -430,6 +429,7 @@ class QWidgetController(BaseController): class ScrollAreaController(BaseController): """Specialized version fro QLabel-based viewers.""" + def __init__(self, parent): super().__init__(parent) @@ -442,10 +442,8 @@ class ScrollAreaController(BaseController): super().updateBothImages(same_group) if not self.referenceViewer.isEnabled(): return - self.referenceViewer._horizontalScrollBar.setValue( - self.selectedViewer._horizontalScrollBar.value()) - self.referenceViewer._verticalScrollBar.setValue( - self.selectedViewer._verticalScrollBar.value()) + self.referenceViewer._horizontalScrollBar.setValue(self.selectedViewer._horizontalScrollBar.value()) + self.referenceViewer._verticalScrollBar.setValue(self.selectedViewer._verticalScrollBar.value()) @pyqtSlot(QPoint) def onDraggedMouse(self, delta): @@ -518,6 +516,7 @@ class ScrollAreaController(BaseController): class GraphicsViewController(BaseController): """Specialized version fro QGraphicsView-based viewers.""" + def __init__(self, parent): super().__init__(parent) @@ -625,10 +624,8 @@ class GraphicsViewController(BaseController): if ignore_update: self.selectedViewer.ignore_signal = True - self._updateFitImage( - self.selectedPixmap, self.selectedViewer) - self._updateFitImage( - self.referencePixmap, self.referenceViewer) + self._updateFitImage(self.selectedPixmap, self.selectedViewer) + self._updateFitImage(self.referencePixmap, self.referenceViewer) if ignore_update: self.selectedViewer.ignore_signal = False @@ -699,6 +696,7 @@ class GraphicsViewController(BaseController): class QWidgetImageViewer(QWidget): """Use a QPixmap, but no scrollbars and no keyboard key sequence for navigation.""" + # FIXME: panning while zoomed-in is broken (due to delta not interpolated right? mouseDragged = pyqtSignal(QPointF) mouseWheeled = pyqtSignal(float) @@ -720,15 +718,13 @@ class QWidgetImageViewer(QWidget): self.setMouseTracking(False) def __repr__(self): - return f'{self._instance_name}' + return f"{self._instance_name}" def connectMouseSignals(self): if not self._dragConnection: - self._dragConnection = self.mouseDragged.connect( - self.controller.onDraggedMouse) + self._dragConnection = self.mouseDragged.connect(self.controller.onDraggedMouse) if not self._wheelConnection: - self._wheelConnection = self.mouseWheeled.connect( - self.controller.scaleImagesBy) + self._wheelConnection = self.mouseWheeled.connect(self.controller.scaleImagesBy) def disconnectMouseSignals(self): if self._dragConnection: @@ -746,7 +742,7 @@ class QWidgetImageViewer(QWidget): painter.drawPixmap(self._rect.topLeft(), self._pixmap) def resetCenter(self): - """ Resets origin """ + """Resets origin""" # Make sure we are not still panning around self._mousePanningDelta = QPointF() self.update() @@ -783,8 +779,7 @@ class QWidgetImageViewer(QWidget): event.ignore() return - self._mousePanningDelta += ( - event.pos() - self._lastMouseClickPoint) * 1.0 / self.current_scale + self._mousePanningDelta += (event.pos() - self._lastMouseClickPoint) * 1.0 / self.current_scale self._lastMouseClickPoint = event.pos() if self._drag: self.mouseDragged.emit(self._mousePanningDelta) @@ -860,6 +855,7 @@ class QWidgetImageViewer(QWidget): class ScalablePixmap(QWidget): """Container for a pixmap that scales up very fast, used in ScrollAreaImageViewer.""" + def __init__(self, parent): super().__init__(parent) self._pixmap = QPixmap() @@ -881,6 +877,7 @@ class ScalablePixmap(QWidget): class ScrollAreaImageViewer(QScrollArea): """Implementation using a pixmap container in a simple scroll area.""" + mouseDragged = pyqtSignal(QPoint) mouseWheeled = pyqtSignal(float, QPointF) @@ -921,7 +918,7 @@ class ScrollAreaImageViewer(QScrollArea): self.setVisible(True) def __repr__(self): - return f'{self._instance_name}' + return f"{self._instance_name}" def toggleScrollBars(self, forceOn=False): if not self.prefs.details_dialog_viewers_show_scrollbars: @@ -938,11 +935,9 @@ class ScrollAreaImageViewer(QScrollArea): def connectMouseSignals(self): if not self._dragConnection: - self._dragConnection = self.mouseDragged.connect( - self.controller.onDraggedMouse) + self._dragConnection = self.mouseDragged.connect(self.controller.onDraggedMouse) if not self._wheelConnection: - self._wheelConnection = self.mouseWheeled.connect( - self.controller.onMouseWheel) + self._wheelConnection = self.mouseWheeled.connect(self.controller.onMouseWheel) def disconnectMouseSignals(self): if self._dragConnection: @@ -955,10 +950,8 @@ class ScrollAreaImageViewer(QScrollArea): def connectScrollBars(self): """Only call once controller is connected.""" # Cyclic connections are handled by Qt - self._verticalScrollBar.valueChanged.connect( - self.controller.onVScrollBarChanged, Qt.UniqueConnection) - self._horizontalScrollBar.valueChanged.connect( - self.controller.onHScrollBarChanged, Qt.UniqueConnection) + self._verticalScrollBar.valueChanged.connect(self.controller.onVScrollBarChanged, Qt.UniqueConnection) + self._horizontalScrollBar.valueChanged.connect(self.controller.onHScrollBarChanged, Qt.UniqueConnection) def contextMenuEvent(self, event): """Block parent's (main window) context menu on right click.""" @@ -987,7 +980,7 @@ class ScrollAreaImageViewer(QScrollArea): event.ignore() return if self._drag: - delta = (event.pos() - self._lastMouseClickPoint) + delta = event.pos() - self._lastMouseClickPoint self._lastMouseClickPoint = event.pos() self.mouseDragged.emit(delta) super().mouseMoveEvent(event) @@ -1064,35 +1057,29 @@ class ScrollAreaImageViewer(QScrollArea): """After scaling, no mouse position, default to center.""" # scrollBar.setMaximum(scrollBar.maximum() - scrollBar.minimum() + scrollBar.pageStep()) self._horizontalScrollBar.setValue( - int(factor * self._horizontalScrollBar.value() - + ((factor - 1) * self._horizontalScrollBar.pageStep() / 2))) + int(factor * self._horizontalScrollBar.value() + ((factor - 1) * self._horizontalScrollBar.pageStep() / 2)) + ) self._verticalScrollBar.setValue( - int(factor * self._verticalScrollBar.value() - + ((factor - 1) * self._verticalScrollBar.pageStep() / 2))) + int(factor * self._verticalScrollBar.value() + ((factor - 1) * self._verticalScrollBar.pageStep() / 2)) + ) def adjustScrollBarsScaled(self, delta): """After scaling with the mouse, update relative to mouse position.""" - self._horizontalScrollBar.setValue( - self._horizontalScrollBar.value() + delta.x()) - self._verticalScrollBar.setValue( - self._verticalScrollBar.value() + delta.y()) + self._horizontalScrollBar.setValue(self._horizontalScrollBar.value() + delta.x()) + self._verticalScrollBar.setValue(self._verticalScrollBar.value() + delta.y()) def adjustScrollBarsAuto(self): """After panning, update accordingly.""" - self.horizontalScrollBar().setValue( - self.horizontalScrollBar().value() - self._mousePanningDelta.x()) - self.verticalScrollBar().setValue( - self.verticalScrollBar().value() - self._mousePanningDelta.y()) + self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - self._mousePanningDelta.x()) + self.verticalScrollBar().setValue(self.verticalScrollBar().value() - self._mousePanningDelta.y()) def adjustScrollBarCentered(self): """Just center in the middle.""" - self._horizontalScrollBar.setValue( - int(self._horizontalScrollBar.maximum() / 2)) - self._verticalScrollBar.setValue( - int(self._verticalScrollBar.maximum() / 2)) + self._horizontalScrollBar.setValue(int(self._horizontalScrollBar.maximum() / 2)) + self._verticalScrollBar.setValue(int(self._verticalScrollBar.maximum() / 2)) def resetCenter(self): - """ Resets origin """ + """Resets origin""" self._mousePanningDelta = QPoint() self.current_scale = 1.0 # self.scaleAt(1.0) @@ -1127,6 +1114,7 @@ class ScrollAreaImageViewer(QScrollArea): class GraphicsViewViewer(QGraphicsView): """Re-Implementation a full-fledged GraphicsView but is a bit buggy.""" + mouseDragged = pyqtSignal() mouseWheeled = pyqtSignal(float, QPointF) @@ -1178,11 +1166,9 @@ class GraphicsViewViewer(QGraphicsView): def connectMouseSignals(self): if not self._dragConnection: - self._dragConnection = self.mouseDragged.connect( - self.controller.syncCenters) + self._dragConnection = self.mouseDragged.connect(self.controller.syncCenters) if not self._wheelConnection: - self._wheelConnection = self.mouseWheeled.connect( - self.controller.onMouseWheel) + self._wheelConnection = self.mouseWheeled.connect(self.controller.onMouseWheel) def disconnectMouseSignals(self): if self._dragConnection: @@ -1195,10 +1181,8 @@ class GraphicsViewViewer(QGraphicsView): def connectScrollBars(self): """Only call once controller is connected.""" # Cyclic connections are handled by Qt - self._verticalScrollBar.valueChanged.connect( - self.controller.onVScrollBarChanged, Qt.UniqueConnection) - self._horizontalScrollBar.valueChanged.connect( - self.controller.onHScrollBarChanged, Qt.UniqueConnection) + self._verticalScrollBar.valueChanged.connect(self.controller.onVScrollBarChanged, Qt.UniqueConnection) + self._horizontalScrollBar.valueChanged.connect(self.controller.onHScrollBarChanged, Qt.UniqueConnection) def toggleScrollBars(self, forceOn=False): if not self.prefs.details_dialog_viewers_show_scrollbars: @@ -1298,7 +1282,7 @@ class GraphicsViewViewer(QGraphicsView): self.centerOn(self._centerPoint) def resetCenter(self): - """ Resets origin """ + """Resets origin""" self._mousePanningDelta = QPointF() self.current_scale = 1.0 @@ -1345,10 +1329,8 @@ class GraphicsViewViewer(QGraphicsView): def adjustScrollBarsScaled(self, delta): """After scaling with the mouse, update relative to mouse position.""" - self._horizontalScrollBar.setValue( - self._horizontalScrollBar.value() + delta.x()) - self._verticalScrollBar.setValue( - self._verticalScrollBar.value() + delta.y()) + self._horizontalScrollBar.setValue(self._horizontalScrollBar.value() + delta.x()) + self._verticalScrollBar.setValue(self._verticalScrollBar.value() + delta.y()) def sizeHint(self): return self.viewport().rect().size() @@ -1356,15 +1338,13 @@ class GraphicsViewViewer(QGraphicsView): def adjustScrollBarsFactor(self, factor): """After scaling, no mouse position, default to center.""" self._horizontalScrollBar.setValue( - int(factor * self._horizontalScrollBar.value() - + ((factor - 1) * self._horizontalScrollBar.pageStep() / 2))) + int(factor * self._horizontalScrollBar.value() + ((factor - 1) * self._horizontalScrollBar.pageStep() / 2)) + ) self._verticalScrollBar.setValue( - int(factor * self._verticalScrollBar.value() - + ((factor - 1) * self._verticalScrollBar.pageStep() / 2))) + int(factor * self._verticalScrollBar.value() + ((factor - 1) * self._verticalScrollBar.pageStep() / 2)) + ) def adjustScrollBarsAuto(self): """After panning, update accordingly.""" - self.horizontalScrollBar().setValue( - self.horizontalScrollBar().value() - self._mousePanningDelta.x()) - self.verticalScrollBar().setValue( - self.verticalScrollBar().value() - self._mousePanningDelta.y()) + self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - self._mousePanningDelta.x()) + self.verticalScrollBar().setValue(self.verticalScrollBar().value() - self._mousePanningDelta.y()) diff --git a/qt/pe/photo.py b/qt/pe/photo.py index a829f21e..22e750a5 100644 --- a/qt/pe/photo.py +++ b/qt/pe/photo.py @@ -30,13 +30,15 @@ class File(PhotoBase): image = QImage(str(self.path)) image = image.convertToFormat(QImage.Format_RGB888) if type(orientation) == str: - logging.warning("Orientation for file '%s' was a str '%s', not an int.", - str(self.path), orientation) + logging.warning("Orientation for file '%s' was a str '%s', not an int.", str(self.path), orientation) try: orientation = int(orientation) except Exception as e: - logging.exception("Skipping transformation because could not \ -convert str to int. %s", e) + logging.exception( + "Skipping transformation because could not \ +convert str to int. %s", + e, + ) return getblocks(image, block_count_per_side) # MYSTERY TO SOLVE: For reasons I cannot explain, orientations 5 and 7 don't work for # duplicate scanning. The transforms seems to work fine (if I try to save the image after diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 4cecb43f..00b77cd6 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -21,19 +21,13 @@ class PreferencesDialog(PreferencesDialogBase): def _setupPreferenceWidgets(self): self._setupFilterHardnessBox() self.widgetsVLayout.addLayout(self.filterHardnessHLayout) - self._setupAddCheckbox( - "matchScaledBox", tr("Match pictures of different dimensions") - ) + self._setupAddCheckbox("matchScaledBox", tr("Match pictures of different dimensions")) self.widgetsVLayout.addWidget(self.matchScaledBox) self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind")) self.widgetsVLayout.addWidget(self.mixFileKindBox) - self._setupAddCheckbox( - "useRegexpBox", tr("Use regular expressions when filtering") - ) + self._setupAddCheckbox("useRegexpBox", tr("Use regular expressions when filtering")) self.widgetsVLayout.addWidget(self.useRegexpBox) - self._setupAddCheckbox( - "removeEmptyFoldersBox", tr("Remove empty folders on delete or move") - ) + self._setupAddCheckbox("removeEmptyFoldersBox", tr("Remove empty folders on delete or move")) self.widgetsVLayout.addWidget(self.removeEmptyFoldersBox) self._setupAddCheckbox( "ignoreHardlinkMatches", @@ -52,45 +46,37 @@ class PreferencesDialog(PreferencesDialogBase): def _setupDisplayPage(self): super()._setupDisplayPage() - self._setupAddCheckbox("details_dialog_override_theme_icons", - tr("Override theme icons in viewer toolbar")) + self._setupAddCheckbox("details_dialog_override_theme_icons", tr("Override theme icons in viewer toolbar")) self.details_dialog_override_theme_icons.setToolTip( - tr("Use our own internal icons instead of those provided by the theme engine")) + tr("Use our own internal icons instead of those provided by the theme engine") + ) # Prevent changing this on platforms where themes are unpredictable self.details_dialog_override_theme_icons.setEnabled(False if not ISLINUX else True) # Insert this right after the vertical title bar option index = self.details_groupbox_layout.indexOf(self.details_dialog_vertical_titlebar) - self.details_groupbox_layout.insertWidget( - index + 1, self.details_dialog_override_theme_icons) - self._setupAddCheckbox("details_dialog_viewers_show_scrollbars", - tr("Show scrollbars in image viewers")) + self.details_groupbox_layout.insertWidget(index + 1, self.details_dialog_override_theme_icons) + self._setupAddCheckbox("details_dialog_viewers_show_scrollbars", tr("Show scrollbars in image viewers")) self.details_dialog_viewers_show_scrollbars.setToolTip( - tr("When the image displayed doesn't fit the viewport, \ -show scrollbars to span the view around")) - self.details_groupbox_layout.insertWidget( - index + 2, self.details_dialog_viewers_show_scrollbars) + tr( + "When the image displayed doesn't fit the viewport, \ +show scrollbars to span the view around" + ) + ) + self.details_groupbox_layout.insertWidget(index + 2, self.details_dialog_viewers_show_scrollbars) def _load(self, prefs, setchecked, section): setchecked(self.matchScaledBox, prefs.match_scaled) - self.cacheTypeRadio.selected_index = ( - 1 if prefs.picture_cache_type == "shelve" else 0 - ) + self.cacheTypeRadio.selected_index = 1 if prefs.picture_cache_type == "shelve" else 0 # Update UI state based on selected scan type scan_type = prefs.get_scan_type(AppMode.Picture) fuzzy_scan = scan_type == ScanType.FuzzyBlock self.filterHardnessSlider.setEnabled(fuzzy_scan) - setchecked(self.details_dialog_override_theme_icons, - prefs.details_dialog_override_theme_icons) - setchecked(self.details_dialog_viewers_show_scrollbars, - prefs.details_dialog_viewers_show_scrollbars) + setchecked(self.details_dialog_override_theme_icons, prefs.details_dialog_override_theme_icons) + setchecked(self.details_dialog_viewers_show_scrollbars, prefs.details_dialog_viewers_show_scrollbars) def _save(self, prefs, ischecked): prefs.match_scaled = ischecked(self.matchScaledBox) - prefs.picture_cache_type = ( - "shelve" if self.cacheTypeRadio.selected_index == 1 else "sqlite" - ) - prefs.details_dialog_override_theme_icons =\ - ischecked(self.details_dialog_override_theme_icons) - prefs.details_dialog_viewers_show_scrollbars =\ - ischecked(self.details_dialog_viewers_show_scrollbars) + prefs.picture_cache_type = "shelve" if self.cacheTypeRadio.selected_index == 1 else "sqlite" + prefs.details_dialog_override_theme_icons = ischecked(self.details_dialog_override_theme_icons) + prefs.details_dialog_viewers_show_scrollbars = ischecked(self.details_dialog_viewers_show_scrollbars) diff --git a/qt/preferences.py b/qt/preferences.py index 578e9be7..51a4be99 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -20,9 +20,7 @@ class Preferences(PreferencesBase): get = self.get_value self.filter_hardness = get("FilterHardness", self.filter_hardness) self.mix_file_kind = get("MixFileKind", self.mix_file_kind) - self.ignore_hardlink_matches = get( - "IgnoreHardlinkMatches", self.ignore_hardlink_matches - ) + self.ignore_hardlink_matches = get("IgnoreHardlinkMatches", self.ignore_hardlink_matches) self.use_regexp = get("UseRegexp", self.use_regexp) self.remove_empty_folders = get("RemoveEmptyFolders", self.remove_empty_folders) self.debug_mode = get("DebugMode", self.debug_mode) @@ -34,37 +32,36 @@ class Preferences(PreferencesBase): self.tableFontSize = get("TableFontSize", self.tableFontSize) self.reference_bold_font = get("ReferenceBoldFont", self.reference_bold_font) - self.details_dialog_titlebar_enabled = get("DetailsDialogTitleBarEnabled", - self.details_dialog_titlebar_enabled) - self.details_dialog_vertical_titlebar = get("DetailsDialogVerticalTitleBar", - self.details_dialog_vertical_titlebar) + self.details_dialog_titlebar_enabled = get("DetailsDialogTitleBarEnabled", self.details_dialog_titlebar_enabled) + self.details_dialog_vertical_titlebar = get( + "DetailsDialogVerticalTitleBar", self.details_dialog_vertical_titlebar + ) # On Windows and MacOS, use internal icons by default - self.details_dialog_override_theme_icons =\ - get("DetailsDialogOverrideThemeIcons", - self.details_dialog_override_theme_icons) if ISLINUX else True - self.details_table_delta_foreground_color =\ - get("DetailsTableDeltaForegroundColor", self.details_table_delta_foreground_color) - self.details_dialog_viewers_show_scrollbars =\ - get("DetailsDialogViewersShowScrollbars", self.details_dialog_viewers_show_scrollbars) - - self.result_table_ref_foreground_color =\ - get("ResultTableRefForegroundColor", self.result_table_ref_foreground_color) - self.result_table_ref_background_color =\ - get("ResultTableRefBackgroundColor", self.result_table_ref_background_color) - self.result_table_delta_foreground_color =\ - get("ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color) - - self.resultWindowIsMaximized = get( - "ResultWindowIsMaximized", self.resultWindowIsMaximized + self.details_dialog_override_theme_icons = ( + get("DetailsDialogOverrideThemeIcons", self.details_dialog_override_theme_icons) if ISLINUX else True ) + self.details_table_delta_foreground_color = get( + "DetailsTableDeltaForegroundColor", self.details_table_delta_foreground_color + ) + self.details_dialog_viewers_show_scrollbars = get( + "DetailsDialogViewersShowScrollbars", self.details_dialog_viewers_show_scrollbars + ) + + self.result_table_ref_foreground_color = get( + "ResultTableRefForegroundColor", self.result_table_ref_foreground_color + ) + self.result_table_ref_background_color = get( + "ResultTableRefBackgroundColor", self.result_table_ref_background_color + ) + self.result_table_delta_foreground_color = get( + "ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color + ) + + self.resultWindowIsMaximized = get("ResultWindowIsMaximized", self.resultWindowIsMaximized) self.resultWindowRect = self.get_rect("ResultWindowRect", self.resultWindowRect) - self.mainWindowIsMaximized = get( - "MainWindowIsMaximized", self.mainWindowIsMaximized - ) + self.mainWindowIsMaximized = get("MainWindowIsMaximized", self.mainWindowIsMaximized) self.mainWindowRect = self.get_rect("MainWindowRect", self.mainWindowRect) - self.directoriesWindowRect = self.get_rect( - "DirectoriesWindowRect", self.directoriesWindowRect - ) + self.directoriesWindowRect = self.get_rect("DirectoriesWindowRect", self.directoriesWindowRect) self.recentResults = get("RecentResults", self.recentResults) self.recentFolders = get("RecentFolders", self.recentFolders) diff --git a/qt/prioritize_dialog.py b/qt/prioritize_dialog.py index 0b291952..d9e7e698 100644 --- a/qt/prioritize_dialog.py +++ b/qt/prioritize_dialog.py @@ -79,12 +79,8 @@ class PrioritizeDialog(QDialog): super().__init__(parent, flags, **kwargs) self._setupUi() self.model = PrioritizeDialogModel(app=app.model) - self.categoryList = ComboboxModel( - model=self.model.category_list, view=self.categoryCombobox - ) - self.criteriaList = ListviewModel( - model=self.model.criteria_list, view=self.criteriaListView - ) + self.categoryList = ComboboxModel(model=self.model.category_list, view=self.categoryCombobox) + self.criteriaList = ListviewModel(model=self.model.criteria_list, view=self.criteriaListView) self.prioritizationList = PrioritizationList( model=self.model.prioritization_list, view=self.prioritizationListView ) @@ -112,12 +108,8 @@ class PrioritizeDialog(QDialog): self.categoryCombobox = QComboBox() self.criteriaListView = QListView() self.criteriaListView.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.addCriteriaButton = QPushButton( - self.style().standardIcon(QStyle.SP_ArrowRight), "" - ) - self.removeCriteriaButton = QPushButton( - self.style().standardIcon(QStyle.SP_ArrowLeft), "" - ) + self.addCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowRight), "") + self.removeCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowLeft), "") self.prioritizationListView = QListView() self.prioritizationListView.setAcceptDrops(True) self.prioritizationListView.setDragEnabled(True) diff --git a/qt/result_window.py b/qt/result_window.py index 06c24e44..97b41413 100644 --- a/qt/result_window.py +++ b/qt/result_window.py @@ -295,9 +295,7 @@ class ResultWindow(QMainWindow): if menu.actions(): menu.clear() self._column_actions = [] - for index, (display, visible) in enumerate( - self.app.model.result_table.columns.menu_items() - ): + for index, (display, visible) in enumerate(self.app.model.result_table.columns.menu_items()): action = menu.addAction(display) action.setCheckable(True) action.setChecked(visible) diff --git a/qt/se/preferences_dialog.py b/qt/se/preferences_dialog.py index 68e06d7e..bca7dede 100644 --- a/qt/se/preferences_dialog.py +++ b/qt/se/preferences_dialog.py @@ -34,15 +34,11 @@ class PreferencesDialog(PreferencesDialogBase): self.verticalLayout_4 = QVBoxLayout(self.widget) self._setupAddCheckbox("wordWeightingBox", tr("Word weighting"), self.widget) self.verticalLayout_4.addWidget(self.wordWeightingBox) - self._setupAddCheckbox( - "matchSimilarBox", tr("Match similar words"), self.widget - ) + self._setupAddCheckbox("matchSimilarBox", tr("Match similar words"), self.widget) self.verticalLayout_4.addWidget(self.matchSimilarBox) self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind"), self.widget) self.verticalLayout_4.addWidget(self.mixFileKindBox) - self._setupAddCheckbox( - "useRegexpBox", tr("Use regular expressions when filtering"), self.widget - ) + self._setupAddCheckbox("useRegexpBox", tr("Use regular expressions when filtering"), self.widget) self.verticalLayout_4.addWidget(self.useRegexpBox) self._setupAddCheckbox( "removeEmptyFoldersBox", @@ -51,17 +47,13 @@ class PreferencesDialog(PreferencesDialogBase): ) self.verticalLayout_4.addWidget(self.removeEmptyFoldersBox) self.horizontalLayout_2 = QHBoxLayout() - self._setupAddCheckbox( - "ignoreSmallFilesBox", tr("Ignore files smaller than"), self.widget - ) + self._setupAddCheckbox("ignoreSmallFilesBox", tr("Ignore files smaller than"), self.widget) self.horizontalLayout_2.addWidget(self.ignoreSmallFilesBox) self.sizeThresholdSpinBox = QSpinBox(self.widget) sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.sizeThresholdSpinBox.sizePolicy().hasHeightForWidth() - ) + sizePolicy.setHeightForWidth(self.sizeThresholdSpinBox.sizePolicy().hasHeightForWidth()) self.sizeThresholdSpinBox.setSizePolicy(sizePolicy) self.sizeThresholdSpinBox.setMaximumSize(QSize(100, 16777215)) self.sizeThresholdSpinBox.setRange(0, 1000000) @@ -96,9 +88,7 @@ class PreferencesDialog(PreferencesDialogBase): self.widget, ) self.verticalLayout_4.addWidget(self.ignoreHardlinkMatches) - self._setupAddCheckbox( - "debugModeBox", tr("Debug mode (restart required)"), self.widget - ) + self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"), self.widget) self.verticalLayout_4.addWidget(self.debugModeBox) self.widgetsVLayout.addWidget(self.widget) self._setupBottomPart() diff --git a/qt/tabbed_window.py b/qt/tabbed_window.py index 2912ddcb..8e747cc6 100644 --- a/qt/tabbed_window.py +++ b/qt/tabbed_window.py @@ -19,6 +19,7 @@ from .directories_dialog import DirectoriesDialog from .result_window import ResultWindow from .ignore_list_dialog import IgnoreListDialog from .exclude_list_dialog import ExcludeListDialog + tr = trget("ui") @@ -135,16 +136,15 @@ class TabWindow(QMainWindow): action.setEnabled(True) self.app.directories_dialog.actionShowResultsWindow.setEnabled( - False if page_type == "ResultWindow" - else self.app.resultWindow is not None) + False if page_type == "ResultWindow" else self.app.resultWindow is not None + ) self.app.actionIgnoreList.setEnabled( - True if self.app.ignoreListDialog is not None - and not page_type == "IgnoreListDialog" else False) - self.app.actionDirectoriesWindow.setEnabled( - False if page_type == "DirectoriesDialog" else True) + True if self.app.ignoreListDialog is not None and not page_type == "IgnoreListDialog" else False + ) + self.app.actionDirectoriesWindow.setEnabled(False if page_type == "DirectoriesDialog" else True) self.app.actionExcludeList.setEnabled( - True if self.app.excludeListDialog is not None - and not page_type == "ExcludeListDialog" else False) + True if self.app.excludeListDialog is not None and not page_type == "ExcludeListDialog" else False + ) self.previous_widget_actions = active_widget.specific_actions self.last_index = current_index @@ -176,8 +176,7 @@ class TabWindow(QMainWindow): index = self.tabWidget.addTab(page, title) # index = self.tabWidget.insertTab(-1, page, title) if isinstance(page, DirectoriesDialog): - self.tabWidget.tabBar().setTabButton( - index, QTabBar.RightSide, None) + self.tabWidget.tabBar().setTabButton(index, QTabBar.RightSide, None) if switch: self.setCurrentIndex(index) return index @@ -250,6 +249,7 @@ class TabWindow(QMainWindow): class TabBarWindow(TabWindow): """Implementation which uses a separate QTabBar and QStackedWidget. The Tab bar is placed next to the menu bar to save real estate.""" + def __init__(self, app, **kwargs): super().__init__(app, **kwargs) @@ -286,8 +286,7 @@ class TabBarWindow(TabWindow): self.tabBar.insertTab(stack_index, title) if isinstance(page, DirectoriesDialog): - self.tabBar.setTabButton( - stack_index, QTabBar.RightSide, None) + self.tabBar.setTabButton(stack_index, QTabBar.RightSide, None) if switch: # switch to the added tab immediately upon creation self.setTabIndex(stack_index) return stack_index diff --git a/qtlib/about_box.py b/qtlib/about_box.py index 7982cc6f..9112dfaa 100644 --- a/qtlib/about_box.py +++ b/qtlib/about_box.py @@ -25,12 +25,7 @@ tr = trget("qtlib") class AboutBox(QDialog): def __init__(self, parent, app, **kwargs): - flags = ( - Qt.CustomizeWindowHint - | Qt.WindowTitleHint - | Qt.WindowSystemMenuHint - | Qt.MSWindowsFixedSizeDialogHint - ) + flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint super().__init__(parent, flags, **kwargs) self.app = app self._setupUi() @@ -39,9 +34,7 @@ class AboutBox(QDialog): self.buttonBox.rejected.connect(self.reject) def _setupUi(self): - self.setWindowTitle( - tr("About {}").format(QCoreApplication.instance().applicationName()) - ) + self.setWindowTitle(tr("About {}").format(QCoreApplication.instance().applicationName())) self.resize(400, 290) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -61,9 +54,7 @@ class AboutBox(QDialog): self.nameLabel.setText(QCoreApplication.instance().applicationName()) self.verticalLayout.addWidget(self.nameLabel) self.versionLabel = QLabel(self) - self.versionLabel.setText( - tr("Version {}").format(QCoreApplication.instance().applicationVersion()) - ) + self.versionLabel.setText(tr("Version {}").format(QCoreApplication.instance().applicationVersion())) self.verticalLayout.addWidget(self.versionLabel) self.label_3 = QLabel(self) self.verticalLayout.addWidget(self.label_3) diff --git a/qtlib/column.py b/qtlib/column.py index a272e8d3..ec2aad3d 100644 --- a/qtlib/column.py +++ b/qtlib/column.py @@ -62,9 +62,7 @@ class Columns: # See moneyguru #14 and #15. This was added in order to allow automatic resizing of columns. for column in self.model.column_list: if column.resizeToFit: - self._headerView.setSectionResizeMode( - column.logical_index, QHeaderView.ResizeToContents - ) + self._headerView.setSectionResizeMode(column.logical_index, QHeaderView.ResizeToContents) # --- Public def setColumnsWidth(self, widths): diff --git a/qtlib/error_report_dialog.py b/qtlib/error_report_dialog.py index adf2226e..ff3598f9 100644 --- a/qtlib/error_report_dialog.py +++ b/qtlib/error_report_dialog.py @@ -34,9 +34,7 @@ class ErrorReportDialog(QDialog): self._setupUi() name = QCoreApplication.applicationName() version = QCoreApplication.applicationVersion() - errorText = "Application Name: {}\nVersion: {}\n\n{}".format( - name, version, error - ) + errorText = "Application Name: {}\nVersion: {}\n\n{}".format(name, version, error) # Under windows, we end up with an error report without linesep if we don't mangle it errorText = errorText.replace("\n", os.linesep) self.errorTextEdit.setPlainText(errorText) diff --git a/qtlib/search_edit.py b/qtlib/search_edit.py index 23d28581..71f3915e 100644 --- a/qtlib/search_edit.py +++ b/qtlib/search_edit.py @@ -102,20 +102,14 @@ class SearchEdit(ClearableEdit): if not bool(self.text()) and self.inactiveText and not self.hasFocus(): panel = QStyleOptionFrame() self.initStyleOption(panel) - textRect = self.style().subElementRect( - QStyle.SE_LineEditContents, panel, self - ) + textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) leftMargin = 2 rightMargin = self._clearButton.iconSize().width() textRect.adjust(leftMargin, 0, -rightMargin, 0) painter = QPainter(self) - disabledColor = ( - self.palette().brush(QPalette.Disabled, QPalette.Text).color() - ) + disabledColor = self.palette().brush(QPalette.Disabled, QPalette.Text).color() painter.setPen(disabledColor) - painter.drawText( - textRect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText - ) + painter.drawText(textRect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText) # --- Event Handlers def _returnPressed(self): @@ -123,6 +117,4 @@ class SearchEdit(ClearableEdit): self.searchChanged.emit() # --- Signals - searchChanged = ( - pyqtSignal() - ) # Emitted when return is pressed or when the test is cleared + searchChanged = pyqtSignal() # Emitted when return is pressed or when the test is cleared diff --git a/qtlib/selectable_list.py b/qtlib/selectable_list.py index 7fac891d..af1e7c17 100644 --- a/qtlib/selectable_list.py +++ b/qtlib/selectable_list.py @@ -76,15 +76,11 @@ class ComboboxModel(SelectableList): class ListviewModel(SelectableList): def __init__(self, model, view, **kwargs): super().__init__(model, view, **kwargs) - self.view.selectionModel().selectionChanged[ - (QItemSelection, QItemSelection) - ].connect(self.selectionChanged) + self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged) # --- Override def _updateSelection(self): - newIndexes = [ - modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows() - ] + newIndexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()] if newIndexes != self.model.selected_indexes: self.model.select(newIndexes) @@ -92,14 +88,10 @@ class ListviewModel(SelectableList): newSelection = QItemSelection() for index in self.model.selected_indexes: newSelection.select(self.createIndex(index, 0), self.createIndex(index, 0)) - self.view.selectionModel().select( - newSelection, QItemSelectionModel.ClearAndSelect - ) + self.view.selectionModel().select(newSelection, QItemSelectionModel.ClearAndSelect) if len(newSelection.indexes()): currentIndex = newSelection.indexes()[0] - self.view.selectionModel().setCurrentIndex( - currentIndex, QItemSelectionModel.Current - ) + self.view.selectionModel().setCurrentIndex(currentIndex, QItemSelectionModel.Current) self.view.scrollTo(currentIndex) # --- Events diff --git a/qtlib/table.py b/qtlib/table.py index b9a11eff..ed40ed7c 100644 --- a/qtlib/table.py +++ b/qtlib/table.py @@ -29,22 +29,16 @@ class Table(QAbstractTableModel): self.view.setModel(self) self.model.view = self if hasattr(self.model, "columns"): - self.columns = Columns( - self.model.columns, self.COLUMNS, view.horizontalHeader() - ) + self.columns = Columns(self.model.columns, self.COLUMNS, view.horizontalHeader()) - self.view.selectionModel().selectionChanged[ - (QItemSelection, QItemSelection) - ].connect(self.selectionChanged) + self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged) def _updateModelSelection(self): # Takes the selection on the view's side and update the model with it. # an _updateViewSelection() call will normally result in an _updateModelSelection() call. # to avoid infinite loops, we check that the selection will actually change before calling # model.select() - newIndexes = [ - modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows() - ] + newIndexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()] if newIndexes != self.model.selected_indexes: self.model.select(newIndexes) @@ -53,17 +47,11 @@ class Table(QAbstractTableModel): newSelection = QItemSelection() columnCount = self.columnCount(QModelIndex()) for index in self.model.selected_indexes: - newSelection.select( - self.createIndex(index, 0), self.createIndex(index, columnCount - 1) - ) - self.view.selectionModel().select( - newSelection, QItemSelectionModel.ClearAndSelect - ) + newSelection.select(self.createIndex(index, 0), self.createIndex(index, columnCount - 1)) + self.view.selectionModel().select(newSelection, QItemSelectionModel.ClearAndSelect) if len(newSelection.indexes()): currentIndex = newSelection.indexes()[0] - self.view.selectionModel().setCurrentIndex( - currentIndex, QItemSelectionModel.Current - ) + self.view.selectionModel().setCurrentIndex(currentIndex, QItemSelectionModel.Current) self.view.scrollTo(currentIndex) # --- Data Model methods diff --git a/qtlib/tree_model.py b/qtlib/tree_model.py index 83c6a79f..9a373a71 100644 --- a/qtlib/tree_model.py +++ b/qtlib/tree_model.py @@ -84,9 +84,7 @@ class DummyNode(TreeNode): class TreeModel(QAbstractItemModel, NodeContainer): def __init__(self, **kwargs): super().__init__(**kwargs) - self._dummyNodes = ( - set() - ) # dummy nodes' reference have to be kept to avoid segfault + self._dummyNodes = set() # dummy nodes' reference have to be kept to avoid segfault # --- Private def _createDummyNode(self, parent, row): @@ -98,8 +96,7 @@ class TreeModel(QAbstractItemModel, NodeContainer): return DummyNode(self, parent, row) def _lastIndex(self): - """Index of the very last item in the tree. - """ + """Index of the very last item in the tree.""" currentIndex = QModelIndex() rowCount = self.rowCount(currentIndex) while rowCount > 0: