dupeguru/qt/directories_dialog.py

372 lines
15 KiB
Python

# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtWidgets import (
QWidget,
QFileDialog,
QHeaderView,
QVBoxLayout,
QHBoxLayout,
QTreeView,
QAbstractItemView,
QSpacerItem,
QSizePolicy,
QPushButton,
QMainWindow,
QMenuBar,
QMenu,
QLabel,
QComboBox,
)
from PyQt5.QtGui import QPixmap, QIcon
from hscommon.trans import trget
from core.app import AppMode
from qtlib.radio_box import RadioBox
from qtlib.recent import Recent
from qtlib.util import moveToScreenCenter, createActions
from . import platform
from .directories_model import DirectoriesModel, DirectoriesDelegate
tr = trget("ui")
class DirectoriesDialog(QMainWindow):
def __init__(self, app, **kwargs):
super().__init__(None, **kwargs)
self.app = app
self.specific_actions = set()
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
self.recentFolders = Recent(self.app, "recentFolders")
self._setupUi()
self._updateScanTypeList()
self.directoriesModel = DirectoriesModel(
self.app.model.directory_tree, view=self.treeView
)
self.directoriesDelegate = DirectoriesDelegate()
self.treeView.setItemDelegate(self.directoriesDelegate)
self._setupColumns()
self.app.recentResults.addMenu(self.menuLoadRecent)
self.app.recentResults.addMenu(self.menuRecentResults)
self.recentFolders.addMenu(self.menuRecentFolders)
self._updateAddButton()
self._updateRemoveButton()
self._updateLoadResultsButton()
self._updateActionsState()
self._setupBindings()
def _setupBindings(self):
self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected)
self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger)
self.scanButton.clicked.connect(self.scanButtonClicked)
self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked)
self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged)
self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton)
self.recentFolders.itemsChanged.connect(self._updateAddButton)
self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders)
self.app.willSavePrefs.connect(self.appWillSavePrefs)
def _setupActions(self):
# (name, shortcut, icon, desc, func)
ACTIONS = [
(
"actionLoadResults",
"Ctrl+L",
"",
tr("Load Results..."),
self.loadResultsTriggered,
),
(
"actionShowResultsWindow",
"",
"",
tr("Scan Results"),
self.app.showResultsWindow,
),
("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered),
("actionLoadDirectories", "", "", tr("Load Directories..."), self.loadDirectoriesTriggered),
("actionSaveDirectories", "", "", tr("Save Directories..."), self.saveDirectoriesTriggered),
]
createActions(ACTIONS, self)
if self.app.use_tabs:
# Keep track of actions which should only be accessible from this window
self.specific_actions.add(self.actionLoadDirectories)
self.specific_actions.add(self.actionSaveDirectories)
def _setupMenu(self):
if not self.app.use_tabs:
# we are our own QMainWindow, we need our own menu bar
self.menubar = QMenuBar(self)
self.menubar.setGeometry(QRect(0, 0, 42, 22))
self.menuFile = QMenu(self.menubar)
self.menuFile.setTitle(tr("File"))
self.menuView = QMenu(self.menubar)
self.menuView.setTitle(tr("View"))
self.menuHelp = QMenu(self.menubar)
self.menuHelp.setTitle(tr("Help"))
self.setMenuBar(self.menubar)
menubar = self.menubar
else:
# we are part of a tab widget, we populate its window's menubar instead
self.menuFile = self.app.main_window.menuFile
self.menuView = self.app.main_window.menuView
self.menuHelp = self.app.main_window.menuHelp
menubar = self.app.main_window.menubar
self.menuLoadRecent = QMenu(self.menuFile)
self.menuLoadRecent.setTitle(tr("Load Recent Results"))
self.menuFile.addAction(self.actionLoadResults)
self.menuFile.addAction(self.menuLoadRecent.menuAction())
self.menuFile.addSeparator()
self.menuFile.addAction(self.app.actionClearPictureCache)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionLoadDirectories)
self.menuFile.addAction(self.actionSaveDirectories)
self.menuFile.addSeparator()
self.menuFile.addAction(self.app.actionQuit)
self.menuView.addAction(self.app.actionDirectoriesWindow)
self.menuView.addAction(self.actionShowResultsWindow)
self.menuView.addAction(self.app.actionIgnoreList)
self.menuView.addAction(self.app.actionExcludeList)
self.menuView.addSeparator()
self.menuView.addAction(self.app.actionPreferences)
self.menuHelp.addAction(self.app.actionShowHelp)
self.menuHelp.addAction(self.app.actionOpenDebugLog)
self.menuHelp.addAction(self.app.actionAbout)
menubar.addAction(self.menuFile.menuAction())
menubar.addAction(self.menuView.menuAction())
menubar.addAction(self.menuHelp.menuAction())
# Recent folders menu
self.menuRecentFolders = QMenu()
self.menuRecentFolders.addAction(self.actionAddFolder)
self.menuRecentFolders.addSeparator()
# Recent results menu
self.menuRecentResults = QMenu()
self.menuRecentResults.addAction(self.actionLoadResults)
self.menuRecentResults.addSeparator()
def _setupUi(self):
self.setWindowTitle(self.app.NAME)
self.resize(420, 338)
self.centralwidget = QWidget(self)
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setContentsMargins(4, 0, 4, 0)
self.verticalLayout.setSpacing(0)
hl = QHBoxLayout()
label = QLabel(tr("Application Mode:"), self)
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(label)
self.appModeRadioBox = RadioBox(
self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False
)
hl.addWidget(self.appModeRadioBox)
self.verticalLayout.addLayout(hl)
hl = QHBoxLayout()
hl.setAlignment(Qt.AlignLeft)
label = QLabel(tr("Scan Type:"), self)
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(label)
self.scanTypeComboBox = QComboBox(self)
self.scanTypeComboBox.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
)
self.scanTypeComboBox.setMaximumWidth(400)
hl.addWidget(self.scanTypeComboBox)
self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget)
self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(self.showPreferencesButton)
self.verticalLayout.addLayout(hl)
self.promptLabel = QLabel(
tr('Select folders to scan and press "Scan".'), self.centralwidget
)
self.verticalLayout.addWidget(self.promptLabel)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.treeView.setAcceptDrops(True)
triggers = (
QAbstractItemView.DoubleClicked
| QAbstractItemView.EditKeyPressed
| QAbstractItemView.SelectedClicked
)
self.treeView.setEditTriggers(triggers)
self.treeView.setDragDropOverwriteMode(True)
self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
self.treeView.setUniformRowHeights(True)
self.verticalLayout.addWidget(self.treeView)
self.horizontalLayout = QHBoxLayout()
self.removeFolderButton = QPushButton(self.centralwidget)
self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus")))
self.removeFolderButton.setShortcut("Del")
self.horizontalLayout.addWidget(self.removeFolderButton)
self.addFolderButton = QPushButton(self.centralwidget)
self.addFolderButton.setIcon(QIcon(QPixmap(":/plus")))
self.horizontalLayout.addWidget(self.addFolderButton)
spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.loadResultsButton = QPushButton(self.centralwidget)
self.loadResultsButton.setText(tr("Load Results"))
self.horizontalLayout.addWidget(self.loadResultsButton)
self.scanButton = QPushButton(self.centralwidget)
self.scanButton.setText(tr("Scan"))
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)
else:
moveToScreenCenter(self)
def _setupColumns(self):
header = self.treeView.header()
header.setStretchLastSection(False)
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.Fixed)
header.resizeSection(1, 100)
def _updateActionsState(self):
self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None)
def _updateAddButton(self):
if self.recentFolders.isEmpty():
self.addFolderButton.setMenu(None)
else:
self.addFolderButton.setMenu(self.menuRecentFolders)
def _updateRemoveButton(self):
indexes = self.treeView.selectedIndexes()
if not indexes:
self.removeFolderButton.setEnabled(False)
return
self.removeFolderButton.setEnabled(True)
def _updateLoadResultsButton(self):
if self.app.recentResults.isEmpty():
self.loadResultsButton.setMenu(None)
else:
self.loadResultsButton.setMenu(self.menuRecentResults)
def _updateScanTypeList(self):
try:
self.scanTypeComboBox.currentIndexChanged[int].disconnect(
self.scanTypeChanged
)
except TypeError:
# Not connected, ignore
pass
self.scanTypeComboBox.clear()
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
for scan_option in scan_options:
self.scanTypeComboBox.addItem(scan_option.label)
SCAN_TYPE_ORDER = [so.scan_type for so in scan_options]
selected_scan_type = self.app.prefs.get_scan_type(self.app.model.app_mode)
scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type)
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
self.app._update_options()
# --- QWidget overrides
def closeEvent(self, event):
event.accept()
if self.app.model.results.is_modified:
title = tr("Unsaved results")
msg = tr("You have unsaved results, do you really want to quit?")
if not self.app.confirm(title, msg):
event.ignore()
if event.isAccepted():
self.app.shutdown()
# --- Events
def addFolderTriggered(self):
title = tr("Select a folder to add to the scanning list")
flags = QFileDialog.ShowDirsOnly
dirpath = str(
QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)
)
if not dirpath:
return
self.lastAddedFolder = dirpath
self.app.model.add_directory(dirpath)
self.recentFolders.insertItem(dirpath)
def appModeButtonSelected(self, index):
if index == 2:
mode = AppMode.Picture
elif index == 1:
mode = AppMode.Music
else:
mode = AppMode.Standard
self.app.model.app_mode = mode
self._updateScanTypeList()
def appWillSavePrefs(self):
self.app.prefs.directoriesWindowRect = self.geometry()
def directoriesModelAddedFolders(self, folders):
for folder in folders:
self.recentFolders.insertItem(folder)
def loadResultsTriggered(self):
title = tr("Select a results file to load")
files = ";;".join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
if destination:
self.app.model.load_from(destination)
self.app.recentResults.insertItem(destination)
def loadDirectoriesTriggered(self):
title = tr("Select a directories file to load")
files = ";;".join([tr("dupeGuru Results (*.dupegurudirs)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
if destination:
self.app.model.load_directories(destination)
def removeFolderButtonClicked(self):
self.directoriesModel.model.remove_selected()
def saveDirectoriesTriggered(self):
title = tr("Select a file to save your directories to")
files = tr("dupeGuru Directories (*.dupegurudirs)")
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files)
if destination:
if not destination.endswith(".dupegurudirs"):
destination = "{}.dupegurudirs".format(destination)
self.app.model.save_directories_as(destination)
def scanButtonClicked(self):
if self.app.model.results.is_modified:
title = tr("Start a new scan")
msg = tr("You have unsaved results, do you really want to continue?")
if not self.app.confirm(title, msg):
return
self.app.model.start_scanning()
def scanTypeChanged(self, index):
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
self.app.prefs.set_scan_type(
self.app.model.app_mode, scan_options[index].scan_type
)
self.app._update_options()
def selectionChanged(self, selected, deselected):
self._updateRemoveButton()