mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-22 06:37:17 +00:00
Converted the result tree into a result table.
--HG-- rename : cocoa/base/PyResultTree.h => cocoa/base/PyResultTable.h rename : cocoa/base/ResultOutline.h => cocoa/base/ResultTable.h rename : cocoa/base/ResultOutline.m => cocoa/base/ResultTable.m rename : core/gui/result_tree.py => core/gui/result_table.py
This commit is contained in:
@@ -15,7 +15,7 @@ from .gui.details_panel import DetailsPanel
|
||||
from .gui.directory_tree import DirectoryTree
|
||||
from .gui.problem_dialog import ProblemDialog
|
||||
from .gui.problem_table import ProblemTable
|
||||
from .gui.result_tree import ResultTree
|
||||
from .gui.result_table import ResultTable
|
||||
from .gui.stats_label import StatsLabel
|
||||
|
||||
# Fix py2app's problems on relative imports
|
||||
@@ -163,8 +163,8 @@ class PyDirectoryOutline(PyOutline):
|
||||
self.py.add_directory(path)
|
||||
|
||||
|
||||
class PyResultOutline(PyOutline):
|
||||
py_class = ResultTree
|
||||
class PyResultTable(PyTable):
|
||||
py_class = ResultTable
|
||||
|
||||
@signature('c@:')
|
||||
def powerMarkerMode(self):
|
||||
@@ -182,9 +182,9 @@ class PyResultOutline(PyOutline):
|
||||
def setDeltaValuesMode_(self, value):
|
||||
self.py.delta_values = value
|
||||
|
||||
@signature('@@:@i')
|
||||
def valueForPath_column_(self, path, column):
|
||||
return self.py.get_node_value(path, column)
|
||||
@signature('@@:ii')
|
||||
def valueForRow_column_(self, row_index, column):
|
||||
return self.py.get_row_value(row_index, column)
|
||||
|
||||
@signature('c@:@')
|
||||
def renameSelected_(self, newname):
|
||||
@@ -200,8 +200,9 @@ class PyResultOutline(PyOutline):
|
||||
def removeSelected(self):
|
||||
self.py.app.remove_selected()
|
||||
|
||||
def rootChildrenCounts(self):
|
||||
return self.py.root_children_counts()
|
||||
@signature('i@:')
|
||||
def selectedDupeCount(self):
|
||||
return self.py.selected_dupe_count
|
||||
|
||||
# python --> cocoa
|
||||
def invalidate_markings(self):
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from hsgui.tree import Tree, Node
|
||||
from hsgui.table import GUITable, Row
|
||||
|
||||
from .base import GUIObject
|
||||
|
||||
class DupeNode(Node):
|
||||
def __init__(self, app, group, dupe):
|
||||
Node.__init__(self, '')
|
||||
self._app = app
|
||||
class DupeRow(Row):
|
||||
def __init__(self, table, group, dupe):
|
||||
Row.__init__(self, table)
|
||||
self._app = table.app
|
||||
self._group = group
|
||||
self._dupe = dupe
|
||||
self._data = None
|
||||
@@ -34,6 +34,10 @@ class DupeNode(Node):
|
||||
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
|
||||
return self._data_delta
|
||||
|
||||
@property
|
||||
def isref(self):
|
||||
return self._dupe is self._group.ref
|
||||
|
||||
@property
|
||||
def markable(self):
|
||||
return self._app.results.is_markable(self._dupe)
|
||||
@@ -47,10 +51,10 @@ class DupeNode(Node):
|
||||
self._app.mark_dupe(self._dupe, value)
|
||||
|
||||
|
||||
class ResultTree(GUIObject, Tree):
|
||||
class ResultTable(GUIObject, GUITable):
|
||||
def __init__(self, view, app):
|
||||
GUIObject.__init__(self, view, app)
|
||||
Tree.__init__(self)
|
||||
GUITable.__init__(self)
|
||||
self._power_marker = False
|
||||
self._delta_values = False
|
||||
self._sort_descriptors = (0, True)
|
||||
@@ -58,60 +62,54 @@ class ResultTree(GUIObject, Tree):
|
||||
#--- Override
|
||||
def connect(self):
|
||||
GUIObject.connect(self)
|
||||
self._refresh()
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
def _select_nodes(self, nodes):
|
||||
Tree._select_nodes(self, nodes)
|
||||
self.app._select_dupes(list(map(attrgetter('_dupe'), nodes)))
|
||||
def _restore_selection(self, previous_selection):
|
||||
if self.app.selected_dupes:
|
||||
to_find = set(self.app.selected_dupes)
|
||||
indexes = [i for i, r in enumerate(self) if r._dupe in to_find]
|
||||
self.selected_indexes = indexes
|
||||
|
||||
#--- Private
|
||||
def _refresh(self):
|
||||
self.clear()
|
||||
def _update_selection(self):
|
||||
rows = self.selected_rows
|
||||
self.app._select_dupes(list(map(attrgetter('_dupe'), rows)))
|
||||
|
||||
def _fill(self):
|
||||
if not self.power_marker:
|
||||
for group in self.app.results.groups:
|
||||
group_node = DupeNode(self.app, group, group.ref)
|
||||
self.append(group_node)
|
||||
self.append(DupeRow(self, group, group.ref))
|
||||
for dupe in group.dupes:
|
||||
group_node.append(DupeNode(self.app, group, dupe))
|
||||
self.append(DupeRow(self, group, dupe))
|
||||
else:
|
||||
for dupe in self.app.results.dupes:
|
||||
group = self.app.results.get_group_of_duplicate(dupe)
|
||||
self.append(DupeNode(self.app, group, dupe))
|
||||
if self.app.selected_dupes:
|
||||
to_find = set(self.app.selected_dupes)
|
||||
nodes = list(self.findall(lambda n: n is not self and n._dupe in to_find))
|
||||
self.selected_nodes = nodes
|
||||
self.append(DupeRow(self, group, dupe))
|
||||
|
||||
#--- Public
|
||||
def get_node_value(self, path, column):
|
||||
def get_row_value(self, index, column):
|
||||
try:
|
||||
node = self.get_node(path)
|
||||
row = self[index]
|
||||
except IndexError:
|
||||
return '---'
|
||||
if self.delta_values:
|
||||
return node.data_delta[column]
|
||||
return row.data_delta[column]
|
||||
else:
|
||||
return node.data[column]
|
||||
return row.data[column]
|
||||
|
||||
def rename_selected(self, newname):
|
||||
node = self.selected_node
|
||||
node._data = None
|
||||
node._data_delta = None
|
||||
row = self.selected_row
|
||||
row._data = None
|
||||
row._data_delta = None
|
||||
return self.app.rename_selected(newname)
|
||||
|
||||
def root_children_counts(self):
|
||||
# This is a speed optimization for cases where there's a lot of results so that there is
|
||||
# not thousands of children_count queries when expandAll is called.
|
||||
return [len(node) for node in self]
|
||||
|
||||
def sort(self, key, asc):
|
||||
if self.power_marker:
|
||||
self.app.results.sort_dupes(key, asc, self.delta_values)
|
||||
else:
|
||||
self.app.results.sort_groups(key, asc)
|
||||
self._sort_descriptors = (key, asc)
|
||||
self._refresh()
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
#--- Properties
|
||||
@@ -126,7 +124,7 @@ class ResultTree(GUIObject, Tree):
|
||||
self._power_marker = value
|
||||
key, asc = self._sort_descriptors
|
||||
self.sort(key, asc)
|
||||
self._refresh()
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
@property
|
||||
@@ -138,22 +136,26 @@ class ResultTree(GUIObject, Tree):
|
||||
if value == self._delta_values:
|
||||
return
|
||||
self._delta_values = value
|
||||
self._refresh()
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
@property
|
||||
def selected_dupe_count(self):
|
||||
return sum(1 for row in self.selected_rows if not row.isref)
|
||||
|
||||
#--- Event Handlers
|
||||
def marking_changed(self):
|
||||
self.view.invalidate_markings()
|
||||
|
||||
def results_changed(self):
|
||||
self._refresh()
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
def results_changed_but_keep_selection(self):
|
||||
# What we want to to here is that instead of restoring selected *dupes* after refresh, we
|
||||
# restore selected *paths*.
|
||||
paths = self.selected_paths
|
||||
self._refresh()
|
||||
self.selected_paths = paths
|
||||
indexes = self.selected_indexes
|
||||
self.refresh()
|
||||
self.select(indexes)
|
||||
self.view.refresh()
|
||||
|
||||
@@ -23,7 +23,7 @@ from .. import app, fs, engine
|
||||
from ..app import DupeGuru as DupeGuruBase
|
||||
from ..gui.details_panel import DetailsPanel
|
||||
from ..gui.directory_tree import DirectoryTree
|
||||
from ..gui.result_tree import ResultTree
|
||||
from ..gui.result_table import ResultTable
|
||||
|
||||
class DupeGuru(DupeGuruBase):
|
||||
def __init__(self):
|
||||
@@ -166,11 +166,11 @@ class TCDupeGuruWithResults(TestCase):
|
||||
self.dpanel = DetailsPanel(self.dpanel_gui, self.app)
|
||||
self.dtree_gui = CallLogger()
|
||||
self.dtree = DirectoryTree(self.dtree_gui, self.app)
|
||||
self.rtree_gui = CallLogger()
|
||||
self.rtree = ResultTree(self.rtree_gui, self.app)
|
||||
self.rtable_gui = CallLogger()
|
||||
self.rtable = ResultTable(self.rtable_gui, self.app)
|
||||
self.dpanel.connect()
|
||||
self.dtree.connect()
|
||||
self.rtree.connect()
|
||||
self.rtable.connect()
|
||||
tmppath = self.tmppath()
|
||||
io.mkdir(tmppath + 'foo')
|
||||
io.mkdir(tmppath + 'bar')
|
||||
@@ -217,42 +217,35 @@ class TCDupeGuruWithResults(TestCase):
|
||||
def test_GetObjects(self):
|
||||
objects = self.objects
|
||||
groups = self.groups
|
||||
n = self.rtree.get_node([0])
|
||||
assert n._group is groups[0]
|
||||
assert n._dupe is objects[0]
|
||||
n = self.rtree.get_node([0, 0])
|
||||
assert n._group is groups[0]
|
||||
assert n._dupe is objects[1]
|
||||
n = self.rtree.get_node([1, 0])
|
||||
assert n._group is groups[1]
|
||||
assert n._dupe is objects[4]
|
||||
r = self.rtable[0]
|
||||
assert r._group is groups[0]
|
||||
assert r._dupe is objects[0]
|
||||
r = self.rtable[1]
|
||||
assert r._group is groups[0]
|
||||
assert r._dupe is objects[1]
|
||||
r = self.rtable[4]
|
||||
assert r._group is groups[1]
|
||||
assert r._dupe is objects[4]
|
||||
|
||||
def test_GetObjects_after_sort(self):
|
||||
objects = self.objects
|
||||
groups = self.groups[:] # we need an un-sorted reference
|
||||
self.rtree.sort(0, False) #0 = Filename
|
||||
n = self.rtree.get_node([0, 0])
|
||||
assert n._group is groups[1]
|
||||
assert n._dupe is objects[4]
|
||||
|
||||
def test_selected_result_node_paths(self):
|
||||
# app.selected_dupes is correctly converted into node paths
|
||||
paths = [[0, 0], [0, 1], [1]]
|
||||
self.rtree.selected_paths = paths
|
||||
eq_(self.rtree.selected_paths, paths)
|
||||
self.rtable.sort(0, False) #0 = Filename
|
||||
r = self.rtable[1]
|
||||
assert r._group is groups[1]
|
||||
assert r._dupe is objects[4]
|
||||
|
||||
def test_selected_result_node_paths_after_deletion(self):
|
||||
# cases where the selected dupes aren't there are correctly handled
|
||||
paths = [[0, 0], [0, 1], [1]]
|
||||
self.rtree.selected_paths = paths
|
||||
self.rtable.select([1, 2, 3])
|
||||
self.app.remove_selected()
|
||||
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
|
||||
eq_(self.rtree.selected_paths, [[0, 0]]) # no exception
|
||||
eq_(self.rtable.selected_indexes, [1]) # no exception
|
||||
|
||||
def test_selectResultNodePaths(self):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.selected_paths = [[0, 0], [0, 1]]
|
||||
self.rtable.select([1, 2])
|
||||
eq_(len(app.selected_dupes), 2)
|
||||
assert app.selected_dupes[0] is objects[1]
|
||||
assert app.selected_dupes[1] is objects[2]
|
||||
@@ -260,7 +253,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
def test_selectResultNodePaths_with_ref(self):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1]]
|
||||
self.rtable.select([1, 2, 3])
|
||||
eq_(len(app.selected_dupes), 3)
|
||||
assert app.selected_dupes[0] is objects[1]
|
||||
assert app.selected_dupes[1] is objects[2]
|
||||
@@ -270,9 +263,9 @@ class TCDupeGuruWithResults(TestCase):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
groups = self.groups[:] #To keep the old order in memory
|
||||
self.rtree.sort(0, False) #0 = Filename
|
||||
self.rtable.sort(0, False) #0 = Filename
|
||||
#Now, the group order is supposed to be reversed
|
||||
self.rtree.selected_paths = [[0, 0], [1], [1, 0]]
|
||||
self.rtable.select([1, 2, 3])
|
||||
eq_(len(app.selected_dupes), 3)
|
||||
assert app.selected_dupes[0] is objects[4]
|
||||
assert app.selected_dupes[1] is groups[0].ref
|
||||
@@ -282,39 +275,26 @@ class TCDupeGuruWithResults(TestCase):
|
||||
# app.selected_dupes is correctly converted into paths
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.power_marker = True
|
||||
self.rtree.selected_paths = [[0], [1], [2]]
|
||||
self.rtree.power_marker = False
|
||||
eq_(self.rtree.selected_paths, [[0, 0], [0, 1], [1, 0]])
|
||||
self.rtable.power_marker = True
|
||||
self.rtable.select([0, 1, 2])
|
||||
self.rtable.power_marker = False
|
||||
eq_(self.rtable.selected_indexes, [1, 2, 4])
|
||||
|
||||
def test_selected_powermarker_node_paths_after_deletion(self):
|
||||
# cases where the selected dupes aren't there are correctly handled
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.power_marker = True
|
||||
self.rtree.selected_paths = [[0], [1], [2]]
|
||||
self.rtable.power_marker = True
|
||||
self.rtable.select([0, 1, 2])
|
||||
app.remove_selected()
|
||||
eq_(self.rtree.selected_paths, []) # no exception
|
||||
|
||||
def test_selectPowerMarkerRows(self):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
|
||||
eq_(len(app.selected_dupes), 3)
|
||||
assert app.selected_dupes[0] is objects[1]
|
||||
assert app.selected_dupes[1] is objects[2]
|
||||
assert app.selected_dupes[2] is objects[4]
|
||||
|
||||
def test_selectPowerMarkerRows_empty(self):
|
||||
self.rtree.selected_paths = []
|
||||
eq_(len(self.app.selected_dupes), 0)
|
||||
eq_(self.rtable.selected_indexes, []) # no exception
|
||||
|
||||
def test_selectPowerMarkerRows_after_sort(self):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
self.rtree.power_marker = True
|
||||
self.rtree.sort(0, False) #0 = Filename
|
||||
self.rtree.selected_paths = [[0], [1], [2]]
|
||||
self.rtable.power_marker = True
|
||||
self.rtable.sort(0, False) #0 = Filename
|
||||
self.rtable.select([0, 1, 2])
|
||||
eq_(len(app.selected_dupes), 3)
|
||||
assert app.selected_dupes[0] is objects[4]
|
||||
assert app.selected_dupes[1] is objects[2]
|
||||
@@ -325,7 +305,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
objects = self.objects
|
||||
app.toggle_selected_mark_state()
|
||||
eq_(app.results.mark_count, 0)
|
||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
||||
self.rtable.select([1, 4])
|
||||
app.toggle_selected_mark_state()
|
||||
eq_(app.results.mark_count, 2)
|
||||
assert not app.results.is_marked(objects[0])
|
||||
@@ -335,10 +315,10 @@ class TCDupeGuruWithResults(TestCase):
|
||||
assert app.results.is_marked(objects[4])
|
||||
|
||||
def test_refreshDetailsWithSelected(self):
|
||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
||||
self.rtable.select([1, 4])
|
||||
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
|
||||
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
||||
self.rtree.selected_paths = []
|
||||
self.rtable.select([])
|
||||
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
|
||||
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
||||
|
||||
@@ -346,7 +326,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
groups = self.groups
|
||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
||||
self.rtable.select([1, 4])
|
||||
app.make_selected_reference()
|
||||
assert groups[0].ref is objects[1]
|
||||
assert groups[1].ref is objects[4]
|
||||
@@ -355,7 +335,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
app = self.app
|
||||
objects = self.objects
|
||||
groups = self.groups
|
||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
|
||||
self.rtable.select([1, 2, 4])
|
||||
#Only [0, 0] and [1, 0] must go ref, not [0, 1] because it is a part of the same group
|
||||
app.make_selected_reference()
|
||||
assert groups[0].ref is objects[1]
|
||||
@@ -363,7 +343,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
|
||||
def test_removeSelected(self):
|
||||
app = self.app
|
||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
||||
self.rtable.select([1, 4])
|
||||
app.remove_selected()
|
||||
eq_(len(app.results.dupes), 1) # the first path is now selected
|
||||
app.remove_selected()
|
||||
@@ -386,10 +366,10 @@ class TCDupeGuruWithResults(TestCase):
|
||||
|
||||
def test_ignore(self):
|
||||
app = self.app
|
||||
self.rtree.selected_path = [1, 0] #The dupe of the second, 2 sized group
|
||||
self.rtable.select([4]) #The dupe of the second, 2 sized group
|
||||
app.add_selected_to_ignore_list()
|
||||
eq_(len(app.scanner.ignore_list), 1)
|
||||
self.rtree.selected_path = [0, 0] #first dupe of the 3 dupes group
|
||||
self.rtable.select([1]) #first dupe of the 3 dupes group
|
||||
app.add_selected_to_ignore_list()
|
||||
#BOTH the ref and the other dupe should have been added
|
||||
eq_(len(app.scanner.ignore_list), 3)
|
||||
@@ -416,7 +396,7 @@ class TCDupeGuruWithResults(TestCase):
|
||||
|
||||
app = self.app
|
||||
app.scanner.ignore_list.Ignore = FakeIgnore
|
||||
self.rtree.selected_path = [1, 0]
|
||||
self.rtable.select([4])
|
||||
app.add_selected_to_ignore_list()
|
||||
|
||||
|
||||
@@ -440,14 +420,14 @@ class TCDupeGuru_renameSelected(TestCase):
|
||||
self.groups = groups
|
||||
self.p = p
|
||||
self.files = files
|
||||
self.rtree_gui = CallLogger()
|
||||
self.rtree = ResultTree(self.rtree_gui, self.app)
|
||||
self.rtree.connect()
|
||||
self.rtable_gui = CallLogger()
|
||||
self.rtable = ResultTable(self.rtable_gui, self.app)
|
||||
self.rtable.connect()
|
||||
|
||||
def test_simple(self):
|
||||
app = self.app
|
||||
g = self.groups[0]
|
||||
self.rtree.selected_path = [0, 0]
|
||||
self.rtable.select([1])
|
||||
assert app.rename_selected('renamed')
|
||||
names = io.listdir(self.p)
|
||||
assert 'renamed' in names
|
||||
@@ -457,7 +437,7 @@ class TCDupeGuru_renameSelected(TestCase):
|
||||
def test_none_selected(self):
|
||||
app = self.app
|
||||
g = self.groups[0]
|
||||
self.rtree.selected_paths = []
|
||||
self.rtable.select([])
|
||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||
assert not app.rename_selected('renamed')
|
||||
msg = logging.warning.calls[0]['msg']
|
||||
@@ -470,7 +450,7 @@ class TCDupeGuru_renameSelected(TestCase):
|
||||
def test_name_already_exists(self):
|
||||
app = self.app
|
||||
g = self.groups[0]
|
||||
self.rtree.selected_path = [0, 0]
|
||||
self.rtable.select([1])
|
||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||
assert not app.rename_selected('foo bar 1')
|
||||
msg = logging.warning.calls[0]['msg']
|
||||
|
||||
Reference in New Issue
Block a user