1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2025-03-10 13:44:37 +00:00

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
This commit is contained in:
Virgil Dupras 2011-01-15 16:29:35 +01:00
parent 30eb26af7d
commit d51f5184d7
7 changed files with 251 additions and 225 deletions

View File

@ -6,14 +6,12 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license # http://www.hardcoded.net/licenses/bsd_license
import logging import logging
import os import os
import os.path as op import os.path as op
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL, pyqtSignal from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, QProcess, SIGNAL, pyqtSignal
from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox, QApplication
from jobprogress import job from jobprogress import job
from jobprogress.qt import Progress 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 core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from qtlib.about_box import AboutBox from qtlib.about_box import AboutBox
from qtlib.recent import Recent
from qtlib.reg import Registration from qtlib.reg import Registration
from . import platform from . import platform
from .result_window import ResultWindow
from .main_window import MainWindow
from .directories_dialog import DirectoriesDialog from .directories_dialog import DirectoriesDialog
from .problem_dialog import ProblemDialog from .problem_dialog import ProblemDialog
from .util import createActions
JOBID2TITLE = { JOBID2TITLE = {
JOB_SCAN: "Scanning for duplicates", JOB_SCAN: "Scanning for duplicates",
@ -54,16 +53,20 @@ class DupeGuru(DupeGuruBase, QObject):
#--- Private #--- Private
def _setup(self): def _setup(self):
self._setupActions()
self.prefs = self._create_preferences() self.prefs = self._create_preferences()
self.prefs.load() self.prefs.load()
self._update_options() self._update_options()
self.main_window = self._create_main_window() self.resultWindow = self._create_result_window()
self._progress = Progress(self.main_window) self._progress = Progress(self.resultWindow)
self.directories_dialog = DirectoriesDialog(self.main_window, self) self.directories_dialog = DirectoriesDialog(self.resultWindow, self)
self.details_dialog = self._create_details_dialog(self.main_window) self.details_dialog = self._create_details_dialog(self.resultWindow)
self.problemDialog = ProblemDialog(parent=self.main_window, app=self) self.problemDialog = ProblemDialog(parent=self.resultWindow, app=self)
self.preferences_dialog = self._create_preferences_dialog(self.main_window) self.preferences_dialog = self._create_preferences_dialog(self.resultWindow)
self.about_box = AboutBox(self.main_window, self) 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.reg = Registration(self)
self.set_registration(self.prefs.registration_code, self.prefs.registration_email) 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 # In some circumstances, the nag is hidden by other window, which may make the user think
# that the application haven't launched. # that the application haven't launched.
QTimer.singleShot(0, self.reg.show_nag) QTimer.singleShot(0, self.reg.show_nag)
if self.prefs.mainWindowIsMaximized: self.directories_dialog.show()
self.main_window.showMaximized()
else:
self.main_window.show()
self.load() self.load()
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate) self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished) 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): def _setup_as_registered(self):
self.prefs.registration_code = self.registration_code self.prefs.registration_code = self.registration_code
self.prefs.registration_email = self.registration_email 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.registerButton.hide()
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email) self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
@ -99,8 +113,8 @@ class DupeGuru(DupeGuruBase, QObject):
def _create_details_dialog(self, parent): def _create_details_dialog(self, parent):
raise NotImplementedError() raise NotImplementedError()
def _create_main_window(self): def _create_result_window(self):
return MainWindow(app=self) return ResultWindow(app=self)
def _create_preferences(self): def _create_preferences(self):
raise NotImplementedError() raise NotImplementedError()
@ -126,7 +140,7 @@ class DupeGuru(DupeGuruBase, QObject):
self._progress.run(jobid, title, func, args=args) self._progress.run(jobid, title, func, args=args)
except job.JobInProgressError: 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." 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): def add_selected_to_ignore_list(self):
dupes = self.without_ref(self.selected_dupes) dupes = self.without_ref(self.selected_dupes)
@ -134,14 +148,14 @@ class DupeGuru(DupeGuruBase, QObject):
return return
title = "Add to Ignore List" title = "Add to Ignore List"
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes)) 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) DupeGuruBase.add_selected_to_ignore_list(self)
def copy_or_move_marked(self, copy): def copy_or_move_marked(self, copy):
opname = 'copy' if copy else 'move' opname = 'copy' if copy else 'move'
title = "Select a directory to {0} marked files to".format(opname) title = "Select a directory to {0} marked files to".format(opname)
flags = QFileDialog.ShowDirsOnly flags = QFileDialog.ShowDirsOnly
destination = str(QFileDialog.getExistingDirectory(self.main_window, title, '', flags)) destination = str(QFileDialog.getExistingDirectory(self.resultWindow, title, '', flags))
if not destination: if not destination:
return return
recreate_path = self.prefs.destination_type recreate_path = self.prefs.destination_type
@ -153,46 +167,32 @@ class DupeGuru(DupeGuruBase, QObject):
return return
title = "Remove duplicates" title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes)) 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) DupeGuruBase.remove_selected(self)
#--- Public #--- Public
def askForRegCode(self): def askForRegCode(self):
self.reg.ask_for_code() 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): def invokeCustomCommand(self):
cmd = self.prefs.custom_command cmd = self.prefs.custom_command
if cmd: if cmd:
self.invoke_command(cmd) self.invoke_command(cmd)
else: else:
msg = "You have no custom command set up. Please, set it up in your preferences." msg = "You have no custom command set up. Please, set it up in your preferences."
QMessageBox.warning(self.main_window, 'Custom Command', msg) QMessageBox.warning(self.resultWindow, '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()
def show_details(self): def show_details(self):
self.details_dialog.show() self.details_dialog.show()
def show_directories(self): def showResultsWindow(self):
self.directories_dialog.show() self.resultWindow.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()
#--- Signals #--- Signals
willSavePrefs = pyqtSignal() willSavePrefs = pyqtSignal()
@ -203,6 +203,9 @@ class DupeGuru(DupeGuruBase, QObject):
self.prefs.save() self.prefs.save()
self.save() self.save()
def checkForUpdateTriggered(self):
QProcess.execute('updater.exe', ['/checknow'])
def job_finished(self, jobid): def job_finished(self, jobid):
self._job_completed(jobid) self._job_completed(jobid)
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE): if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE):
@ -210,10 +213,40 @@ class DupeGuru(DupeGuruBase, QObject):
self.problemDialog.show() self.problemDialog.show()
else: else:
msg = "All files were processed successfully." 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: elif jobid == JOB_SCAN:
if not self.results.groups: if not self.results.groups:
title = "Scanning complete" title = "Scan complete"
msg = "No duplicates found." 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)

View File

@ -6,17 +6,20 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license # http://www.hardcoded.net/licenses/bsd_license
from PyQt4.QtCore import SIGNAL, Qt, QSize from PyQt4.QtCore import QSize, QRect
from PyQt4.QtGui import (QDialog, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLayout, QTreeView, from PyQt4.QtGui import (QWidget, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLayout, QTreeView,
QAbstractItemView, QSpacerItem, QSizePolicy, QPushButton, QApplication) QAbstractItemView, QSpacerItem, QSizePolicy, QPushButton, QApplication, QMessageBox, QMainWindow,
QMenuBar, QMenu)
from core.app import NoScannableFileError
from . import platform from . import platform
from .directories_model import DirectoriesModel, DirectoriesDelegate from .directories_model import DirectoriesModel, DirectoriesDelegate
from .util import createActions
class DirectoriesDialog(QDialog): class DirectoriesDialog(QMainWindow):
def __init__(self, parent, app): def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint QMainWindow.__init__(self, None)
QDialog.__init__(self, parent, flags)
self.app = app self.app = app
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
self.directoriesModel = DirectoriesModel(self.app) self.directoriesModel = DirectoriesModel(self.app)
@ -24,21 +27,61 @@ class DirectoriesDialog(QDialog):
self._setupUi() self._setupUi()
self._updateRemoveButton() self._updateRemoveButton()
self.connect(self.doneButton, SIGNAL('clicked()'), self.doneButtonClicked) self.scanButton.clicked.connect(self.scanButtonClicked)
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked) self.addButton.clicked.connect(self.addButtonClicked)
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked) self.removeButton.clicked.connect(self.removeButtonClicked)
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged) self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged)
self.app.willSavePrefs.connect(self.appWillSavePrefs) 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): def _setupUi(self):
self.setWindowTitle("Directories") self.setWindowTitle(self.app.NAME)
self.resize(420, 338) self.resize(420, 338)
self.verticalLayout = QVBoxLayout(self) self.centralwidget = QWidget(self)
self.treeView = QTreeView(self) self.verticalLayout = QVBoxLayout(self.centralwidget)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setItemDelegate(self.directoriesDelegate) self.treeView.setItemDelegate(self.directoriesDelegate)
self.treeView.setModel(self.directoriesModel) self.treeView.setModel(self.directoriesModel)
self.treeView.setAcceptDrops(True) 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.setDragDropOverwriteMode(True)
self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
self.treeView.setUniformRowHeights(True) self.treeView.setUniformRowHeights(True)
@ -51,31 +94,35 @@ class DirectoriesDialog(QDialog):
self.horizontalLayout = QHBoxLayout() self.horizontalLayout = QHBoxLayout()
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem) self.horizontalLayout.addItem(spacerItem)
self.removeButton = QPushButton(self) self.removeButton = QPushButton(self.centralwidget)
self.removeButton.setText("Remove") self.removeButton.setText("Remove")
self.removeButton.setShortcut("Del") self.removeButton.setShortcut("Del")
self.removeButton.setMinimumSize(QSize(91, 0)) self.removeButton.setMinimumSize(QSize(91, 0))
self.removeButton.setMaximumSize(QSize(16777215, 32)) self.removeButton.setMaximumSize(QSize(16777215, 32))
self.horizontalLayout.addWidget(self.removeButton) self.horizontalLayout.addWidget(self.removeButton)
self.addButton = QPushButton(self) self.addButton = QPushButton(self.centralwidget)
self.addButton.setText("Add") self.addButton.setText("Add")
self.addButton.setMinimumSize(QSize(91, 0)) self.addButton.setMinimumSize(QSize(91, 0))
self.addButton.setMaximumSize(QSize(16777215, 32)) self.addButton.setMaximumSize(QSize(16777215, 32))
self.horizontalLayout.addWidget(self.addButton) self.horizontalLayout.addWidget(self.addButton)
spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Fixed, QSizePolicy.Minimum) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Fixed, QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1) self.horizontalLayout.addItem(spacerItem1)
self.doneButton = QPushButton(self) self.scanButton = QPushButton(self.centralwidget)
self.doneButton.setText("Done") self.scanButton.setText("Scan")
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.doneButton.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.scanButton.sizePolicy().hasHeightForWidth())
self.doneButton.setSizePolicy(sizePolicy) self.scanButton.setSizePolicy(sizePolicy)
self.doneButton.setMinimumSize(QSize(91, 0)) self.scanButton.setMinimumSize(QSize(91, 0))
self.doneButton.setMaximumSize(QSize(16777215, 32)) self.scanButton.setMaximumSize(QSize(16777215, 32))
self.doneButton.setDefault(True) self.scanButton.setDefault(True)
self.horizontalLayout.addWidget(self.doneButton) self.horizontalLayout.addWidget(self.scanButton)
self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.addLayout(self.horizontalLayout)
self.setCentralWidget(self.centralwidget)
self._setupActions()
self._setupMenu()
if self.app.prefs.directoriesWindowRect is not None: if self.app.prefs.directoriesWindowRect is not None:
self.setGeometry(self.app.prefs.directoriesWindowRect) self.setGeometry(self.app.prefs.directoriesWindowRect)
@ -90,6 +137,17 @@ class DirectoriesDialog(QDialog):
node = index.internalPointer() node = index.internalPointer()
# label = 'Remove' if node.parent is None else 'Exclude' # 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 #--- Events
def addButtonClicked(self): def addButtonClicked(self):
title = "Select a directory to add to the scanning list" title = "Select a directory to add to the scanning list"
@ -103,8 +161,13 @@ class DirectoriesDialog(QDialog):
def appWillSavePrefs(self): def appWillSavePrefs(self):
self.app.prefs.directoriesWindowRect = self.geometry() self.app.prefs.directoriesWindowRect = self.geometry()
def doneButtonClicked(self): def loadResultsTriggered(self):
self.hide() 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): def removeButtonClicked(self):
indexes = self.treeView.selectedIndexes() indexes = self.treeView.selectedIndexes()
@ -116,6 +179,18 @@ class DirectoriesDialog(QDialog):
row = index.row() row = index.row()
self.app.remove_directory(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): def selectionChanged(self, selected, deselected):
self._updateRemoveButton() self._updateRemoveButton()

View File

@ -36,8 +36,8 @@ class Preferences(PreferencesBase):
self.columns_width[index] = width self.columns_width[index] = width
self.columns_visible = get('ColumnsVisible', self.columns_visible) self.columns_visible = get('ColumnsVisible', self.columns_visible)
self.mainWindowIsMaximized = get('MainWindowIsMaximized', self.mainWindowIsMaximized) self.resultWindowIsMaximized = get('ResultWindowIsMaximized', self.resultWindowIsMaximized)
self.mainWindowRect = self.get_rect('MainWindowRect', self.mainWindowRect) self.resultWindowRect = self.get_rect('ResultWindowRect', self.resultWindowRect)
self.detailsWindowRect = self.get_rect('DetailsWindowRect', self.detailsWindowRect) self.detailsWindowRect = self.get_rect('DetailsWindowRect', self.detailsWindowRect)
self.directoriesWindowRect = self.get_rect('DirectoriesWindowRect', self.directoriesWindowRect) self.directoriesWindowRect = self.get_rect('DirectoriesWindowRect', self.directoriesWindowRect)
self.recentResults = get('RecentResults', self.recentResults) self.recentResults = get('RecentResults', self.recentResults)
@ -59,8 +59,8 @@ class Preferences(PreferencesBase):
self.destination_type = 1 self.destination_type = 1
self.custom_command = '' self.custom_command = ''
self.mainWindowIsMaximized = False self.resultWindowIsMaximized = False
self.mainWindowRect = None self.resultWindowRect = None
self.detailsWindowRect = None self.detailsWindowRect = None
self.directoriesWindowRect = None self.directoriesWindowRect = None
self.recentResults = [] self.recentResults = []
@ -89,8 +89,8 @@ class Preferences(PreferencesBase):
set_('ColumnsWidth', self.columns_width) set_('ColumnsWidth', self.columns_width)
set_('ColumnsVisible', self.columns_visible) set_('ColumnsVisible', self.columns_visible)
set_('MainWindowIsMaximized', self.mainWindowIsMaximized) set_('ResultWindowIsMaximized', self.resultWindowIsMaximized)
self.set_rect('MainWindowRect', self.mainWindowRect) self.set_rect('ResultWindowRect', self.resultWindowRect)
self.set_rect('DetailsWindowRect', self.detailsWindowRect) self.set_rect('DetailsWindowRect', self.detailsWindowRect)
self.set_rect('DirectoriesWindowRect', self.directoriesWindowRect) self.set_rect('DirectoriesWindowRect', self.directoriesWindowRect)
set_('RecentResults', self.recentResults) set_('RecentResults', self.recentResults)

View File

@ -8,21 +8,18 @@
import sys import sys
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl, QRect from PyQt4.QtCore import Qt, SIGNAL, QUrl, QRect
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView, from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QLabel, QHeaderView, QMessageBox,
QMessageBox, QInputDialog, QLineEdit, QDesktopServices, QFileDialog, QAction, QMenuBar, QInputDialog, QLineEdit, QDesktopServices, QFileDialog, QMenuBar, QWidget, QVBoxLayout,
QToolBar, QWidget, QVBoxLayout, QAbstractItemView, QStatusBar) QAbstractItemView, QStatusBar)
from hscommon.util import nonone 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 .results_model import ResultsModel, ResultsView
from .stats_label import StatsLabel from .stats_label import StatsLabel
from .util import createActions
class MainWindow(QMainWindow): class ResultWindow(QMainWindow):
def __init__(self, app): def __init__(self, app):
QMainWindow.__init__(self, None) QMainWindow.__init__(self, None)
self.app = app self.app = app
@ -41,11 +38,8 @@ class MainWindow(QMainWindow):
def _setupActions(self): def _setupActions(self):
# (name, shortcut, icon, desc, func) # (name, shortcut, icon, desc, func)
ACTIONS = [ 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), ('actionDetails', 'Ctrl+3', 'details', "Details", self.detailsTriggered),
('actionActions', '', 'actions', "Actions", self.actionsTriggered), ('actionActions', '', 'actions', "Actions", self.actionsTriggered),
('actionPreferences', 'Ctrl+5', 'preferences', "Preferences", self.preferencesTriggered),
('actionDelta', 'Ctrl+2', 'delta', "Delta Values", self.deltaTriggered), ('actionDelta', 'Ctrl+2', 'delta', "Delta Values", self.deltaTriggered),
('actionPowerMarker', 'Ctrl+1', 'power_marker', "Power Marker", self.powerMarkerTriggered), ('actionPowerMarker', 'Ctrl+1', 'power_marker', "Power Marker", self.powerMarkerTriggered),
('actionDeleteMarked', 'Ctrl+D', '', "Send Marked to Recycle Bin", self.deleteTriggered), ('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), ('actionInvertMarking', 'Ctrl+Alt+A', '', "Invert Marking", self.markInvertTriggered),
('actionMarkSelected', '', '', "Mark Selected", self.markSelectedTriggered), ('actionMarkSelected', '', '', "Mark Selected", self.markSelectedTriggered),
('actionClearIgnoreList', '', '', "Clear Ignore List", self.clearIgnoreListTriggered), ('actionClearIgnoreList', '', '', "Clear Ignore List", self.clearIgnoreListTriggered),
('actionQuit', 'Ctrl+Q', '', "Quit", self.close),
('actionApplyFilter', 'Ctrl+F', '', "Apply Filter", self.applyFilterTriggered), ('actionApplyFilter', 'Ctrl+F', '', "Apply Filter", self.applyFilterTriggered),
('actionCancelFilter', 'Ctrl+Shift+F', '', "Cancel Filter", self.cancelFilterTriggered), ('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), ('actionExport', '', '', "Export To HTML", self.exportTriggered),
('actionLoadResults', 'Ctrl+L', '', "Load Results...", self.loadResultsTriggered),
('actionSaveResults', 'Ctrl+S', '', "Save Results...", self.saveResultsTriggered), ('actionSaveResults', 'Ctrl+S', '', "Save Results...", self.saveResultsTriggered),
('actionOpenDebugLog', '', '', "Open Debug Log", self.openDebugLogTriggered),
('actionInvokeCustomCommand', 'Ctrl+I', '', "Invoke Custom Command", self.app.invokeCustomCommand), ('actionInvokeCustomCommand', 'Ctrl+I', '', "Invoke Custom Command", self.app.invokeCustomCommand),
] ]
for name, shortcut, icon, desc, func in ACTIONS: createActions(ACTIONS, self)
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)
self.actionDelta.setCheckable(True) self.actionDelta.setCheckable(True)
self.actionPowerMarker.setCheckable(True) self.actionPowerMarker.setCheckable(True)
@ -100,14 +79,10 @@ class MainWindow(QMainWindow):
self.menuActions.setTitle("Actions") self.menuActions.setTitle("Actions")
self.menuColumns = QMenu(self.menubar) self.menuColumns = QMenu(self.menubar)
self.menuColumns.setTitle("Columns") self.menuColumns.setTitle("Columns")
self.menuModes = QMenu(self.menubar) self.menuView = QMenu(self.menubar)
self.menuModes.setTitle("Modes") self.menuView.setTitle("View")
self.menuWindow = QMenu(self.menubar)
self.menuWindow.setTitle("Windows")
self.menuHelp = QMenu(self.menubar) self.menuHelp = QMenu(self.menubar)
self.menuHelp.setTitle("Help") self.menuHelp.setTitle("Help")
self.menuLoadRecent = QMenu(self.menuFile)
self.menuLoadRecent.setTitle("Load Recent Results")
self.setMenuBar(self.menubar) self.setMenuBar(self.menubar)
self.menuActions.addAction(self.actionDeleteMarked) self.menuActions.addAction(self.actionDeleteMarked)
@ -131,32 +106,27 @@ class MainWindow(QMainWindow):
self.menuMark.addAction(self.actionMarkNone) self.menuMark.addAction(self.actionMarkNone)
self.menuMark.addAction(self.actionInvertMarking) self.menuMark.addAction(self.actionInvertMarking)
self.menuMark.addAction(self.actionMarkSelected) self.menuMark.addAction(self.actionMarkSelected)
self.menuModes.addAction(self.actionPowerMarker) self.menuView.addAction(self.actionPowerMarker)
self.menuModes.addAction(self.actionDelta) self.menuView.addAction(self.actionDelta)
self.menuWindow.addAction(self.actionDetails) self.menuView.addSeparator()
self.menuWindow.addAction(self.actionDirectories) self.menuView.addAction(self.actionDetails)
self.menuWindow.addAction(self.actionPreferences) self.menuView.addAction(self.app.actionPreferences)
self.menuHelp.addAction(self.actionShowHelp) self.menuHelp.addAction(self.app.actionShowHelp)
self.menuHelp.addAction(self.actionRegister) self.menuHelp.addAction(self.app.actionRegister)
self.menuHelp.addAction(self.actionCheckForUpdate) self.menuHelp.addAction(self.app.actionCheckForUpdate)
self.menuHelp.addAction(self.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionOpenDebugLog)
self.menuHelp.addAction(self.actionAbout) self.menuHelp.addAction(self.app.actionAbout)
self.menuFile.addAction(self.actionScan)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionLoadResults)
self.menuFile.addAction(self.menuLoadRecent.menuAction())
self.menuFile.addAction(self.actionSaveResults) self.menuFile.addAction(self.actionSaveResults)
self.menuFile.addAction(self.actionExport) self.menuFile.addAction(self.actionExport)
self.menuFile.addAction(self.actionClearIgnoreList) self.menuFile.addAction(self.actionClearIgnoreList)
self.menuFile.addSeparator() self.menuFile.addSeparator()
self.menuFile.addAction(self.actionQuit) self.menuFile.addAction(self.app.actionQuit)
self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuMark.menuAction()) self.menubar.addAction(self.menuMark.menuAction())
self.menubar.addAction(self.menuActions.menuAction()) self.menubar.addAction(self.menuActions.menuAction())
self.menubar.addAction(self.menuColumns.menuAction()) self.menubar.addAction(self.menuColumns.menuAction())
self.menubar.addAction(self.menuModes.menuAction()) self.menubar.addAction(self.menuView.menuAction())
self.menubar.addAction(self.menuWindow.menuAction())
self.menubar.addAction(self.menuHelp.menuAction()) self.menubar.addAction(self.menuHelp.menuAction())
# Columns menu # Columns menu
@ -190,27 +160,8 @@ class MainWindow(QMainWindow):
actionMenu.addAction(self.actionRenameSelected) actionMenu.addAction(self.actionRenameSelected)
self.actionActions.setMenu(actionMenu) 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): def _setupUi(self):
self.setWindowTitle(QCoreApplication.instance().applicationName()) self.setWindowTitle("dupeGuru Results")
self.resize(630, 514) self.resize(630, 514)
self.centralwidget = QWidget(self) self.centralwidget = QWidget(self)
self.verticalLayout_2 = QVBoxLayout(self.centralwidget) self.verticalLayout_2 = QVBoxLayout(self.centralwidget)
@ -230,17 +181,16 @@ class MainWindow(QMainWindow):
self.setCentralWidget(self.centralwidget) self.setCentralWidget(self.centralwidget)
self._setupActions() self._setupActions()
self._setupMenu() 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 = QStatusBar(self)
self.statusbar.setSizeGripEnabled(True) self.statusbar.setSizeGripEnabled(True)
self.setStatusBar(self.statusbar) self.setStatusBar(self.statusbar)
self.statusLabel = QLabel(self) self.statusLabel = QLabel(self)
self.statusbar.addPermanentWidget(self.statusLabel, 1) self.statusbar.addPermanentWidget(self.statusLabel, 1)
if self.app.prefs.mainWindowRect is not None and not self.app.prefs.mainWindowIsMaximized: if self.app.prefs.resultWindowIsMaximized:
self.setGeometry(self.app.prefs.mainWindowRect) 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 # Platform-specific setup
if sys.platform == 'linux2': if sys.platform == 'linux2':
@ -249,11 +199,6 @@ class MainWindow(QMainWindow):
self.actionHardlinkMarked.setVisible(False) self.actionHardlinkMarked.setVisible(False)
#--- Private #--- 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): def _load_columns(self):
h = self.resultsView.horizontalHeader() h = self.resultsView.horizontalHeader()
h.setResizeMode(QHeaderView.Interactive) h.setResizeMode(QHeaderView.Interactive)
@ -270,19 +215,7 @@ class MainWindow(QMainWindow):
colid = action.column_index colid = action.column_index
action.setChecked(not h.isSectionHidden(colid)) 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 #--- Actions
def aboutTriggered(self):
self.app.show_about_box()
def actionsTriggered(self): def actionsTriggered(self):
self.actionsButton.showMenu() self.actionsButton.showMenu()
@ -303,9 +236,6 @@ class MainWindow(QMainWindow):
def cancelFilterTriggered(self): def cancelFilterTriggered(self):
self.app.apply_filter('') self.app.apply_filter('')
def checkForUpdateTriggered(self):
QProcess.execute('updater.exe', ['/checknow'])
def clearIgnoreListTriggered(self): def clearIgnoreListTriggered(self):
title = "Clear Ignore List" title = "Clear Ignore List"
count = len(self.app.scanner.ignore_list) count = len(self.app.scanner.ignore_list)
@ -313,7 +243,7 @@ class MainWindow(QMainWindow):
QMessageBox.information(self, title, "Nothing to clear.") QMessageBox.information(self, title, "Nothing to clear.")
return return
msg = "Do you really want to remove all {0} items from the ignore list?".format(count) 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() self.app.scanner.ignore_list.Clear()
QMessageBox.information(self, title, "Ignore list cleared.") QMessageBox.information(self, title, "Ignore list cleared.")
@ -326,7 +256,7 @@ class MainWindow(QMainWindow):
return return
title = "Delete duplicates" title = "Delete duplicates"
msg = "You are about to send {0} files to the recycle bin. Continue?".format(count) 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() self.app.delete_marked()
def deltaTriggered(self): def deltaTriggered(self):
@ -335,9 +265,6 @@ class MainWindow(QMainWindow):
def detailsTriggered(self): def detailsTriggered(self):
self.app.show_details() self.app.show_details()
def directoriesTriggered(self):
self.app.show_directories()
def exportTriggered(self): def exportTriggered(self):
h = self.resultsView.horizontalHeader() h = self.resultsView.horizontalHeader()
column_ids = [] column_ids = []
@ -354,17 +281,9 @@ class MainWindow(QMainWindow):
return return
title = "Delete and hardlink duplicates" title = "Delete and hardlink duplicates"
msg = "You are about to send {0} files to the trash and hardlink them afterwards. Continue?".format(count) 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) 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): def makeReferenceTriggered(self):
self.app.make_selected_reference() self.app.make_selected_reference()
@ -383,9 +302,6 @@ class MainWindow(QMainWindow):
def moveTriggered(self): def moveTriggered(self):
self.app.copy_or_move_marked(False) self.app.copy_or_move_marked(False)
def openDebugLogTriggered(self):
self.app.openDebugLog()
def openTriggered(self): def openTriggered(self):
self.app.open_selected() self.app.open_selected()
@ -395,16 +311,13 @@ class MainWindow(QMainWindow):
def preferencesTriggered(self): def preferencesTriggered(self):
self.app.show_preferences() self.app.show_preferences()
def registerTrigerred(self):
self.app.reg.ask_for_code()
def removeMarkedTriggered(self): def removeMarkedTriggered(self):
count = self.app.results.mark_count count = self.app.results.mark_count
if not count: if not count:
return return
title = "Remove duplicates" title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(count) 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() self.app.remove_marked()
def removeSelectedTriggered(self): def removeSelectedTriggered(self):
@ -422,23 +335,7 @@ class MainWindow(QMainWindow):
destination = QFileDialog.getSaveFileName(self, title, '', files) destination = QFileDialog.getSaveFileName(self, title, '', files)
if destination: if destination:
self.app.save_as(destination) self.app.save_as(destination)
self.recentResults.insertItem(destination) self.app.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()
#--- Events #--- Events
def appWillSavePrefs(self): def appWillSavePrefs(self):
@ -451,8 +348,8 @@ class MainWindow(QMainWindow):
visible.append(not h.isSectionHidden(i)) visible.append(not h.isSectionHidden(i))
prefs.columns_width = widths prefs.columns_width = widths
prefs.columns_visible = visible prefs.columns_visible = visible
prefs.mainWindowIsMaximized = self.isMaximized() prefs.resultWindowIsMaximized = self.isMaximized()
prefs.mainWindowRect = self.geometry() prefs.resultWindowRect = self.geometry()
def columnToggled(self, action): def columnToggled(self, action):
colid = action.column_index colid = action.column_index

21
qt/base/util.py Normal file
View File

@ -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)

View File

@ -20,7 +20,7 @@ from core_pe.scanner import ScannerPE
from ..base.app import DupeGuru as DupeGuruBase from ..base.app import DupeGuru as DupeGuruBase
from .block import getblocks from .block import getblocks
from .details_dialog import DetailsDialog from .details_dialog import DetailsDialog
from .main_window import MainWindow from .result_window import ResultWindow
from .preferences import Preferences from .preferences import Preferences
from .preferences_dialog import PreferencesDialog from .preferences_dialog import PreferencesDialog
@ -79,8 +79,8 @@ class DupeGuru(DupeGuruBase):
def _create_details_dialog(self, parent): def _create_details_dialog(self, parent):
return DetailsDialog(parent, self) return DetailsDialog(parent, self)
def _create_main_window(self): def _create_result_window(self):
return MainWindow(app=self) return ResultWindow(app=self)
def _create_preferences(self): def _create_preferences(self):
return Preferences() return Preferences()

View File

@ -9,11 +9,11 @@
from PyQt4.QtCore import SIGNAL from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QMessageBox, QAction 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): def _setupUi(self):
MainWindowBase._setupUi(self) ResultWindowBase._setupUi(self)
self.actionClearPictureCache = QAction("Clear Picture Cache", self) self.actionClearPictureCache = QAction("Clear Picture Cache", self)
self.menuFile.insertAction(self.actionClearIgnoreList, self.actionClearPictureCache) self.menuFile.insertAction(self.actionClearIgnoreList, self.actionClearPictureCache)
self.connect(self.actionClearPictureCache, SIGNAL("triggered()"), self.clearPictureCacheTriggered) self.connect(self.actionClearPictureCache, SIGNAL("triggered()"), self.clearPictureCacheTriggered)
@ -21,7 +21,7 @@ class MainWindow(MainWindowBase):
def clearPictureCacheTriggered(self): def clearPictureCacheTriggered(self):
title = "Clear Picture Cache" title = "Clear Picture Cache"
msg = "Do you really want to remove all your cached picture analysis?" 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() self.app.scanner.clear_picture_cache()
QMessageBox.information(self, title, "Picture cache cleared.") QMessageBox.information(self, title, "Picture cache cleared.")