Adapted the Qt codebase to the addition of core.gui.result_tree and core.gui.stats_label.

This commit is contained in:
Virgil Dupras 2010-02-12 15:39:29 +01:00
parent c3a972d39b
commit cab6d924aa
14 changed files with 142 additions and 208 deletions

View File

@ -14,9 +14,16 @@ http://www.hardcoded.net/licenses/hs_license
{
self = [super initWithNibName:@"DetailsPanel" pyClassName:@"PyDetailsPanel" pyParent:aPy];
[self window]; //So the detailsTable is initialized.
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[super dealloc];
}
- (PyDetailsPanel *)py
{
return (PyDetailsPanel *)py;

View File

@ -13,9 +13,16 @@ http://www.hardcoded.net/licenses/hs_license
{
self = [super initWithPyClassName:@"PyDirectoryOutline" pyParent:aPyParent view:aOutlineView];
[outlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[super dealloc];
}
- (PyDirectoryOutline *)py
{
return (PyDirectoryOutline *)py;

View File

@ -16,11 +16,13 @@ http://www.hardcoded.net/licenses/hs_license
{
self = [super initWithPyClassName:@"PyResultOutline" pyParent:aPyParent view:aOutlineView];
_rootChildrenCounts = nil;
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[_deltaColumns release];
[super dealloc];
}

View File

@ -14,11 +14,13 @@ http://www.hardcoded.net/licenses/hs_license
{
self = [super initWithPyClassName:@"PyStatsLabel" pyParent:aPyParent];
labelView = [aLabelView retain];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[labelView release];
[super dealloc];
}

View File

@ -13,8 +13,11 @@ class DetailsPanel(GUIObject):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
self._table = []
def connect(self):
GUIObject.connect(self)
self._refresh()
self.connect()
self.view.refresh()
#--- Private
def _refresh(self):

View File

@ -53,7 +53,9 @@ class DirectoryTree(GUIObject, Tree):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
Tree.__init__(self)
self.connect()
def connect(self):
GUIObject.connect(self)
self._refresh()
self.view.refresh()

View File

@ -54,11 +54,13 @@ class ResultTree(GUIObject, Tree):
self._power_marker = False
self._delta_values = False
self._sort_descriptors = (0, True)
self.connect()
#--- Override
def connect(self):
GUIObject.connect(self)
self._refresh()
self.view.refresh()
#--- Override
def _select_nodes(self, nodes):
Tree._select_nodes(self, nodes)
self.app._select_dupes(map(attrgetter('_dupe'), nodes))

View File

@ -10,9 +10,8 @@
from .base import GUIObject
class StatsLabel(GUIObject):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
self.connect()
def connect(self):
GUIObject.connect(self)
self.view.refresh()
@property

View File

@ -12,13 +12,12 @@ import logging
import os
import os.path as op
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox
from hsutil import job
from hsutil.reg import RegistrationRequired
from core import fs
from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from qtlib.about_box import AboutBox
@ -148,10 +147,6 @@ class DupeGuru(DupeGuruBase, QObject):
if self.main_window._confirm(title, msg):
DupeGuruBase.add_selected_to_ignore_list(self)
def apply_filter(self, filter):
DupeGuruBase.apply_filter(self, filter)
self.emit(SIGNAL('resultsChanged()'))
@demo_method
def copy_or_move_marked(self, copy):
opname = 'copy' if copy else 'move'
@ -165,14 +160,6 @@ class DupeGuru(DupeGuruBase, QObject):
delete_marked = demo_method(DupeGuruBase.delete_marked)
def make_selected_reference(self):
DupeGuruBase.make_selected_reference(self)
self.emit(SIGNAL('resultsChanged()'))
def remove_duplicates(self, duplicates):
DupeGuruBase.remove_duplicates(self, duplicates)
self.emit(SIGNAL('resultsChanged()'))
def remove_selected(self):
dupes = self.without_ref(self.selected_dupes)
if not dupes:
@ -186,37 +173,10 @@ class DupeGuru(DupeGuruBase, QObject):
def askForRegCode(self):
self.reg.ask_for_code()
def mark_all(self):
self.results.mark_all()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_invert(self):
self.results.mark_invert()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_none(self):
self.results.mark_none()
self.emit(SIGNAL('dupeMarkingChanged()'))
def openDebugLog(self):
debugLogPath = op.join(self.appdata, 'debug.log')
self._open_path(debugLogPath)
def remove_marked_duplicates(self):
marked = [d for d in self.results.dupes if self.results.is_marked(d)]
self.remove_duplicates(marked)
def rename_dupe(self, dupe, newname):
try:
dupe.rename(newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def select_dupes(self, dupes):
self._select_dupes(dupes)
def show_about_box(self):
self.about_box.show()
@ -238,11 +198,6 @@ class DupeGuru(DupeGuruBase, QObject):
self.prefs.save()
self._update_options()
def toggle_marking_for_dupes(self, dupes):
for dupe in dupes:
self.results.mark_toggle(dupe)
self.emit(SIGNAL('dupeMarkingChanged()'))
#--- Events
def application_will_terminate(self):
self.save()
@ -253,7 +208,7 @@ class DupeGuru(DupeGuruBase, QObject):
self.reg.show_nag()
def job_finished(self, jobid):
self.emit(SIGNAL('resultsChanged()'))
self._job_completed(jobid)
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0:
msg = "{0} files could not be processed.".format(self.results.mark_count)
QMessageBox.warning(self.main_window, 'Warning', msg)

View File

@ -23,6 +23,7 @@ class DetailsDialog(QDialog):
self.tableModel = DetailsModel(self.model)
# tableView is defined in subclasses
self.tableView.setModel(self.tableModel)
self.model.connect()
def _setupUi(self): # Virtual
pass

View File

@ -64,6 +64,7 @@ class DirectoriesModel(TreeModel):
def __init__(self, app):
TreeModel.__init__(self)
self.model = DirectoryTree(self, app)
self.model.connect()
def _createNode(self, ref, row):
return RefNode(self, None, ref, row)

View File

@ -8,7 +8,7 @@
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
QMessageBox, QInputDialog, QLineEdit, QItemSelectionModel, QDesktopServices)
QMessageBox, QInputDialog, QLineEdit, QDesktopServices)
from hsutil.misc import nonone
@ -16,7 +16,8 @@ from core.app import NoScannableFileError, AllFilesAreRefError
import dg_rc
from main_window_ui import Ui_MainWindow
from results_model import ResultsDelegate, ResultsModel
from results_model import ResultsModel
from stats_label import StatsLabel
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, app):
@ -24,23 +25,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app = app
self._last_filter = None
self._setupUi()
self.resultsDelegate = ResultsDelegate()
self.resultsModel = ResultsModel(self.app)
self.resultsView.setModel(self.resultsModel)
self.resultsView.setItemDelegate(self.resultsDelegate)
self.resultsModel = ResultsModel(self.app, self.resultsView)
self.stats = StatsLabel(app, self.statusLabel)
self._load_columns()
self._update_column_actions_status()
self.resultsView.expandAll()
self._update_status_line()
self.connect(self.app, SIGNAL('resultsChanged()'), self.resultsChanged)
self.connect(self.app, SIGNAL('dupeMarkingChanged()'), self.dupeMarkingChanged)
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
self.connect(self.resultsView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self.resultsModel, SIGNAL('modelReset()'), self.resultsReset)
self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
self.connect(self.resultsView, SIGNAL('spacePressed()'), self.resultsSpacePressed)
def _setupUi(self):
self.setupUi(self)
@ -108,11 +102,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
h.setSectionHidden(index, not visible)
h.setResizeMode(0, QHeaderView.Stretch)
def _redraw_results(self):
# HACK. this is the only way I found to update the widget without reseting everything
self.resultsView.scroll(0, 1)
self.resultsView.scroll(0, -1)
def _save_columns(self):
h = self.resultsView.header()
widths = []
@ -131,9 +120,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
colid = action.column_index
action.setChecked(not h.isSectionHidden(colid))
def _update_status_line(self):
self.statusLabel.setText(self.app.stat_line)
#--- Actions
def aboutTriggered(self):
self.app.show_about_box()
@ -185,8 +171,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.delete_marked()
def deltaTriggered(self):
self.resultsModel.delta = self.actionDelta.isChecked()
self._redraw_results()
self.resultsModel.delta_values = self.actionDelta.isChecked()
def detailsTriggered(self):
self.app.show_details()
@ -217,8 +202,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.mark_none()
def markSelectedTriggered(self):
dupes = self.resultsView.selectedDupes()
self.app.toggle_marking_for_dupes(dupes)
self.app.toggle_selected_mark_state()
def moveTriggered(self):
self.app.copy_or_move_marked(False)
@ -245,7 +229,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(count)
if self._confirm(title, msg):
self.app.remove_marked_duplicates()
self.app.remove_marked()
def removeSelectedTriggered(self):
self.app.remove_selected()
@ -292,25 +276,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def contextMenuEvent(self, event):
self.actionActions.menu().exec_(event.globalPos())
def dupeMarkingChanged(self):
self._redraw_results()
self._update_status_line()
def resultsChanged(self):
self.resultsView.model().reset()
def resultsDoubleClicked(self):
self.app.open_selected()
def resultsReset(self):
self.resultsView.expandAll()
if self.app.selected_dupes:
[modelIndex] = self.resultsModel.indexesForDupes(self.app.selected_dupes[:1])
if modelIndex.isValid():
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
self.resultsView.selectionModel().setCurrentIndex(modelIndex, flags)
self._update_status_line()
def selectionChanged(self, selected, deselected):
self.app.select_dupes(self.resultsView.selectedDupes())
def resultsSpacePressed(self):
self.app.toggle_selected_mark_state()

View File

@ -6,74 +6,62 @@
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QModelIndex, QRect
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import (QBrush, QStyledItemDelegate, QFont, QTreeView, QColor, QItemSelectionModel,
QItemSelection)
from qtlib.tree_model import TreeNode, TreeModel
from qtlib.tree_model import TreeModel, RefNode
class ResultNode(TreeNode):
def __init__(self, model, parent, row, dupe, group):
TreeNode.__init__(self, model, parent, row)
self.dupe = dupe
self.group = group
self._normalData = None
self._deltaData = None
def _createNode(self, ref, row):
return ResultNode(self.model, self, row, ref, self.group)
def _getChildren(self):
return self.group.dupes if self.dupe is self.group.ref else []
def invalidate(self):
self._normalData = None
self._deltaData = None
TreeNode.invalidate(self)
@property
def normalData(self):
if self._normalData is None:
self._normalData = self.model._app._get_display_info(self.dupe, self.group, delta=False)
return self._normalData
@property
def deltaData(self):
if self._deltaData is None:
self._deltaData = self.model._app._get_display_info(self.dupe, self.group, delta=True)
return self._deltaData
from core.gui.result_tree import ResultTree as ResultTreeModel
class ResultsDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
QStyledItemDelegate.initStyleOption(self, option, index)
node = index.internalPointer()
if node.group.ref is node.dupe:
ref = node.ref
if ref._group.ref is ref._dupe:
newfont = QFont(option.font)
newfont.setBold(True)
option.font = newfont
class ResultsModel(TreeModel):
def __init__(self, app):
def __init__(self, app, view):
TreeModel.__init__(self)
self.view = view
self._app = app
self._results = app.results
self._data = app.data
self._delta_columns = app.DELTA_COLUMNS
self.delta = False
self._power_marker = False
TreeModel.__init__(self)
self.resultsDelegate = ResultsDelegate()
self.model = ResultTreeModel(self, app)
self.view.setItemDelegate(self.resultsDelegate)
self.view.setModel(self)
self.model.connect()
self.connect(self.view.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
def _createNode(self, ref, row):
if self.power_marker:
# ref is a dupe
group = self._results.get_group_of_duplicate(ref)
return ResultNode(self, None, row, ref, group)
else:
# ref is a group
return ResultNode(self, None, row, ref.ref, ref)
return RefNode(self, None, ref, row)
def _getChildren(self):
return self._results.dupes if self.power_marker else self._results.groups
return list(self.model)
def _updateSelection(self):
selectedIndexes = []
for path in self.model.selected_paths:
modelIndex = self.findIndex(path)
if modelIndex.isValid():
selectedIndexes.append(modelIndex)
if selectedIndexes:
selection = QItemSelection()
for modelIndex in selectedIndexes:
selection.select(modelIndex, modelIndex)
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
self.view.selectionModel().select(selection, flags)
flags = QItemSelectionModel.Rows
self.view.selectionModel().setCurrentIndex(selectedIndexes[0], flags)
else:
self.view.selectionModel().clear()
def columnCount(self, parent):
return len(self._data.COLUMNS)
@ -82,51 +70,23 @@ class ResultsModel(TreeModel):
if not index.isValid():
return None
node = index.internalPointer()
ref = node.ref
if role == Qt.DisplayRole:
data = node.deltaData if self.delta else node.normalData
data = ref.data_delta if self.model.delta_values else ref.data
return data[index.column()]
elif role == Qt.CheckStateRole:
if index.column() == 0 and node.dupe is not node.group.ref:
state = Qt.Checked if self._results.is_marked(node.dupe) else Qt.Unchecked
return state
if index.column() == 0 and ref.markable:
return Qt.Checked if ref.marked else Qt.Unchecked
elif role == Qt.ForegroundRole:
if node.dupe is node.group.ref or node.dupe.is_ref:
if ref._dupe is ref._group.ref or ref._dupe.is_ref:
return QBrush(Qt.blue)
elif self.delta and index.column() in self._delta_columns:
elif self.model.delta_values and index.column() in self._delta_columns:
return QBrush(QColor(255, 142, 40)) # orange
elif role == Qt.EditRole:
if index.column() == 0:
return node.normalData[index.column()]
return ref.data[index.column()]
return None
def dupesForIndexes(self, indexes):
nodes = [index.internalPointer() for index in indexes]
return [node.dupe for node in nodes]
def indexesForDupes(self, dupes):
def index(dupe):
try:
if self.power_marker:
row = self._results.dupes.index(dupe)
node = self.subnodes[row]
assert node.dupe is dupe
return self.createIndex(row, 0, node)
else:
group = self._results.get_group_of_duplicate(dupe)
row = self._results.groups.index(group)
node = self.subnodes[row]
if dupe is group.ref:
assert node.dupe is dupe
return self.createIndex(row, 0, node)
subrow = group.dupes.index(dupe)
subnode = node.subnodes[subrow]
assert subnode.dupe is dupe
return self.createIndex(subrow, 0, subnode)
except ValueError: # the dupe is not there anymore
return QModelIndex()
return map(index, dupes)
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
@ -144,48 +104,61 @@ class ResultsModel(TreeModel):
if not index.isValid():
return False
node = index.internalPointer()
ref = node.ref
if role == Qt.CheckStateRole:
if index.column() == 0:
self._app.toggle_marking_for_dupes([node.dupe])
self._app.mark_dupe(ref._dupe, value.toBool())
return True
if role == Qt.EditRole:
if index.column() == 0:
value = unicode(value.toString())
if self._app.rename_dupe(node.dupe, value):
node.invalidate()
return True
return self.model.rename_selected(value)
return False
def sort(self, column, order):
if self.power_marker:
self._results.sort_dupes(column, order == Qt.AscendingOrder, self.delta)
else:
self._results.sort_groups(column, order == Qt.AscendingOrder)
self.reset()
def toggleMarked(self, indexes):
assert indexes
dupes = self.dupesForIndexes(indexes)
self._app.toggle_marking_for_dupes(dupes)
self.model.sort(column, order == Qt.AscendingOrder)
#--- Properties
@property
def power_marker(self):
return self._power_marker
return self.model.power_marker
@power_marker.setter
def power_marker(self, value):
if value == self._power_marker:
return
self._power_marker = value
self.model.power_marker = value
@property
def delta_values(self):
return self.model.delta_values
@delta_values.setter
def delta_values(self, value):
self.model.delta_values = value
#--- Events
def selectionChanged(self, selected, deselected):
indexes = self.view.selectionModel().selectedRows()
nodes = [index.internalPointer() for index in indexes]
self.model.selected_nodes = [node.ref for node in nodes]
#--- model --> view
def refresh(self):
self.reset()
self._updateSelection()
self.view.expandAll()
def invalidate_markings(self):
# redraw view
# HACK. this is the only way I found to update the widget without reseting everything
self.view.scroll(0, 1)
self.view.scroll(0, -1)
class ResultsView(QTreeView):
#--- Override
def keyPressEvent(self, event):
if event.text() == ' ':
self.model().toggleMarked(self.selectionModel().selectedRows())
self.emit(SIGNAL('spacePressed()'))
return
QTreeView.keyPressEvent(self, event)
@ -193,11 +166,3 @@ class ResultsView(QTreeView):
self.emit(SIGNAL('doubleClicked()'))
# We don't call the superclass' method because the default behavior is to rename the cell.
def setModel(self, model):
assert isinstance(model, ResultsModel)
QTreeView.setModel(self, model)
#--- Public
def selectedDupes(self):
return self.model().dupesForIndexes(self.selectionModel().selectedRows())

20
qt/base/stats_label.py Normal file
View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-12
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "HS" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license
from core.gui.stats_label import StatsLabel as StatsLabelModel
class StatsLabel(object):
def __init__(self, app, view):
self.view = view
self.model = StatsLabelModel(self, app)
self.model.connect()
def refresh(self):
self.view.setText(self.model.display)