mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
229 lines
9.5 KiB
Python
229 lines
9.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Picard, the next-generation MusicBrainz tagger
|
|
#
|
|
# Copyright (C) 2006-2008, 2011 Lukáš Lalinský
|
|
# Copyright (C) 2008-2009 Nikolai Prokoschenko
|
|
# Copyright (C) 2009-2010, 2014-2015, 2018-2022 Philipp Wolfer
|
|
# Copyright (C) 2011-2013 Michael Wiencek
|
|
# Copyright (C) 2011-2013 Wieland Hoffmann
|
|
# Copyright (C) 2013 Calvin Walton
|
|
# Copyright (C) 2013 Ionuț Ciocîrlan
|
|
# Copyright (C) 2013-2014 Sophist-UK
|
|
# Copyright (C) 2013-2015, 2018-2021, 2023-2024 Laurent Monin
|
|
# Copyright (C) 2015 Alex Berman
|
|
# Copyright (C) 2015 Ohm Patel
|
|
# Copyright (C) 2016 Suhas
|
|
# Copyright (C) 2016-2017 Sambhav Kothari
|
|
# Copyright (C) 2021 Bob Swift
|
|
# Copyright (C) 2021 Gabriel Ferreira
|
|
#
|
|
# 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.
|
|
|
|
|
|
import re
|
|
|
|
from PyQt6 import (
|
|
QtCore,
|
|
QtGui,
|
|
QtWidgets,
|
|
)
|
|
|
|
from picard.config import (
|
|
BoolOption,
|
|
Option,
|
|
TextOption,
|
|
get_config,
|
|
)
|
|
from picard.const.sys import IS_WIN
|
|
from picard.i18n import (
|
|
N_,
|
|
gettext as _,
|
|
)
|
|
from picard.util import system_supports_long_paths
|
|
|
|
from picard.ui import PicardDialog
|
|
from picard.ui.options import (
|
|
OptionsPage,
|
|
register_options_page,
|
|
)
|
|
from picard.ui.ui_options_renaming_compat import Ui_RenamingCompatOptionsPage
|
|
from picard.ui.ui_win_compat_dialog import Ui_WinCompatDialog
|
|
|
|
|
|
DEFAULT_REPLACEMENT = '_'
|
|
DEFAULT_WIN_COMPAT_REPLACEMENTS = {
|
|
'*': DEFAULT_REPLACEMENT,
|
|
':': DEFAULT_REPLACEMENT,
|
|
'<': DEFAULT_REPLACEMENT,
|
|
'>': DEFAULT_REPLACEMENT,
|
|
'?': DEFAULT_REPLACEMENT,
|
|
'|': DEFAULT_REPLACEMENT,
|
|
'"': DEFAULT_REPLACEMENT,
|
|
}
|
|
|
|
|
|
class RenamingCompatOptionsPage(OptionsPage):
|
|
|
|
NAME = 'filerenaming_compat'
|
|
TITLE = N_("Compatibility")
|
|
PARENT = 'filerenaming'
|
|
ACTIVE = True
|
|
HELP_URL = "/config/options_filerenaming_compat.html"
|
|
|
|
options = [
|
|
BoolOption('setting', 'windows_compatibility', True, title=N_("Windows compatibility")),
|
|
BoolOption('setting', 'windows_long_paths', system_supports_long_paths() if IS_WIN else False, title=N_("Windows long path support")),
|
|
BoolOption('setting', 'ascii_filenames', False, title=N_("Replace non-ASCII characters")),
|
|
BoolOption('setting', 'replace_spaces_with_underscores', False, title=N_("Replace spaces with underscores")),
|
|
TextOption('setting', 'replace_dir_separator', DEFAULT_REPLACEMENT, title=N_("Replacement character to use for directory separators")),
|
|
Option('setting', 'win_compat_replacements', DEFAULT_WIN_COMPAT_REPLACEMENTS, title=N_("Replacement characters used for Windows compatibility"))
|
|
]
|
|
|
|
options_changed = QtCore.pyqtSignal(dict)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
config = get_config()
|
|
self.win_compat_replacements = config.setting['win_compat_replacements']
|
|
self.ui = Ui_RenamingCompatOptionsPage()
|
|
self.ui.setupUi(self)
|
|
self.ui.ascii_filenames.toggled.connect(self.on_options_changed)
|
|
self.ui.windows_compatibility.toggled.connect(self.on_options_changed)
|
|
self.ui.windows_long_paths.toggled.connect(self.on_options_changed)
|
|
self.ui.replace_spaces_with_underscores.toggled.connect(self.on_options_changed)
|
|
self.ui.replace_dir_separator.textChanged.connect(self.on_options_changed)
|
|
self.ui.btn_windows_compatibility_change.clicked.connect(self.open_win_compat_dialog)
|
|
|
|
def load(self):
|
|
config = get_config()
|
|
self.win_compat_replacements = config.setting['win_compat_replacements']
|
|
try:
|
|
self.ui.windows_long_paths.toggled.disconnect(self.toggle_windows_long_paths)
|
|
except TypeError:
|
|
pass
|
|
if IS_WIN:
|
|
self.ui.windows_compatibility.setChecked(True)
|
|
self.ui.windows_compatibility.setEnabled(False)
|
|
else:
|
|
self.ui.windows_compatibility.setChecked(config.setting['windows_compatibility'])
|
|
self.ui.windows_long_paths.setChecked(config.setting['windows_long_paths'])
|
|
self.ui.ascii_filenames.setChecked(config.setting['ascii_filenames'])
|
|
self.ui.replace_spaces_with_underscores.setChecked(config.setting['replace_spaces_with_underscores'])
|
|
self.ui.replace_dir_separator.setText(config.setting['replace_dir_separator'])
|
|
self.ui.windows_long_paths.toggled.connect(self.toggle_windows_long_paths)
|
|
|
|
def save(self):
|
|
config = get_config()
|
|
options = self.get_options()
|
|
for key, value in options.items():
|
|
config.setting[key] = value
|
|
|
|
def toggle_windows_long_paths(self, state):
|
|
if state and not system_supports_long_paths():
|
|
dialog = QtWidgets.QMessageBox(
|
|
QtWidgets.QMessageBox.Icon.Information,
|
|
_("Windows long path support"),
|
|
_(
|
|
"Enabling long paths on Windows might cause files being saved with path names "
|
|
"exceeding the 259 character limit traditionally imposed by the Windows API. "
|
|
"Some software might not be able to properly access those files."
|
|
),
|
|
QtWidgets.QMessageBox.StandardButton.Ok,
|
|
self)
|
|
dialog.exec()
|
|
|
|
def on_options_changed(self):
|
|
self.options_changed.emit(self.get_options())
|
|
|
|
def get_options(self):
|
|
return {
|
|
'ascii_filenames': self.ui.ascii_filenames.isChecked(),
|
|
'windows_compatibility': self.ui.windows_compatibility.isChecked(),
|
|
'windows_long_paths': self.ui.windows_long_paths.isChecked(),
|
|
'replace_spaces_with_underscores': self.ui.replace_spaces_with_underscores.isChecked(),
|
|
'replace_dir_separator': self.ui.replace_dir_separator.text(),
|
|
'win_compat_replacements': self.win_compat_replacements,
|
|
}
|
|
|
|
def open_win_compat_dialog(self):
|
|
dialog = WinCompatDialog(self.win_compat_replacements, parent=self)
|
|
if dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
|
|
self.win_compat_replacements = dialog.replacements
|
|
self.on_options_changed()
|
|
|
|
|
|
class WinCompatReplacementValidator(QtGui.QValidator):
|
|
_re_valid_win_replacement = re.compile(r'^[^"*:<>?|/\\\s]?$')
|
|
|
|
def validate(self, text: str, pos):
|
|
if self._re_valid_win_replacement.match(text):
|
|
state = QtGui.QValidator.State.Acceptable
|
|
else:
|
|
state = QtGui.QValidator.State.Invalid
|
|
return state, text, pos
|
|
|
|
|
|
class WinCompatDialog(PicardDialog):
|
|
def __init__(self, replacements, parent=None):
|
|
super().__init__(parent)
|
|
self.replacements = dict(replacements)
|
|
self.ui = Ui_WinCompatDialog()
|
|
self.ui.setupUi(self)
|
|
self.ui.replace_asterisk.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_colon.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_gt.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_lt.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_pipe.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_questionmark.setValidator(WinCompatReplacementValidator())
|
|
self.ui.replace_quotationmark.setValidator(WinCompatReplacementValidator())
|
|
self.ui.buttonbox.accepted.connect(self.accept)
|
|
self.ui.buttonbox.rejected.connect(self.reject)
|
|
reset_button = QtWidgets.QPushButton(_("Restore &Defaults"))
|
|
self.ui.buttonbox.addButton(reset_button, QtWidgets.QDialogButtonBox.ButtonRole.ResetRole)
|
|
reset_button.clicked.connect(self.restore_defaults)
|
|
self.load()
|
|
|
|
def load(self):
|
|
self.ui.replace_asterisk.setText(self.replacements['*'])
|
|
self.ui.replace_colon.setText(self.replacements[':'])
|
|
self.ui.replace_gt.setText(self.replacements['>'])
|
|
self.ui.replace_lt.setText(self.replacements['<'])
|
|
self.ui.replace_pipe.setText(self.replacements['|'])
|
|
self.ui.replace_questionmark.setText(self.replacements['?'])
|
|
self.ui.replace_quotationmark.setText(self.replacements['"'])
|
|
|
|
def accept(self):
|
|
self.replacements['*'] = self.ui.replace_asterisk.text()
|
|
self.replacements[':'] = self.ui.replace_colon.text()
|
|
self.replacements['>'] = self.ui.replace_gt.text()
|
|
self.replacements['<'] = self.ui.replace_lt.text()
|
|
self.replacements['|'] = self.ui.replace_pipe.text()
|
|
self.replacements['?'] = self.ui.replace_questionmark.text()
|
|
self.replacements['"'] = self.ui.replace_quotationmark.text()
|
|
super().accept()
|
|
|
|
def restore_defaults(self):
|
|
self.ui.replace_asterisk.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_colon.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_gt.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_lt.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_pipe.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_questionmark.setText(DEFAULT_REPLACEMENT)
|
|
self.ui.replace_quotationmark.setText(DEFAULT_REPLACEMENT)
|
|
|
|
|
|
register_options_page(RenamingCompatOptionsPage)
|