1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-03-12 11:31:39 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
0db66baace Merge pull request #907 from glubsy/missing_renamed_regex
Missing renamed regex
2021-08-03 22:26:08 -05:00
e3828ae2ca Merge pull request #911 from glubsy/fix_757_fix_regression
Fix infinite recursion
2021-06-22 22:44:12 -05:00
glubsy
23c59787e5 Fix infinite recursion
Force the Results to update its internal __dupes list whenever at least one group has re-prioritized and changed its dupes/ref.
2021-06-23 05:36:10 +02:00
2f8d603251 Merge pull request #910 from glubsy/757_fix
Fix refs appearing in dupes-only view
2021-06-22 21:54:49 -05:00
glubsy
a51f263632 Fix refs appearing in dupes-only view
* Some refs appeared in the dupes-only view after a re-prioritization was done a second time.
* It seems the core.Results.__dupes list was not properly updated whenever core.app.Dupeguru.reprioritize_groups() -> core.Results.sort_dupes() was called.
When a re-prioritization is done, some refs became dupe, and some dupes became ref in their place. So we need to update the new state of the internal list of dupes kept by the Results object, instead of relying on the outdated cached one.
* Fix #757.
2021-06-22 22:57:57 +02:00
4641bd6ec9 Merge pull request #905 from glubsy/fix_863
Fix exception when deleting while in delta view
2021-06-19 20:29:47 -05:00
glubsy
a6f83ad3d7 Fix missing regexp after rename
* Doing a full match should be safer to avoid partial results which would result in overly aggressive filtering.
* Add new tests to test suite to cover this issue.
* Fixes #903.
2021-06-19 02:00:25 +02:00
glubsy
ab8750eedb Fix partial regex match yielding false positive 2021-06-17 03:49:59 +02:00
glubsy
22033211d6 Fix exception when deleting while in delta view 2021-05-31 23:49:21 +02:00
0b46ca2222 Merge pull request #879 from glubsy/fix_unicode
Fix stripping (japanese) unicode characters
2021-05-25 19:11:19 -05:00
72e0f76242 Merge pull request #898 from AlttiRi/master
Change reference background color #894
2021-05-25 19:10:31 -05:00
[Alt'tiRi]
65c1d463f8 Change reference background color #894 2021-05-22 02:52:41 +03:00
e6c791ab0a Merge pull request #884 from samusz/master
Small typo
2021-05-09 23:32:32 -05:00
Sacha Muszlak
78f5088101 Merge pull request #1 from samusz/samusz-patch-1
typo correction
2021-05-07 09:41:47 +02:00
Sacha Muszlak
095df5eb95 typo correction 2021-05-07 09:40:08 +02:00
glubsy
f1ae478433 Fix including character at the border 2021-04-29 05:29:35 +02:00
glubsy
c4dcfd3d4b Fix stripping (japanese) unicode characters
* Accents are getting removed from Unicode characters to generate similar "words".
* Non-latin characters which cannot be processed that way (eg. japanese, greek, russian, etc.) should not be filtered out at all otherwise files are erroneously skipped or detected as dupes if only some characters make it passed the filter.
* Starting from an arbitrary unicode codepoint (converted to decimal), above which we know it is pointless to try any sort of processing, we leave the characters as is.
* Fix #878.
2021-04-29 05:15:34 +02:00
0840104edf Merge pull request #873 from glubsy/fix_857
Fix 857
2021-04-20 20:05:05 -05:00
glubsy
6b4b436251 Fix crash on shutdown
* Fixes "'DetailsPanel' object has no attribute '_table'" error on shutdown if the Results table is updated (item removed) while the Details Dialog is shown as a floating window.
* It seems that QApplication.quit() triggers some sort of refresh on the floating QDockWidget, which in turn makes calls to the underlying model that is possibly being destroyed, ie. there might be a race condition here.
* Closing or hiding the QDockWidget before the cal to quit() is a workaround. Similarly, this is already done in the quitTriggered() method anyway.
* This fixes #857.
2021-04-16 17:54:49 +02:00
glubsy
d18b8c10ec Remove redundant assignment
The "app" field is already set in the parent class.
2021-04-15 18:03:00 +02:00
17 changed files with 285 additions and 54 deletions

View File

@@ -770,6 +770,8 @@ class DupeGuru(Broadcaster):
for group in self.results.groups:
if group.prioritize(key_func=sort_key):
count += 1
if count:
self.results.refresh_required = True
self._results_changed()
msg = tr("{} duplicate groups were changed by the re-prioritization.").format(
count

View File

@@ -108,18 +108,9 @@ class Directories:
found_files = []
# print(f"len of files: {len(files)} {files}")
for f in files:
found = False
for expr in self._exclude_list.compiled_files:
if expr.match(f):
found = True
break
if not found:
for expr in self._exclude_list.compiled_paths:
if expr.match(root + os.sep + f):
found = True
break
if not found:
found_files.append(fs.get_file(rootPath + f, fileclasses=fileclasses))
if not self._exclude_list.is_excluded(root, f):
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

View File

@@ -26,8 +26,19 @@ def getwords(s):
# We decompose the string so that ascii letters with accents can be part of the word.
s = normalize("NFD", s)
s = multi_replace(s, "-_&+():;\\[]{}.,<>/?~!@#$*", " ").lower()
# logging.debug(f"DEBUG chars for: {s}\n"
# f"{[c for c in s if ord(c) != 32]}\n"
# f"{[ord(c) for c in s if ord(c) != 32]}")
# HACK We shouldn't ignore non-ascii characters altogether. Any Unicode char
# above common european characters that cannot be "sanitized" (ie. stripped
# of their accents, etc.) are preserved as is. The arbitrary limit is
# obtained from this one: ord("\u037e") GREEK QUESTION MARK
s = "".join(
c for c in s if c in string.ascii_letters + string.digits + string.whitespace
c for c in s
if (ord(c) <= 894
and c in string.ascii_letters + string.digits + string.whitespace
)
or ord(c) > 894
)
return [_f for _f in s.split(" ") if _f] # remove empty elements

View File

@@ -81,7 +81,7 @@ class ExcludeList(Markable):
yield self.is_marked(regex), regex
def __contains__(self, item):
return self.isExcluded(item)
return self.has_entry(item)
def __len__(self):
"""Returns the total number of regexes regardless of mark status."""
@@ -173,7 +173,9 @@ class ExcludeList(Markable):
[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
marked_count = [x for marked, x in self if marked]
# If there is no item, the compiled Pattern will be '' and match everything!
if not marked_count:
@@ -197,14 +199,14 @@ class ExcludeList(Markable):
else:
self._cached_compiled_union_paths =\
(re.compile('|'.join(paths_marked)),)
self._dirty = False
@property
def compiled(self):
"""Should be used by other classes to retrieve the up-to-date list of patterns."""
if self._use_union:
if self._dirty:
self.build_compiled_caches(True)
self._dirty = False
self.build_compiled_caches(self._use_union)
return self._cached_compiled_union_all
return self._excluded_compiled
@@ -215,8 +217,7 @@ class ExcludeList(Markable):
The interface should be expected to be a generator, even if it returns only
one item (one Pattern in the union case)."""
if self._dirty:
self.build_compiled_caches(True if self._use_union else False)
self._dirty = False
self.build_compiled_caches(self._use_union)
return self._cached_compiled_union_files if self._use_union\
else self._cached_compiled_files
@@ -224,8 +225,7 @@ class ExcludeList(Markable):
def compiled_paths(self):
"""Returns patterns with only separators in them, for more precise filtering."""
if self._dirty:
self.build_compiled_caches(True if self._use_union else False)
self._dirty = False
self.build_compiled_caches(self._use_union)
return self._cached_compiled_union_paths if self._use_union\
else self._cached_compiled_paths
@@ -233,7 +233,7 @@ class ExcludeList(Markable):
def add(self, regex, forced=False):
"""This interface should throw exceptions if there is an error during
regex compilation"""
if self.isExcluded(regex):
if self.has_entry(regex):
# This exception should never be ignored
raise AlreadyThereException()
if regex in forbidden_regexes:
@@ -256,12 +256,27 @@ class ExcludeList(Markable):
"""Returns the number of marked regexes only."""
return len([x for marked, x in self if marked])
def isExcluded(self, regex):
def has_entry(self, regex):
for item in self._excluded:
if regex == item[0]:
return True
return False
def is_excluded(self, dirname, filename):
"""Return True if the file or the absolute path to file is supposed to be
filtered out, False otherwise."""
matched = False
for expr in self.compiled_files:
if expr.fullmatch(filename):
matched = True
break
if not matched:
for expr in self.compiled_paths:
if expr.fullmatch(dirname + sep + filename):
matched = True
break
return matched
def remove(self, regex):
for item in self._excluded:
if item[0] == regex:
@@ -286,9 +301,11 @@ class ExcludeList(Markable):
break
if not found:
return
if is_compilable and was_marked:
# Not marked by default when added, add it back
self.mark(newregex)
if is_compilable:
self._add_compiled(newregex)
if was_marked:
# Not marked by default when added, add it back
self.mark(newregex)
# def change_index(self, regex, new_index):
# """Internal list must be a list, not dict."""
@@ -300,7 +317,7 @@ class ExcludeList(Markable):
if regex not in default_regexes:
self.unmark(regex)
for default_regex in default_regexes:
if not self.isExcluded(default_regex):
if not self.has_entry(default_regex):
self.add(default_regex)
self.mark(default_regex)
@@ -399,9 +416,9 @@ class ExcludeDict(ExcludeList):
if self._use_union:
return
try:
self._excluded_compiled.add(self._excluded[regex]["compiled"])
self._excluded_compiled.add(self._excluded.get(regex).get("compiled"))
except Exception as e:
logging.warning(f"Exception while adding regex {regex} to compiled set: {e}")
logging.error(f"Exception while adding regex {regex} to compiled set: {e}")
return
def is_compilable(self, regex):
@@ -425,7 +442,7 @@ class ExcludeDict(ExcludeList):
"compiled": compiled
}
def isExcluded(self, regex):
def has_entry(self, regex):
if regex in self._excluded.keys():
return True
return False
@@ -451,14 +468,16 @@ class ExcludeDict(ExcludeList):
previous = self._excluded.pop(regex)
iscompilable, error, compiled = self.compile_re(newregex)
self._excluded[newregex] = {
"index": previous["index"],
"index": previous.get('index'),
"compilable": iscompilable,
"error": error,
"compiled": compiled
}
self._remove_compiled(regex)
if was_marked and iscompilable:
self.mark(newregex)
if iscompilable:
self._add_compiled(newregex)
if was_marked:
self.mark(newregex)
def save_to_xml(self, outfile):
"""Create a XML file that can be used by load_from_xml.
@@ -492,8 +511,8 @@ def ordered_keys(_dict):
if ISWINDOWS:
def has_sep(x):
return '\\' + sep in x
def has_sep(regexp):
return '\\' + sep in regexp
else:
def has_sep(x):
return sep in x
def has_sep(regexp):
return sep in regexp

View File

@@ -7,6 +7,8 @@
# from hscommon.trans import tr
from .exclude_list_table import ExcludeListTable
from core.exclude import has_sep
from os import sep
import logging
@@ -30,9 +32,10 @@ class ExcludeListDialogCore:
self.refresh()
def rename_selected(self, newregex):
"""Renames the selected regex to ``newregex``.
If there's more than one selected row, the first one is used.
"""Rename the selected regex to ``newregex``.
If there is more than one selected row, the first one is used.
:param str newregex: The regex to rename the row's regex to.
:return bool: true if success, false if error.
"""
try:
r = self.exclude_list_table.selected_rows[0]
@@ -52,17 +55,37 @@ class ExcludeListDialogCore:
self.exclude_list_table.add(regex)
def test_string(self, test_string):
"""Sets property on row to highlight if its regex matches test_string supplied."""
"""Set the highlight property on each row when its regex matches the
test_string supplied. Return True if any row matched."""
matched = False
for row in self.exclude_list_table.rows:
compiled_regex = self.exclude_list.get_compiled(row.regex)
if compiled_regex and compiled_regex.match(test_string):
matched = True
if self.is_match(test_string, compiled_regex):
row.highlight = True
matched = True
else:
row.highlight = False
return matched
def is_match(self, test_string, compiled_regex):
# This method is like an inverted version of ExcludeList.is_excluded()
if not compiled_regex:
return False
matched = False
# Test only the filename portion of the path
if not has_sep(compiled_regex.pattern) and sep in test_string:
filename = test_string.rsplit(sep, 1)[1]
if compiled_regex.fullmatch(filename):
matched = True
return matched
# Test the entire path + filename
if compiled_regex.fullmatch(test_string):
matched = True
return matched
def reset_rows_highlight(self):
for row in self.exclude_list_table.rows:
row.highlight = False

View File

@@ -36,7 +36,7 @@ class ExcludeListTable(GUITable, DupeGuruGUIObject):
return ExcludeListRow(self, self.dialog.exclude_list.is_marked(regex), regex), 0
def _do_delete(self):
self.dalog.exclude_list.remove(self.selected_row.regex)
self.dialog.exclude_list.remove(self.selected_row.regex)
# --- Override
def add(self, regex):

View File

@@ -41,6 +41,8 @@ class DupeRow(Row):
# table.DELTA_COLUMNS are always "delta"
self._delta_columns = self.table.DELTA_COLUMNS.copy()
dupe_info = self.data
if self._group.ref is None:
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 (

View File

@@ -52,6 +52,7 @@ class Results(Markable):
self.app = app
self.problems = [] # (dupe, error_msg)
self.is_modified = False
self.refresh_required = False
def _did_mark(self, dupe):
self.__marked_size += dupe.size
@@ -94,8 +95,9 @@ class Results(Markable):
# ---Private
def __get_dupe_list(self):
if self.__dupes is None:
if self.__dupes is None or self.refresh_required:
self.__dupes = flatten(group.dupes for group in self.groups)
self.refresh_required = False
if None in self.__dupes:
# This is debug logging to try to figure out #44
logging.warning(

View File

@@ -473,6 +473,24 @@ files: {self.d._exclude_list.compiled_files} all: {self.d._exclude_list.compiled
assert "file_ending_with_subdir" not in files
assert "file_which_shouldnt_match" in files
# This should match the directory only
regex6 = r".*/subdir.*"
if ISWINDOWS:
regex6 = r".*\\.*subdir.*"
self.d._exclude_list.rename(regex5, regex6)
self.d._exclude_list.remove(regex1)
assert regex1 not in self.d._exclude_list
assert regex5 not in self.d._exclude_list
assert self.d._exclude_list.error(regex6) is None
# This still should not be affected
eq_(self.d.get_state(p1["$Recycle.Bin"]["subdir"]), DirectoryState.Normal)
files = self.get_files_and_expect_num_result(5)
# These files are under the "/subdir" directory
assert "somesubdirfile.png" not in files
assert "unwanted_subdirfile.gif" not in files
# This file under "subdar" directory should not be filtered out
assert "file_ending_with_subdir" in files
def test_japanese_unicode(self, tmpdir):
p1 = Path(str(tmpdir))
p1["$Recycle.Bin"].mkdir()

View File

@@ -69,6 +69,10 @@ class TestCasegetwords:
eq_(["a", "b", "c", "d"], getwords("a b c d"))
eq_(["a", "b", "c", "d"], getwords(" a b c d "))
def test_unicode(self):
eq_(["e", "c", "0", "a", "o", "u", "e", "u"], getwords("é ç 0 à ö û è ¤ ù"))
eq_(["02", "君のこころは輝いてるかい?", "国木田花丸", "solo", "ver"], getwords("02 君のこころは輝いてるかい? 国木田花丸 Solo Ver"))
def test_splitter_chars(self):
eq_(
[chr(i) for i in range(ord("a"), ord("z") + 1)],
@@ -85,7 +89,7 @@ class TestCasegetwords:
eq_(["foo", "bar"], getwords("FOO BAR"))
def test_decompose_unicode(self):
eq_(getwords("foo\xe9bar"), ["fooebar"])
eq_(["fooebar"], getwords("foo\xe9bar"))
class TestCasegetfields:

View File

@@ -188,6 +188,28 @@ class TestCaseListEmpty:
self.exclude_list.rename(regex_renamed_compilable, regex_compilable)
eq_(self.exclude_list.is_marked(regex_compilable), True)
def test_rename_regex_file_to_path(self):
regex = r".*/one.*"
if ISWINDOWS:
regex = r".*\\one.*"
regex2 = r".*one.*"
self.exclude_list.add(regex)
self.exclude_list.mark(regex)
compiled_re = [x.pattern for x in self.exclude_list._excluded_compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex in compiled_re
assert regex not in files_re
assert regex in paths_re
self.exclude_list.rename(regex, regex2)
compiled_re = [x.pattern for x in self.exclude_list._excluded_compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex not in compiled_re
assert regex2 in compiled_re
assert regex2 in files_re
assert regex2 not in paths_re
def test_restore_default(self):
"""Only unmark previously added regexes and mark the pre-defined ones"""
regex = r"one"
@@ -213,6 +235,73 @@ class TestCaseListEmpty:
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)
self.exclude_list = self.app.exclude_list
def test_add_mark_and_remove_regex(self):
regex1 = r"one"
regex2 = r"two"
self.exclude_list.add(regex1)
assert(regex1 in self.exclude_list)
self.exclude_list.add(regex2)
self.exclude_list.mark(regex1)
self.exclude_list.mark(regex2)
eq_(len(self.exclude_list), 2)
eq_(len(self.exclude_list.compiled), 1)
compiled_files = [x for x in self.exclude_list.compiled_files]
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)
eq_(len(self.exclude_list), 1)
def test_rename_regex_file_to_path(self):
regex = r".*/one.*"
if ISWINDOWS:
regex = r".*\\one.*"
regex2 = r".*one.*"
self.exclude_list.add(regex)
self.exclude_list.mark(regex)
eq_(len([x for x in self.exclude_list]), 1)
compiled_re = [x.pattern for x in self.exclude_list.compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex in compiled_re
assert regex not in files_re
assert regex in paths_re
self.exclude_list.rename(regex, regex2)
eq_(len([x for x in self.exclude_list]), 1)
compiled_re = [x.pattern for x in self.exclude_list.compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex not in compiled_re
assert regex2 in compiled_re
assert regex2 in files_re
assert regex2 not in paths_re
def test_restore_default(self):
"""Only unmark previously added regexes and mark the pre-defined ones"""
regex = r"one"
self.exclude_list.add(regex)
self.exclude_list.mark(regex)
self.exclude_list.restore_defaults()
eq_(len(default_regexes), self.exclude_list.marked_count)
# added regex shouldn't be marked
eq_(self.exclude_list.is_marked(regex), False)
# added regex shouldn't be in compiled list either
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])
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):
@@ -221,6 +310,73 @@ class TestCaseDictEmpty(TestCaseListEmpty):
self.exclude_list = self.app.exclude_list
class TestCaseDictEmptyUnion(TestCaseDictEmpty):
"""Same, but with union regex"""
def setup_method(self, method):
self.app = DupeGuru()
self.app.exclude_list = ExcludeDict(union_regex=True)
self.exclude_list = self.app.exclude_list
def test_add_mark_and_remove_regex(self):
regex1 = r"one"
regex2 = r"two"
self.exclude_list.add(regex1)
assert(regex1 in self.exclude_list)
self.exclude_list.add(regex2)
self.exclude_list.mark(regex1)
self.exclude_list.mark(regex2)
eq_(len(self.exclude_list), 2)
eq_(len(self.exclude_list.compiled), 1)
compiled_files = [x for x in self.exclude_list.compiled_files]
# two patterns joined into one
eq_(len(compiled_files), 1)
self.exclude_list.remove(regex2)
assert(regex2 not in self.exclude_list)
eq_(len(self.exclude_list), 1)
def test_rename_regex_file_to_path(self):
regex = r".*/one.*"
if ISWINDOWS:
regex = r".*\\one.*"
regex2 = r".*one.*"
self.exclude_list.add(regex)
self.exclude_list.mark(regex)
marked_re = [x for marked, x in self.exclude_list if marked]
eq_(len(marked_re), 1)
compiled_re = [x.pattern for x in self.exclude_list.compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex in compiled_re
assert regex not in files_re
assert regex in paths_re
self.exclude_list.rename(regex, regex2)
compiled_re = [x.pattern for x in self.exclude_list.compiled]
files_re = [x.pattern for x in self.exclude_list.compiled_files]
paths_re = [x.pattern for x in self.exclude_list.compiled_paths]
assert regex not in compiled_re
assert regex2 in compiled_re
assert regex2 in files_re
assert regex2 not in paths_re
def test_restore_default(self):
"""Only unmark previously added regexes and mark the pre-defined ones"""
regex = r"one"
self.exclude_list.add(regex)
self.exclude_list.mark(regex)
self.exclude_list.restore_defaults()
eq_(len(default_regexes), self.exclude_list.marked_count)
# added regex shouldn't be marked
eq_(self.exclude_list.is_marked(regex), False)
# added regex shouldn't be in compiled list either
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])
assert compiled_escaped == default_escaped
eq_(len(default_regexes), len(compiled[0].pattern.split("|")))
def split_union(pattern_object):
"""Returns list of strings for each union pattern"""
return [x for x in pattern_object.pattern.split("|")]

View File

@@ -11,7 +11,7 @@
1. Install Xcode if desired
2. Install [Homebrew][homebrew], if not on the path after install (arm based Macs) create `~/.zshrc`
with `export PATH="/opt/homebrew/bin:$PATH"`. Will need to reload terminal or source the file to take
affect.
effect.
3. Install qt5 with `brew`. If you are using a version of macos without system python 3.6+ then you will
also need to install that via brew or with pyenv.

View File

@@ -271,6 +271,9 @@ class DupeGuru(QObject):
self.willSavePrefs.emit()
self.prefs.save()
self.model.save()
# Workaround for #857, hide() or close().
if self.details_dialog is not None:
self.details_dialog.close()
QApplication.quit()
# --- Signals

View File

@@ -51,7 +51,7 @@ class DetailsDialog(QDockWidget):
if not self.titleBarWidget(): # default title bar
self.setTitleBarWidget(QWidget()) # disables title bar
# Windows (and MacOS?) users cannot move a floating window which
# has not native decoration so we force it to dock for now
# has no native decoration so we force it to dock for now
if not ISLINUX:
self.setFloating(False)
elif self.titleBarWidget() is not None: # title bar is disabled

View File

@@ -116,31 +116,32 @@ class ExcludeListDialog(QDialog):
if not input_text:
self.reset_input_style()
return
# if at least one row matched, we know whether table is highlighted or not
# If at least one row matched, we know whether table is highlighted or not
self._row_matched = self.model.test_string(input_text)
self.table.refresh()
# Test the string currently in the input text box as well
input_regex = self.inputLine.text()
if not input_regex:
self.reset_input_style()
return
compiled = None
try:
compiled = re.compile(input_regex)
except re.error:
self.reset_input_style()
return
match = compiled.match(input_text)
if match:
self._input_styled = True
if self.model.is_match(input_text, compiled):
self.inputLine.setStyleSheet("background-color: rgb(10, 200, 10);")
self._input_styled = True
else:
self.reset_input_style()
def reset_input_style(self):
"""Reset regex input line background"""
if self._input_styled:
self._input_styled = False
self.inputLine.setStyleSheet(self.styleSheet())
self._input_styled = False
def reset_table_style(self):
if self._row_matched:

View File

@@ -19,7 +19,6 @@ tr = trget("ui")
class DetailsDialog(DetailsDialogBase):
def __init__(self, parent, app):
self.vController = None
self.app = app
super().__init__(parent, app)
def _setupUi(self):

View File

@@ -102,7 +102,7 @@ class Preferences(PreferencesBase):
self.details_dialog_override_theme_icons = False if not ISLINUX else True
self.details_dialog_viewers_show_scrollbars = True
self.result_table_ref_foreground_color = QColor(Qt.blue)
self.result_table_ref_background_color = QColor(Qt.darkGray)
self.result_table_ref_background_color = QColor(Qt.lightGray)
self.result_table_delta_foreground_color = QColor(255, 142, 40) # orange
self.resultWindowIsMaximized = False
self.resultWindowRect = None