mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
223 lines
8.2 KiB
Python
223 lines
8.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Picard, the next-generation MusicBrainz tagger
|
|
#
|
|
# Copyright (C) 2007 Lukáš Lalinský
|
|
# Copyright (C) 2013-2015, 2018-2024 Laurent Monin
|
|
# Copyright (C) 2016-2017 Sambhav Kothari
|
|
# Copyright (C) 2017 Frederik “Freso” S. Olesen
|
|
# Copyright (C) 2017 Sophist-UK
|
|
# Copyright (C) 2018 Vishal Choudhary
|
|
# Copyright (C) 2019, 2021-2022, 2024 Philipp Wolfer
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
from html import escape as html_escape
|
|
|
|
from PyQt6 import (
|
|
QtCore,
|
|
QtGui,
|
|
QtWidgets,
|
|
)
|
|
|
|
from picard import PICARD_DISPLAY_NAME
|
|
from picard.config import get_config
|
|
from picard.const.sys import (
|
|
IS_LINUX,
|
|
IS_MACOS,
|
|
IS_WIN,
|
|
)
|
|
from picard.i18n import (
|
|
N_,
|
|
gettext as _,
|
|
)
|
|
from picard.util import find_existing_path
|
|
|
|
from picard.ui.enums import MainAction
|
|
|
|
|
|
class StandardButton(QtWidgets.QPushButton):
|
|
|
|
OK = 0
|
|
CANCEL = 1
|
|
HELP = 2
|
|
CLOSE = 4
|
|
|
|
__types = {
|
|
OK: (N_("&Ok"), 'SP_DialogOkButton'),
|
|
CANCEL: (N_("&Cancel"), 'SP_DialogCancelButton'),
|
|
HELP: (N_("&Help"), 'SP_DialogHelpButton'),
|
|
CLOSE: (N_("Clos&e"), 'SP_DialogCloseButton'),
|
|
}
|
|
|
|
def __init__(self, btntype):
|
|
label = _(self.__types[btntype][0])
|
|
args = [label]
|
|
if not IS_WIN and not IS_MACOS:
|
|
iconname = self.__types[btntype][1]
|
|
if hasattr(QtWidgets.QStyle, iconname):
|
|
icon = QtCore.QCoreApplication.instance().style().standardIcon(getattr(QtWidgets.QStyle, iconname))
|
|
args = [icon, label]
|
|
super().__init__(*args)
|
|
|
|
|
|
def find_starting_directory():
|
|
config = get_config()
|
|
if config.setting['starting_directory']:
|
|
path = config.setting['starting_directory_path']
|
|
else:
|
|
path = config.persist['current_directory'] or QtCore.QDir.homePath()
|
|
return find_existing_path(path)
|
|
|
|
|
|
def _picardize_caption(caption):
|
|
return _("%s - %s") % (caption, PICARD_DISPLAY_NAME)
|
|
|
|
|
|
def _filedialog_caption(caption, default_caption=""):
|
|
if not caption:
|
|
caption = default_caption
|
|
return _picardize_caption(caption)
|
|
|
|
|
|
def _filedialog_options(options, default=None):
|
|
if options is None:
|
|
# returns default flags or empty enum flag
|
|
return default or QtWidgets.QFileDialog.Option(0)
|
|
else:
|
|
return options
|
|
|
|
|
|
class FileDialog(QtWidgets.QFileDialog):
|
|
"""Wrap QFileDialog & its static methods"""
|
|
|
|
def __init__(self, parent=None, caption="", directory="", filter=""):
|
|
if not caption:
|
|
caption = _("Select a file or a directory")
|
|
caption = _picardize_caption(caption)
|
|
super().__init__(parent=parent, caption=caption, directory=directory, filter=filter)
|
|
|
|
@staticmethod
|
|
def getSaveFileName(parent=None, caption="", dir="", filter="", selectedFilter="", options=None):
|
|
caption = _filedialog_caption(caption, _("Select a target file"))
|
|
options = _filedialog_options(options)
|
|
return QtWidgets.QFileDialog.getSaveFileName(
|
|
parent=parent, caption=caption, directory=dir,
|
|
filter=filter, initialFilter=selectedFilter, options=options
|
|
)
|
|
|
|
@staticmethod
|
|
def getOpenFileName(parent=None, caption="", dir="", filter="", selectedFilter="", options=None):
|
|
caption = _filedialog_caption(caption, _("Select a file"))
|
|
options = _filedialog_options(options)
|
|
return QtWidgets.QFileDialog.getOpenFileName(
|
|
parent=parent, caption=caption, directory=dir,
|
|
filter=filter, initialFilter=selectedFilter, options=options
|
|
)
|
|
|
|
@staticmethod
|
|
def getOpenFileNames(parent=None, caption="", dir="", filter="", selectedFilter="", options=None):
|
|
caption = _filedialog_caption(caption, _("Select one or more files"))
|
|
options = _filedialog_options(options)
|
|
return QtWidgets.QFileDialog.getOpenFileNames(
|
|
parent=parent, caption=caption, directory=dir,
|
|
filter=filter, initialFilter=selectedFilter, options=options
|
|
)
|
|
|
|
@staticmethod
|
|
def getExistingDirectory(parent=None, caption="", dir="", options=None):
|
|
caption = _filedialog_caption(caption, _("Select a directory"))
|
|
options = _filedialog_options(options, default=QtWidgets.QFileDialog.Option.ShowDirsOnly)
|
|
return QtWidgets.QFileDialog.getExistingDirectory(
|
|
parent=parent, caption=caption, directory=dir, options=options
|
|
)
|
|
|
|
@staticmethod
|
|
def getMultipleDirectories(parent=None, caption="", directory="", filter=""):
|
|
"""Custom file selection dialog which allows the selection
|
|
of multiple directories.
|
|
Depending on the platform, dialog may fallback on non-native.
|
|
"""
|
|
if not caption:
|
|
caption = _("Select one or more directories")
|
|
file_dialog = FileDialog(parent=parent, caption=caption, directory=directory, filter=filter)
|
|
file_dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory)
|
|
file_dialog.setOption(QtWidgets.QFileDialog.Option.ShowDirsOnly)
|
|
# The native dialog doesn't allow selecting >1 directory
|
|
file_dialog.setOption(QtWidgets.QFileDialog.Option.DontUseNativeDialog)
|
|
for view in file_dialog.findChildren((QtWidgets.QListView, QtWidgets.QTreeView)):
|
|
if isinstance(view.model(), QtGui.QFileSystemModel):
|
|
view.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
|
|
|
|
# Allow access to all mounted drives in the sidebar
|
|
root_volume = "/"
|
|
volume_paths = []
|
|
for volume in QtCore.QStorageInfo.mountedVolumes():
|
|
if volume.isValid() and volume.isReady():
|
|
path = volume.rootPath()
|
|
if volume.isRoot():
|
|
root_volume = path
|
|
else:
|
|
if not IS_LINUX or (path.startswith("/media/") or path.startswith("/mnt/")):
|
|
volume_paths.append(path)
|
|
paths = [
|
|
root_volume,
|
|
QtCore.QDir.homePath(),
|
|
QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.MusicLocation),
|
|
]
|
|
file_dialog.setSidebarUrls(QtCore.QUrl.fromLocalFile(p) for p in paths + sorted(volume_paths) if p)
|
|
dirs = ()
|
|
if file_dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
|
|
dirs = file_dialog.selectedFiles()
|
|
return tuple(dirs)
|
|
|
|
|
|
def qlistwidget_items(qlistwidget):
|
|
"""Yield all items from a QListWidget"""
|
|
for i in range(qlistwidget.count()):
|
|
yield qlistwidget.item(i)
|
|
|
|
|
|
def changes_require_restart_warning(parent, warnings=None, notes=None):
|
|
"""Display a warning dialog about modified options requiring a restart"""
|
|
if not warnings:
|
|
return
|
|
text = '<p><ul>'
|
|
for warning in warnings:
|
|
text += '<li>' + html_escape(warning) + '</li>'
|
|
text += "</ul></p>"
|
|
if notes:
|
|
for note in notes:
|
|
text += "<p><em>" + html_escape(note) + "</em></p>"
|
|
text += "<p><strong>" + _("You have to restart Picard for the changes to take effect.") + "</strong></p>"
|
|
QtWidgets.QMessageBox.warning(
|
|
parent,
|
|
_("Changes only applied on restart"),
|
|
text
|
|
)
|
|
|
|
|
|
def menu_builder(menu, main_actions, *args):
|
|
"""Adds each argument to menu, depending on their type"""
|
|
for arg in args:
|
|
if arg == '-':
|
|
menu.addSeparator()
|
|
elif isinstance(arg, QtWidgets.QMenu):
|
|
menu.addMenu(arg)
|
|
elif isinstance(arg, MainAction) and main_actions[arg]:
|
|
menu.addAction(main_actions[arg])
|
|
elif isinstance(arg, QtWidgets.QWidgetAction):
|
|
menu.addAction(arg)
|