mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-05-07 09:19:50 +00:00
Compare commits
2 Commits
2e13c4ccb5
...
47dbe805bb
Author | SHA1 | Date | |
---|---|---|---|
47dbe805bb | |||
f11fccc889 |
16
core/app.py
16
core/app.py
@ -264,7 +264,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):
|
||||
@ -390,7 +390,7 @@ class DupeGuru(Broadcaster):
|
||||
g = self.results.get_group_of_duplicate(dupe)
|
||||
for other in g:
|
||||
if other is not dupe:
|
||||
self.ignore_list.Ignore(str(other.path), str(dupe.path))
|
||||
self.ignore_list.ignore(str(other.path), str(dupe.path))
|
||||
self.remove_duplicates(dupes)
|
||||
self.ignore_list_dialog.refresh()
|
||||
|
||||
@ -592,9 +592,8 @@ class DupeGuru(Broadcaster):
|
||||
changed_groups = set()
|
||||
for dupe in dupes:
|
||||
g = self.results.get_group_of_duplicate(dupe)
|
||||
if g not in changed_groups:
|
||||
if self.results.make_ref(dupe):
|
||||
changed_groups.add(g)
|
||||
if g not in changed_groups and self.results.make_ref(dupe):
|
||||
changed_groups.add(g)
|
||||
# It's not always obvious to users what this action does, so to make it a bit clearer,
|
||||
# we change our selection to the ref of all changed groups. However, we also want to keep
|
||||
# the files that were ref before and weren't changed by the action. In effect, what this
|
||||
@ -644,15 +643,14 @@ class DupeGuru(Broadcaster):
|
||||
|
||||
def open_selected(self):
|
||||
"""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
|
||||
if len(self.selected_dupes) > 10 and not self.view.ask_yes_no(MSG_MANY_FILES_TO_OPEN):
|
||||
return
|
||||
for dupe in self.selected_dupes:
|
||||
desktop.open_path(dupe.path)
|
||||
|
||||
def purge_ignore_list(self):
|
||||
"""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.filter(lambda f, s: op.exists(f) and op.exists(s))
|
||||
self.ignore_list_dialog.refresh()
|
||||
|
||||
def remove_directories(self, indexes):
|
||||
|
@ -15,16 +15,21 @@ class DupeGuruGUIObject(Listener):
|
||||
self.app = app
|
||||
|
||||
def directories_changed(self):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def dupes_selected(self):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def marking_changed(self):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def results_changed(self):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def results_changed_but_keep_selection(self):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
@ -16,7 +16,7 @@ class ExcludeListTable(GUITable, DupeGuruGUIObject):
|
||||
def __init__(self, exclude_list_dialog, app):
|
||||
GUITable.__init__(self)
|
||||
DupeGuruGUIObject.__init__(self, app)
|
||||
self.columns = Columns(self)
|
||||
self._columns = Columns(self)
|
||||
self.dialog = exclude_list_dialog
|
||||
|
||||
def rename_selected(self, newname):
|
||||
|
@ -24,7 +24,7 @@ class IgnoreListDialog:
|
||||
return
|
||||
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.ignore_list.clear()
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
|
@ -22,7 +22,7 @@ class IgnoreListTable(GUITable):
|
||||
|
||||
def __init__(self, ignore_list_dialog):
|
||||
GUITable.__init__(self)
|
||||
self.columns = Columns(self)
|
||||
self._columns = Columns(self)
|
||||
self.view = None
|
||||
self.dialog = ignore_list_dialog
|
||||
|
||||
|
@ -21,7 +21,7 @@ class ProblemTable(GUITable):
|
||||
|
||||
def __init__(self, problem_dialog):
|
||||
GUITable.__init__(self)
|
||||
self.columns = Columns(self)
|
||||
self._columns = Columns(self)
|
||||
self.dialog = problem_dialog
|
||||
|
||||
# --- Override
|
||||
|
@ -82,7 +82,7 @@ class ResultTable(GUITable, DupeGuruGUIObject):
|
||||
def __init__(self, app):
|
||||
GUITable.__init__(self)
|
||||
DupeGuruGUIObject.__init__(self, app)
|
||||
self.columns = Columns(self, prefaccess=app, savename="ResultTable")
|
||||
self._columns = Columns(self, prefaccess=app, savename="ResultTable")
|
||||
self._power_marker = False
|
||||
self._delta_values = False
|
||||
self._sort_descriptors = ("name", True)
|
||||
@ -190,4 +190,4 @@ class ResultTable(GUITable, DupeGuruGUIObject):
|
||||
self.view.refresh()
|
||||
|
||||
def save_session(self):
|
||||
self.columns.save_columns()
|
||||
self._columns.save_columns()
|
||||
|
@ -20,8 +20,7 @@ class IgnoreList:
|
||||
|
||||
# ---Override
|
||||
def __init__(self):
|
||||
self._ignored = {}
|
||||
self._count = 0
|
||||
self.clear()
|
||||
|
||||
def __iter__(self):
|
||||
for first, seconds in self._ignored.items():
|
||||
@ -32,7 +31,7 @@ class IgnoreList:
|
||||
return self._count
|
||||
|
||||
# ---Public
|
||||
def AreIgnored(self, first, second):
|
||||
def are_ignored(self, first, second):
|
||||
def do_check(first, second):
|
||||
try:
|
||||
matches = self._ignored[first]
|
||||
@ -42,23 +41,23 @@ class IgnoreList:
|
||||
|
||||
return do_check(first, second) or do_check(second, first)
|
||||
|
||||
def Clear(self):
|
||||
def clear(self):
|
||||
self._ignored = {}
|
||||
self._count = 0
|
||||
|
||||
def Filter(self, func):
|
||||
def filter(self, func):
|
||||
"""Applies a filter on all ignored items, and remove all matches where func(first,second)
|
||||
doesn't return True.
|
||||
"""
|
||||
filtered = IgnoreList()
|
||||
for first, second in self:
|
||||
if func(first, second):
|
||||
filtered.Ignore(first, second)
|
||||
filtered.ignore(first, second)
|
||||
self._ignored = filtered._ignored
|
||||
self._count = filtered._count
|
||||
|
||||
def Ignore(self, first, second):
|
||||
if self.AreIgnored(first, second):
|
||||
def ignore(self, first, second):
|
||||
if self.are_ignored(first, second):
|
||||
return
|
||||
try:
|
||||
matches = self._ignored[first]
|
||||
@ -109,7 +108,7 @@ class IgnoreList:
|
||||
for sfn in subfile_elems:
|
||||
subfile_path = sfn.get("path")
|
||||
if subfile_path:
|
||||
self.Ignore(file_path, subfile_path)
|
||||
self.ignore(file_path, subfile_path)
|
||||
|
||||
def save_to_xml(self, outfile):
|
||||
"""Create a XML file that can be used by load_from_xml.
|
||||
|
@ -17,9 +17,11 @@ class Markable:
|
||||
# in self.__marked, and is not affected by __inverted. Thus, self.mark while __inverted
|
||||
# is True will launch _DidUnmark.
|
||||
def _did_mark(self, o):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def _did_unmark(self, o):
|
||||
# Implemented in child classes
|
||||
pass
|
||||
|
||||
def _get_markable_count(self):
|
||||
|
@ -167,7 +167,7 @@ class Scanner:
|
||||
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.are_ignored(str(m.first.path), str(m.second.path))]
|
||||
logging.info("Grouping matches")
|
||||
groups = engine.get_groups(matches)
|
||||
if self.scan_type in {
|
||||
|
@ -369,13 +369,13 @@ class TestCaseDupeGuruWithResults:
|
||||
open(p1, "w").close()
|
||||
open(p2, "w").close()
|
||||
dne = "/does_not_exist"
|
||||
app.ignore_list.Ignore(dne, p1)
|
||||
app.ignore_list.Ignore(p2, dne)
|
||||
app.ignore_list.Ignore(p1, p2)
|
||||
app.ignore_list.ignore(dne, p1)
|
||||
app.ignore_list.ignore(p2, dne)
|
||||
app.ignore_list.ignore(p1, p2)
|
||||
app.purge_ignore_list()
|
||||
eq_(1, len(app.ignore_list))
|
||||
assert app.ignore_list.AreIgnored(p1, p2)
|
||||
assert not app.ignore_list.AreIgnored(dne, p1)
|
||||
assert app.ignore_list.are_ignored(p1, p2)
|
||||
assert not app.ignore_list.are_ignored(dne, p1)
|
||||
|
||||
def test_only_unicode_is_added_to_ignore_list(self, do_setup):
|
||||
def fake_ignore(first, second):
|
||||
@ -385,7 +385,7 @@ class TestCaseDupeGuruWithResults:
|
||||
self.fail()
|
||||
|
||||
app = self.app
|
||||
app.ignore_list.Ignore = fake_ignore
|
||||
app.ignore_list.ignore = fake_ignore
|
||||
self.rtable.select([4])
|
||||
app.add_selected_to_ignore_list()
|
||||
|
||||
|
@ -151,8 +151,8 @@ class TestApp(TestAppBase):
|
||||
def __init__(self):
|
||||
def link_gui(gui):
|
||||
gui.view = self.make_logger()
|
||||
if hasattr(gui, "columns"): # tables
|
||||
gui.columns.view = self.make_logger()
|
||||
if hasattr(gui, "_columns"): # tables
|
||||
gui._columns.view = self.make_logger()
|
||||
return gui
|
||||
|
||||
TestAppBase.__init__(self)
|
||||
|
@ -16,54 +16,54 @@ from ..ignore import IgnoreList
|
||||
def test_empty():
|
||||
il = IgnoreList()
|
||||
eq_(0, len(il))
|
||||
assert not il.AreIgnored("foo", "bar")
|
||||
assert not il.are_ignored("foo", "bar")
|
||||
|
||||
|
||||
def test_simple():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
assert il.AreIgnored("foo", "bar")
|
||||
assert il.AreIgnored("bar", "foo")
|
||||
assert not il.AreIgnored("foo", "bleh")
|
||||
assert not il.AreIgnored("bleh", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
assert il.are_ignored("foo", "bar")
|
||||
assert il.are_ignored("bar", "foo")
|
||||
assert not il.are_ignored("foo", "bleh")
|
||||
assert not il.are_ignored("bleh", "bar")
|
||||
eq_(1, len(il))
|
||||
|
||||
|
||||
def test_multiple():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("foo", "bleh")
|
||||
il.Ignore("bleh", "bar")
|
||||
il.Ignore("aybabtu", "bleh")
|
||||
assert il.AreIgnored("foo", "bar")
|
||||
assert il.AreIgnored("bar", "foo")
|
||||
assert il.AreIgnored("foo", "bleh")
|
||||
assert il.AreIgnored("bleh", "bar")
|
||||
assert not il.AreIgnored("aybabtu", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("foo", "bleh")
|
||||
il.ignore("bleh", "bar")
|
||||
il.ignore("aybabtu", "bleh")
|
||||
assert il.are_ignored("foo", "bar")
|
||||
assert il.are_ignored("bar", "foo")
|
||||
assert il.are_ignored("foo", "bleh")
|
||||
assert il.are_ignored("bleh", "bar")
|
||||
assert not il.are_ignored("aybabtu", "bar")
|
||||
eq_(4, len(il))
|
||||
|
||||
|
||||
def test_clear():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Clear()
|
||||
assert not il.AreIgnored("foo", "bar")
|
||||
assert not il.AreIgnored("bar", "foo")
|
||||
il.ignore("foo", "bar")
|
||||
il.clear()
|
||||
assert not il.are_ignored("foo", "bar")
|
||||
assert not il.are_ignored("bar", "foo")
|
||||
eq_(0, len(il))
|
||||
|
||||
|
||||
def test_add_same_twice():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("bar", "foo")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("bar", "foo")
|
||||
eq_(1, len(il))
|
||||
|
||||
|
||||
def test_save_to_xml():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("foo", "bleh")
|
||||
il.Ignore("bleh", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("foo", "bleh")
|
||||
il.ignore("bleh", "bar")
|
||||
f = io.BytesIO()
|
||||
il.save_to_xml(f)
|
||||
f.seek(0)
|
||||
@ -79,17 +79,17 @@ def test_save_to_xml():
|
||||
|
||||
def test_save_then_load():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("foo", "bleh")
|
||||
il.Ignore("bleh", "bar")
|
||||
il.Ignore("\u00e9", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("foo", "bleh")
|
||||
il.ignore("bleh", "bar")
|
||||
il.ignore("\u00e9", "bar")
|
||||
f = io.BytesIO()
|
||||
il.save_to_xml(f)
|
||||
f.seek(0)
|
||||
il = IgnoreList()
|
||||
il.load_from_xml(f)
|
||||
eq_(4, len(il))
|
||||
assert il.AreIgnored("\u00e9", "bar")
|
||||
assert il.are_ignored("\u00e9", "bar")
|
||||
|
||||
|
||||
def test_load_xml_with_empty_file_tags():
|
||||
@ -103,16 +103,16 @@ def test_load_xml_with_empty_file_tags():
|
||||
|
||||
def test_are_ignore_works_when_a_child_is_a_key_somewhere_else():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("bar", "baz")
|
||||
assert il.AreIgnored("bar", "foo")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("bar", "baz")
|
||||
assert il.are_ignored("bar", "foo")
|
||||
|
||||
|
||||
def test_no_dupes_when_a_child_is_a_key_somewhere_else():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("bar", "baz")
|
||||
il.Ignore("bar", "foo")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("bar", "baz")
|
||||
il.ignore("bar", "foo")
|
||||
eq_(2, len(il))
|
||||
|
||||
|
||||
@ -121,7 +121,7 @@ def test_iterate():
|
||||
il = IgnoreList()
|
||||
expected = [("foo", "bar"), ("bar", "baz"), ("foo", "baz")]
|
||||
for i in expected:
|
||||
il.Ignore(i[0], i[1])
|
||||
il.ignore(i[0], i[1])
|
||||
for i in il:
|
||||
expected.remove(i) # No exception should be raised
|
||||
assert not expected # expected should be empty
|
||||
@ -129,18 +129,18 @@ def test_iterate():
|
||||
|
||||
def test_filter():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("bar", "baz")
|
||||
il.Ignore("foo", "baz")
|
||||
il.Filter(lambda f, s: f == "bar")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("bar", "baz")
|
||||
il.ignore("foo", "baz")
|
||||
il.filter(lambda f, s: f == "bar")
|
||||
eq_(1, len(il))
|
||||
assert not il.AreIgnored("foo", "bar")
|
||||
assert il.AreIgnored("bar", "baz")
|
||||
assert not il.are_ignored("foo", "bar")
|
||||
assert il.are_ignored("bar", "baz")
|
||||
|
||||
|
||||
def test_save_with_non_ascii_items():
|
||||
il = IgnoreList()
|
||||
il.Ignore("\xac", "\xbf")
|
||||
il.ignore("\xac", "\xbf")
|
||||
f = io.BytesIO()
|
||||
try:
|
||||
il.save_to_xml(f)
|
||||
@ -151,29 +151,29 @@ def test_save_with_non_ascii_items():
|
||||
def test_len():
|
||||
il = IgnoreList()
|
||||
eq_(0, len(il))
|
||||
il.Ignore("foo", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
eq_(1, len(il))
|
||||
|
||||
|
||||
def test_nonzero():
|
||||
il = IgnoreList()
|
||||
assert not il
|
||||
il.Ignore("foo", "bar")
|
||||
il.ignore("foo", "bar")
|
||||
assert il
|
||||
|
||||
|
||||
def test_remove():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("foo", "baz")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("foo", "baz")
|
||||
il.remove("bar", "foo")
|
||||
eq_(len(il), 1)
|
||||
assert not il.AreIgnored("foo", "bar")
|
||||
assert not il.are_ignored("foo", "bar")
|
||||
|
||||
|
||||
def test_remove_non_existant():
|
||||
il = IgnoreList()
|
||||
il.Ignore("foo", "bar")
|
||||
il.Ignore("foo", "baz")
|
||||
il.ignore("foo", "bar")
|
||||
il.ignore("foo", "baz")
|
||||
with raises(ValueError):
|
||||
il.remove("foo", "bleh")
|
||||
|
@ -391,8 +391,8 @@ def test_ignore_list(fake_fileexists):
|
||||
f2.path = Path("dir2/foobar")
|
||||
f3.path = Path("dir3/foobar")
|
||||
ignore_list = IgnoreList()
|
||||
ignore_list.Ignore(str(f1.path), str(f2.path))
|
||||
ignore_list.Ignore(str(f1.path), str(f3.path))
|
||||
ignore_list.ignore(str(f1.path), str(f2.path))
|
||||
ignore_list.ignore(str(f1.path), str(f3.path))
|
||||
r = s.get_dupe_groups([f1, f2, f3], ignore_list=ignore_list)
|
||||
eq_(len(r), 1)
|
||||
g = r[0]
|
||||
@ -415,8 +415,8 @@ def test_ignore_list_checks_for_unicode(fake_fileexists):
|
||||
f2.path = Path("foo2\u00e9")
|
||||
f3.path = Path("foo3\u00e9")
|
||||
ignore_list = IgnoreList()
|
||||
ignore_list.Ignore(str(f1.path), str(f2.path))
|
||||
ignore_list.Ignore(str(f1.path), str(f3.path))
|
||||
ignore_list.ignore(str(f1.path), str(f2.path))
|
||||
ignore_list.ignore(str(f1.path), str(f3.path))
|
||||
r = s.get_dupe_groups([f1, f2, f3], ignore_list=ignore_list)
|
||||
eq_(len(r), 1)
|
||||
g = r[0]
|
||||
|
@ -290,8 +290,10 @@ class TestCaseDeleteIfEmpty:
|
||||
|
||||
|
||||
class TestCaseOpenIfFilename:
|
||||
FILE_NAME = "test.txt"
|
||||
|
||||
def test_file_name(self, tmpdir):
|
||||
filepath = str(tmpdir.join("test.txt"))
|
||||
filepath = str(tmpdir.join(self.FILE_NAME))
|
||||
open(filepath, "wb").write(b"test_data")
|
||||
file, close = open_if_filename(filepath)
|
||||
assert close
|
||||
@ -307,7 +309,7 @@ class TestCaseOpenIfFilename:
|
||||
eq_("test_data", file.read())
|
||||
|
||||
def test_mode_is_passed_to_open(self, tmpdir):
|
||||
filepath = str(tmpdir.join("test.txt"))
|
||||
filepath = str(tmpdir.join(self.FILE_NAME))
|
||||
open(filepath, "w").close()
|
||||
file, close = open_if_filename(filepath, "a")
|
||||
eq_("a", file.mode)
|
||||
@ -315,8 +317,10 @@ class TestCaseOpenIfFilename:
|
||||
|
||||
|
||||
class TestCaseFileOrPath:
|
||||
FILE_NAME = "test.txt"
|
||||
|
||||
def test_path(self, tmpdir):
|
||||
filepath = str(tmpdir.join("test.txt"))
|
||||
filepath = str(tmpdir.join(self.FILE_NAME))
|
||||
open(filepath, "wb").write(b"test_data")
|
||||
with FileOrPath(filepath) as fp:
|
||||
eq_(b"test_data", fp.read())
|
||||
@ -329,7 +333,7 @@ class TestCaseFileOrPath:
|
||||
eq_("test_data", fp.read())
|
||||
|
||||
def test_mode_is_passed_to_open(self, tmpdir):
|
||||
filepath = str(tmpdir.join("test.txt"))
|
||||
filepath = str(tmpdir.join(self.FILE_NAME))
|
||||
open(filepath, "w").close()
|
||||
with FileOrPath(filepath, "a") as fp:
|
||||
eq_("a", fp.mode)
|
||||
|
@ -230,8 +230,8 @@ def log_calls(func):
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
unifiedArgs = _unify_args(func, args, kwargs)
|
||||
wrapper.calls.append(unifiedArgs)
|
||||
unified_args = _unify_args(func, args, kwargs)
|
||||
wrapper.calls.append(unified_args)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
wrapper.calls = []
|
||||
|
51
package.py
51
package.py
@ -26,6 +26,10 @@ from hscommon.build import (
|
||||
copy_all,
|
||||
)
|
||||
|
||||
ENTRY_SCRIPT = "run.py"
|
||||
LOCALE_DIR = "build/locale"
|
||||
HELP_DIR = "build/help"
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser()
|
||||
@ -33,6 +37,15 @@ def parse_args():
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def check_loc_doc():
|
||||
if not op.exists(LOCALE_DIR):
|
||||
print('Locale files are missing. Have you run "build.py --loc"?')
|
||||
# include help files if they are built otherwise exit as they should be included?
|
||||
if not op.exists(HELP_DIR):
|
||||
print('Help files are missing. Have you run "build.py --doc"?')
|
||||
return op.exists(LOCALE_DIR) and op.exists(HELP_DIR)
|
||||
|
||||
|
||||
def copy_files_to_package(destpath, packages, with_so):
|
||||
# when with_so is true, we keep .so files in the package, and otherwise, we don't. We need this
|
||||
# flag because when building debian src pkg, we *don't* want .so files (they're compiled later)
|
||||
@ -40,17 +53,13 @@ def copy_files_to_package(destpath, packages, with_so):
|
||||
if op.exists(destpath):
|
||||
shutil.rmtree(destpath)
|
||||
os.makedirs(destpath)
|
||||
shutil.copy("run.py", op.join(destpath, "run.py"))
|
||||
shutil.copy(ENTRY_SCRIPT, op.join(destpath, ENTRY_SCRIPT))
|
||||
extra_ignores = ["*.so"] if not with_so else None
|
||||
copy_packages(packages, destpath, extra_ignores=extra_ignores)
|
||||
# include locale files if they are built otherwise exit as it will break
|
||||
# the localization
|
||||
if not op.exists("build/locale"):
|
||||
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||
return
|
||||
# include help files if they are built otherwise exit as they should be included?
|
||||
if not op.exists("build/help"):
|
||||
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||
if not check_loc_doc():
|
||||
print("Exiting...")
|
||||
return
|
||||
shutil.copytree(op.join("build", "help"), op.join(destpath, "help"))
|
||||
shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale"))
|
||||
@ -152,12 +161,8 @@ def package_windows():
|
||||
arch = "x86"
|
||||
# include locale files if they are built otherwise exit as it will break
|
||||
# the localization
|
||||
if not op.exists("build/locale"):
|
||||
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||
return
|
||||
# include help files if they are built otherwise exit as they should be included?
|
||||
if not op.exists("build/help"):
|
||||
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||
if not check_loc_doc():
|
||||
print("Exiting...")
|
||||
return
|
||||
# create version information file from template
|
||||
try:
|
||||
@ -180,11 +185,11 @@ def package_windows():
|
||||
"--windowed",
|
||||
"--noconfirm",
|
||||
"--icon=images/dgse_logo.ico",
|
||||
"--add-data=build/locale;locale",
|
||||
"--add-data=build/help;help",
|
||||
"--add-data={0};locale".format(LOCALE_DIR),
|
||||
"--add-data={0};help".format(HELP_DIR),
|
||||
"--version-file=win_version_info.txt",
|
||||
"--paths=C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\{0}".format(arch),
|
||||
"run.py",
|
||||
ENTRY_SCRIPT,
|
||||
]
|
||||
)
|
||||
# remove version info file
|
||||
@ -200,12 +205,8 @@ def package_windows():
|
||||
def package_macos():
|
||||
# include locale files if they are built otherwise exit as it will break
|
||||
# the localization
|
||||
if not op.exists("build/locale"):
|
||||
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||
return
|
||||
# include help files if they are built otherwise exit as they should be included?
|
||||
if not op.exists("build/help"):
|
||||
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||
if not check_loc_doc():
|
||||
print("Exiting")
|
||||
return
|
||||
# run pyinstaller from here:
|
||||
import PyInstaller.__main__
|
||||
@ -217,9 +218,9 @@ def package_macos():
|
||||
"--noconfirm",
|
||||
"--icon=images/dupeguru.icns",
|
||||
"--osx-bundle-identifier=com.hardcoded-software.dupeguru",
|
||||
"--add-data=build/locale:locale",
|
||||
"--add-data=build/help:help",
|
||||
"run.py",
|
||||
"--add-data={0}:locale".format(LOCALE_DIR),
|
||||
"--add-data={0}:help".format(HELP_DIR),
|
||||
ENTRY_SCRIPT,
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -64,6 +64,8 @@ class DirectoriesDelegate(QStyledItemDelegate):
|
||||
|
||||
|
||||
class DirectoriesModel(TreeModel):
|
||||
MIME_TYPE_FORMAT = "text/uri-list"
|
||||
|
||||
def __init__(self, model, view, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.model = model
|
||||
@ -104,9 +106,9 @@ class DirectoriesModel(TreeModel):
|
||||
|
||||
def dropMimeData(self, mime_data, action, row, column, parent_index):
|
||||
# the data in mimeData is urlencoded **in utf-8**
|
||||
if not mime_data.hasFormat("text/uri-list"):
|
||||
if not mime_data.hasFormat(self.MIME_TYPE_FORMAT):
|
||||
return False
|
||||
data = bytes(mime_data.data("text/uri-list")).decode("ascii")
|
||||
data = bytes(mime_data.data(self.MIME_TYPE_FORMAT)).decode("ascii")
|
||||
urls = data.split("\r\n")
|
||||
paths = [QUrl(url).toLocalFile() for url in urls if url]
|
||||
for path in paths:
|
||||
@ -129,7 +131,7 @@ class DirectoriesModel(TreeModel):
|
||||
return None
|
||||
|
||||
def mimeTypes(self):
|
||||
return ["text/uri-list"]
|
||||
return [self.MIME_TYPE_FORMAT]
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
|
||||
|
@ -15,7 +15,7 @@ tr = trget("ui")
|
||||
class ExcludeListTable(Table):
|
||||
"""Model for exclude list"""
|
||||
|
||||
COLUMNS = [Column("marked", defaultWidth=15), Column("regex", defaultWidth=230)]
|
||||
COLUMNS = [Column("marked", default_width=15), Column("regex", default_width=230)]
|
||||
|
||||
def __init__(self, app, view, **kwargs):
|
||||
model = app.model.exclude_list_dialog.exclude_list_table # pointer to GUITable
|
||||
|
@ -13,6 +13,6 @@ class IgnoreListTable(Table):
|
||||
"""Ignore list model"""
|
||||
|
||||
COLUMNS = [
|
||||
Column("path1", defaultWidth=230),
|
||||
Column("path2", defaultWidth=230),
|
||||
Column("path1", default_width=230),
|
||||
Column("path2", default_width=230),
|
||||
]
|
||||
|
@ -10,23 +10,23 @@ from ..results_model import ResultsModel as ResultsModelBase
|
||||
|
||||
class ResultsModel(ResultsModelBase):
|
||||
COLUMNS = [
|
||||
Column("marked", defaultWidth=30),
|
||||
Column("name", defaultWidth=200),
|
||||
Column("folder_path", defaultWidth=180),
|
||||
Column("size", defaultWidth=60),
|
||||
Column("duration", defaultWidth=60),
|
||||
Column("bitrate", defaultWidth=50),
|
||||
Column("samplerate", defaultWidth=60),
|
||||
Column("extension", defaultWidth=40),
|
||||
Column("mtime", defaultWidth=120),
|
||||
Column("title", defaultWidth=120),
|
||||
Column("artist", defaultWidth=120),
|
||||
Column("album", defaultWidth=120),
|
||||
Column("genre", defaultWidth=80),
|
||||
Column("year", defaultWidth=40),
|
||||
Column("track", defaultWidth=40),
|
||||
Column("comment", defaultWidth=120),
|
||||
Column("percentage", defaultWidth=60),
|
||||
Column("words", defaultWidth=120),
|
||||
Column("dupe_count", defaultWidth=80),
|
||||
Column("marked", default_width=30),
|
||||
Column("name", default_width=200),
|
||||
Column("folder_path", default_width=180),
|
||||
Column("size", default_width=60),
|
||||
Column("duration", default_width=60),
|
||||
Column("bitrate", default_width=50),
|
||||
Column("samplerate", default_width=60),
|
||||
Column("extension", default_width=40),
|
||||
Column("mtime", default_width=120),
|
||||
Column("title", default_width=120),
|
||||
Column("artist", default_width=120),
|
||||
Column("album", default_width=120),
|
||||
Column("genre", default_width=80),
|
||||
Column("year", default_width=40),
|
||||
Column("track", default_width=40),
|
||||
Column("comment", default_width=120),
|
||||
Column("percentage", default_width=60),
|
||||
Column("words", default_width=120),
|
||||
Column("dupe_count", default_width=80),
|
||||
]
|
||||
|
@ -201,16 +201,12 @@ class BaseController(QObject):
|
||||
# the SelectedImageViewer widget sometimes ends up being bigger
|
||||
# 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._updateImage(
|
||||
self.referencePixmap, self.scaledReferencePixmap, self.referenceViewer, selected_size, same_group
|
||||
)
|
||||
self._updateImage(self.selectedPixmap, self.selectedViewer, same_group)
|
||||
self._updateImage(self.referencePixmap, self.referenceViewer, same_group)
|
||||
if ignore_update:
|
||||
self.selectedViewer.ignore_signal = False
|
||||
|
||||
def _updateImage(self, pixmap, scaledpixmap, viewer, target_size=None, same_group=False):
|
||||
def _updateImage(self, pixmap, viewer, same_group=False):
|
||||
# WARNING this is called on every resize event, might need to split
|
||||
# into a separate function depending on the implementation used
|
||||
if pixmap.isNull():
|
||||
@ -340,8 +336,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)
|
||||
self._updateImage(self.selectedPixmap, self.selectedViewer, True)
|
||||
self._updateImage(self.referencePixmap, self.referenceViewer, True)
|
||||
self.centerViews()
|
||||
|
||||
self.parent.verticalToolBar.buttonZoomIn.setEnabled(False)
|
||||
|
@ -10,14 +10,14 @@ from ..results_model import ResultsModel as ResultsModelBase
|
||||
|
||||
class ResultsModel(ResultsModelBase):
|
||||
COLUMNS = [
|
||||
Column("marked", defaultWidth=30),
|
||||
Column("name", defaultWidth=200),
|
||||
Column("folder_path", defaultWidth=180),
|
||||
Column("size", defaultWidth=60),
|
||||
Column("extension", defaultWidth=40),
|
||||
Column("dimensions", defaultWidth=100),
|
||||
Column("exif_timestamp", defaultWidth=120),
|
||||
Column("mtime", defaultWidth=120),
|
||||
Column("percentage", defaultWidth=60),
|
||||
Column("dupe_count", defaultWidth=80),
|
||||
Column("marked", default_width=30),
|
||||
Column("name", default_width=200),
|
||||
Column("folder_path", default_width=180),
|
||||
Column("size", default_width=60),
|
||||
Column("extension", default_width=40),
|
||||
Column("dimensions", default_width=100),
|
||||
Column("exif_timestamp", default_width=120),
|
||||
Column("mtime", default_width=120),
|
||||
Column("percentage", default_width=60),
|
||||
Column("dupe_count", default_width=80),
|
||||
]
|
||||
|
@ -12,8 +12,8 @@ from qtlib.table import Table
|
||||
|
||||
class ProblemTable(Table):
|
||||
COLUMNS = [
|
||||
Column("path", defaultWidth=150),
|
||||
Column("msg", defaultWidth=150),
|
||||
Column("path", default_width=150),
|
||||
Column("msg", default_width=150),
|
||||
]
|
||||
|
||||
def __init__(self, model, view, **kwargs):
|
||||
|
@ -295,7 +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)
|
||||
@ -389,7 +389,7 @@ class ResultWindow(QMainWindow):
|
||||
# --- Private
|
||||
def _update_column_actions_status(self):
|
||||
# Update menu checked state
|
||||
menu_items = self.app.model.result_table.columns.menu_items()
|
||||
menu_items = self.app.model.result_table._columns.menu_items()
|
||||
for action, (display, visible) in zip(self._column_actions, menu_items):
|
||||
action.setChecked(visible)
|
||||
|
||||
@ -483,16 +483,16 @@ class ResultWindow(QMainWindow):
|
||||
def columnToggled(self, action):
|
||||
index = action.item_index
|
||||
if index == -1:
|
||||
self.app.model.result_table.columns.reset_to_defaults()
|
||||
self.app.model.result_table._columns.reset_to_defaults()
|
||||
self._update_column_actions_status()
|
||||
else:
|
||||
visible = self.app.model.result_table.columns.toggle_menu_item(index)
|
||||
visible = self.app.model.result_table._columns.toggle_menu_item(index)
|
||||
action.setChecked(visible)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.actionActions.menu().exec_(event.globalPos())
|
||||
|
||||
def resultsDoubleClicked(self, modelIndex):
|
||||
def resultsDoubleClicked(self, model_index):
|
||||
self.app.model.open_selected()
|
||||
|
||||
def resultsSpacePressed(self):
|
||||
|
@ -95,7 +95,7 @@ class ResultsModel(Table):
|
||||
|
||||
# --- Events
|
||||
def appWillSavePrefs(self):
|
||||
self.model.columns.save_columns()
|
||||
self.model._columns.save_columns()
|
||||
|
||||
# --- model --> view
|
||||
def invalidate_markings(self):
|
||||
|
@ -10,13 +10,13 @@ from ..results_model import ResultsModel as ResultsModelBase
|
||||
|
||||
class ResultsModel(ResultsModelBase):
|
||||
COLUMNS = [
|
||||
Column("marked", defaultWidth=30),
|
||||
Column("name", defaultWidth=200),
|
||||
Column("folder_path", defaultWidth=180),
|
||||
Column("size", defaultWidth=60),
|
||||
Column("extension", defaultWidth=40),
|
||||
Column("mtime", defaultWidth=120),
|
||||
Column("percentage", defaultWidth=60),
|
||||
Column("words", defaultWidth=120),
|
||||
Column("dupe_count", defaultWidth=80),
|
||||
Column("marked", default_width=30),
|
||||
Column("name", default_width=200),
|
||||
Column("folder_path", default_width=180),
|
||||
Column("size", default_width=60),
|
||||
Column("extension", default_width=40),
|
||||
Column("mtime", default_width=120),
|
||||
Column("percentage", default_width=60),
|
||||
Column("words", default_width=120),
|
||||
Column("dupe_count", default_width=80),
|
||||
]
|
||||
|
@ -14,38 +14,38 @@ class Column:
|
||||
def __init__(
|
||||
self,
|
||||
attrname,
|
||||
defaultWidth,
|
||||
default_width,
|
||||
editor=None,
|
||||
alignment=Qt.AlignLeft,
|
||||
cantTruncate=False,
|
||||
cant_truncate=False,
|
||||
painter=None,
|
||||
resizeToFit=False,
|
||||
resize_to_fit=False,
|
||||
):
|
||||
self.attrname = attrname
|
||||
self.defaultWidth = defaultWidth
|
||||
self.default_width = default_width
|
||||
self.editor = editor
|
||||
# See moneyguru #15. Painter attribute was added to allow custom painting of amount value and
|
||||
# currency information. Can be used as a pattern for custom painting of any column.
|
||||
self.painter = painter
|
||||
self.alignment = alignment
|
||||
# This is to indicate, during printing, that a column can't have its data truncated.
|
||||
self.cantTruncate = cantTruncate
|
||||
self.resizeToFit = resizeToFit
|
||||
self.cant_truncate = cant_truncate
|
||||
self.resize_to_fit = resize_to_fit
|
||||
|
||||
|
||||
class Columns:
|
||||
def __init__(self, model, columns, headerView):
|
||||
def __init__(self, model, columns, header_view):
|
||||
self.model = model
|
||||
self._headerView = headerView
|
||||
self._headerView.setDefaultAlignment(Qt.AlignLeft)
|
||||
self._header_view = header_view
|
||||
self._header_view.setDefaultAlignment(Qt.AlignLeft)
|
||||
|
||||
def setspecs(col, modelcol):
|
||||
modelcol.default_width = col.defaultWidth
|
||||
modelcol.default_width = col.default_width
|
||||
modelcol.editor = col.editor
|
||||
modelcol.painter = col.painter
|
||||
modelcol.resizeToFit = col.resizeToFit
|
||||
modelcol.resize_to_fit = col.resize_to_fit
|
||||
modelcol.alignment = col.alignment
|
||||
modelcol.cantTruncate = col.cantTruncate
|
||||
modelcol.cant_truncate = col.cant_truncate
|
||||
|
||||
if columns:
|
||||
for col in columns:
|
||||
@ -56,16 +56,16 @@ class Columns:
|
||||
for modelcol in self.model.column_list:
|
||||
setspecs(col, modelcol)
|
||||
self.model.view = self
|
||||
self._headerView.sectionMoved.connect(self.headerSectionMoved)
|
||||
self._headerView.sectionResized.connect(self.headerSectionResized)
|
||||
self._header_view.sectionMoved.connect(self.header_section_moved)
|
||||
self._header_view.sectionResized.connect(self.header_section_resized)
|
||||
|
||||
# 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)
|
||||
if column.resize_to_fit:
|
||||
self._header_view.setSectionResizeMode(column.logical_index, QHeaderView.ResizeToContents)
|
||||
|
||||
# --- Public
|
||||
def setColumnsWidth(self, widths):
|
||||
def set_columns_width(self, widths):
|
||||
# `widths` can be None. If it is, then default widths are set.
|
||||
columns = self.model.column_list
|
||||
if not widths:
|
||||
@ -73,38 +73,38 @@ class Columns:
|
||||
for column, width in zip(columns, widths):
|
||||
if width == 0: # column was hidden before.
|
||||
width = column.default_width
|
||||
self._headerView.resizeSection(column.logical_index, width)
|
||||
self._header_view.resizeSection(column.logical_index, width)
|
||||
|
||||
def setColumnsOrder(self, columnIndexes):
|
||||
if not columnIndexes:
|
||||
def set_columns_order(self, column_indexes):
|
||||
if not column_indexes:
|
||||
return
|
||||
for destIndex, columnIndex in enumerate(columnIndexes):
|
||||
for dest_index, column_index in enumerate(column_indexes):
|
||||
# moveSection takes 2 visual index arguments, so we have to get our visual index first
|
||||
visualIndex = self._headerView.visualIndex(columnIndex)
|
||||
self._headerView.moveSection(visualIndex, destIndex)
|
||||
visual_index = self._header_view.visualIndex(column_index)
|
||||
self._header_view.moveSection(visual_index, dest_index)
|
||||
|
||||
# --- Events
|
||||
def headerSectionMoved(self, logicalIndex, oldVisualIndex, newVisualIndex):
|
||||
attrname = self.model.column_by_index(logicalIndex).name
|
||||
self.model.move_column(attrname, newVisualIndex)
|
||||
def header_section_moved(self, logical_index, old_visual_index, new_visual_index):
|
||||
attrname = self.model.column_by_index(logical_index).name
|
||||
self.model.move_column(attrname, new_visual_index)
|
||||
|
||||
def headerSectionResized(self, logicalIndex, oldSize, newSize):
|
||||
attrname = self.model.column_by_index(logicalIndex).name
|
||||
self.model.resize_column(attrname, newSize)
|
||||
def header_section_resized(self, logical_index, old_size, new_size):
|
||||
attrname = self.model.column_by_index(logical_index).name
|
||||
self.model.resize_column(attrname, new_size)
|
||||
|
||||
# --- model --> view
|
||||
def restore_columns(self):
|
||||
columns = self.model.ordered_columns
|
||||
indexes = [col.logical_index for col in columns]
|
||||
self.setColumnsOrder(indexes)
|
||||
self.set_columns_order(indexes)
|
||||
widths = [col.width for col in self.model.column_list]
|
||||
if not any(widths):
|
||||
widths = None
|
||||
self.setColumnsWidth(widths)
|
||||
self.set_columns_width(widths)
|
||||
for column in self.model.column_list:
|
||||
visible = self.model.column_is_visible(column.name)
|
||||
self._headerView.setSectionHidden(column.logical_index, not visible)
|
||||
self._header_view.setSectionHidden(column.logical_index, not visible)
|
||||
|
||||
def set_column_visible(self, colname, visible):
|
||||
column = self.model.column_by_name(colname)
|
||||
self._headerView.setSectionHidden(column.logical_index, not visible)
|
||||
self._header_view.setSectionHidden(column.logical_index, not visible)
|
||||
|
@ -28,8 +28,8 @@ class Table(QAbstractTableModel):
|
||||
self.view = view
|
||||
self.view.setModel(self)
|
||||
self.model.view = self
|
||||
if hasattr(self.model, "columns"):
|
||||
self.columns = Columns(self.model.columns, self.COLUMNS, view.horizontalHeader())
|
||||
if hasattr(self.model, "_columns"):
|
||||
self._columns = Columns(self.model._columns, self.COLUMNS, view.horizontalHeader())
|
||||
|
||||
self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged)
|
||||
|
||||
@ -82,28 +82,28 @@ class Table(QAbstractTableModel):
|
||||
return False
|
||||
|
||||
def columnCount(self, index):
|
||||
return self.model.columns.columns_count()
|
||||
return self.model._columns.columns_count()
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
column = self.model._columns.column_by_index(index.column())
|
||||
return self._getData(row, column, role)
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return self.INVALID_INDEX_FLAGS
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
column = self.model._columns.column_by_index(index.column())
|
||||
return self._getFlags(row, column)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation != Qt.Horizontal:
|
||||
return None
|
||||
if section >= self.model.columns.columns_count():
|
||||
if section >= self.model._columns.columns_count():
|
||||
return None
|
||||
column = self.model.columns.column_by_index(section)
|
||||
column = self.model._columns.column_by_index(section)
|
||||
if role == Qt.DisplayRole:
|
||||
return column.display
|
||||
elif role == Qt.TextAlignmentRole:
|
||||
@ -123,11 +123,11 @@ class Table(QAbstractTableModel):
|
||||
if not index.isValid():
|
||||
return False
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
column = self.model._columns.column_by_index(index.column())
|
||||
return self._setData(row, column, value, role)
|
||||
|
||||
def sort(self, section, order):
|
||||
column = self.model.columns.column_by_index(section)
|
||||
column = self.model._columns.column_by_index(section)
|
||||
attrname = column.name
|
||||
self.model.sort_by(attrname, desc=order == Qt.DescendingOrder)
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
# Created On: 2012/01/23
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
||||
class TextField:
|
||||
def __init__(self, model, view):
|
||||
self.model = model
|
||||
self.view = view
|
||||
self.model.view = self
|
||||
# Make TextField also work for QLabel, which doesn't allow editing
|
||||
if hasattr(self.view, "editingFinished"):
|
||||
self.view.editingFinished.connect(self.editingFinished)
|
||||
|
||||
def editingFinished(self):
|
||||
self.model.text = self.view.text()
|
||||
|
||||
# model --> view
|
||||
def refresh(self):
|
||||
self.view.setText(self.model.text)
|
Loading…
x
Reference in New Issue
Block a user