From d51f5184d781b84ce94d916a3318e84eeefc1381 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sat, 15 Jan 2011 16:29:35 +0100 Subject: [PATCH] The directories dialog is now the main window. There's probably many glitches left to fix due to that change, but the basic functionalities are there. --HG-- rename : qt/base/main_window.py => qt/base/result_window.py rename : qt/pe/main_window.py => qt/pe/result_window.py --- qt/base/app.py | 133 +++++++++------ qt/base/directories_dialog.py | 127 +++++++++++--- qt/base/preferences.py | 12 +- qt/base/{main_window.py => result_window.py} | 169 ++++--------------- qt/base/util.py | 21 +++ qt/pe/app.py | 6 +- qt/pe/{main_window.py => result_window.py} | 8 +- 7 files changed, 251 insertions(+), 225 deletions(-) rename qt/base/{main_window.py => result_window.py} (69%) create mode 100644 qt/base/util.py rename qt/pe/{main_window.py => result_window.py} (83%) diff --git a/qt/base/app.py b/qt/base/app.py index c849b30b..92e5f3b2 100644 --- a/qt/base/app.py +++ b/qt/base/app.py @@ -6,14 +6,12 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license - - import logging import os import os.path as op -from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL, pyqtSignal -from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox +from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, QProcess, SIGNAL, pyqtSignal +from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox, QApplication from jobprogress import job from jobprogress.qt import Progress @@ -21,13 +19,14 @@ from jobprogress.qt import Progress from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE from qtlib.about_box import AboutBox +from qtlib.recent import Recent from qtlib.reg import Registration from . import platform - -from .main_window import MainWindow +from .result_window import ResultWindow from .directories_dialog import DirectoriesDialog from .problem_dialog import ProblemDialog +from .util import createActions JOBID2TITLE = { JOB_SCAN: "Scanning for duplicates", @@ -54,16 +53,20 @@ class DupeGuru(DupeGuruBase, QObject): #--- Private def _setup(self): + self._setupActions() 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.problemDialog = ProblemDialog(parent=self.main_window, app=self) - self.preferences_dialog = self._create_preferences_dialog(self.main_window) - self.about_box = AboutBox(self.main_window, self) + self.resultWindow = self._create_result_window() + self._progress = Progress(self.resultWindow) + self.directories_dialog = DirectoriesDialog(self.resultWindow, self) + self.details_dialog = self._create_details_dialog(self.resultWindow) + self.problemDialog = ProblemDialog(parent=self.resultWindow, app=self) + self.preferences_dialog = self._create_preferences_dialog(self.resultWindow) + self.about_box = AboutBox(self.resultWindow, self) + + self.recentResults = Recent(self, self.directories_dialog.menuLoadRecent, 'recentResults') + self.recentResults.mustOpenItem.connect(self.load_from) self.reg = Registration(self) self.set_registration(self.prefs.registration_code, self.prefs.registration_email) @@ -73,19 +76,30 @@ class DupeGuru(DupeGuruBase, QObject): # In some circumstances, the nag is hidden by other window, which may make the user think # that the application haven't launched. QTimer.singleShot(0, self.reg.show_nag) - if self.prefs.mainWindowIsMaximized: - self.main_window.showMaximized() - else: - self.main_window.show() + self.directories_dialog.show() self.load() self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate) self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished) + def _setupActions(self): + # Setup actions that are common to both the directory dialog and the results window. + # (name, shortcut, icon, desc, func) + ACTIONS = [ + ('actionQuit', 'Ctrl+Q', '', "Quit", self.quitTriggered), + ('actionPreferences', 'Ctrl+5', 'preferences', "Preferences", self.preferencesTriggered), + ('actionShowHelp', 'F1', '', "dupeGuru Help", self.showHelpTriggered), + ('actionAbout', '', '', "About dupeGuru", self.showAboutBoxTriggered), + ('actionRegister', '', '', "Register dupeGuru", self.registerTriggered), + ('actionCheckForUpdate', '', '', "Check for Update", self.checkForUpdateTriggered), + ('actionOpenDebugLog', '', '', "Open Debug Log", self.openDebugLogTriggered), + ] + createActions(ACTIONS, self) + 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.actionRegister.setVisible(False) self.about_box.registerButton.hide() self.about_box.registeredEmailLabel.setText(self.prefs.registration_email) @@ -99,8 +113,8 @@ class DupeGuru(DupeGuruBase, QObject): def _create_details_dialog(self, parent): raise NotImplementedError() - def _create_main_window(self): - return MainWindow(app=self) + def _create_result_window(self): + return ResultWindow(app=self) def _create_preferences(self): raise NotImplementedError() @@ -126,7 +140,7 @@ class DupeGuru(DupeGuruBase, QObject): self._progress.run(jobid, title, func, args=args) 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) + QMessageBox.information(self.resultWindow, 'Action in progress', msg) def add_selected_to_ignore_list(self): dupes = self.without_ref(self.selected_dupes) @@ -134,14 +148,14 @@ class DupeGuru(DupeGuruBase, QObject): 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.main_window._confirm(title, msg): + if self.confirm(title, msg): DupeGuruBase.add_selected_to_ignore_list(self) 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 = str(QFileDialog.getExistingDirectory(self.main_window, title, '', flags)) + destination = str(QFileDialog.getExistingDirectory(self.resultWindow, title, '', flags)) if not destination: return recreate_path = self.prefs.destination_type @@ -153,46 +167,32 @@ class DupeGuru(DupeGuruBase, QObject): return title = "Remove duplicates" msg = "You are about to remove {0} files from results. Continue?".format(len(dupes)) - if self.main_window._confirm(title, msg): + if self.confirm(title, msg): DupeGuruBase.remove_selected(self) #--- Public def askForRegCode(self): self.reg.ask_for_code() + def confirm(self, title, msg, default_button=QMessageBox.Yes): + active = QApplication.activeWindow() + buttons = QMessageBox.Yes | QMessageBox.No + answer = QMessageBox.question(active, title, msg, buttons, default_button) + return answer == QMessageBox.Yes + def invokeCustomCommand(self): cmd = self.prefs.custom_command if cmd: self.invoke_command(cmd) else: msg = "You have no custom command set up. Please, set it up in your preferences." - QMessageBox.warning(self.main_window, 'Custom Command', msg) - - def openDebugLog(self): - debugLogPath = op.join(self.appdata, 'debug.log') - self._open_path(debugLogPath) - - def show_about_box(self): - self.about_box.show() + QMessageBox.warning(self.resultWindow, 'Custom Command', msg) def show_details(self): self.details_dialog.show() - def show_directories(self): - self.directories_dialog.show() - - def show_help(self): - base_path = platform.HELP_PATH.format(self.EDITION) - url = QUrl.fromLocalFile(op.abspath(op.join(base_path, 'index.html'))) - 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 showResultsWindow(self): + self.resultWindow.show() #--- Signals willSavePrefs = pyqtSignal() @@ -203,6 +203,9 @@ class DupeGuru(DupeGuruBase, QObject): self.prefs.save() self.save() + def checkForUpdateTriggered(self): + QProcess.execute('updater.exe', ['/checknow']) + def job_finished(self, jobid): self._job_completed(jobid) if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE): @@ -210,10 +213,40 @@ class DupeGuru(DupeGuruBase, QObject): self.problemDialog.show() else: msg = "All files were processed successfully." - QMessageBox.information(self.main_window, 'Operation Complete', msg) + QMessageBox.information(self.resultWindow, 'Operation Complete', msg) elif jobid == JOB_SCAN: if not self.results.groups: - title = "Scanning complete" + title = "Scan complete" msg = "No duplicates found." - QMessageBox.information(self.main_window, title, msg) + QMessageBox.information(self.resultWindow, title, msg) + else: + self.showResultsWindow() + elif jobid == JOB_LOAD: + self.showResultsWindow() + + def openDebugLogTriggered(self): + debugLogPath = op.join(self.appdata, 'debug.log') + self._open_path(debugLogPath) + + def preferencesTriggered(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 quitTriggered(self): + self.directories_dialog.close() + + def registerTriggered(self): + self.reg.ask_for_code() + + def showAboutBoxTriggered(self): + self.about_box.show() + + def showHelpTriggered(self): + base_path = platform.HELP_PATH.format(self.EDITION) + url = QUrl.fromLocalFile(op.abspath(op.join(base_path, 'index.html'))) + QDesktopServices.openUrl(url) diff --git a/qt/base/directories_dialog.py b/qt/base/directories_dialog.py index 5a7b2cec..07c65e89 100644 --- a/qt/base/directories_dialog.py +++ b/qt/base/directories_dialog.py @@ -6,17 +6,20 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -from PyQt4.QtCore import SIGNAL, Qt, QSize -from PyQt4.QtGui import (QDialog, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLayout, QTreeView, - QAbstractItemView, QSpacerItem, QSizePolicy, QPushButton, QApplication) +from PyQt4.QtCore import QSize, QRect +from PyQt4.QtGui import (QWidget, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLayout, QTreeView, + QAbstractItemView, QSpacerItem, QSizePolicy, QPushButton, QApplication, QMessageBox, QMainWindow, + QMenuBar, QMenu) + +from core.app import NoScannableFileError from . import platform from .directories_model import DirectoriesModel, DirectoriesDelegate +from .util import createActions -class DirectoriesDialog(QDialog): +class DirectoriesDialog(QMainWindow): def __init__(self, parent, app): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint - QDialog.__init__(self, parent, flags) + QMainWindow.__init__(self, None) self.app = app self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.directoriesModel = DirectoriesModel(self.app) @@ -24,21 +27,61 @@ class DirectoriesDialog(QDialog): 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.scanButton.clicked.connect(self.scanButtonClicked) + self.addButton.clicked.connect(self.addButtonClicked) + self.removeButton.clicked.connect(self.removeButtonClicked) + self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged) self.app.willSavePrefs.connect(self.appWillSavePrefs) + def _setupActions(self): + # (name, shortcut, icon, desc, func) + ACTIONS = [ + ('actionLoadResults', 'Ctrl+L', '', "Load Results...", self.loadResultsTriggered), + ('actionShowResultsWindow', '', '', "Results Window", self.app.showResultsWindow), + ] + createActions(ACTIONS, self) + + def _setupMenu(self): + self.menubar = QMenuBar(self) + self.menubar.setGeometry(QRect(0, 0, 42, 22)) + self.menuFile = QMenu(self.menubar) + self.menuFile.setTitle("File") + self.menuView = QMenu(self.menubar) + self.menuView.setTitle("View") + self.menuHelp = QMenu(self.menubar) + self.menuHelp.setTitle("Help") + self.menuLoadRecent = QMenu(self.menuFile) + self.menuLoadRecent.setTitle("Load Recent Results") + self.setMenuBar(self.menubar) + + self.menuFile.addAction(self.actionLoadResults) + self.menuFile.addAction(self.menuLoadRecent.menuAction()) + self.menuFile.addSeparator() + self.menuFile.addAction(self.app.actionQuit) + self.menuView.addAction(self.app.actionPreferences) + self.menuView.addAction(self.actionShowResultsWindow) + self.menuHelp.addAction(self.app.actionShowHelp) + self.menuHelp.addAction(self.app.actionRegister) + self.menuHelp.addAction(self.app.actionCheckForUpdate) + self.menuHelp.addAction(self.app.actionOpenDebugLog) + self.menuHelp.addAction(self.app.actionAbout) + + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + def _setupUi(self): - self.setWindowTitle("Directories") + self.setWindowTitle(self.app.NAME) self.resize(420, 338) - self.verticalLayout = QVBoxLayout(self) - self.treeView = QTreeView(self) + self.centralwidget = QWidget(self) + self.verticalLayout = QVBoxLayout(self.centralwidget) + self.treeView = QTreeView(self.centralwidget) self.treeView.setItemDelegate(self.directoriesDelegate) self.treeView.setModel(self.directoriesModel) self.treeView.setAcceptDrops(True) - self.treeView.setEditTriggers(QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed|QAbstractItemView.SelectedClicked) + triggers = QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed\ + |QAbstractItemView.SelectedClicked + self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setUniformRowHeights(True) @@ -51,31 +94,35 @@ class DirectoriesDialog(QDialog): self.horizontalLayout = QHBoxLayout() spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) - self.removeButton = QPushButton(self) + self.removeButton = QPushButton(self.centralwidget) self.removeButton.setText("Remove") self.removeButton.setShortcut("Del") self.removeButton.setMinimumSize(QSize(91, 0)) self.removeButton.setMaximumSize(QSize(16777215, 32)) self.horizontalLayout.addWidget(self.removeButton) - self.addButton = QPushButton(self) + self.addButton = QPushButton(self.centralwidget) self.addButton.setText("Add") self.addButton.setMinimumSize(QSize(91, 0)) self.addButton.setMaximumSize(QSize(16777215, 32)) self.horizontalLayout.addWidget(self.addButton) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Fixed, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) - self.doneButton = QPushButton(self) - self.doneButton.setText("Done") + self.scanButton = QPushButton(self.centralwidget) + self.scanButton.setText("Scan") sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.doneButton.sizePolicy().hasHeightForWidth()) - self.doneButton.setSizePolicy(sizePolicy) - self.doneButton.setMinimumSize(QSize(91, 0)) - self.doneButton.setMaximumSize(QSize(16777215, 32)) - self.doneButton.setDefault(True) - self.horizontalLayout.addWidget(self.doneButton) + sizePolicy.setHeightForWidth(self.scanButton.sizePolicy().hasHeightForWidth()) + self.scanButton.setSizePolicy(sizePolicy) + self.scanButton.setMinimumSize(QSize(91, 0)) + self.scanButton.setMaximumSize(QSize(16777215, 32)) + self.scanButton.setDefault(True) + self.horizontalLayout.addWidget(self.scanButton) self.verticalLayout.addLayout(self.horizontalLayout) + self.setCentralWidget(self.centralwidget) + + self._setupActions() + self._setupMenu() if self.app.prefs.directoriesWindowRect is not None: self.setGeometry(self.app.prefs.directoriesWindowRect) @@ -90,6 +137,17 @@ class DirectoriesDialog(QDialog): node = index.internalPointer() # label = 'Remove' if node.parent is None else 'Exclude' + #--- QWidget overrides + def closeEvent(self, event): + event.accept() + if self.app.results.is_modified: + title = "Unsaved results" + msg = "You have unsaved results, do you really want to quit?" + if not self.app.confirm(title, msg): + event.ignore() + if event.isAccepted(): + QApplication.quit() + #--- Events def addButtonClicked(self): title = "Select a directory to add to the scanning list" @@ -103,8 +161,13 @@ class DirectoriesDialog(QDialog): def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() - def doneButtonClicked(self): - self.hide() + def loadResultsTriggered(self): + title = "Select a results file to load" + files = "dupeGuru Results (*.dupeguru)" + destination = QFileDialog.getOpenFileName(self, title, '', files) + if destination: + self.app.load_from(destination) + self.app.recentResults.insertItem(destination) def removeButtonClicked(self): indexes = self.treeView.selectedIndexes() @@ -116,6 +179,18 @@ class DirectoriesDialog(QDialog): row = index.row() self.app.remove_directory(row) + def scanButtonClicked(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.app.confirm(title, msg): + return + try: + self.app.start_scanning() + except NoScannableFileError: + msg = "The selected directories contain no scannable file." + QMessageBox.warning(self, title, msg) + def selectionChanged(self, selected, deselected): self._updateRemoveButton() diff --git a/qt/base/preferences.py b/qt/base/preferences.py index 1e7d6c01..9fd4f8f3 100644 --- a/qt/base/preferences.py +++ b/qt/base/preferences.py @@ -36,8 +36,8 @@ class Preferences(PreferencesBase): self.columns_width[index] = width self.columns_visible = get('ColumnsVisible', self.columns_visible) - self.mainWindowIsMaximized = get('MainWindowIsMaximized', self.mainWindowIsMaximized) - self.mainWindowRect = self.get_rect('MainWindowRect', self.mainWindowRect) + self.resultWindowIsMaximized = get('ResultWindowIsMaximized', self.resultWindowIsMaximized) + self.resultWindowRect = self.get_rect('ResultWindowRect', self.resultWindowRect) self.detailsWindowRect = self.get_rect('DetailsWindowRect', self.detailsWindowRect) self.directoriesWindowRect = self.get_rect('DirectoriesWindowRect', self.directoriesWindowRect) self.recentResults = get('RecentResults', self.recentResults) @@ -59,8 +59,8 @@ class Preferences(PreferencesBase): self.destination_type = 1 self.custom_command = '' - self.mainWindowIsMaximized = False - self.mainWindowRect = None + self.resultWindowIsMaximized = False + self.resultWindowRect = None self.detailsWindowRect = None self.directoriesWindowRect = None self.recentResults = [] @@ -89,8 +89,8 @@ class Preferences(PreferencesBase): set_('ColumnsWidth', self.columns_width) set_('ColumnsVisible', self.columns_visible) - set_('MainWindowIsMaximized', self.mainWindowIsMaximized) - self.set_rect('MainWindowRect', self.mainWindowRect) + set_('ResultWindowIsMaximized', self.resultWindowIsMaximized) + self.set_rect('ResultWindowRect', self.resultWindowRect) self.set_rect('DetailsWindowRect', self.detailsWindowRect) self.set_rect('DirectoriesWindowRect', self.directoriesWindowRect) set_('RecentResults', self.recentResults) diff --git a/qt/base/main_window.py b/qt/base/result_window.py similarity index 69% rename from qt/base/main_window.py rename to qt/base/result_window.py index da97e5eb..b982cda2 100644 --- a/qt/base/main_window.py +++ b/qt/base/result_window.py @@ -8,21 +8,18 @@ import sys -from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl, QRect -from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView, - QMessageBox, QInputDialog, QLineEdit, QDesktopServices, QFileDialog, QAction, QMenuBar, - QToolBar, QWidget, QVBoxLayout, QAbstractItemView, QStatusBar) +from PyQt4.QtCore import Qt, SIGNAL, QUrl, QRect +from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QLabel, QHeaderView, QMessageBox, + QInputDialog, QLineEdit, QDesktopServices, QFileDialog, QMenuBar, QWidget, QVBoxLayout, + QAbstractItemView, QStatusBar) from hscommon.util import nonone -from qtlib.recent import Recent -from core.app import NoScannableFileError - -from . import dg_rc from .results_model import ResultsModel, ResultsView from .stats_label import StatsLabel +from .util import createActions -class MainWindow(QMainWindow): +class ResultWindow(QMainWindow): def __init__(self, app): QMainWindow.__init__(self, None) self.app = app @@ -41,11 +38,8 @@ class MainWindow(QMainWindow): def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ - ('actionScan', 'Ctrl+T', self.app.LOGO_NAME, "Start Scan", self.scanTriggered), - ('actionDirectories', 'Ctrl+4', 'folder', "Directories", self.directoriesTriggered), ('actionDetails', 'Ctrl+3', 'details', "Details", self.detailsTriggered), ('actionActions', '', 'actions', "Actions", self.actionsTriggered), - ('actionPreferences', 'Ctrl+5', 'preferences', "Preferences", self.preferencesTriggered), ('actionDelta', 'Ctrl+2', 'delta', "Delta Values", self.deltaTriggered), ('actionPowerMarker', 'Ctrl+1', 'power_marker', "Power Marker", self.powerMarkerTriggered), ('actionDeleteMarked', 'Ctrl+D', '', "Send Marked to Recycle Bin", self.deleteTriggered), @@ -64,28 +58,13 @@ class MainWindow(QMainWindow): ('actionInvertMarking', 'Ctrl+Alt+A', '', "Invert Marking", self.markInvertTriggered), ('actionMarkSelected', '', '', "Mark Selected", self.markSelectedTriggered), ('actionClearIgnoreList', '', '', "Clear Ignore List", self.clearIgnoreListTriggered), - ('actionQuit', 'Ctrl+Q', '', "Quit", self.close), ('actionApplyFilter', 'Ctrl+F', '', "Apply Filter", self.applyFilterTriggered), ('actionCancelFilter', 'Ctrl+Shift+F', '', "Cancel Filter", self.cancelFilterTriggered), - ('actionShowHelp', 'F1', '', "dupeGuru Help", self.showHelpTriggered), - ('actionAbout', '', '', "About dupeGuru", self.aboutTriggered), - ('actionRegister', '', '', "Register dupeGuru", self.registerTrigerred), - ('actionCheckForUpdate', '', '', "Check for Update", self.checkForUpdateTriggered), ('actionExport', '', '', "Export To HTML", self.exportTriggered), - ('actionLoadResults', 'Ctrl+L', '', "Load Results...", self.loadResultsTriggered), ('actionSaveResults', 'Ctrl+S', '', "Save Results...", self.saveResultsTriggered), - ('actionOpenDebugLog', '', '', "Open Debug Log", self.openDebugLogTriggered), ('actionInvokeCustomCommand', 'Ctrl+I', '', "Invoke Custom Command", self.app.invokeCustomCommand), ] - for name, shortcut, icon, desc, func in ACTIONS: - action = QAction(self) - if icon: - action.setIcon(QIcon(QPixmap(':/' + icon))) - if shortcut: - action.setShortcut(shortcut) - action.setText(desc) - action.triggered.connect(func) - setattr(self, name, action) + createActions(ACTIONS, self) self.actionDelta.setCheckable(True) self.actionPowerMarker.setCheckable(True) @@ -100,14 +79,10 @@ class MainWindow(QMainWindow): self.menuActions.setTitle("Actions") self.menuColumns = QMenu(self.menubar) self.menuColumns.setTitle("Columns") - self.menuModes = QMenu(self.menubar) - self.menuModes.setTitle("Modes") - self.menuWindow = QMenu(self.menubar) - self.menuWindow.setTitle("Windows") + self.menuView = QMenu(self.menubar) + self.menuView.setTitle("View") self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle("Help") - self.menuLoadRecent = QMenu(self.menuFile) - self.menuLoadRecent.setTitle("Load Recent Results") self.setMenuBar(self.menubar) self.menuActions.addAction(self.actionDeleteMarked) @@ -131,32 +106,27 @@ class MainWindow(QMainWindow): self.menuMark.addAction(self.actionMarkNone) self.menuMark.addAction(self.actionInvertMarking) self.menuMark.addAction(self.actionMarkSelected) - self.menuModes.addAction(self.actionPowerMarker) - self.menuModes.addAction(self.actionDelta) - self.menuWindow.addAction(self.actionDetails) - self.menuWindow.addAction(self.actionDirectories) - self.menuWindow.addAction(self.actionPreferences) - self.menuHelp.addAction(self.actionShowHelp) - self.menuHelp.addAction(self.actionRegister) - self.menuHelp.addAction(self.actionCheckForUpdate) - self.menuHelp.addAction(self.actionOpenDebugLog) - self.menuHelp.addAction(self.actionAbout) - self.menuFile.addAction(self.actionScan) - self.menuFile.addSeparator() - self.menuFile.addAction(self.actionLoadResults) - self.menuFile.addAction(self.menuLoadRecent.menuAction()) + self.menuView.addAction(self.actionPowerMarker) + self.menuView.addAction(self.actionDelta) + self.menuView.addSeparator() + self.menuView.addAction(self.actionDetails) + self.menuView.addAction(self.app.actionPreferences) + self.menuHelp.addAction(self.app.actionShowHelp) + self.menuHelp.addAction(self.app.actionRegister) + self.menuHelp.addAction(self.app.actionCheckForUpdate) + self.menuHelp.addAction(self.app.actionOpenDebugLog) + self.menuHelp.addAction(self.app.actionAbout) self.menuFile.addAction(self.actionSaveResults) self.menuFile.addAction(self.actionExport) self.menuFile.addAction(self.actionClearIgnoreList) self.menuFile.addSeparator() - self.menuFile.addAction(self.actionQuit) + self.menuFile.addAction(self.app.actionQuit) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuMark.menuAction()) self.menubar.addAction(self.menuActions.menuAction()) self.menubar.addAction(self.menuColumns.menuAction()) - self.menubar.addAction(self.menuModes.menuAction()) - self.menubar.addAction(self.menuWindow.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Columns menu @@ -190,27 +160,8 @@ class MainWindow(QMainWindow): actionMenu.addAction(self.actionRenameSelected) self.actionActions.setMenu(actionMenu) - def _setupToolbar(self): - self.toolBar = QToolBar(self) - self.toolBar.setMovable(False) - self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) - self.toolBar.setFloatable(False) - self.addToolBar(Qt.ToolBarArea(Qt.TopToolBarArea), self.toolBar) - - self.toolBar.addAction(self.actionScan) - button = QToolButton(self.toolBar) - button.setDefaultAction(self.actionActions.menu().menuAction()) - button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) - self.actionsButton = button - self.toolBar.addWidget(button) - self.toolBar.addAction(self.actionDirectories) - self.toolBar.addAction(self.actionDetails) - self.toolBar.addAction(self.actionPreferences) - self.toolBar.addAction(self.actionDelta) - self.toolBar.addAction(self.actionPowerMarker) - def _setupUi(self): - self.setWindowTitle(QCoreApplication.instance().applicationName()) + self.setWindowTitle("dupeGuru Results") self.resize(630, 514) self.centralwidget = QWidget(self) self.verticalLayout_2 = QVBoxLayout(self.centralwidget) @@ -230,17 +181,16 @@ class MainWindow(QMainWindow): self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() - self._setupToolbar() - self.recentResults = Recent(self.app, self.menuLoadRecent, 'recentResults') - self.recentResults.mustOpenItem.connect(self.app.load_from) self.statusbar = QStatusBar(self) self.statusbar.setSizeGripEnabled(True) self.setStatusBar(self.statusbar) self.statusLabel = QLabel(self) self.statusbar.addPermanentWidget(self.statusLabel, 1) - if self.app.prefs.mainWindowRect is not None and not self.app.prefs.mainWindowIsMaximized: - self.setGeometry(self.app.prefs.mainWindowRect) + if self.app.prefs.resultWindowIsMaximized: + self.setWindowState(self.windowState() | Qt.WindowMaximized) + if self.app.prefs.resultWindowRect is not None and not self.app.prefs.resultWindowIsMaximized: + self.setGeometry(self.app.prefs.resultWindowRect) # Platform-specific setup if sys.platform == 'linux2': @@ -249,11 +199,6 @@ class MainWindow(QMainWindow): self.actionHardlinkMarked.setVisible(False) #--- 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.horizontalHeader() h.setResizeMode(QHeaderView.Interactive) @@ -270,19 +215,7 @@ class MainWindow(QMainWindow): colid = action.column_index action.setChecked(not h.isSectionHidden(colid)) - #--- QWidget overrides - def closeEvent(self, event): - event.accept() - if self.app.results.is_modified: - title = "Unsaved results" - msg = "You have unsaved results, do you really want to quit?" - if not self._confirm(title, msg): - event.ignore() - #--- Actions - def aboutTriggered(self): - self.app.show_about_box() - def actionsTriggered(self): self.actionsButton.showMenu() @@ -303,9 +236,6 @@ class MainWindow(QMainWindow): def cancelFilterTriggered(self): self.app.apply_filter('') - def checkForUpdateTriggered(self): - QProcess.execute('updater.exe', ['/checknow']) - def clearIgnoreListTriggered(self): title = "Clear Ignore List" count = len(self.app.scanner.ignore_list) @@ -313,7 +243,7 @@ class MainWindow(QMainWindow): 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): + if self.app.confirm(title, msg, QMessageBox.No): self.app.scanner.ignore_list.Clear() QMessageBox.information(self, title, "Ignore list cleared.") @@ -326,7 +256,7 @@ class MainWindow(QMainWindow): return title = "Delete duplicates" msg = "You are about to send {0} files to the recycle bin. Continue?".format(count) - if self._confirm(title, msg): + if self.app.confirm(title, msg): self.app.delete_marked() def deltaTriggered(self): @@ -335,9 +265,6 @@ class MainWindow(QMainWindow): def detailsTriggered(self): self.app.show_details() - def directoriesTriggered(self): - self.app.show_directories() - def exportTriggered(self): h = self.resultsView.horizontalHeader() column_ids = [] @@ -354,17 +281,9 @@ class MainWindow(QMainWindow): return title = "Delete and hardlink duplicates" msg = "You are about to send {0} files to the trash and hardlink them afterwards. Continue?".format(count) - if self._confirm(title, msg): + if self.app.confirm(title, msg): self.app.delete_marked(replace_with_hardlinks=True) - def loadResultsTriggered(self): - title = "Select a results file to load" - files = "dupeGuru Results (*.dupeguru)" - destination = QFileDialog.getOpenFileName(self, title, '', files) - if destination: - self.app.load_from(destination) - self.recentResults.insertItem(destination) - def makeReferenceTriggered(self): self.app.make_selected_reference() @@ -383,9 +302,6 @@ class MainWindow(QMainWindow): def moveTriggered(self): self.app.copy_or_move_marked(False) - def openDebugLogTriggered(self): - self.app.openDebugLog() - def openTriggered(self): self.app.open_selected() @@ -395,16 +311,13 @@ class MainWindow(QMainWindow): def preferencesTriggered(self): self.app.show_preferences() - def registerTrigerred(self): - self.app.reg.ask_for_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): + if self.app.confirm(title, msg): self.app.remove_marked() def removeSelectedTriggered(self): @@ -422,23 +335,7 @@ class MainWindow(QMainWindow): destination = QFileDialog.getSaveFileName(self, title, '', files) if destination: self.app.save_as(destination) - self.recentResults.insertItem(destination) - - 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() - - def showHelpTriggered(self): - self.app.show_help() + self.app.recentResults.insertItem(destination) #--- Events def appWillSavePrefs(self): @@ -451,8 +348,8 @@ class MainWindow(QMainWindow): visible.append(not h.isSectionHidden(i)) prefs.columns_width = widths prefs.columns_visible = visible - prefs.mainWindowIsMaximized = self.isMaximized() - prefs.mainWindowRect = self.geometry() + prefs.resultWindowIsMaximized = self.isMaximized() + prefs.resultWindowRect = self.geometry() def columnToggled(self, action): colid = action.column_index diff --git a/qt/base/util.py b/qt/base/util.py new file mode 100644 index 00000000..11f8d70d --- /dev/null +++ b/qt/base/util.py @@ -0,0 +1,21 @@ +# Created By: Virgil Dupras +# Created On: 2011-01-15 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" 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/bsd_license + +from PyQt4.QtGui import QPixmap, QIcon, QAction + +def createActions(actions, target): + # actions = [(name, shortcut, icon, desc, func)] + for name, shortcut, icon, desc, func in actions: + action = QAction(target) + if icon: + action.setIcon(QIcon(QPixmap(':/' + icon))) + if shortcut: + action.setShortcut(shortcut) + action.setText(desc) + action.triggered.connect(func) + setattr(target, name, action) \ No newline at end of file diff --git a/qt/pe/app.py b/qt/pe/app.py index c3b339df..7bb648d1 100644 --- a/qt/pe/app.py +++ b/qt/pe/app.py @@ -20,7 +20,7 @@ from core_pe.scanner import ScannerPE from ..base.app import DupeGuru as DupeGuruBase from .block import getblocks from .details_dialog import DetailsDialog -from .main_window import MainWindow +from .result_window import ResultWindow from .preferences import Preferences from .preferences_dialog import PreferencesDialog @@ -79,8 +79,8 @@ class DupeGuru(DupeGuruBase): def _create_details_dialog(self, parent): return DetailsDialog(parent, self) - def _create_main_window(self): - return MainWindow(app=self) + def _create_result_window(self): + return ResultWindow(app=self) def _create_preferences(self): return Preferences() diff --git a/qt/pe/main_window.py b/qt/pe/result_window.py similarity index 83% rename from qt/pe/main_window.py rename to qt/pe/result_window.py index a06483d1..b03b23b9 100644 --- a/qt/pe/main_window.py +++ b/qt/pe/result_window.py @@ -9,11 +9,11 @@ from PyQt4.QtCore import SIGNAL from PyQt4.QtGui import QMessageBox, QAction -from ..base.main_window import MainWindow as MainWindowBase +from ..base.result_window import ResultWindow as ResultWindowBase -class MainWindow(MainWindowBase): +class ResultWindow(ResultWindowBase): def _setupUi(self): - MainWindowBase._setupUi(self) + ResultWindowBase._setupUi(self) self.actionClearPictureCache = QAction("Clear Picture Cache", self) self.menuFile.insertAction(self.actionClearIgnoreList, self.actionClearPictureCache) self.connect(self.actionClearPictureCache, SIGNAL("triggered()"), self.clearPictureCacheTriggered) @@ -21,7 +21,7 @@ class MainWindow(MainWindowBase): def clearPictureCacheTriggered(self): title = "Clear Picture Cache" msg = "Do you really want to remove all your cached picture analysis?" - if self._confirm(title, msg, QMessageBox.No): + if self.app.confirm(title, msg, QMessageBox.No): self.app.scanner.clear_picture_cache() QMessageBox.information(self, title, "Picture cache cleared.") \ No newline at end of file