#include #include #include #include #include #include #include #include #include #include #include #include #include #include "../ui_qt.h" #include "qt_dialogs.h" #ifndef CXX_BUILD extern "C" { #endif #ifdef HAVE_CONFIG_H #include "../../../config.h" #endif #include #include #include #include #ifdef HAVE_MENU #include "../../../menu/menu_displaylist.h" #endif #include "../../../file_path_special.h" #include "../../../playlist.h" #include "../../../setting_list.h" #include "../../../configuration.h" #include "../../../core_info.h" #include "../../../verbosity.h" #ifndef CXX_BUILD } #endif PlaylistModel::PlaylistModel(QObject *parent) : QAbstractListModel(parent) { m_imageFormats = QVector::fromList(QImageReader::supportedImageFormats()); m_fileSanitizerRegex = QRegularExpression("[&*/:`<>?\\|]"); setThumbnailCacheLimit(500); connect(this, &PlaylistModel::imageLoaded, this, &PlaylistModel::onImageLoaded); } int PlaylistModel::rowCount(const QModelIndex & /* parent */) const { return m_contents.count(); } int PlaylistModel::columnCount(const QModelIndex & /* parent */) const { return 1; } QVariant PlaylistModel::data(const QModelIndex &index, int role) const { if (index.column() == 0) { if (!index.isValid() || index.row() >= m_contents.size() || index.row() < 0) return QVariant(); switch (role) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: return m_contents.at(index.row())["label_noext"]; case HASH: return QVariant::fromValue(m_contents.at(index.row())); case THUMBNAIL: { QPixmap *cachedPreview = m_cache.object(getCurrentTypeThumbnailPath(index)); if (cachedPreview) return *cachedPreview; } break; } } return QVariant(); } Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractListModel::flags(index) | Qt::ItemIsEditable; } bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { QHash hash = m_contents.at(index.row()); hash["label"] = value.toString(); hash["label_noext"] = QFileInfo(value.toString()).completeBaseName(); m_contents.replace(index.row(), hash); emit dataChanged(index, index, { role }); return true; } return false; } QVariant PlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME); return section + 1; } void PlaylistModel::setThumbnailType(const ThumbnailType type) { m_thumbnailType = type; } void PlaylistModel::setThumbnailCacheLimit(int limit) { m_cache.setMaxCost(limit * 1024); } QString PlaylistModel::getThumbnailPath(const QModelIndex &index, QString type) const { return getThumbnailPath(m_contents.at(index.row()), type); } QString PlaylistModel::getPlaylistThumbnailsDir( const QString playlistName) const { settings_t *settings = config_get_ptr(); const char *path_dir_thumbnails = settings->paths.directory_thumbnails; return QDir::cleanPath(QString(path_dir_thumbnails)) + "/" + playlistName; } bool PlaylistModel::isSupportedImage(const QString path) const { QByteArray extension; QString extensionStr; int lastIndex = path.lastIndexOf('.'); if (lastIndex >= 0) { extensionStr = path.mid(lastIndex + 1); if (!extensionStr.isEmpty()) extension = extensionStr.toLower().toUtf8(); } if (!extension.isEmpty() && m_imageFormats.contains(extension)) return true; return false; } QString PlaylistModel::getSanitizedThumbnailName(QString label) const { return label.replace(m_fileSanitizerRegex, "_") + ".png"; } QString PlaylistModel::getThumbnailPath(const QHash &hash, QString type) const { /* use thumbnail widgets to show regular image files */ if (isSupportedImage(hash["path"])) return hash["path"]; return getPlaylistThumbnailsDir(hash.value("db_name")) + "/" + type + "/" + getSanitizedThumbnailName(hash["label_noext"]); } QString PlaylistModel::getCurrentTypeThumbnailPath(const QModelIndex &index) const { switch (m_thumbnailType) { case THUMBNAIL_TYPE_BOXART: return getThumbnailPath(index, THUMBNAIL_BOXART); case THUMBNAIL_TYPE_SCREENSHOT: return getThumbnailPath(index, THUMBNAIL_SCREENSHOT); case THUMBNAIL_TYPE_TITLE_SCREEN: return getThumbnailPath(index, THUMBNAIL_TITLE); default: break; } return QString(); } void PlaylistModel::reloadThumbnail(const QModelIndex &index) { if (index.isValid()) { reloadThumbnailPath(getCurrentTypeThumbnailPath(index)); loadThumbnail(index); } } void PlaylistModel::reloadSystemThumbnails(const QString system) { int i = 0; settings_t *settings = config_get_ptr(); const char *path_dir_thumbnails = settings->paths.directory_thumbnails; QString path = QDir::cleanPath(QString(path_dir_thumbnails)) + "/" + system; QList keys = m_cache.keys(); QList pending = m_pendingImages.values(); for (i = 0; i < keys.size(); i++) { QString key = keys.at(i); if (key.startsWith(path)) m_cache.remove(key); } for (i = 0; i < pending.size(); i++) { QString key = pending.at(i); if (key.startsWith(path)) m_pendingImages.remove(key); } } void PlaylistModel::reloadThumbnailPath(const QString path) { m_cache.remove(path); m_pendingImages.remove(path); } void PlaylistModel::loadThumbnail(const QModelIndex &index) { QString path = getCurrentTypeThumbnailPath(index); if (!m_pendingImages.contains(path) && !m_cache.contains(path)) { m_pendingImages.insert(path); QtConcurrent::run(this, &PlaylistModel::loadImage, index, path); } } void PlaylistModel::loadImage(const QModelIndex &index, const QString &path) { const QImage image = QImage(path); if (!image.isNull()) emit imageLoaded(image, index, path); } void PlaylistModel::onImageLoaded(const QImage image, const QModelIndex &index, const QString &path) { QPixmap *pixmap = new QPixmap(QPixmap::fromImage(image)); const int cost = pixmap->width() * pixmap->height() * pixmap->depth() / (8 * 1024); m_cache.insert(path, pixmap, cost); if (index.isValid()) emit dataChanged(index, index, { THUMBNAIL }); m_pendingImages.remove(path); } static inline bool comp_hash_name_key_lower(const QHash &lhs, const QHash &rhs) { return lhs.value("name").toLower() < rhs.value("name").toLower(); } bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions) { int i; PlaylistEntryDialog *playlistDialog = playlistEntryDialog(); QStringList dirList = dir.entryList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name); 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; /* Needed to update progress dialog while doing * a lot of stuff on the main thread. */ if (i % 25 == 0) qApp->processEvents(); if (fileInfo.isDir()) { QDir fileInfoDir(path); bool success = addDirectoryFilesToList( dialog, list, fileInfoDir, extensions); if (!success) return false; continue; } if (fileInfo.isFile()) { 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 *archive_list = file_archive_get_file_list(pathData, NULL); if (archive_list) { if (archive_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) + "#" + archive_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(archive_list); } } } } if (add) list.append(fileInfo.absoluteFilePath()); } } return true; } void MainWindow::onPlaylistFilesDropped(QStringList files) { addFilesToPlaylist(files); } /* Takes a list of files and folders and adds them to the * currently selected playlist. Folders will have their * contents added recursively. */ void MainWindow::addFilesToPlaylist(QStringList files) { int i; QStringList list; QString currentPlaylistPath; QByteArray currentPlaylistArray; QScopedPointer dialog(NULL); QHash selectedCore; QHash itemToAdd; QString selectedDatabase; QString selectedName; QString selectedPath; QStringList selectedExtensions; playlist_config_t playlist_config; QListWidgetItem *currentItem = m_listWidget->currentItem(); PlaylistEntryDialog *playlistDialog = playlistEntryDialog(); const char *currentPlaylistData = NULL; playlist_t *playlist = NULL; settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); /* Assume a blank list means we will manually enter in all fields. */ if (files.isEmpty()) { /* Make sure hash isn't blank, that would mean there's * multiple entries to add at once. */ itemToAdd["label"] = ""; itemToAdd["path"] = ""; } else if (files.count() == 1) { QString path = files.at(0); QFileInfo info(path); if (info.isFile()) { itemToAdd["label"] = info.completeBaseName(); itemToAdd["path"] = path; } } if (currentItem) { currentPlaylistPath = currentItem->data(Qt::UserRole).toString(); if (!currentPlaylistPath.isEmpty()) { currentPlaylistArray = currentPlaylistPath.toUtf8(); currentPlaylistData = currentPlaylistArray.constData(); } } if (currentPlaylistPath == ALL_PLAYLISTS_TOKEN) { showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CANNOT_ADD_TO_ALL_PLAYLISTS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } /* a blank itemToAdd means there will be multiple */ if (!playlistDialog->showDialog(itemToAdd)) return; selectedName = m_playlistEntryDialog->getSelectedName(); 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(); else selectedDatabase.append(".lpl"); 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()) { showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } if (files.isEmpty()) files.append(selectedPath); for (i = 0; i < files.count(); i++) { QString path(files.at(i)); QFileInfo fileInfo(path); if (dialog->wasCanceled()) return; /* Needed to update progress dialog while * doing a lot of stuff on the main thread. */ if (i % 25 == 0) qApp->processEvents(); if (fileInfo.isDir()) { QDir dir(path); bool success = addDirectoryFilesToList( dialog.data(), list, dir, selectedExtensions); if (!success) return; continue; } if (fileInfo.isFile()) { 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. */ showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); return; } } dialog->setLabelText(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_ADDING_FILES_TO_PLAYLIST)); dialog->setMaximum(list.count()); playlist_config_set_path(&playlist_config, currentPlaylistData); playlist = playlist_init(&playlist_config); for (i = 0; i < list.count(); i++) { QFileInfo fileInfo; QByteArray fileBaseNameArray; QByteArray pathArray; QByteArray corePathArray; QByteArray coreNameArray; QByteArray databaseArray; QString fileName = list.at(i); const char *pathData = NULL; const char *fileNameNoExten = NULL; const char *corePathData = NULL; const char *coreNameData = NULL; const char *databaseData = NULL; /* Cancel out of everything, the * current progress will not be written * to the playlist at all. */ if (dialog->wasCanceled()) { playlist_free(playlist); return; } if (fileName.isEmpty()) continue; /* a modal QProgressDialog calls processEvents() * automatically in setValue() */ dialog->setValue(i + 1); fileInfo = fileName; /* Make sure we're looking at a user-specified field * and not just "" * in case it was a folder with one file in it */ if ( files.count() == 1 && list.count() == 1 && i == 0 && playlistDialog->nameFieldEnabled()) { fileBaseNameArray = selectedName.toUtf8(); pathArray = QDir::toNativeSeparators(selectedPath).toUtf8(); } /* Otherwise just use the file name itself (minus extension) * for the playlist entry title */ else { fileBaseNameArray = fileInfo.completeBaseName().toUtf8(); pathArray = QDir::toNativeSeparators(fileName).toUtf8(); } fileNameNoExten = fileBaseNameArray.constData(); pathData = pathArray.constData(); if (selectedCore.isEmpty()) { corePathData = "DETECT"; coreNameData = "DETECT"; } else { corePathArray = QDir::toNativeSeparators( selectedCore.value("core_path")).toUtf8(); coreNameArray = selectedCore.value("core_name").toUtf8(); corePathData = corePathArray.constData(); coreNameData = coreNameArray.constData(); } databaseArray = selectedDatabase.toUtf8(); databaseData = databaseArray.constData(); 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 = 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); } } { struct playlist_entry entry = {0}; /* the push function reads our entry as const, * so these casts are safe */ entry.path = const_cast(pathData); entry.label = const_cast(fileNameNoExten); entry.core_path = const_cast(corePathData); entry.core_name = const_cast(coreNameData); entry.crc32 = const_cast("00000000|crc"); entry.db_name = const_cast(databaseData); playlist_push(playlist, &entry); } } playlist_write_file(playlist); playlist_free(playlist); reloadPlaylists(); } bool MainWindow::updateCurrentPlaylistEntry( const QHash &contentHash) { QString path; QString label; QString corePath; QString coreName; QString dbName; QString crc32; QByteArray playlistPathArray; QByteArray pathArray; QByteArray labelArray; QByteArray corePathArray; QByteArray coreNameArray; QByteArray dbNameArray; QByteArray crc32Array; playlist_config_t playlist_config; QString playlistPath = getCurrentPlaylistPath(); const char *playlistPathData = NULL; const char *pathData = NULL; const char *labelData = NULL; const char *corePathData = NULL; const char *coreNameData = NULL; const char *dbNameData = NULL; const char *crc32Data = NULL; playlist_t *playlist = NULL; unsigned index = 0; bool ok = false; settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if ( playlistPath.isEmpty() || contentHash.isEmpty() || !contentHash.contains("index")) return false; index = contentHash.value("index").toUInt(&ok); if (!ok) return false; path = contentHash.value("path"); label = contentHash.value("label"); coreName = contentHash.value("core_name"); corePath = contentHash.value("core_path"); dbName = contentHash.value("db_name"); crc32 = contentHash.value("crc32"); if ( path.isEmpty() || label.isEmpty() || coreName.isEmpty() || corePath.isEmpty() ) return false; playlistPathArray = playlistPath.toUtf8(); pathArray = QDir::toNativeSeparators(path).toUtf8(); labelArray = label.toUtf8(); coreNameArray = coreName.toUtf8(); corePathArray = QDir::toNativeSeparators(corePath).toUtf8(); if (!dbName.isEmpty()) { dbNameArray = (dbName + ".lpl").toUtf8(); dbNameData = dbNameArray.constData(); } playlistPathData = playlistPathArray.constData(); pathData = pathArray.constData(); labelData = labelArray.constData(); coreNameData = coreNameArray.constData(); corePathData = corePathArray.constData(); if (!crc32.isEmpty()) { crc32Array = crc32.toUtf8(); crc32Data = crc32Array.constData(); } 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 */ pathArray = QDir::toNativeSeparators(QString(pathData) + "#" + list->elems[0].data).toUtf8(); pathData = pathArray.constData(); } string_list_free(list); } } playlist_config_set_path(&playlist_config, playlistPathData); playlist = playlist_init(&playlist_config); { struct playlist_entry entry = {0}; /* the update function reads our entry as const, so these casts are safe */ entry.path = const_cast(pathData); entry.label = const_cast(labelData); entry.core_path = const_cast(corePathData); entry.core_name = const_cast(coreNameData); entry.crc32 = const_cast(crc32Data); entry.db_name = const_cast(dbNameData); playlist_update(playlist, index, &entry); } playlist_write_file(playlist); playlist_free(playlist); reloadPlaylists(); return true; } void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) { QString currentPlaylistDirPath; QString currentPlaylistPath; QString currentPlaylistFileName; QFile currentPlaylistFile; QFileInfo currentPlaylistFileInfo; QMap coreList; QScopedPointer menu; QScopedPointer associateMenu; QScopedPointer hiddenPlaylistsMenu; QScopedPointer downloadAllThumbnailsMenu; QScopedPointer hideAction; QScopedPointer newPlaylistAction; QScopedPointer deletePlaylistAction; QScopedPointer renamePlaylistAction; QScopedPointer downloadAllThumbnailsEntireSystemAction; QScopedPointer downloadAllThumbnailsThisPlaylistAction; QPointer selectedAction; playlist_config_t playlist_config; QPoint cursorPos = QCursor::pos(); settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; QDir playlistDir(path_dir_playlist); QListWidgetItem *selectedItem = m_listWidget->itemAt( m_listWidget->viewport()->mapFromGlobal(cursorPos)); QString playlistDirAbsPath = playlistDir.absolutePath(); core_info_list_t *core_info_list = NULL; unsigned i = 0; int j = 0; bool specialPlaylist = false; bool foundHiddenPlaylist = false; playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if (selectedItem) { currentPlaylistPath = selectedItem->data(Qt::UserRole).toString(); currentPlaylistFile.setFileName(currentPlaylistPath); currentPlaylistFileInfo = QFileInfo(currentPlaylistPath); currentPlaylistFileName = currentPlaylistFileInfo.fileName(); currentPlaylistDirPath = currentPlaylistFileInfo.absoluteDir().absolutePath(); } menu.reset(new QMenu(this)); menu->setObjectName("menu"); hiddenPlaylistsMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_HIDDEN_PLAYLISTS), this)); newPlaylistAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NEW_PLAYLIST)) + "...", this)); hiddenPlaylistsMenu->setObjectName("hiddenPlaylistsMenu"); menu->addAction(newPlaylistAction.data()); if (currentPlaylistFile.exists()) { deletePlaylistAction.reset(new QAction( QString(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_DELETE_PLAYLIST)) + "...", this)); menu->addAction(deletePlaylistAction.data()); renamePlaylistAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_RENAME_PLAYLIST)) + "...", this)); menu->addAction(renamePlaylistAction.data()); } if (selectedItem) { hideAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_HIDE), this)); menu->addAction(hideAction.data()); } for (j = 0; j < m_listWidget->count(); j++) { QListWidgetItem *item = m_listWidget->item(j); bool hidden = m_listWidget->isItemHidden(item); if (hidden) { QAction *action = hiddenPlaylistsMenu->addAction(item->text()); action->setProperty("row", j); action->setProperty("core_path", item->data(Qt::UserRole).toString()); foundHiddenPlaylist = true; } } if (!foundHiddenPlaylist) { QAction *action = hiddenPlaylistsMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE)); action->setProperty("row", -1); } menu->addMenu(hiddenPlaylistsMenu.data()); /* Don't just compare strings in case there are case differences on Windows that should be ignored. special playlists like history etc. can't have an association */ if (QDir(currentPlaylistDirPath) != QDir(playlistDirAbsPath)) specialPlaylist = true; if (!specialPlaylist) { associateMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ASSOCIATE_CORE), this)); associateMenu->setObjectName("associateMenu"); core_info_get_list(&core_info_list); for (i = 0; i < core_info_list->count && core_info_list->count > 0; i++) { const core_info_t *core = &core_info_list->list[i]; coreList[core->core_name] = core; } { QMapIterator coreListIterator(coreList); QVector > cores; while (coreListIterator.hasNext()) { QString key, name; const core_info_t *core = NULL; QHash hash; coreListIterator.next(); key = coreListIterator.key(); core = coreList.value(key); if (string_is_empty(core->core_name)) name = core->display_name; else name = core->core_name; if (name.isEmpty()) continue; hash["name"] = name; hash["core_path"] = core->path; cores.append(hash); } std::sort(cores.begin(), cores.end(), comp_hash_name_key_lower); for (j = 0; j < cores.count(); j++) { const QHash &hash = cores.at(j); QAction *action = associateMenu->addAction(hash.value("name")); action->setProperty("core_path", hash.value("core_path")); } } menu->addMenu(associateMenu.data()); } if (!specialPlaylist) { downloadAllThumbnailsMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS), this)); downloadAllThumbnailsMenu->setObjectName("downloadAllThumbnailsMenu"); downloadAllThumbnailsThisPlaylistAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS_THIS_PLAYLIST), downloadAllThumbnailsMenu.data())); downloadAllThumbnailsEntireSystemAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS_ENTIRE_SYSTEM), downloadAllThumbnailsMenu.data())); downloadAllThumbnailsMenu->addAction(downloadAllThumbnailsThisPlaylistAction.data()); downloadAllThumbnailsMenu->addAction(downloadAllThumbnailsEntireSystemAction.data()); menu->addMenu(downloadAllThumbnailsMenu.data()); } selectedAction = menu->exec(cursorPos); if (!selectedAction) return; if (!specialPlaylist && selectedAction->parent() == associateMenu.data()) { core_info_t *coreInfo = NULL; playlist_t *cachedPlaylist = playlist_get_cached(); playlist_t *playlist = NULL; bool loadPlaylist = true; QByteArray currentPlaylistPathByteArray = currentPlaylistPath.toUtf8(); const char *currentPlaylistPathCString = currentPlaylistPathByteArray.data(); QByteArray corePathByteArray = selectedAction->property("core_path").toString().toUtf8(); const char *corePath = corePathByteArray.data(); /* Load playlist, if required */ if (cachedPlaylist) { if (string_is_equal(currentPlaylistPathCString, playlist_get_conf_path(cachedPlaylist))) { playlist = cachedPlaylist; loadPlaylist = false; } } if (loadPlaylist) { playlist_config_set_path(&playlist_config, currentPlaylistPathCString); playlist = playlist_init(&playlist_config); } if (playlist) { /* Get core info */ if (core_info_find(corePath, &coreInfo)) { /* Set new core association */ playlist_set_default_core_path(playlist, coreInfo->path); playlist_set_default_core_name(playlist, coreInfo->display_name); } else { playlist_set_default_core_path(playlist, "DETECT"); playlist_set_default_core_name(playlist, "DETECT"); } /* Write changes to disk */ playlist_write_file(playlist); /* Free playlist, if required */ if (loadPlaylist) playlist_free(playlist); } } else if (selectedItem && selectedAction == deletePlaylistAction.data()) { if (currentPlaylistFile.exists()) { if (showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST)).arg(selectedItem->text()), MainWindow::MSGBOX_TYPE_QUESTION_YESNO, Qt::ApplicationModal, false)) { if (currentPlaylistFile.remove()) reloadPlaylists(); else showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); } } } else if (selectedItem && selectedAction == renamePlaylistAction.data()) { if (currentPlaylistFile.exists()) { QString oldName = selectedItem->text(); QString name = QInputDialog::getText(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_RENAME_PLAYLIST), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ENTER_NEW_PLAYLIST_NAME), QLineEdit::Normal, oldName); if (!name.isEmpty()) { renamePlaylistItem(selectedItem, name); reloadPlaylists(); } } } else if (selectedAction == newPlaylistAction.data()) { QString name = QInputDialog::getText(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NEW_PLAYLIST), msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ENTER_NEW_PLAYLIST_NAME)); QString newPlaylistPath = playlistDirAbsPath + "/" + name + ".lpl"; QFile file(newPlaylistPath); if (!name.isEmpty()) { if (file.open(QIODevice::WriteOnly)) file.close(); reloadPlaylists(); } } else if (selectedItem && selectedAction == hideAction.data()) { int row = m_listWidget->row(selectedItem); if (row >= 0) { QStringList hiddenPlaylists = m_settings->value("hidden_playlists").toStringList(); if (!hiddenPlaylists.contains(currentPlaylistFileName)) { hiddenPlaylists.append(currentPlaylistFileName); m_settings->setValue("hidden_playlists", hiddenPlaylists); } m_listWidget->setRowHidden(row, true); } } else if (selectedAction->parent() == hiddenPlaylistsMenu.data()) { QVariant rowVariant = selectedAction->property("row"); if (rowVariant.isValid()) { QStringList hiddenPlaylists = m_settings->value("hidden_playlists").toStringList(); int row = rowVariant.toInt(); if (row >= 0) { QString playlistPath = selectedAction->property("core_path").toString(); QFileInfo playlistFileInfo(playlistPath); QString playlistFileName = playlistFileInfo.fileName(); if (hiddenPlaylists.contains(playlistFileName)) { hiddenPlaylists.removeOne(playlistFileName); m_settings->setValue("hidden_playlists", hiddenPlaylists); } m_listWidget->setRowHidden(row, false); } } } else if (selectedItem && !specialPlaylist && selectedAction->parent() == downloadAllThumbnailsMenu.data()) { if (selectedAction == downloadAllThumbnailsEntireSystemAction.data()) { int row = m_listWidget->row(selectedItem); if (row >= 0) downloadAllThumbnails(currentPlaylistFileInfo.completeBaseName()); } else if (selectedAction == downloadAllThumbnailsThisPlaylistAction.data()) downloadPlaylistThumbnails(currentPlaylistPath); } setCoreActions(); } void MainWindow::deferReloadPlaylists() { emit gotReloadPlaylists(); } void MainWindow::onGotReloadPlaylists() { reloadPlaylists(); } void MainWindow::reloadPlaylists() { int i = 0; QString currentPlaylistPath; QListWidgetItem *allPlaylistsItem = NULL; QListWidgetItem *favoritesPlaylistsItem = NULL; QListWidgetItem *imagePlaylistsItem = NULL; QListWidgetItem *musicPlaylistsItem = NULL; QListWidgetItem *videoPlaylistsItem = NULL; QListWidgetItem *firstItem = NULL; settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; QDir playlistDir(path_dir_playlist); QStringList hiddenPlaylists = m_settings->value( "hidden_playlists").toStringList(); QListWidgetItem *currentItem = m_listWidget->currentItem(); if (currentItem) currentPlaylistPath = currentItem->data(Qt::UserRole).toString(); getPlaylistFiles(); m_listWidget->clear(); m_listWidget->setSelectionBehavior(QAbstractItemView::SelectRows); m_listWidget->setSelectionMode(QAbstractItemView::SingleSelection); m_listWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); allPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ALL_PLAYLISTS)); allPlaylistsItem->setData(Qt::UserRole, ALL_PLAYLISTS_TOKEN); favoritesPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES_TAB)); favoritesPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_favorites); m_historyPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_HISTORY_TAB)); m_historyPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_history); imagePlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB)); imagePlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_image_history); musicPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB)); musicPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_music_history); videoPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB)); videoPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_video_history); m_listWidget->addItem(allPlaylistsItem); m_listWidget->addItem(favoritesPlaylistsItem); m_listWidget->addItem(m_historyPlaylistsItem); m_listWidget->addItem(imagePlaylistsItem); m_listWidget->addItem(musicPlaylistsItem); m_listWidget->addItem(videoPlaylistsItem); if (hiddenPlaylists.contains(ALL_PLAYLISTS_TOKEN)) m_listWidget->setRowHidden(m_listWidget->row(allPlaylistsItem), true); if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_favorites).fileName())) m_listWidget->setRowHidden(m_listWidget->row(favoritesPlaylistsItem), true); if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_history).fileName())) m_listWidget->setRowHidden(m_listWidget->row(m_historyPlaylistsItem), true); if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_image_history).fileName())) m_listWidget->setRowHidden(m_listWidget->row(imagePlaylistsItem), true); if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_music_history).fileName())) m_listWidget->setRowHidden(m_listWidget->row(musicPlaylistsItem), true); if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_video_history).fileName())) m_listWidget->setRowHidden(m_listWidget->row(videoPlaylistsItem), true); for (i = 0; i < m_playlistFiles.count(); i++) { QIcon icon; QString iconPath; QListWidgetItem *item = NULL; const QString &file = m_playlistFiles.at(i); /* don't show view files */ if (file.endsWith(".lvw", Qt::CaseInsensitive)) continue; QString fileDisplayName = file; QString fileName = file; bool hasIcon = false; fileDisplayName.remove(".lpl"); iconPath = QString( settings->paths.directory_assets) + ICON_PATH + fileDisplayName + ".png"; hasIcon = QFile::exists(iconPath); if (hasIcon) icon = QIcon(iconPath); else icon = m_folderIcon; item = new QListWidgetItem(icon, fileDisplayName); item->setFlags(item->flags() | Qt::ItemIsEditable); item->setData(Qt::UserRole, playlistDir.absoluteFilePath(file)); m_listWidget->addItem(item); if (hiddenPlaylists.contains(fileName)) { int row = m_listWidget->row(item); if (row >= 0) m_listWidget->setRowHidden(row, true); } } if (m_listWidget->count() > 0) { firstItem = m_listWidget->item(0); if (firstItem) { bool foundCurrent = false; bool foundInitial = false; QString initialPlaylist = m_settings->value("initial_playlist", m_historyPlaylistsItem->data(Qt::UserRole).toString()).toString(); QListWidgetItem *initialItem = NULL; for (i = 0; i < m_listWidget->count(); i++) { QString path; QListWidgetItem *item = m_listWidget->item(i); if (item) { path = item->data(Qt::UserRole).toString(); if (!path.isEmpty()) { /* don't break early here since we want * to make sure we've found both initial * and current items if they exist */ if (!foundInitial && path == initialPlaylist) { foundInitial = true; initialItem = item; } if ( !foundCurrent && !currentPlaylistPath.isEmpty() && path == currentPlaylistPath) { foundCurrent = true; m_listWidget->setCurrentItem(item); } } } } if (!foundCurrent) { if (foundInitial && initialItem) m_listWidget->setCurrentItem(initialItem); else { /* the previous playlist must be gone now, * just select the first one */ m_listWidget->setCurrentItem(firstItem); } } } } } QString MainWindow::getCurrentPlaylistPath() { QString playlistPath; QListWidgetItem *playlistItem = m_listWidget->currentItem(); if (!playlistItem) return playlistPath; playlistPath = playlistItem->data(Qt::UserRole).toString(); return playlistPath; } bool MainWindow::currentPlaylistIsSpecial() { QFileInfo currentPlaylistFileInfo; QString currentPlaylistPath; QString currentPlaylistDirPath; settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); QString playlistDirAbsPath = playlistDir.absolutePath(); QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); if (!currentPlaylistItem) return false; currentPlaylistPath = currentPlaylistItem->data(Qt::UserRole).toString(); currentPlaylistFileInfo = QFileInfo(currentPlaylistPath); currentPlaylistDirPath = currentPlaylistFileInfo.absoluteDir().absolutePath(); /* Don't just compare strings in case there are * case differences on Windows that should be ignored. */ if (QDir(currentPlaylistDirPath) != QDir(playlistDirAbsPath)) return true; return false; } bool MainWindow::currentPlaylistIsAll() { QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); if ( currentPlaylistItem && currentPlaylistItem->data(Qt::UserRole).toString() == ALL_PLAYLISTS_TOKEN) return true; return false; } void MainWindow::deleteCurrentPlaylistItem() { QByteArray playlistArray; playlist_config_t playlist_config; QString playlistPath = getCurrentPlaylistPath(); QHash contentHash = getCurrentContentHash(); playlist_t *playlist = NULL; const char *playlistData = NULL; unsigned index = 0; bool ok = false; bool isAllPlaylist = currentPlaylistIsAll(); settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if (isAllPlaylist) return; if (playlistPath.isEmpty()) return; if (contentHash.isEmpty()) return; playlistArray = playlistPath.toUtf8(); playlistData = playlistArray.constData(); index = contentHash.value("index").toUInt(&ok); if (!ok) return; if (!showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM)).arg(contentHash["label"]), MainWindow::MSGBOX_TYPE_QUESTION_YESNO, Qt::ApplicationModal, false)) return; playlist_config_set_path(&playlist_config, playlistData); playlist = playlist_init(&playlist_config); playlist_delete_index(playlist, index); playlist_write_file(playlist); playlist_free(playlist); reloadPlaylists(); } QString MainWindow::getPlaylistDefaultCore(QString plName) { size_t len; playlist_config_t playlist_config; char playlist_path[PATH_MAX_LENGTH]; QByteArray plNameByteArray = plName.toUtf8(); const char *plNameCString = plNameByteArray.data(); playlist_t *cachedPlaylist = playlist_get_cached(); playlist_t *playlist = NULL; bool loadPlaylist = true; QString corePath = QString(); settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if (!settings || string_is_empty(plNameCString)) return corePath; /* Get playlist path */ len = fill_pathname_join_special( playlist_path, settings->paths.directory_playlist, plNameCString, sizeof(playlist_path)); strlcpy(playlist_path + len, ".lpl", sizeof(playlist_path) - len); /* Load playlist, if required */ if (cachedPlaylist) { if (string_is_equal(playlist_path, playlist_get_conf_path(cachedPlaylist))) { playlist = cachedPlaylist; loadPlaylist = false; } } if (loadPlaylist) { playlist_config_set_path(&playlist_config, playlist_path); playlist = playlist_init(&playlist_config); } if (playlist) { const char *defaultCorePath = playlist_get_default_core_path(playlist); /* Get default core path */ if (!string_is_empty(defaultCorePath) && !string_is_equal(defaultCorePath, "DETECT")) corePath = QString::fromUtf8(defaultCorePath); /* Free playlist, if required */ if (loadPlaylist) playlist_free(playlist); } return corePath; } void MainWindow::getPlaylistFiles() { settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); m_playlistFiles = playlistDir.entryList( QDir::NoDotAndDotDot | QDir::Readable | QDir::Files, QDir::Name); } void PlaylistModel::getPlaylistItems(QString path) { QByteArray pathArray; playlist_config_t playlist_config; const char *pathData = NULL; const char *playlistName = NULL; playlist_t *playlist = NULL; unsigned playlistSize = 0; unsigned i = 0; settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); pathArray.append(path); pathData = pathArray.constData(); if (!string_is_empty(pathData)) playlistName = path_basename(pathData); playlist_config_set_path(&playlist_config, pathData); playlist = playlist_init(&playlist_config); playlistSize = playlist_get_size(playlist); for (i = 0; i < playlistSize; i++) { QHash hash; const struct playlist_entry *entry = NULL; playlist_get_index(playlist, i, &entry); if (string_is_empty(entry->path)) continue; hash["path"] = entry->path; hash["index"] = QString::number(i); if (string_is_empty(entry->label)) { hash["label"] = entry->path; hash["label_noext"] = entry->path; } else { hash["label"] = entry->label; hash["label_noext"] = entry->label; } if (!string_is_empty(entry->core_path)) hash["core_path"] = entry->core_path; if (!string_is_empty(entry->core_name)) hash["core_name"] = entry->core_name; if (!string_is_empty(entry->crc32)) hash["crc32"] = entry->crc32; if (!string_is_empty(entry->db_name)) { hash["db_name"] = entry->db_name; hash["db_name"].remove(".lpl"); } if (!string_is_empty(playlistName)) { hash["pl_name"] = playlistName; hash["pl_name"].remove(".lpl"); } m_contents.append(hash); } playlist_free(playlist); playlist = NULL; } void PlaylistModel::addPlaylistItems(const QStringList &paths, bool add) { int i; if (paths.isEmpty()) return; beginResetModel(); m_contents.clear(); for (i = 0; i < paths.size(); i++) getPlaylistItems(paths.at(i)); endResetModel(); } void PlaylistModel::addDir(QString path, QFlags showHidden) { QDir dir = path; int i = 0; QStringList dirList = dir.entryList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Files | showHidden, QDir::Name); if (dirList.count() == 0) return; beginResetModel(); m_contents.clear(); for (i = 0; i < dirList.count(); i++) { QHash hash; QString fileName = dirList.at(i); QString filePath( QDir::toNativeSeparators(dir.absoluteFilePath(fileName))); QFileInfo fileInfo(filePath); hash["path"] = filePath; hash["label"] = hash["path"]; hash["label_noext"] = fileInfo.completeBaseName(); hash["db_name"] = fileInfo.dir().dirName(); m_contents.append(hash); } endResetModel(); } void MainWindow::setAllPlaylistsListMaxCount(int count) { if (count < 1) count = 0; m_allPlaylistsListMaxCount = count; } void MainWindow::setAllPlaylistsGridMaxCount(int count) { if (count < 1) count = 0; m_allPlaylistsGridMaxCount = count; }