1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-23 07:01:39 +00:00

Initial commit.

--HG--
extra : convert_revision : svn%3Ac306627e-7827-47d3-bdf0-9a457c9553a1/trunk%402
This commit is contained in:
hsoft
2009-06-01 09:55:11 +00:00
parent 4f197ffd5a
commit e9a97afdf8
354 changed files with 38083 additions and 0 deletions

0
base/qt/__init__.py Normal file
View File

35
base/qt/about_box.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
# Unit Name: about_box
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QCoreApplication, SIGNAL
from PyQt4.QtGui import QDialog, QDialogButtonBox, QPixmap
from about_box_ui import Ui_AboutBox
class AboutBox(QDialog, Ui_AboutBox):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be done in the Designer
self.setWindowTitle(u"About %s" % QCoreApplication.instance().applicationName())
self.nameLabel.setText(QCoreApplication.instance().applicationName())
self.versionLabel.setText('Version ' + QCoreApplication.instance().applicationVersion())
self.logoLabel.setPixmap(QPixmap(':/%s_big' % self.app.LOGO_NAME))
self.registerButton = self.buttonBox.addButton("Register", QDialogButtonBox.ActionRole)
#--- Events
def buttonClicked(self, button):
if button is self.registerButton:
self.app.ask_for_reg_code()

133
base/qt/about_box.ui Normal file
View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutBox</class>
<widget class="QDialog" name="AboutBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>190</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>About dupeGuru</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="logoLabel">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="dg.qrc">:/logo_me_big</pixmap>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="nameLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>dupeGuru</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Copyright Hardcoded Software 2009</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Registered To:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="registeredEmailLabel">
<property name="text">
<string>UNREGISTERED</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="dg.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutBox</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutBox</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

269
base/qt/app.py Normal file
View File

@@ -0,0 +1,269 @@
#!/usr/bin/env python
# Unit Name: app
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import logging
import os.path as op
import traceback
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
from hsutil import job
from hsutil.reg import RegistrationRequired
from dupeguru import data_pe
from dupeguru.app import (DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY,
JOB_DELETE)
from main_window import MainWindow
from directories_dialog import DirectoriesDialog
from about_box import AboutBox
from reg import Registration
from error_report_dialog import ErrorReportDialog
JOBID2TITLE = {
JOB_SCAN: "Scanning for duplicates",
JOB_LOAD: "Loading",
JOB_MOVE: "Moving",
JOB_COPY: "Copying",
JOB_DELETE: "Sending files to the recycle bin",
}
class Progress(QProgressDialog, job.ThreadedJobPerformer):
def __init__(self, parent):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QProgressDialog.__init__(self, '', u"Cancel", 0, 100, parent, flags)
self.setModal(True)
self.setAutoReset(False)
self.setAutoClose(False)
self._timer = QTimer()
self._jobid = ''
self.connect(self._timer, SIGNAL('timeout()'), self.updateProgress)
def updateProgress(self):
# the values might change before setValue happens
last_progress = self.last_progress
last_desc = self.last_desc
if not self._job_running or last_progress is None:
self._timer.stop()
self.close()
self.emit(SIGNAL('finished(QString)'), self._jobid)
if self._last_error is not None:
s = ''.join(traceback.format_exception(*self._last_error))
dialog = ErrorReportDialog(self.parent(), s)
dialog.exec_()
return
if self.wasCanceled():
self.job_cancelled = True
return
if last_desc:
self.setLabelText(last_desc)
self.setValue(last_progress)
def run(self, jobid, title, target, args=()):
self._jobid = jobid
self.reset()
self.setLabelText('')
self.run_threaded(target, args)
self.setWindowTitle(title)
self.show()
self._timer.start(500)
def demo_method(method):
def wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except RegistrationRequired:
msg = "The demo version of dupeGuru only allows 10 actions (delete/move/copy) per session."
QMessageBox.information(self.main_window, 'Demo', msg)
return wrapper
class DupeGuru(DupeGuruBase, QObject):
LOGO_NAME = '<replace this>'
NAME = '<replace this>'
DELTA_COLUMNS = frozenset()
def __init__(self, data_module, appid):
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
DupeGuruBase.__init__(self, data_module, appdata, appid)
QObject.__init__(self)
self._setup()
#--- Private
def _setup(self):
self.selected_dupe = None
self.prefs = self._create_preferences()
self.prefs.load()
self._update_options()
self.main_window = self._create_main_window()
self._progress = Progress(self.main_window)
self.directories_dialog = DirectoriesDialog(self.main_window, self)
self.details_dialog = self._create_details_dialog(self.main_window)
self.preferences_dialog = self._create_preferences_dialog(self.main_window)
self.about_box = AboutBox(self.main_window, self)
self.reg = Registration(self)
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
if not self.registered:
self.reg.show_nag()
self.main_window.show()
self.load()
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
def _setup_as_registered(self):
self.prefs.registration_code = self.registration_code
self.prefs.registration_email = self.registration_email
self.main_window.actionRegister.setVisible(False)
self.about_box.registerButton.hide()
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
def _update_options(self):
self.scanner.mix_file_kind = self.prefs.mix_file_kind
self.options['escape_filter_regexp'] = self.prefs.use_regexp
self.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
#--- Virtual
def _create_details_dialog(self, parent):
raise NotImplementedError()
def _create_main_window(self):
return MainWindow(app=self)
def _create_preferences(self):
raise NotImplementedError()
def _create_preferences_dialog(self, parent):
raise NotImplementedError()
#--- Override
def _start_job(self, jobid, func):
title = JOBID2TITLE[jobid]
try:
j = self._progress.create_job()
self._progress.run(jobid, title, func, args=(j, ))
except job.JobInProgressError:
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
QMessageBox.information(self.main_window, 'Action in progress', msg)
#--- Public
def add_dupes_to_ignore_list(self, duplicates):
for dupe in duplicates:
self.AddToIgnoreList(dupe)
self.remove_duplicates(duplicates)
def ApplyFilter(self, filter):
DupeGuruBase.ApplyFilter(self, filter)
self.emit(SIGNAL('resultsChanged()'))
def ask_for_reg_code(self):
if self.reg.ask_for_code():
self._setup_ui_as_registered()
@demo_method
def copy_or_move_marked(self, copy):
opname = 'copy' if copy else 'move'
title = "Select a directory to {0} marked files to".format(opname)
flags = QFileDialog.ShowDirsOnly
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
if not destination:
return
recreate_path = self.prefs.destination_type
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
delete_marked = demo_method(DupeGuruBase.delete_marked)
def make_reference(self, duplicates):
DupeGuruBase.make_reference(self, duplicates)
self.emit(SIGNAL('resultsChanged()'))
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 open_selected(self):
if self.selected_dupe is None:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path))
QDesktopServices.openUrl(url)
def remove_duplicates(self, duplicates):
self.results.remove_duplicates(duplicates)
self.emit(SIGNAL('resultsChanged()'))
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.move(dupe.parent, newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def reveal_selected(self):
if self.selected_dupe is None:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path[:-1]))
QDesktopServices.openUrl(url)
def select_duplicate(self, dupe):
self.selected_dupe = dupe
self.emit(SIGNAL('duplicateSelected()'))
def show_about_box(self):
self.about_box.show()
def show_details(self):
self.details_dialog.show()
def show_directories(self):
self.directories_dialog.show()
def show_help(self):
url = QUrl.fromLocalFile(op.abspath('help/intro.htm'))
QDesktopServices.openUrl(url)
def show_preferences(self):
self.preferences_dialog.load()
result = self.preferences_dialog.exec_()
if result == QDialog.Accepted:
self.preferences_dialog.save()
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()
self.SaveIgnoreList()
def job_finished(self, jobid):
self.emit(SIGNAL('resultsChanged()'))
if jobid == JOB_LOAD:
self.emit(SIGNAL('directoriesChanged()'))
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)

78
base/qt/details_table.py Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python
# Unit Name: details_table
# Created By: Virgil Dupras
# Created On: 2009-05-17
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, SIGNAL, QAbstractTableModel, QVariant
from PyQt4.QtGui import QHeaderView, QTableView
HEADER = ['Attribute', 'Selected', 'Reference']
class DetailsModel(QAbstractTableModel):
def __init__(self, app):
QAbstractTableModel.__init__(self)
self._app = app
self._data = app.data
self._dupe_data = None
self._ref_data = None
self.connect(app, SIGNAL('duplicateSelected()'), self.duplicateSelected)
def columnCount(self, parent):
return len(HEADER)
def data(self, index, role):
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole:
return QVariant()
column = index.column()
row = index.row()
if column == 0:
return QVariant(self._data.COLUMNS[row]['display'])
elif column == 1 and self._dupe_data:
return QVariant(self._dupe_data[row])
elif column == 2 and self._ref_data:
return QVariant(self._ref_data[row])
return QVariant()
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER):
return QVariant(HEADER[section])
return QVariant()
def rowCount(self, parent):
return len(self._data.COLUMNS)
#--- Events
def duplicateSelected(self):
dupe = self._app.selected_dupe
group = self._app.results.get_group_of_duplicate(dupe)
ref = group.ref
self._dupe_data = self._data.GetDisplayInfo(dupe, group)
self._ref_data = self._data.GetDisplayInfo(ref, group)
self.reset()
class DetailsTable(QTableView):
def __init__(self, *args):
QTableView.__init__(self, *args)
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QTableView.SelectRows)
self.setShowGrid(False)
def setModel(self, model):
QTableView.setModel(self, model)
# The model needs to be set to set header stuff
hheader = self.horizontalHeader()
hheader.setHighlightSections(False)
hheader.setStretchLastSection(False)
hheader.resizeSection(0, 100)
hheader.setResizeMode(0, QHeaderView.Fixed)
hheader.setResizeMode(1, QHeaderView.Stretch)
hheader.setResizeMode(2, QHeaderView.Stretch)
vheader = self.verticalHeader()
vheader.setVisible(False)
vheader.setDefaultSectionSize(18)

17
base/qt/dg.qrc Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="details">images/details32.png</file>
<file alias="logo_pe">images/dgpe_logo_32.png</file>
<file alias="logo_pe_big">images/dgpe_logo_128.png</file>
<file alias="logo_me">images/dgme_logo_32.png</file>
<file alias="logo_me_big">images/dgme_logo_128.png</file>
<file alias="logo_se">images/dgse_logo_32.png</file>
<file alias="logo_se_big">images/dgse_logo_128.png</file>
<file alias="folder">images/folderwin32.png</file>
<file alias="gear">images/gear.png</file>
<file alias="preferences">images/preferences32.png</file>
<file alias="actions">images/actions32.png</file>
<file alias="delta">images/delta32.png</file>
<file alias="power_marker">images/power_marker32.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
# Unit Name: directories_dialog
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QDialog, QFileDialog, QHeaderView
from directories_dialog_ui import Ui_DirectoriesDialog
from directories_model import DirectoriesModel, DirectoriesDelegate
class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self._updateRemoveButton()
self.connect(self.doneButton, SIGNAL('clicked()'), self.doneButtonClicked)
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.app, SIGNAL('directoriesChanged()'), self.directoriesChanged)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be done in the Designer
self.directoriesModel = DirectoriesModel(self.app)
self.directoriesDelegate = DirectoriesDelegate()
self.treeView.setItemDelegate(self.directoriesDelegate)
self.treeView.setModel(self.directoriesModel)
header = self.treeView.header()
header.setStretchLastSection(False)
header.setResizeMode(0, QHeaderView.Stretch)
header.setResizeMode(1, QHeaderView.Fixed)
header.resizeSection(1, 100)
def _updateRemoveButton(self):
indexes = self.treeView.selectedIndexes()
if not indexes:
self.removeButton.setEnabled(False)
return
self.removeButton.setEnabled(True)
index = indexes[0]
node = index.internalPointer()
# label = 'Remove' if node.parent is None else 'Exclude'
def addButtonClicked(self):
title = u"Select a directory to add to the scanning list"
flags = QFileDialog.ShowDirsOnly
dirpath = unicode(QFileDialog.getExistingDirectory(self, title, '', flags))
if not dirpath:
return
self.app.AddDirectory(dirpath)
self.directoriesModel.reset()
def directoriesChanged(self):
self.directoriesModel.reset()
def doneButtonClicked(self):
self.hide()
def removeButtonClicked(self):
indexes = self.treeView.selectedIndexes()
if not indexes:
return
index = indexes[0]
node = index.internalPointer()
if node.parent is None:
row = index.row()
del self.app.directories[row]
self.directoriesModel.reset()
def selectionChanged(self, selected, deselected):
self._updateRemoveButton()

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DirectoriesDialog</class>
<widget class="QDialog" name="DirectoriesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>420</width>
<height>338</height>
</rect>
</property>
<property name="windowTitle">
<string>Directories</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="treeView">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addButton">
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="doneButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Done</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python
# Unit Name: directories_model
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import QVariant, QModelIndex, Qt, QRect, QEvent, QPoint
from PyQt4.QtGui import QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush
from tree_model import TreeNode, TreeModel
HEADERS = ['Name', 'State']
STATES = ['Normal', 'Reference', 'Excluded']
class DirectoriesDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QComboBox(parent);
editor.addItems(STATES)
return editor
def setEditorData(self, editor, index):
value, ok = index.model().data(index, Qt.EditRole).toInt()
assert ok
editor.setCurrentIndex(value);
press = QMouseEvent(QEvent.MouseButtonPress, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
release = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QApplication.sendEvent(editor, press)
QApplication.sendEvent(editor, release)
# editor.showPopup() # this causes a weird glitch. the ugly workaround is above.
def setModelData(self, editor, model, index):
value = QVariant(editor.currentIndex())
model.setData(index, value, Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class DirectoryNode(TreeNode):
def __init__(self, parent, ref, row):
TreeNode.__init__(self, parent, row)
self.ref = ref
def _get_children(self):
children = []
for index, directory in enumerate(self.ref.dirs):
node = DirectoryNode(self, directory, index)
children.append(node)
return children
class DirectoriesModel(TreeModel):
def __init__(self, app):
self._dirs = app.directories
TreeModel.__init__(self)
def _root_nodes(self):
nodes = []
for index, directory in enumerate(self._dirs):
nodes.append(DirectoryNode(None, directory, index))
return nodes
def columnCount(self, parent):
return 2
def data(self, index, role):
if not index.isValid():
return QVariant()
node = index.internalPointer()
if role == Qt.DisplayRole:
if index.column() == 0:
return QVariant(node.ref.name)
else:
return QVariant(STATES[self._dirs.GetState(node.ref.path)])
elif role == Qt.EditRole and index.column() == 1:
return QVariant(self._dirs.GetState(node.ref.path))
elif role == Qt.ForegroundRole:
state = self._dirs.GetState(node.ref.path)
if state == 1:
return QVariant(QBrush(Qt.blue))
elif state == 2:
return QVariant(QBrush(Qt.red))
return QVariant()
def flags(self, index):
if not index.isValid():
return 0
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 1:
result |= Qt.ItemIsEditable
return result
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal:
if role == Qt.DisplayRole and section < len(HEADERS):
return QVariant(HEADERS[section])
return QVariant()
def setData(self, index, value, role):
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
return False
node = index.internalPointer()
state, ok = value.toInt()
assert ok
self._dirs.SetState(node.ref.path, state)
return True

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python
# Unit Name: error_report_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-23
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QUrl
from PyQt4.QtGui import QDialog, QDesktopServices
from error_report_dialog_ui import Ui_ErrorReportDialog
class ErrorReportDialog(QDialog, Ui_ErrorReportDialog):
def __init__(self, parent, error):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.setupUi(self)
self.errorTextEdit.setPlainText(error)
def accept(self):
text = self.errorTextEdit.toPlainText()
url = QUrl("mailto:support@hardcoded.net?SUBJECT=Error Report&BODY=%s" % text)
QDesktopServices.openUrl(url)
QDialog.accept(self)

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ErrorReportDialog</class>
<widget class="QDialog" name="ErrorReportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>553</width>
<height>349</height>
</rect>
</property>
<property name="windowTitle">
<string>Error Report</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Something went wrong. Would you like to send the error report to Hardcoded Software?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="errorTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="dontSendButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Don't Send</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sendButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Send</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>sendButton</sender>
<signal>clicked()</signal>
<receiver>ErrorReportDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>485</x>
<y>320</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>174</y>
</hint>
</hints>
</connection>
<connection>
<sender>dontSendButton</sender>
<signal>clicked()</signal>
<receiver>ErrorReportDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>373</x>
<y>320</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>174</y>
</hint>
</hints>
</connection>
</connections>
</ui>

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

@@ -0,0 +1,20 @@
#!/usr/bin/env python
# Unit Name: gen
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import os
def print_and_do(cmd):
print cmd
os.system(cmd)
print_and_do("pyuic4 main_window.ui > main_window_ui.py")
print_and_do("pyuic4 directories_dialog.ui > directories_dialog_ui.py")
print_and_do("pyuic4 about_box.ui > about_box_ui.py")
print_and_do("pyuic4 reg_submit_dialog.ui > reg_submit_dialog_ui.py")
print_and_do("pyuic4 reg_demo_dialog.ui > reg_demo_dialog_ui.py")
print_and_do("pyuic4 error_report_dialog.ui > error_report_dialog_ui.py")
print_and_do("pyrcc4 dg.qrc > dg_rc.py")

304
base/qt/main_window.py Normal file
View File

@@ -0,0 +1,304 @@
#!/usr/bin/env python
# Unit Name: main_window
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
QMessageBox, QInputDialog, QLineEdit)
from hsutil.misc import nonone
from dupeguru.app import NoScannableFileError, AllFilesAreRefError
import dg_rc
from main_window_ui import Ui_MainWindow
from results_model import ResultsDelegate, ResultsModel
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, app):
QMainWindow.__init__(self, None)
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._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)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
h = self.resultsView.header()
h.setHighlightSections(False)
h.setMovable(True)
h.setStretchLastSection(False)
h.setDefaultAlignment(Qt.AlignLeft)
self.setWindowTitle(QCoreApplication.instance().applicationName())
self.actionScan.setIcon(QIcon(QPixmap(':/%s' % self.app.LOGO_NAME)))
# Columns menu
menu = self.menuColumns
self._column_actions = []
for index, column in enumerate(self.app.data.COLUMNS):
action = menu.addAction(column['display'])
action.setCheckable(True)
action.column_index = index
self._column_actions.append(action)
menu.addSeparator()
action = menu.addAction("Reset to Defaults")
action.column_index = -1
# Action menu
actionMenu = QMenu('Actions', self.toolBar)
actionMenu.setIcon(QIcon(QPixmap(":/actions")))
actionMenu.addAction(self.actionDeleteMarked)
actionMenu.addAction(self.actionMoveMarked)
actionMenu.addAction(self.actionCopyMarked)
actionMenu.addAction(self.actionRemoveMarked)
actionMenu.addSeparator()
actionMenu.addAction(self.actionRemoveSelected)
actionMenu.addAction(self.actionIgnoreSelected)
actionMenu.addAction(self.actionMakeSelectedReference)
actionMenu.addSeparator()
actionMenu.addAction(self.actionOpenSelected)
actionMenu.addAction(self.actionRevealSelected)
actionMenu.addAction(self.actionRenameSelected)
self.actionActions.setMenu(actionMenu)
button = QToolButton(self.toolBar)
button.setDefaultAction(actionMenu.menuAction())
button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.actionsButton = button
self.toolBar.insertWidget(self.actionActions, button) # the action is a placeholder
self.toolBar.removeAction(self.actionActions)
self.statusLabel = QLabel(self)
self.statusbar.addPermanentWidget(self.statusLabel, 1)
#--- Private
def _confirm(self, title, msg, default_button=QMessageBox.Yes):
buttons = QMessageBox.Yes | QMessageBox.No
answer = QMessageBox.question(self, title, msg, buttons, default_button)
return answer == QMessageBox.Yes
def _load_columns(self):
h = self.resultsView.header()
h.setResizeMode(QHeaderView.Interactive)
prefs = self.app.prefs
attrs = zip(prefs.columns_width, prefs.columns_visible)
for index, (width, visible) in enumerate(attrs):
h.resizeSection(index, width)
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 = []
visible = []
for i in range(len(self.app.data.COLUMNS)):
widths.append(h.sectionSize(i))
visible.append(not h.isSectionHidden(i))
prefs = self.app.prefs
prefs.columns_width = widths
prefs.columns_visible = visible
prefs.save()
def _update_column_actions_status(self):
h = self.resultsView.header()
for action in self._column_actions:
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()
def actionsTriggered(self):
self.actionsButton.showMenu()
def addToIgnoreListTriggered(self):
dupes = self.resultsView.selectedDupes()
if not dupes:
return
title = "Add to Ignore List"
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
if self._confirm(title, msg):
self.app.add_dupes_to_ignore_list(dupes)
def applyFilterTriggered(self):
title = "Apply Filter"
msg = "Type the filter you want to apply on your results. See help for details."
text = nonone(self._last_filter, '[*]')
answer, ok = QInputDialog.getText(self, title, msg, QLineEdit.Normal, text)
if not ok:
return
answer = unicode(answer)
self.app.ApplyFilter(answer)
self._last_filter = answer
def cancelFilterTriggered(self):
self.app.ApplyFilter('')
def checkForUpdateTriggered(self):
QProcess.execute('updater.exe', ['/checknow'])
def clearIgnoreListTriggered(self):
title = "Clear Ignore List"
count = len(self.app.scanner.ignore_list)
if not count:
QMessageBox.information(self, title, "Nothing to clear.")
return
msg = "Do you really want to remove all {0} items from the ignore list?".format(count)
if self._confirm(title, msg, QMessageBox.No):
self.app.scanner.ignore_list.Clear()
QMessageBox.information(self, title, "Ignore list cleared.")
def copyTriggered(self):
self.app.copy_or_move_marked(True)
def deleteTriggered(self):
count = self.app.results.mark_count
if not count:
return
title = "Delete duplicates"
msg = "You are about to send {0} files to the recycle bin. Continue?".format(count)
if self._confirm(title, msg):
self.app.delete_marked()
def deltaTriggered(self):
self.resultsModel.delta = self.actionDelta.isChecked()
self._redraw_results()
def detailsTriggered(self):
self.app.show_details()
def directoriesTriggered(self):
self.app.show_directories()
def makeReferenceTriggered(self):
self.app.make_reference(self.resultsView.selectedDupes())
def markAllTriggered(self):
self.app.mark_all()
def markInvertTriggered(self):
self.app.mark_invert()
def markNoneTriggered(self):
self.app.mark_none()
def markSelectedTriggered(self):
dupes = self.resultsView.selectedDupes()
self.app.toggle_marking_for_dupes(dupes)
def moveTriggered(self):
self.app.copy_or_move_marked(False)
def openTriggered(self):
self.app.open_selected()
def powerMarkerTriggered(self):
self.resultsModel.power_marker = self.actionPowerMarker.isChecked()
def preferencesTriggered(self):
self.app.show_preferences()
def registerTrigerred(self):
self.app.ask_for_reg_code()
def removeMarkedTriggered(self):
count = self.app.results.mark_count
if not count:
return
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()
def removeSelectedTriggered(self):
dupes = self.resultsView.selectedDupes()
if not dupes:
return
title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
if self._confirm(title, msg):
self.app.remove_duplicates(dupes)
def renameTriggered(self):
self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
def revealTriggered(self):
self.app.reveal_selected()
def scanTriggered(self):
title = "Start a new scan"
if len(self.app.results.groups) > 0:
msg = "Are you sure you want to start a new duplicate scan?"
if not self._confirm(title, msg):
return
try:
self.app.start_scanning()
except NoScannableFileError:
msg = "The selected directories contain no scannable file."
QMessageBox.warning(self, title, msg)
self.app.show_directories()
except AllFilesAreRefError:
msg = "You cannot make a duplicate scan with only reference directories."
QMessageBox.warning(self, title, msg)
def showHelpTriggered(self):
self.app.show_help()
#--- Events
def application_will_terminate(self):
self._save_columns()
def columnToggled(self, action):
colid = action.column_index
if colid == -1:
self.app.prefs.reset_columns()
self._load_columns()
else:
h = self.resultsView.header()
h.setSectionHidden(colid, not h.isSectionHidden(colid))
self._update_column_actions_status()
def dupeMarkingChanged(self):
self._redraw_results()
self._update_status_line()
def resultsChanged(self):
self.resultsView.model().reset()
def resultsReset(self):
self.resultsView.expandAll()
self._update_status_line()
def selectionChanged(self, selected, deselected):
index = self.resultsView.selectionModel().currentIndex()
dupe = index.internalPointer().dupe if index.isValid() else None
self.app.select_duplicate(dupe)

911
base/qt/main_window.ui Normal file
View File

@@ -0,0 +1,911 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>514</height>
</rect>
</property>
<property name="windowTitle">
<string>dupeGuru</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ResultsView" name="resultsView">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuColumns">
<property name="title">
<string>Columns</string>
</property>
</widget>
<widget class="QMenu" name="menuActions">
<property name="title">
<string>Actions</string>
</property>
<addaction name="actionDeleteMarked"/>
<addaction name="actionMoveMarked"/>
<addaction name="actionCopyMarked"/>
<addaction name="actionRemoveMarked"/>
<addaction name="separator"/>
<addaction name="actionRemoveSelected"/>
<addaction name="actionIgnoreSelected"/>
<addaction name="actionMakeSelectedReference"/>
<addaction name="separator"/>
<addaction name="actionOpenSelected"/>
<addaction name="actionRevealSelected"/>
<addaction name="actionRenameSelected"/>
<addaction name="separator"/>
<addaction name="actionApplyFilter"/>
<addaction name="actionCancelFilter"/>
</widget>
<widget class="QMenu" name="menuMark">
<property name="title">
<string>Mark</string>
</property>
<addaction name="actionMarkAll"/>
<addaction name="actionMarkNone"/>
<addaction name="actionInvertMarking"/>
<addaction name="actionMarkSelected"/>
</widget>
<widget class="QMenu" name="menuModes">
<property name="title">
<string>Modes</string>
</property>
<addaction name="actionPowerMarker"/>
<addaction name="actionDelta"/>
</widget>
<widget class="QMenu" name="menuWindow">
<property name="title">
<string>Windows</string>
</property>
<addaction name="actionDetails"/>
<addaction name="actionDirectories"/>
<addaction name="actionPreferences"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionShowHelp"/>
<addaction name="actionRegister"/>
<addaction name="actionCheckForUpdate"/>
<addaction name="actionAbout"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionScan"/>
<addaction name="separator"/>
<addaction name="actionClearIgnoreList"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuMark"/>
<addaction name="menuActions"/>
<addaction name="menuColumns"/>
<addaction name="menuModes"/>
<addaction name="menuWindow"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionScan"/>
<addaction name="actionActions"/>
<addaction name="actionDirectories"/>
<addaction name="actionDetails"/>
<addaction name="actionPreferences"/>
<addaction name="actionDelta"/>
<addaction name="actionPowerMarker"/>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
</widget>
<action name="actionScan">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/logo_pe</normaloff>:/logo_pe</iconset>
</property>
<property name="text">
<string>Start Scan</string>
</property>
<property name="toolTip">
<string>Start scanning for duplicates</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionDirectories">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/folder</normaloff>:/folder</iconset>
</property>
<property name="text">
<string>Directories</string>
</property>
<property name="shortcut">
<string>Ctrl+4</string>
</property>
</action>
<action name="actionDetails">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/details</normaloff>:/details</iconset>
</property>
<property name="text">
<string>Details</string>
</property>
<property name="shortcut">
<string>Ctrl+3</string>
</property>
</action>
<action name="actionActions">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/actions</normaloff>:/actions</iconset>
</property>
<property name="text">
<string>Actions</string>
</property>
</action>
<action name="actionPreferences">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/preferences</normaloff>:/preferences</iconset>
</property>
<property name="text">
<string>Preferences</string>
</property>
<property name="shortcut">
<string>Ctrl+5</string>
</property>
</action>
<action name="actionDelta">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/delta</normaloff>:/delta</iconset>
</property>
<property name="text">
<string>Delta Values</string>
</property>
<property name="shortcut">
<string>Ctrl+2</string>
</property>
</action>
<action name="actionPowerMarker">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/power_marker</normaloff>:/power_marker</iconset>
</property>
<property name="text">
<string>Power Marker</string>
</property>
<property name="shortcut">
<string>Ctrl+1</string>
</property>
</action>
<action name="actionDeleteMarked">
<property name="text">
<string>Send Marked to Recycle Bin</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
<action name="actionMoveMarked">
<property name="text">
<string>Move Marked to...</string>
</property>
<property name="shortcut">
<string>Ctrl+M</string>
</property>
</action>
<action name="actionCopyMarked">
<property name="text">
<string>Copy Marked to...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+M</string>
</property>
</action>
<action name="actionRemoveMarked">
<property name="text">
<string>Remove Marked from Results</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionRemoveSelected">
<property name="text">
<string>Remove Selected from Results</string>
</property>
<property name="shortcut">
<string>Ctrl+Del</string>
</property>
</action>
<action name="actionIgnoreSelected">
<property name="text">
<string>Add Selected to Ignore List</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+Del</string>
</property>
</action>
<action name="actionMakeSelectedReference">
<property name="text">
<string>Make Selected Reference</string>
</property>
<property name="shortcut">
<string>Ctrl+Space</string>
</property>
</action>
<action name="actionOpenSelected">
<property name="text">
<string>Open Selected with Default Application</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionRevealSelected">
<property name="text">
<string>Open Containing Folder of Selected</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+O</string>
</property>
</action>
<action name="actionRenameSelected">
<property name="text">
<string>Rename Selected</string>
</property>
<property name="shortcut">
<string>F2</string>
</property>
</action>
<action name="actionMarkAll">
<property name="text">
<string>Mark All</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="actionMarkNone">
<property name="text">
<string>Mark None</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+A</string>
</property>
</action>
<action name="actionInvertMarking">
<property name="text">
<string>Invert Marking</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+A</string>
</property>
</action>
<action name="actionMarkSelected">
<property name="text">
<string>Mark Selected</string>
</property>
</action>
<action name="actionClearIgnoreList">
<property name="text">
<string>Clear Ignore List</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionApplyFilter">
<property name="text">
<string>Apply Filter</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionCancelFilter">
<property name="text">
<string>Cancel Filter</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+F</string>
</property>
</action>
<action name="actionShowHelp">
<property name="text">
<string>dupeGuru Help</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About dupeGuru</string>
</property>
</action>
<action name="actionRegister">
<property name="text">
<string>Register dupeGuru</string>
</property>
</action>
<action name="actionCheckForUpdate">
<property name="text">
<string>Check for Update</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>ResultsView</class>
<extends>QTreeView</extends>
<header>results_model</header>
</customwidget>
</customwidgets>
<resources>
<include location="dg.qrc"/>
</resources>
<connections>
<connection>
<sender>actionDirectories</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>directoriesTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionActions</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>actionsTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCopyMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>copyTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDeleteMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>deleteTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDelta</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>deltaTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDetails</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>detailsTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionIgnoreSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>addToIgnoreListTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMakeSelectedReference</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>makeReferenceTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMoveMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>moveTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionOpenSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>openTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionPowerMarker</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>powerMarkerTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionPreferences</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>preferencesTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRemoveMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>removeMarkedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRemoveSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>removeSelectedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRevealSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>revealTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRenameSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>renameTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionScan</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>scanTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionClearIgnoreList</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>clearIgnoreListTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkAll</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markAllTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkNone</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markNoneTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markSelectedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionInvertMarking</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markInvertTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionApplyFilter</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>applyFilterTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCancelFilter</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>cancelFilterTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionShowHelp</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>showHelpTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionAbout</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>aboutTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRegister</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>registerTrigerred()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCheckForUpdate</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>checkForUpdateTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>directoriesTriggered()</slot>
<slot>scanTriggered()</slot>
<slot>actionsTriggered()</slot>
<slot>detailsTriggered()</slot>
<slot>preferencesTriggered()</slot>
<slot>deltaTriggered()</slot>
<slot>powerMarkerTriggered()</slot>
<slot>deleteTriggered()</slot>
<slot>moveTriggered()</slot>
<slot>copyTriggered()</slot>
<slot>removeMarkedTriggered()</slot>
<slot>removeSelectedTriggered()</slot>
<slot>addToIgnoreListTriggered()</slot>
<slot>makeReferenceTriggered()</slot>
<slot>openTriggered()</slot>
<slot>revealTriggered()</slot>
<slot>renameTriggered()</slot>
<slot>clearIgnoreListTriggered()</slot>
<slot>clearPictureCacheTriggered()</slot>
<slot>markAllTriggered()</slot>
<slot>markNoneTriggered()</slot>
<slot>markInvertTriggered()</slot>
<slot>markSelectedTriggered()</slot>
<slot>applyFilterTriggered()</slot>
<slot>cancelFilterTriggered()</slot>
<slot>showHelpTriggered()</slot>
<slot>aboutTriggered()</slot>
<slot>registerTrigerred()</slot>
<slot>checkForUpdateTriggered()</slot>
</slots>
</ui>

109
base/qt/preferences.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python
# Unit Name: preferences
# Created By: Virgil Dupras
# Created On: 2009-05-03
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import QSettings, QVariant
from hsutil.misc import tryint
def variant_to_py(v):
value = None
ok = False
t = v.type()
if t == QVariant.String:
value = unicode(v.toString())
ok = True # anyway
# might be bool or int, try them
if v == 'true':
value = True
elif value == 'false':
value = False
else:
value = tryint(value, value)
elif t == QVariant.Int:
value, ok = v.toInt()
elif t == QVariant.Bool:
value, ok = v.toBool(), True
elif t in (QVariant.List, QVariant.StringList):
value, ok = map(variant_to_py, v.toList()), True
if not ok:
raise TypeError()
return value
class Preferences(object):
# (width, is_visible)
COLUMNS_DEFAULT_ATTRS = []
def __init__(self):
self.reset()
self.reset_columns()
def _load_specific(self, settings, get):
# load prefs specific to the dg edition
pass
def load(self):
self.reset()
settings = QSettings()
def get(name, default):
if settings.contains(name):
return variant_to_py(settings.value(name))
else:
return default
self.filter_hardness = get('FilterHardness', self.filter_hardness)
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
self.use_regexp = get('UseRegexp', self.use_regexp)
self.remove_empty_folders = get('RemoveEmptyFolders', self.remove_empty_folders)
self.destination_type = get('DestinationType', self.destination_type)
widths = get('ColumnsWidth', self.columns_width)
# only set nonzero values
for index, width in enumerate(widths[:len(self.columns_width)]):
if width > 0:
self.columns_width[index] = width
self.columns_visible = get('ColumnsVisible', self.columns_visible)
self.registration_code = get('RegistrationCode', self.registration_code)
self.registration_email = get('RegistrationEmail', self.registration_email)
self._load_specific(settings, get)
def _reset_specific(self):
# reset prefs specific to the dg edition
pass
def reset(self):
self.filter_hardness = 95
self.mix_file_kind = True
self.use_regexp = False
self.remove_empty_folders = False
self.destination_type = 1
self.registration_code = ''
self.registration_email = ''
self._reset_specific()
def reset_columns(self):
self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS]
self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS]
def _save_specific(self, settings, set_):
# save prefs specific to the dg edition
pass
def save(self):
settings = QSettings()
def set_(name, value):
settings.setValue(name, QVariant(value))
set_('FilterHardness', self.filter_hardness)
set_('MixFileKind', self.mix_file_kind)
set_('UseRegexp', self.use_regexp)
set_('RemoveEmptyFolders', self.remove_empty_folders)
set_('DestinationType', self.destination_type)
set_('ColumnsWidth', self.columns_width)
set_('ColumnsVisible', self.columns_visible)
set_('RegistrationCode', self.registration_code)
set_('RegistrationEmail', self.registration_email)
self._save_specific(settings, set_)

34
base/qt/reg.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
# Unit Name: reg
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from hashlib import md5
from PyQt4.QtGui import QDialog
from reg_submit_dialog import RegSubmitDialog
from reg_demo_dialog import RegDemoDialog
class Registration(object):
def __init__(self, app):
self.app = app
def ask_for_code(self):
dialog = RegSubmitDialog(self.app.main_window, self.app.is_code_valid)
result = dialog.exec_()
code = unicode(dialog.codeEdit.text())
email = unicode(dialog.emailEdit.text())
dialog.setParent(None) # free it
if result == QDialog.Accepted and self.app.is_code_valid(code, email):
self.app.set_registration(code, email)
return True
return False
def show_nag(self):
dialog = RegDemoDialog(self.app.main_window, self)
dialog.exec_()
dialog.setParent(None) # free it

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python
# Unit Name: reg_demo_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-10
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
from reg_demo_dialog_ui import Ui_RegDemoDialog
class RegDemoDialog(QDialog, Ui_RegDemoDialog):
def __init__(self, parent, reg):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.reg = reg
self._setupUi()
self.connect(self.enterCodeButton, SIGNAL('clicked()'), self.enterCodeClicked)
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
appname = QCoreApplication.instance().applicationName()
title = self.windowTitle()
title = title.replace('$appname', appname)
self.setWindowTitle(title)
title = self.titleLabel.text()
title = title.replace('$appname', appname)
self.titleLabel.setText(title)
desc = self.demoDescLabel.text()
desc = desc.replace('$appname', appname)
self.demoDescLabel.setText(desc)
#--- Events
def enterCodeClicked(self):
if self.reg.ask_for_code():
self.accept()
def purchaseClicked(self):
url = QUrl('http://www.hardcoded.net/purchase.htm')
QDesktopServices.openUrl(url)

140
base/qt/reg_demo_dialog.ui Normal file
View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegDemoDialog</class>
<widget class="QDialog" name="RegDemoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>387</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>$appname Demo Version</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>$appname Demo Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="demoDescLabel">
<property name="text">
<string>You are currently running a demo version of $appname. This version has limited functionalities, and you need to buy it to have access to these functionalities.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="tryButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Try Demo</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="enterCodeButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Enter Code</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="purchaseButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Purchase</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>tryButton</sender>
<signal>clicked()</signal>
<receiver>RegDemoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>112</x>
<y>161</y>
</hint>
<hint type="destinationlabel">
<x>201</x>
<y>94</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python
# Unit Name: reg_submit_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
from reg_submit_dialog_ui import Ui_RegSubmitDialog
class RegSubmitDialog(QDialog, Ui_RegSubmitDialog):
def __init__(self, parent, is_valid_func):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self._setupUi()
self.is_valid_func = is_valid_func
self.connect(self.submitButton, SIGNAL('clicked()'), self.submitClicked)
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
appname = QCoreApplication.instance().applicationName()
prompt = self.promptLabel.text()
prompt = prompt.replace('$appname', appname)
self.promptLabel.setText(prompt)
#--- Events
def purchaseClicked(self):
url = QUrl('http://www.hardcoded.net/purchase.htm')
QDesktopServices.openUrl(url)
def submitClicked(self):
code = unicode(self.codeEdit.text())
email = unicode(self.emailEdit.text())
title = "Registration"
if self.is_valid_func(code, email):
msg = "This code is valid. Thanks!"
QMessageBox.information(self, title, msg)
self.accept()
else:
msg = "This code is invalid"
QMessageBox.warning(self, title, msg)

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegSubmitDialog</class>
<widget class="QDialog" name="RegSubmitDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>134</height>
</rect>
</property>
<property name="windowTitle">
<string>Enter your registration code</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="promptLabel">
<property name="text">
<string>Please enter your $appname registration code and registered e-mail (the e-mail you used for the purchase), then press &quot;Submit&quot;.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Registration code:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Registered e-mail:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="codeEdit"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="emailEdit"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="purchaseButton">
<property name="text">
<string>Purchase</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="submitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Submit</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cancelButton</sender>
<signal>clicked()</signal>
<receiver>RegSubmitDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>260</x>
<y>159</y>
</hint>
<hint type="destinationlabel">
<x>198</x>
<y>97</y>
</hint>
</hints>
</connection>
</connections>
</ui>

175
base/qt/results_model.py Normal file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python
# Unit Name:
# Created By: Virgil Dupras
# Created On: 2009-04-23
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QVariant, QModelIndex, QRect
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor
from tree_model import TreeNode, TreeModel
class ResultNode(TreeNode):
def __init__(self, model, parent, row, dupe, group):
TreeNode.__init__(self, parent, row)
self.model = model
self.dupe = dupe
self.group = group
self._normalData = None
self._deltaData = None
def _get_children(self):
children = []
if self.dupe is self.group.ref:
for index, dupe in enumerate(self.group.dupes):
children.append(ResultNode(self.model, self, index, dupe, self.group))
return children
def reset(self):
self._normalData = None
self._deltaData = None
@property
def normalData(self):
if self._normalData is None:
self._normalData = self.model._data.GetDisplayInfo(self.dupe, self.group, delta=False)
return self._normalData
@property
def deltaData(self):
if self._deltaData is None:
self._deltaData = self.model._data.GetDisplayInfo(self.dupe, self.group, delta=True)
return self._deltaData
class ResultsDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
QStyledItemDelegate.initStyleOption(self, option, index)
node = index.internalPointer()
if node.group.ref is node.dupe:
newfont = QFont(option.font)
newfont.setBold(True)
option.font = newfont
class ResultsModel(TreeModel):
def __init__(self, app):
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)
def _root_nodes(self):
nodes = []
if self.power_marker:
for index, dupe in enumerate(self._results.dupes):
group = self._results.get_group_of_duplicate(dupe)
nodes.append(ResultNode(self, None, index, dupe, group))
else:
for index, group in enumerate(self._results.groups):
nodes.append(ResultNode(self, None, index, group.ref, group))
return nodes
def columnCount(self, parent):
return len(self._data.COLUMNS)
def data(self, index, role):
if not index.isValid():
return QVariant()
node = index.internalPointer()
if role == Qt.DisplayRole:
data = node.deltaData if self.delta else node.normalData
return QVariant(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 QVariant(state)
elif role == Qt.ForegroundRole:
if node.dupe is node.group.ref or node.dupe.is_ref:
return QVariant(QBrush(Qt.blue))
elif self.delta and index.column() in self._delta_columns:
return QVariant(QBrush(QColor(255, 142, 40))) # orange
elif role == Qt.EditRole:
if index.column() == 0:
return QVariant(node.normalData[index.column()])
return QVariant()
def dupesForIndexes(self, indexes):
nodes = [index.internalPointer() for index in indexes]
return [node.dupe for node in nodes]
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
return flags
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self._data.COLUMNS):
return QVariant(self._data.COLUMNS[section]['display'])
return QVariant()
def setData(self, index, value, role):
if not index.isValid():
return False
node = index.internalPointer()
if role == Qt.CheckStateRole:
if index.column() == 0:
self._app.toggle_marking_for_dupes([node.dupe])
return True
if role == Qt.EditRole:
if index.column() == 0:
value = unicode(value.toString())
if self._app.rename_dupe(node.dupe, value):
node.reset()
return True
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)
#--- Properties
@property
def power_marker(self):
return self._power_marker
@power_marker.setter
def power_marker(self, value):
if value == self._power_marker:
return
self._power_marker = value
self.reset()
class ResultsView(QTreeView):
#--- Override
def keyPressEvent(self, event):
if event.text() == ' ':
self.model().toggleMarked(self.selectionModel().selectedRows())
return
QTreeView.keyPressEvent(self, event)
def setModel(self, model):
assert isinstance(model, ResultsModel)
QTreeView.setModel(self, model)
#--- Public
def selectedDupes(self):
return self.model().dupesForIndexes(self.selectionModel().selectedRows())

66
base/qt/tree_model.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python
# Unit Name: tree_model
# Created By: Virgil Dupras
# Created On: 2009-05-04
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QAbstractItemModel, QVariant, QModelIndex
class TreeNode(object):
def __init__(self, parent, row):
self.parent = parent
self.row = row
self._children = None
def _get_children(self):
raise NotImplementedError()
@property
def children(self):
if self._children is None:
self._children = self._get_children()
return self._children
class TreeModel(QAbstractItemModel):
def __init__(self):
QAbstractItemModel.__init__(self)
self._nodes = None
def _root_nodes(self):
raise NotImplementedError()
def index(self, row, column, parent):
if not self.nodes:
return QModelIndex()
if not parent.isValid():
return self.createIndex(row, column, self.nodes[row])
node = parent.internalPointer()
return self.createIndex(row, column, node.children[row])
def parent(self, index):
if not index.isValid():
return QModelIndex()
node = index.internalPointer()
if node.parent is None:
return QModelIndex()
else:
return self.createIndex(node.parent.row, 0, node.parent)
def reset(self):
self._nodes = None
QAbstractItemModel.reset(self)
def rowCount(self, parent):
if not parent.isValid():
return len(self.nodes)
node = parent.internalPointer()
return len(node.children)
@property
def nodes(self):
if self._nodes is None:
self._nodes = self._root_nodes()
return self._nodes