diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index b58764d5ea..925f694afa 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -3728,6 +3728,12 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_CORE, "コア:") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_DATABASE, "データベース:") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS, + "拡張子:") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS_PLACEHOLDER, + "(スペースで区切る。 既定は全部の拡張子)") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_FILTER_INSIDE_ARCHIVES, + "アーカイブの中に絞り込み") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FOR_THUMBNAILS, "(サムネイルを見つかることに使う)") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2bdf54430b..be02410a94 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7266,6 +7266,18 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_DATABASE, "Database:" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS, + "Extensions:" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS_PLACEHOLDER, + "(space-separated; includes all by default)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_FILTER_INSIDE_ARCHIVES, + "Filter inside archives" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_QT_FOR_THUMBNAILS, "(used to find thumbnails)" diff --git a/msg_hash.h b/msg_hash.h index 2a76a08cb5..33b1719565 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1971,6 +1971,9 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_PATH, MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_CORE, MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_DATABASE, + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS, + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS_PLACEHOLDER, + MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_FILTER_INSIDE_ARCHIVES, MENU_ENUM_LABEL_VALUE_QT_FOR_THUMBNAILS, MENU_ENUM_LABEL_VALUE_QT_CANNOT_ADD_TO_ALL_PLAYLISTS, MENU_ENUM_LABEL_VALUE_QT_DELETE, diff --git a/ui/drivers/qt/playlist.cpp b/ui/drivers/qt/playlist.cpp index 25f8abc53a..b143c2235a 100644 --- a/ui/drivers/qt/playlist.cpp +++ b/ui/drivers/qt/playlist.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "../ui_qt.h" #include "flowlayout.h" @@ -38,29 +39,86 @@ inline static bool comp_hash_label_key_lower(const QHash &lhs, } /* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */ -static void addDirectoryFilesToList(QStringList &list, QDir &dir) +bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions) { + PlaylistEntryDialog *playlistDialog = playlistEntryDialog(); QStringList dirList = dir.entryList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name); int i; for (i = 0; i < dirList.count(); i++) { QString path(dir.path() + "/" + dirList.at(i)); + QByteArray pathArray = path.toUtf8(); QFileInfo fileInfo(path); + const char *pathData = pathArray.constData(); + + if (dialog->wasCanceled()) + return false; + + if (i % 25 == 0) + { + /* Needed to update progress dialog while doing a lot of stuff on the main thread. */ + qApp->processEvents(); + } if (fileInfo.isDir()) { QDir fileInfoDir(path); + bool success = addDirectoryFilesToList(dialog, list, fileInfoDir, extensions); + + if (!success) + return false; - addDirectoryFilesToList(list, fileInfoDir); continue; } if (fileInfo.isFile()) { - list.append(fileInfo.absoluteFilePath()); + bool add = false; + + if (extensions.isEmpty()) + add = true; + else + { + if (extensions.contains(fileInfo.suffix())) + add = true; + else + { + if (path_is_compressed_file(pathData)) + { + struct string_list *list = file_archive_get_file_list(pathData, NULL); + + if (list) + { + if (list->size == 1) + { + /* Assume archives with one file should have that file loaded directly. + * Don't just extend this to add all files in a zip, because we might hit + * something like MAME/FBA where only the archives themselves are valid content. */ + pathArray = (QString(pathData) + "#" + list->elems[0].data).toUtf8(); + pathData = pathArray.constData(); + + if (!extensions.isEmpty() && playlistDialog->filterInArchive()) + { + /* If the user chose to filter extensions inside archives, and this particular file inside the archive + * doesn't have one of the chosen extensions, then we skip it. */ + if (extensions.contains(QFileInfo(pathData).suffix())) + add = true; + } + } + + string_list_free(list); + } + } + } + } + + if (add) + list.append(fileInfo.absoluteFilePath()); } } + + return true; } void MainWindow::onPlaylistFilesDropped(QStringList files) @@ -82,6 +140,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) QString selectedDatabase; QString selectedName; QString selectedPath; + QStringList selectedExtensions; const char *currentPlaylistData = NULL; playlist_t *playlist = NULL; int i; @@ -130,6 +189,10 @@ void MainWindow::addFilesToPlaylist(QStringList files) selectedPath = m_playlistEntryDialog->getSelectedPath(); selectedCore = m_playlistEntryDialog->getSelectedCore(); selectedDatabase = m_playlistEntryDialog->getSelectedDatabase(); + selectedExtensions = m_playlistEntryDialog->getSelectedExtensions(); + + if (!selectedExtensions.isEmpty()) + selectedExtensions.replaceInStrings(QRegularExpression("^\\."), ""); if (selectedDatabase.isEmpty()) selectedDatabase = QFileInfo(currentPlaylistPath).fileName(); @@ -138,6 +201,9 @@ void MainWindow::addFilesToPlaylist(QStringList files) dialog.reset(new QProgressDialog(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_GATHERING_LIST_OF_FILES), "Cancel", 0, 0, this)); dialog->setWindowModality(Qt::ApplicationModal); + dialog->show(); + + qApp->processEvents(); if (selectedName.isEmpty() || selectedPath.isEmpty() || selectedDatabase.isEmpty()) @@ -166,12 +232,37 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (fileInfo.isDir()) { QDir dir(path); - addDirectoryFilesToList(list, dir); + bool success = addDirectoryFilesToList(dialog.data(), list, dir, selectedExtensions); + + if (!success) + return; + continue; } if (fileInfo.isFile()) - list.append(fileInfo.absoluteFilePath()); + { + bool add = false; + + if (selectedExtensions.isEmpty()) + add = true; + else + { + QByteArray pathArray = path.toUtf8(); + const char *pathData = pathArray.constData(); + + if (selectedExtensions.contains(fileInfo.suffix())) + add = true; + else if (playlistDialog->filterInArchive() && path_is_compressed_file(pathData)) + { + /* We'll add it here but really just delay the check until later when the archive contents are iterated. */ + add = true; + } + } + + if (add) + list.append(fileInfo.absoluteFilePath()); + } else if (files.count() == 1) { /* If adding a single file, tell user that it doesn't exist. */ @@ -202,6 +293,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (dialog->wasCanceled()) { + /* Cancel out of everything, the current progress will not be written to the playlist at all. */ playlist_free(playlist); return; } @@ -209,6 +301,9 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (fileName.isEmpty()) continue; + /* a modal QProgressDialog calls processEvents() automatically in setValue() */ + dialog->setValue(i + 1); + fileInfo = fileName; if (files.count() == 1 && list.count() == 1 && i == 0) @@ -224,9 +319,6 @@ void MainWindow::addFilesToPlaylist(QStringList files) fileNameNoExten = fileBaseNameArray.constData(); - /* a modal QProgressDialog calls processEvents() automatically in setValue() */ - dialog->setValue(i + 1); - pathData = pathArray.constData(); if (selectedCore.isEmpty()) @@ -253,9 +345,22 @@ void MainWindow::addFilesToPlaylist(QStringList files) { if (list->size == 1) { - /* assume archives with one file should have that file loaded directly */ + /* Assume archives with one file should have that file loaded directly. + * Don't just extend this to add all files in a zip, because we might hit + * something like MAME/FBA where only the archives themselves are valid content. */ pathArray = QDir::toNativeSeparators(QString(pathData) + "#" + list->elems[0].data).toUtf8(); pathData = pathArray.constData(); + + if (!selectedExtensions.isEmpty() && playlistDialog->filterInArchive()) + { + /* If the user chose to filter extensions inside archives, and this particular file inside the archive + * doesn't have one of the chosen extensions, then we skip it. */ + if (!selectedExtensions.contains(QFileInfo(pathData).suffix())) + { + string_list_free(list); + continue; + } + } } string_list_free(list); diff --git a/ui/drivers/qt/playlistentrydialog.cpp b/ui/drivers/qt/playlistentrydialog.cpp index d28dcf4000..559df6a22b 100644 --- a/ui/drivers/qt/playlistentrydialog.cpp +++ b/ui/drivers/qt/playlistentrydialog.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -33,17 +34,20 @@ PlaylistEntryDialog::PlaylistEntryDialog(MainWindow *mainwindow, QWidget *parent ,m_settings(mainwindow->settings()) ,m_nameLineEdit(new QLineEdit(this)) ,m_pathLineEdit(new QLineEdit(this)) + ,m_extensionsLineEdit(new QLineEdit(this)) ,m_coreComboBox(new QComboBox(this)) ,m_databaseComboBox(new QComboBox(this)) + ,m_extensionArchiveCheckBox(new QCheckBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_FILTER_INSIDE_ARCHIVES), this)) { QFormLayout *form = new QFormLayout(); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QVBoxLayout *databaseVBoxLayout = new QVBoxLayout(); QHBoxLayout *pathHBoxLayout = new QHBoxLayout(); + QHBoxLayout *extensionHBoxLayout = new QHBoxLayout(); QLabel *databaseLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FOR_THUMBNAILS), this); QToolButton *pathPushButton = new QToolButton(this); - pathPushButton->setText("..."); + pathPushButton->setText(QStringLiteral("...")); pathHBoxLayout->addWidget(m_pathLineEdit); pathHBoxLayout->addWidget(pathPushButton); @@ -51,6 +55,14 @@ PlaylistEntryDialog::PlaylistEntryDialog(MainWindow *mainwindow, QWidget *parent databaseVBoxLayout->addWidget(m_databaseComboBox); databaseVBoxLayout->addWidget(databaseLabel); + extensionHBoxLayout->addWidget(m_extensionsLineEdit); + extensionHBoxLayout->addWidget(m_extensionArchiveCheckBox); + + m_extensionsLineEdit->setPlaceholderText(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS_PLACEHOLDER)); + + /* Ensure placeholder text is completely visible. */ + m_extensionsLineEdit->setMinimumWidth(QFontMetrics(m_extensionsLineEdit->font()).boundingRect(m_extensionsLineEdit->placeholderText()).width() + m_extensionsLineEdit->frameSize().width()); + setWindowTitle(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY)); form->setFormAlignment(Qt::AlignCenter); @@ -68,6 +80,7 @@ PlaylistEntryDialog::PlaylistEntryDialog(MainWindow *mainwindow, QWidget *parent form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_PATH), pathHBoxLayout); form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_CORE), m_coreComboBox); form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_DATABASE), databaseVBoxLayout); + form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLAYLIST_ENTRY_EXTENSIONS), extensionHBoxLayout); qobject_cast(layout())->addLayout(form); layout()->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -76,6 +89,11 @@ PlaylistEntryDialog::PlaylistEntryDialog(MainWindow *mainwindow, QWidget *parent connect(pathPushButton, SIGNAL(clicked()), this, SLOT(onPathClicked())); } +bool PlaylistEntryDialog::filterInArchive() +{ + return m_extensionArchiveCheckBox->isChecked(); +} + void PlaylistEntryDialog::onPathClicked() { QString filePath = QFileDialog::getOpenFileName(this); @@ -233,6 +251,18 @@ const QString PlaylistEntryDialog::getSelectedDatabase() return m_databaseComboBox->currentData(Qt::UserRole).toString(); } +const QStringList PlaylistEntryDialog::getSelectedExtensions() +{ + QStringList list; + QString text = m_extensionsLineEdit->text(); + + /* Otherwise it would create a QStringList with a single blank entry... */ + if (!text.isEmpty()) + list = text.split(' '); + + return list; +} + void PlaylistEntryDialog::onAccepted() { } diff --git a/ui/drivers/qt/playlistentrydialog.h b/ui/drivers/qt/playlistentrydialog.h index 723f46304e..351744ea82 100644 --- a/ui/drivers/qt/playlistentrydialog.h +++ b/ui/drivers/qt/playlistentrydialog.h @@ -6,6 +6,7 @@ class QSettings; class QLineEdit; class QComboBox; +class QCheckBox; class MainWindow; class PlaylistEntryDialog : public QDialog @@ -17,6 +18,8 @@ public: const QString getSelectedDatabase(); const QString getSelectedName(); const QString getSelectedPath(); + const QStringList getSelectedExtensions(); + bool filterInArchive(); void setEntryValues(const QHash &contentHash); public slots: bool showDialog(const QHash &hash = QHash()); @@ -32,8 +35,10 @@ private: QSettings *m_settings; QLineEdit *m_nameLineEdit; QLineEdit *m_pathLineEdit; + QLineEdit *m_extensionsLineEdit; QComboBox *m_coreComboBox; QComboBox *m_databaseComboBox; + QCheckBox *m_extensionArchiveCheckBox; }; #endif diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 0084890e88..ff76519487 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -429,6 +429,7 @@ private: bool updateCurrentPlaylistEntry(const QHash &contentHash); int extractArchive(QString path); void removeUpdateTempFiles(); + bool addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions); QVector > getPlaylistItems(QString pathString); LoadCoreWindow *m_loadCoreWindow;