1 #include <QFileInfo>
2 #include <QListWidgetItem>
3 #include <QApplication>
4 #include <QProgressDialog>
5 #include <QDir>
6 #include <QMenu>
7 #include <QSettings>
8 #include <QInputDialog>
9 #include <QLayout>
10 #include <QScreen>
11 #include <QRegularExpression>
12 #include <QImageReader>
13 #include <QtConcurrent>
14 
15 #include "../ui_qt.h"
16 #include "playlistentrydialog.h"
17 
18 #ifndef CXX_BUILD
19 extern "C" {
20 #endif
21 
22 #ifdef HAVE_CONFIG_H
23 #include "../../../config.h"
24 #endif
25 
26 #include <file/file_path.h>
27 #include <file/archive_file.h>
28 #include <lists/string_list.h>
29 #include <string/stdstring.h>
30 
31 #ifdef HAVE_MENU
32 #include "../../../menu/menu_displaylist.h"
33 #endif
34 
35 #include "../../../file_path_special.h"
36 #include "../../../playlist.h"
37 #include "../../../setting_list.h"
38 #include "../../../configuration.h"
39 #include "../../../core_info.h"
40 #include "../../../verbosity.h"
41 
42 #ifndef CXX_BUILD
43 }
44 #endif
45 
PlaylistModel(QObject * parent)46 PlaylistModel::PlaylistModel(QObject *parent)
47    : QAbstractListModel(parent)
48 {
49    m_imageFormats       = QVector<QByteArray>::fromList(QImageReader::supportedImageFormats());
50    m_fileSanitizerRegex = QRegularExpression("[&*/:`<>?\\|]");
51    setThumbnailCacheLimit(500);
52    connect(this, &PlaylistModel::imageLoaded, this, &PlaylistModel::onImageLoaded);
53 }
54 
rowCount(const QModelIndex &) const55 int PlaylistModel::rowCount(const QModelIndex & /* parent */) const
56 {
57    return m_contents.count();
58 }
59 
columnCount(const QModelIndex &) const60 int PlaylistModel::columnCount(const QModelIndex & /* parent */) const
61 {
62    return 1;
63 }
64 
data(const QModelIndex & index,int role) const65 QVariant PlaylistModel::data(const QModelIndex &index, int role) const
66 {
67    if (index.column() == 0)
68    {
69       if (!index.isValid())
70          return QVariant();
71 
72       if (index.row() >= m_contents.size() || index.row() < 0)
73          return QVariant();
74 
75       switch (role)
76       {
77          case Qt::DisplayRole:
78          case Qt::EditRole:
79          case Qt::ToolTipRole:
80             return m_contents.at(index.row())["label_noext"];
81          case HASH:
82             return QVariant::fromValue(m_contents.at(index.row()));
83          case THUMBNAIL:
84          {
85             QPixmap *cachedPreview = m_cache.object(getCurrentTypeThumbnailPath(index));
86             if (cachedPreview)
87                return *cachedPreview;
88             return QVariant();
89          }
90       }
91    }
92    return QVariant();
93 }
94 
flags(const QModelIndex & index) const95 Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const
96 {
97    if (!index.isValid())
98       return Qt::ItemIsEnabled;
99 
100    return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
101 }
102 
setData(const QModelIndex & index,const QVariant & value,int role)103 bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role)
104 {
105    if (index.isValid() && role == Qt::EditRole)
106    {
107       QHash<QString, QString> hash = m_contents.at(index.row());
108 
109       hash["label"]       = value.toString();
110       hash["label_noext"] = QFileInfo(value.toString()).completeBaseName();
111 
112       m_contents.replace(index.row(), hash);
113       emit dataChanged(index, index, { role });
114       return true;
115    }
116    return false;
117 }
118 
headerData(int section,Qt::Orientation orientation,int role) const119 QVariant PlaylistModel::headerData(int section,
120       Qt::Orientation orientation, int role) const
121 {
122    if (role != Qt::DisplayRole)
123       return QVariant();
124 
125    if (orientation == Qt::Horizontal)
126       return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME);
127    return section + 1;
128 }
129 
setThumbnailType(const ThumbnailType type)130 void PlaylistModel::setThumbnailType(const ThumbnailType type)
131 {
132    m_thumbnailType = type;
133 }
134 
setThumbnailCacheLimit(int limit)135 void PlaylistModel::setThumbnailCacheLimit(int limit)
136 {
137    m_cache.setMaxCost(limit * 1024);
138 }
139 
getThumbnailPath(const QModelIndex & index,QString type) const140 QString PlaylistModel::getThumbnailPath(const QModelIndex &index,
141       QString type) const
142 {
143    return getThumbnailPath(m_contents.at(index.row()), type);
144 }
145 
getPlaylistThumbnailsDir(const QString playlistName) const146 QString PlaylistModel::getPlaylistThumbnailsDir(
147       const QString playlistName) const
148 {
149    settings_t *settings            = config_get_ptr();
150    const char *path_dir_thumbnails = settings->paths.directory_thumbnails;
151    return QDir::cleanPath(QString(path_dir_thumbnails)) + "/" + playlistName;
152 }
153 
isSupportedImage(const QString path) const154 bool PlaylistModel::isSupportedImage(const QString path) const
155 {
156    QByteArray extension;
157    QString extensionStr;
158    int lastIndex = path.lastIndexOf('.');
159 
160    if (lastIndex >= 0)
161    {
162       extensionStr = path.mid(lastIndex + 1);
163 
164       if (!extensionStr.isEmpty())
165          extension = extensionStr.toLower().toUtf8();
166    }
167 
168    if (!extension.isEmpty() && m_imageFormats.contains(extension))
169       return true;
170 
171    return false;
172 }
173 
getSanitizedThumbnailName(QString label) const174 QString PlaylistModel::getSanitizedThumbnailName(QString label) const
175 {
176    return label.replace(m_fileSanitizerRegex, "_") + ".png";
177 }
178 
getThumbnailPath(const QHash<QString,QString> & hash,QString type) const179 QString PlaylistModel::getThumbnailPath(const QHash<QString, QString> &hash, QString type) const
180 {
181    /* use thumbnail widgets to show regular image files */
182    if (isSupportedImage(hash["path"]))
183       return hash["path"];
184 
185    return getPlaylistThumbnailsDir(hash.value("db_name"))
186       + "/" + type + "/" + getSanitizedThumbnailName(hash["label_noext"]);
187 }
188 
getCurrentTypeThumbnailPath(const QModelIndex & index) const189 QString PlaylistModel::getCurrentTypeThumbnailPath(const QModelIndex &index) const
190 {
191    switch (m_thumbnailType)
192    {
193       case THUMBNAIL_TYPE_BOXART:
194          return getThumbnailPath(index, THUMBNAIL_BOXART);
195       case THUMBNAIL_TYPE_SCREENSHOT:
196          return getThumbnailPath(index, THUMBNAIL_SCREENSHOT);
197       case THUMBNAIL_TYPE_TITLE_SCREEN:
198          return getThumbnailPath(index, THUMBNAIL_TITLE);
199       default:
200          break;
201    }
202 
203    return QString();
204 }
205 
reloadThumbnail(const QModelIndex & index)206 void PlaylistModel::reloadThumbnail(const QModelIndex &index)
207 {
208    if (index.isValid())
209    {
210       reloadThumbnailPath(getCurrentTypeThumbnailPath(index));
211       loadThumbnail(index);
212    }
213 }
214 
reloadSystemThumbnails(const QString system)215 void PlaylistModel::reloadSystemThumbnails(const QString system)
216 {
217    int i = 0;
218    QString key;
219    settings_t *settings            = config_get_ptr();
220    const char *path_dir_thumbnails = settings->paths.directory_thumbnails;
221    QString           path          = QDir::cleanPath(QString(path_dir_thumbnails)) + "/" + system;
222    QList<QString>             keys = m_cache.keys();
223    QList<QString>          pending = m_pendingImages.values();
224 
225    for (i = 0; i < keys.size(); i++)
226    {
227       key = keys.at(i);
228       if (key.startsWith(path))
229          m_cache.remove(key);
230    }
231 
232    for (i = 0; i < pending.size(); i++)
233    {
234       key = pending.at(i);
235       if (key.startsWith(path))
236          m_pendingImages.remove(key);
237    }
238 }
239 
reloadThumbnailPath(const QString path)240 void PlaylistModel::reloadThumbnailPath(const QString path)
241 {
242    m_cache.remove(path);
243    m_pendingImages.remove(path);
244 }
245 
loadThumbnail(const QModelIndex & index)246 void PlaylistModel::loadThumbnail(const QModelIndex &index)
247 {
248    QString path = getCurrentTypeThumbnailPath(index);
249 
250    if (!m_pendingImages.contains(path) && !m_cache.contains(path))
251    {
252       m_pendingImages.insert(path);
253       QtConcurrent::run(this, &PlaylistModel::loadImage, index, path);
254    }
255 }
256 
loadImage(const QModelIndex & index,const QString & path)257 void PlaylistModel::loadImage(const QModelIndex &index, const QString &path)
258 {
259    const QImage image = QImage(path);
260    if (!image.isNull())
261       emit imageLoaded(image, index, path);
262 }
263 
onImageLoaded(const QImage image,const QModelIndex & index,const QString & path)264 void PlaylistModel::onImageLoaded(const QImage image, const QModelIndex &index, const QString &path)
265 {
266    QPixmap *pixmap = new QPixmap(QPixmap::fromImage(image));
267    const int  cost = pixmap->width() * pixmap->height() * pixmap->depth() / (8 * 1024);
268    m_cache.insert(path, pixmap, cost);
269    if (index.isValid())
270       emit dataChanged(index, index, { THUMBNAIL });
271    m_pendingImages.remove(path);
272 }
273 
comp_hash_name_key_lower(const QHash<QString,QString> & lhs,const QHash<QString,QString> & rhs)274 static inline bool comp_hash_name_key_lower(const QHash<QString, QString> &lhs, const QHash<QString, QString> &rhs)
275 {
276    return lhs.value("name").toLower() < rhs.value("name").toLower();
277 }
278 
addDirectoryFilesToList(QProgressDialog * dialog,QStringList & list,QDir & dir,QStringList & extensions)279 bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog,
280       QStringList &list, QDir &dir, QStringList &extensions)
281 {
282    int i;
283    PlaylistEntryDialog *playlistDialog = playlistEntryDialog();
284    QStringList                 dirList = dir.entryList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name);
285 
286    for (i = 0; i < dirList.count(); i++)
287    {
288       QString path(dir.path() + "/" + dirList.at(i));
289       QByteArray pathArray = path.toUtf8();
290       QFileInfo fileInfo(path);
291       const char *pathData = pathArray.constData();
292 
293       if (dialog->wasCanceled())
294          return false;
295 
296       /* Needed to update progress dialog while doing
297        * a lot of stuff on the main thread. */
298       if (i % 25 == 0)
299          qApp->processEvents();
300 
301       if (fileInfo.isDir())
302       {
303          QDir fileInfoDir(path);
304          bool success = addDirectoryFilesToList(
305                dialog, list, fileInfoDir, extensions);
306 
307          if (!success)
308             return false;
309 
310          continue;
311       }
312 
313       if (fileInfo.isFile())
314       {
315          bool add = false;
316 
317          if (extensions.isEmpty())
318             add = true;
319          else
320          {
321             if (extensions.contains(fileInfo.suffix()))
322                add = true;
323             else
324             {
325                if (path_is_compressed_file(pathData))
326                {
327                   struct string_list *archive_list =
328                      file_archive_get_file_list(pathData, NULL);
329 
330                   if (archive_list)
331                   {
332                      if (archive_list->size == 1)
333                      {
334                         /* Assume archives with one file should have
335                          * that file loaded directly.
336                          * Don't just extend this to add all files
337                          * in a ZIP, because we might hit something like
338                          * MAME/FBA where only the archives themselves
339                          * are valid content. */
340                         pathArray = (QString(pathData) + "#"
341                               + archive_list->elems[0].data).toUtf8();
342                         pathData = pathArray.constData();
343 
344                         if (!extensions.isEmpty() && playlistDialog->filterInArchive())
345                         {
346                            /* If the user chose to filter extensions
347                             * inside archives, and this particular file
348                             * inside the archive
349                             * doesn't have one of the chosen extensions,
350                             * then we skip it. */
351                            if (extensions.contains(QFileInfo(pathData).suffix()))
352                               add = true;
353                         }
354                      }
355 
356                      string_list_free(archive_list);
357                   }
358                }
359             }
360          }
361 
362          if (add)
363             list.append(fileInfo.absoluteFilePath());
364       }
365    }
366 
367    return true;
368 }
369 
onPlaylistFilesDropped(QStringList files)370 void MainWindow::onPlaylistFilesDropped(QStringList files)
371 {
372    addFilesToPlaylist(files);
373 }
374 
375 /* Takes a list of files and folders and adds them to the
376  * currently selected playlist. Folders will have their
377  * contents added recursively. */
addFilesToPlaylist(QStringList files)378 void MainWindow::addFilesToPlaylist(QStringList files)
379 {
380    int i;
381    QStringList list;
382    QString currentPlaylistPath;
383    QByteArray currentPlaylistArray;
384    QScopedPointer<QProgressDialog> dialog(NULL);
385    QHash<QString, QString> selectedCore;
386    QHash<QString, QString> itemToAdd;
387    QString selectedDatabase;
388    QString selectedName;
389    QString selectedPath;
390    QStringList selectedExtensions;
391    playlist_config_t playlist_config;
392    QListWidgetItem        *currentItem = m_listWidget->currentItem();
393    PlaylistEntryDialog *playlistDialog = playlistEntryDialog();
394    const char *currentPlaylistData     = NULL;
395    playlist_t *playlist                = NULL;
396    settings_t *settings                = config_get_ptr();
397 
398    playlist_config.capacity            = COLLECTION_SIZE;
399    playlist_config.old_format          = settings->bools.playlist_use_old_format;
400    playlist_config.compress            = settings->bools.playlist_compression;
401    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
402    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
403 
404    /* Assume a blank list means we will manually enter in all fields. */
405    if (files.isEmpty())
406    {
407       /* Make sure hash isn't blank, that would mean there's
408        * multiple entries to add at once. */
409       itemToAdd["label"] = "";
410       itemToAdd["path"]  = "";
411    }
412    else if (files.count() == 1)
413    {
414       QString path = files.at(0);
415       QFileInfo info(path);
416 
417       if (info.isFile())
418       {
419          itemToAdd["label"] = info.completeBaseName();
420          itemToAdd["path"]  = path;
421       }
422    }
423 
424    if (currentItem)
425    {
426       currentPlaylistPath = currentItem->data(Qt::UserRole).toString();
427 
428       if (!currentPlaylistPath.isEmpty())
429       {
430          currentPlaylistArray = currentPlaylistPath.toUtf8();
431          currentPlaylistData  = currentPlaylistArray.constData();
432       }
433    }
434 
435    if (currentPlaylistPath == ALL_PLAYLISTS_TOKEN)
436    {
437       showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CANNOT_ADD_TO_ALL_PLAYLISTS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
438       return;
439    }
440 
441    /* a blank itemToAdd means there will be multiple */
442    if (!playlistDialog->showDialog(itemToAdd))
443       return;
444 
445    selectedName       = m_playlistEntryDialog->getSelectedName();
446    selectedPath       = m_playlistEntryDialog->getSelectedPath();
447    selectedCore       = m_playlistEntryDialog->getSelectedCore();
448    selectedDatabase   = m_playlistEntryDialog->getSelectedDatabase();
449    selectedExtensions = m_playlistEntryDialog->getSelectedExtensions();
450 
451    if (!selectedExtensions.isEmpty())
452       selectedExtensions.replaceInStrings(QRegularExpression("^\\."), "");
453 
454    if (selectedDatabase.isEmpty())
455       selectedDatabase = QFileInfo(currentPlaylistPath).fileName();
456    else
457       selectedDatabase.append(".lpl");
458 
459    dialog.reset(new QProgressDialog(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_GATHERING_LIST_OF_FILES), "Cancel", 0, 0, this));
460    dialog->setWindowModality(Qt::ApplicationModal);
461    dialog->show();
462 
463    qApp->processEvents();
464 
465    if (     selectedName.isEmpty()
466          || selectedPath.isEmpty()
467          || selectedDatabase.isEmpty())
468    {
469       showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PLEASE_FILL_OUT_REQUIRED_FIELDS), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
470       return;
471    }
472 
473    if (files.isEmpty())
474       files.append(selectedPath);
475 
476    for (i = 0; i < files.count(); i++)
477    {
478       QString path(files.at(i));
479       QFileInfo fileInfo(path);
480 
481       if (dialog->wasCanceled())
482          return;
483 
484       /* Needed to update progress dialog while
485        * doing a lot of stuff on the main thread. */
486       if (i % 25 == 0)
487          qApp->processEvents();
488 
489       if (fileInfo.isDir())
490       {
491          QDir dir(path);
492          bool success = addDirectoryFilesToList(
493                dialog.data(), list, dir, selectedExtensions);
494 
495          if (!success)
496             return;
497 
498          continue;
499       }
500 
501       if (fileInfo.isFile())
502       {
503          bool add = false;
504 
505          if (selectedExtensions.isEmpty())
506             add = true;
507          else
508          {
509             QByteArray pathArray = path.toUtf8();
510             const char *pathData = pathArray.constData();
511 
512             if (selectedExtensions.contains(fileInfo.suffix()))
513                add = true;
514             else if (playlistDialog->filterInArchive()
515                   && path_is_compressed_file(pathData))
516             {
517                /* We'll add it here, but really just delay
518                 * the check until later when the archive
519                 * contents are iterated. */
520                add = true;
521             }
522          }
523 
524          if (add)
525             list.append(fileInfo.absoluteFilePath());
526       }
527       else if (files.count() == 1)
528       {
529          /* If adding a single file, tell user that it doesn't exist. */
530          showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
531          return;
532       }
533    }
534 
535    dialog->setLabelText(msg_hash_to_str(
536             MENU_ENUM_LABEL_VALUE_QT_ADDING_FILES_TO_PLAYLIST));
537    dialog->setMaximum(list.count());
538 
539    playlist_config_set_path(&playlist_config, currentPlaylistData);
540    playlist = playlist_init(&playlist_config);
541 
542    for (i = 0; i < list.count(); i++)
543    {
544       QFileInfo fileInfo;
545       QByteArray fileBaseNameArray;
546       QByteArray pathArray;
547       QByteArray corePathArray;
548       QByteArray coreNameArray;
549       QByteArray databaseArray;
550       QString fileName            = list.at(i);
551       const char *pathData        = NULL;
552       const char *fileNameNoExten = NULL;
553       const char *corePathData    = NULL;
554       const char *coreNameData    = NULL;
555       const char *databaseData    = NULL;
556 
557       /* Cancel out of everything, the
558        * current progress will not be written
559        * to the playlist at all. */
560       if (dialog->wasCanceled())
561       {
562          playlist_free(playlist);
563          return;
564       }
565 
566       if (fileName.isEmpty())
567          continue;
568 
569       /* a modal QProgressDialog calls processEvents()
570        * automatically in setValue() */
571       dialog->setValue(i + 1);
572 
573       fileInfo = fileName;
574 
575       /* Make sure we're looking at a user-specified field
576        * and not just "<multiple>"
577        * in case it was a folder with one file in it */
578       if (     files.count() == 1
579             && list.count()  == 1
580             && i == 0
581             && playlistDialog->nameFieldEnabled())
582       {
583          fileBaseNameArray = selectedName.toUtf8();
584          pathArray = QDir::toNativeSeparators(selectedPath).toUtf8();
585       }
586       /* Otherwise just use the file name itself (minus extension)
587        * for the playlist entry title */
588       else
589       {
590          fileBaseNameArray = fileInfo.completeBaseName().toUtf8();
591          pathArray         = QDir::toNativeSeparators(fileName).toUtf8();
592       }
593 
594       fileNameNoExten      = fileBaseNameArray.constData();
595 
596       pathData             = pathArray.constData();
597 
598       if (selectedCore.isEmpty())
599       {
600          corePathData = "DETECT";
601          coreNameData = "DETECT";
602       }
603       else
604       {
605          corePathArray = QDir::toNativeSeparators(
606                selectedCore.value("core_path")).toUtf8();
607          coreNameArray = selectedCore.value("core_name").toUtf8();
608          corePathData  = corePathArray.constData();
609          coreNameData  = coreNameArray.constData();
610       }
611 
612       databaseArray = selectedDatabase.toUtf8();
613       databaseData = databaseArray.constData();
614 
615       if (path_is_compressed_file(pathData))
616       {
617          struct string_list *list = file_archive_get_file_list(pathData, NULL);
618 
619          if (list)
620          {
621             if (list->size == 1)
622             {
623                /* Assume archives with one file should have that
624                 * file loaded directly.
625                 * Don't just extend this to add all files in a zip,
626                 * because we might hit
627                 * something like MAME/FBA where only the archives
628                 * themselves are valid content. */
629                pathArray = QDir::toNativeSeparators(QString(pathData)
630                      + "#" + list->elems[0].data).toUtf8();
631                pathData  = pathArray.constData();
632 
633                if (     !selectedExtensions.isEmpty()
634                      &&  playlistDialog->filterInArchive())
635                {
636                   /* If the user chose to filter extensions inside archives,
637                    * and this particular file inside the archive
638                    * doesn't have one of the chosen extensions,
639                    * then we skip it. */
640                   if (!selectedExtensions.contains(
641                            QFileInfo(pathData).suffix()))
642                   {
643                      string_list_free(list);
644                      continue;
645                   }
646                }
647             }
648 
649             string_list_free(list);
650          }
651       }
652 
653       {
654          struct playlist_entry entry = {0};
655 
656          /* the push function reads our entry as const,
657           * so these casts are safe */
658          entry.path      = const_cast<char*>(pathData);
659          entry.label     = const_cast<char*>(fileNameNoExten);
660          entry.core_path = const_cast<char*>(corePathData);
661          entry.core_name = const_cast<char*>(coreNameData);
662          entry.crc32     = const_cast<char*>("00000000|crc");
663          entry.db_name   = const_cast<char*>(databaseData);
664 
665          playlist_push(playlist, &entry);
666       }
667    }
668 
669    playlist_write_file(playlist);
670    playlist_free(playlist);
671 
672    reloadPlaylists();
673 }
674 
updateCurrentPlaylistEntry(const QHash<QString,QString> & contentHash)675 bool MainWindow::updateCurrentPlaylistEntry(
676       const QHash<QString, QString> &contentHash)
677 {
678    QString path;
679    QString label;
680    QString corePath;
681    QString coreName;
682    QString dbName;
683    QString crc32;
684    QByteArray playlistPathArray;
685    QByteArray pathArray;
686    QByteArray labelArray;
687    QByteArray corePathArray;
688    QByteArray coreNameArray;
689    QByteArray dbNameArray;
690    QByteArray crc32Array;
691    playlist_config_t playlist_config;
692    QString playlistPath         = getCurrentPlaylistPath();
693    const char *playlistPathData = NULL;
694    const char *pathData         = NULL;
695    const char *labelData        = NULL;
696    const char *corePathData     = NULL;
697    const char *coreNameData     = NULL;
698    const char *dbNameData       = NULL;
699    const char *crc32Data        = NULL;
700    playlist_t *playlist         = NULL;
701    unsigned index               = 0;
702    bool ok                      = false;
703    settings_t *settings         = config_get_ptr();
704 
705    playlist_config.capacity            = COLLECTION_SIZE;
706    playlist_config.old_format          = settings->bools.playlist_use_old_format;
707    playlist_config.compress            = settings->bools.playlist_compression;
708    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
709    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
710 
711    if (  playlistPath.isEmpty() ||
712          contentHash.isEmpty()  ||
713          !contentHash.contains("index"))
714       return false;
715 
716    index = contentHash.value("index").toUInt(&ok);
717 
718    if (!ok)
719       return false;
720 
721    path     = contentHash.value("path");
722    label    = contentHash.value("label");
723    coreName = contentHash.value("core_name");
724    corePath = contentHash.value("core_path");
725    dbName   = contentHash.value("db_name");
726    crc32    = contentHash.value("crc32");
727 
728    if (path.isEmpty()     ||
729        label.isEmpty()    ||
730        coreName.isEmpty() ||
731        corePath.isEmpty()
732       )
733       return false;
734 
735    playlistPathArray = playlistPath.toUtf8();
736    pathArray         = QDir::toNativeSeparators(path).toUtf8();
737    labelArray        = label.toUtf8();
738    coreNameArray     = coreName.toUtf8();
739    corePathArray     = QDir::toNativeSeparators(corePath).toUtf8();
740 
741    if (!dbName.isEmpty())
742    {
743       dbNameArray    = (dbName + ".lpl").toUtf8();
744       dbNameData     = dbNameArray.constData();
745    }
746 
747    playlistPathData  = playlistPathArray.constData();
748    pathData          = pathArray.constData();
749    labelData         = labelArray.constData();
750    coreNameData      = coreNameArray.constData();
751    corePathData      = corePathArray.constData();
752 
753    if (!crc32.isEmpty())
754    {
755       crc32Array     = crc32.toUtf8();
756       crc32Data      = crc32Array.constData();
757    }
758 
759    if (path_is_compressed_file(pathData))
760    {
761       struct string_list *list = file_archive_get_file_list(pathData, NULL);
762 
763       if (list)
764       {
765          if (list->size == 1)
766          {
767             /* assume archives with one file should have that file loaded directly */
768             pathArray = QDir::toNativeSeparators(QString(pathData) + "#" + list->elems[0].data).toUtf8();
769             pathData = pathArray.constData();
770          }
771 
772          string_list_free(list);
773       }
774    }
775 
776    playlist_config_set_path(&playlist_config, playlistPathData);
777    playlist = playlist_init(&playlist_config);
778 
779    {
780       struct playlist_entry entry = {0};
781 
782       /* the update function reads our entry as const, so these casts are safe */
783       entry.path      = const_cast<char*>(pathData);
784       entry.label     = const_cast<char*>(labelData);
785       entry.core_path = const_cast<char*>(corePathData);
786       entry.core_name = const_cast<char*>(coreNameData);
787       entry.crc32     = const_cast<char*>(crc32Data);
788       entry.db_name   = const_cast<char*>(dbNameData);
789 
790       playlist_update(playlist, index, &entry);
791    }
792 
793    playlist_write_file(playlist);
794    playlist_free(playlist);
795 
796    reloadPlaylists();
797 
798    return true;
799 }
800 
onPlaylistWidgetContextMenuRequested(const QPoint &)801 void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&)
802 {
803    QString currentPlaylistDirPath;
804    QString currentPlaylistPath;
805    QString currentPlaylistFileName;
806    QFile currentPlaylistFile;
807    QFileInfo currentPlaylistFileInfo;
808    QMap<QString, const core_info_t*> coreList;
809    QScopedPointer<QMenu> menu;
810    QScopedPointer<QMenu> associateMenu;
811    QScopedPointer<QMenu> hiddenPlaylistsMenu;
812    QScopedPointer<QMenu> downloadAllThumbnailsMenu;
813    QScopedPointer<QAction> hideAction;
814    QScopedPointer<QAction> newPlaylistAction;
815    QScopedPointer<QAction> deletePlaylistAction;
816    QScopedPointer<QAction> renamePlaylistAction;
817    QScopedPointer<QAction> downloadAllThumbnailsEntireSystemAction;
818    QScopedPointer<QAction> downloadAllThumbnailsThisPlaylistAction;
819    QPointer<QAction> selectedAction;
820    playlist_config_t playlist_config;
821    QPoint cursorPos                 = QCursor::pos();
822    settings_t *settings             = config_get_ptr();
823    const char *path_dir_playlist    = settings->paths.directory_playlist;
824    QDir playlistDir(path_dir_playlist);
825    QListWidgetItem *selectedItem    = m_listWidget->itemAt(
826          m_listWidget->viewport()->mapFromGlobal(cursorPos));
827    QString playlistDirAbsPath       = playlistDir.absolutePath();
828    core_info_list_t *core_info_list = NULL;
829    unsigned i                       = 0;
830    int j                            = 0;
831    bool specialPlaylist             = false;
832    bool foundHiddenPlaylist         = false;
833 
834    playlist_config.capacity            = COLLECTION_SIZE;
835    playlist_config.old_format          = settings->bools.playlist_use_old_format;
836    playlist_config.compress            = settings->bools.playlist_compression;
837    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
838    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
839 
840    if (selectedItem)
841    {
842       currentPlaylistPath = selectedItem->data(Qt::UserRole).toString();
843       currentPlaylistFile.setFileName(currentPlaylistPath);
844 
845       currentPlaylistFileInfo = QFileInfo(currentPlaylistPath);
846       currentPlaylistFileName = currentPlaylistFileInfo.fileName();
847       currentPlaylistDirPath = currentPlaylistFileInfo.absoluteDir().absolutePath();
848    }
849 
850    menu.reset(new QMenu(this));
851    menu->setObjectName("menu");
852 
853    hiddenPlaylistsMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_HIDDEN_PLAYLISTS), this));
854    newPlaylistAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NEW_PLAYLIST)) + "...", this));
855 
856    hiddenPlaylistsMenu->setObjectName("hiddenPlaylistsMenu");
857 
858    menu->addAction(newPlaylistAction.data());
859 
860    if (currentPlaylistFile.exists())
861    {
862       deletePlaylistAction.reset(new QAction(
863                QString(msg_hash_to_str(
864                      MENU_ENUM_LABEL_VALUE_QT_DELETE_PLAYLIST)) + "...",
865                this));
866       menu->addAction(deletePlaylistAction.data());
867 
868       renamePlaylistAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_RENAME_PLAYLIST)) + "...", this));
869       menu->addAction(renamePlaylistAction.data());
870    }
871 
872    if (selectedItem)
873    {
874       hideAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_HIDE), this));
875       menu->addAction(hideAction.data());
876    }
877 
878    for (j = 0; j < m_listWidget->count(); j++)
879    {
880       QListWidgetItem *item = m_listWidget->item(j);
881       bool           hidden = m_listWidget->isItemHidden(item);
882 
883       if (hidden)
884       {
885          QAction *action = hiddenPlaylistsMenu->addAction(item->text());
886          action->setProperty("row", j);
887          action->setProperty("core_path", item->data(Qt::UserRole).toString());
888          foundHiddenPlaylist = true;
889       }
890    }
891 
892    if (!foundHiddenPlaylist)
893    {
894       QAction *action = hiddenPlaylistsMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE));
895       action->setProperty("row", -1);
896    }
897 
898    menu->addMenu(hiddenPlaylistsMenu.data());
899 
900    /* Don't just compare strings in case there are case differences on Windows that should be ignored. */
901    if (QDir(currentPlaylistDirPath) != QDir(playlistDirAbsPath))
902    {
903       /* special playlists like history etc. can't have an association */
904       specialPlaylist = true;
905    }
906 
907    if (!specialPlaylist)
908    {
909       associateMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ASSOCIATE_CORE), this));
910       associateMenu->setObjectName("associateMenu");
911 
912       core_info_get_list(&core_info_list);
913 
914       for (i = 0; i < core_info_list->count && core_info_list->count > 0; i++)
915       {
916          const core_info_t   *core = &core_info_list->list[i];
917          coreList[core->core_name] = core;
918       }
919 
920       {
921          QMapIterator<QString, const core_info_t*> coreListIterator(coreList);
922          QVector<QHash<QString, QString> > cores;
923 
924          while (coreListIterator.hasNext())
925          {
926             QString key, name;
927             const core_info_t *core = NULL;
928             QHash<QString, QString> hash;
929 
930             coreListIterator.next();
931 
932             key = coreListIterator.key();
933             core = coreList.value(key);
934 
935             if (string_is_empty(core->core_name))
936                name = core->display_name;
937             else
938                name = core->core_name;
939 
940             if (name.isEmpty())
941                continue;
942 
943             hash["name"] = name;
944             hash["core_path"] = core->path;
945 
946             cores.append(hash);
947          }
948 
949          std::sort(cores.begin(), cores.end(), comp_hash_name_key_lower);
950 
951          for (j = 0; j < cores.count(); j++)
952          {
953             const QHash<QString, QString> &hash = cores.at(j);
954             QAction *action = associateMenu->addAction(hash.value("name"));
955 
956             action->setProperty("core_path", hash.value("core_path"));
957          }
958       }
959 
960       menu->addMenu(associateMenu.data());
961    }
962 
963    if (!specialPlaylist)
964    {
965       downloadAllThumbnailsMenu.reset(new QMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS), this));
966       downloadAllThumbnailsMenu->setObjectName("downloadAllThumbnailsMenu");
967 
968       downloadAllThumbnailsThisPlaylistAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS_THIS_PLAYLIST), downloadAllThumbnailsMenu.data()));
969       downloadAllThumbnailsEntireSystemAction.reset(new QAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS_ENTIRE_SYSTEM), downloadAllThumbnailsMenu.data()));
970 
971       downloadAllThumbnailsMenu->addAction(downloadAllThumbnailsThisPlaylistAction.data());
972       downloadAllThumbnailsMenu->addAction(downloadAllThumbnailsEntireSystemAction.data());
973 
974       menu->addMenu(downloadAllThumbnailsMenu.data());
975    }
976 
977    selectedAction = menu->exec(cursorPos);
978 
979    if (!selectedAction)
980       return;
981 
982    if (!specialPlaylist && selectedAction->parent() == associateMenu.data())
983    {
984       core_info_t *coreInfo                   = NULL;
985       playlist_t *cachedPlaylist              = playlist_get_cached();
986       playlist_t *playlist                    = NULL;
987       bool loadPlaylist                       = true;
988       QByteArray currentPlaylistPathByteArray = currentPlaylistPath.toUtf8();
989       const char *currentPlaylistPathCString  = currentPlaylistPathByteArray.data();
990       QByteArray corePathByteArray            = selectedAction->property("core_path").toString().toUtf8();
991       const char *corePath                    = corePathByteArray.data();
992 
993       /* Load playlist, if required */
994       if (cachedPlaylist)
995       {
996          if (string_is_equal(currentPlaylistPathCString,
997                   playlist_get_conf_path(cachedPlaylist)))
998          {
999             playlist = cachedPlaylist;
1000             loadPlaylist = false;
1001          }
1002       }
1003 
1004       if (loadPlaylist)
1005       {
1006          playlist_config_set_path(&playlist_config, currentPlaylistPathCString);
1007          playlist = playlist_init(&playlist_config);
1008       }
1009 
1010       if (playlist)
1011       {
1012          /* Get core info */
1013          if (core_info_find(corePath, &coreInfo))
1014          {
1015             /* Set new core association */
1016             playlist_set_default_core_path(playlist, coreInfo->path);
1017             playlist_set_default_core_name(playlist, coreInfo->display_name);
1018          }
1019          else
1020          {
1021             playlist_set_default_core_path(playlist, "DETECT");
1022             playlist_set_default_core_name(playlist, "DETECT");
1023          }
1024 
1025          /* Write changes to disk */
1026          playlist_write_file(playlist);
1027 
1028          /* Free playlist, if required */
1029          if (loadPlaylist)
1030             playlist_free(playlist);
1031       }
1032    }
1033    else if (selectedItem && selectedAction == deletePlaylistAction.data())
1034    {
1035       if (currentPlaylistFile.exists())
1036       {
1037          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))
1038          {
1039             if (currentPlaylistFile.remove())
1040                reloadPlaylists();
1041             else
1042                showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false);
1043          }
1044       }
1045    }
1046    else if (selectedItem && selectedAction == renamePlaylistAction.data())
1047    {
1048       if (currentPlaylistFile.exists())
1049       {
1050          QString oldName = selectedItem->text();
1051          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);
1052 
1053          if (!name.isEmpty())
1054          {
1055             renamePlaylistItem(selectedItem, name);
1056             reloadPlaylists();
1057          }
1058       }
1059    }
1060    else if (selectedAction == newPlaylistAction.data())
1061    {
1062       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));
1063       QString newPlaylistPath = playlistDirAbsPath + "/" + name + ".lpl";
1064       QFile file(newPlaylistPath);
1065 
1066       if (!name.isEmpty())
1067       {
1068          if (file.open(QIODevice::WriteOnly))
1069             file.close();
1070 
1071          reloadPlaylists();
1072       }
1073    }
1074    else if (selectedItem && selectedAction == hideAction.data())
1075    {
1076       int row = m_listWidget->row(selectedItem);
1077 
1078       if (row >= 0)
1079       {
1080          QStringList hiddenPlaylists = m_settings->value("hidden_playlists").toStringList();
1081 
1082          if (!hiddenPlaylists.contains(currentPlaylistFileName))
1083          {
1084             hiddenPlaylists.append(currentPlaylistFileName);
1085             m_settings->setValue("hidden_playlists", hiddenPlaylists);
1086          }
1087 
1088          m_listWidget->setRowHidden(row, true);
1089       }
1090    }
1091    else if (selectedAction->parent() == hiddenPlaylistsMenu.data())
1092    {
1093       QVariant rowVariant = selectedAction->property("row");
1094 
1095       if (rowVariant.isValid())
1096       {
1097          QStringList hiddenPlaylists = m_settings->value("hidden_playlists").toStringList();
1098          int row = rowVariant.toInt();
1099 
1100          if (row >= 0)
1101          {
1102             QString playlistPath     = selectedAction->property("core_path").toString();
1103             QFileInfo playlistFileInfo(playlistPath);
1104             QString playlistFileName = playlistFileInfo.fileName();
1105 
1106             if (hiddenPlaylists.contains(playlistFileName))
1107             {
1108                hiddenPlaylists.removeOne(playlistFileName);
1109                m_settings->setValue("hidden_playlists", hiddenPlaylists);
1110             }
1111 
1112             m_listWidget->setRowHidden(row, false);
1113          }
1114       }
1115    }
1116    else if (selectedItem && !specialPlaylist && selectedAction->parent() == downloadAllThumbnailsMenu.data())
1117    {
1118       if (selectedAction == downloadAllThumbnailsEntireSystemAction.data())
1119       {
1120          int row = m_listWidget->row(selectedItem);
1121 
1122          if (row >= 0)
1123             downloadAllThumbnails(currentPlaylistFileInfo.completeBaseName());
1124       }
1125       else if (selectedAction == downloadAllThumbnailsThisPlaylistAction.data())
1126       {
1127          downloadPlaylistThumbnails(currentPlaylistPath);
1128       }
1129    }
1130 
1131    setCoreActions();
1132 }
1133 
deferReloadPlaylists()1134 void MainWindow::deferReloadPlaylists()
1135 {
1136    emit gotReloadPlaylists();
1137 }
1138 
onGotReloadPlaylists()1139 void MainWindow::onGotReloadPlaylists()
1140 {
1141    reloadPlaylists();
1142 }
1143 
reloadPlaylists()1144 void MainWindow::reloadPlaylists()
1145 {
1146    int i = 0;
1147    QString currentPlaylistPath;
1148    QListWidgetItem *allPlaylistsItem       = NULL;
1149    QListWidgetItem *favoritesPlaylistsItem = NULL;
1150    QListWidgetItem *imagePlaylistsItem     = NULL;
1151    QListWidgetItem *musicPlaylistsItem     = NULL;
1152    QListWidgetItem *videoPlaylistsItem     = NULL;
1153    QListWidgetItem *firstItem              = NULL;
1154    settings_t *settings                    = config_get_ptr();
1155    const char *path_dir_playlist           = settings->paths.directory_playlist;
1156    QDir playlistDir(path_dir_playlist);
1157    QStringList hiddenPlaylists             = m_settings->value(
1158          "hidden_playlists").toStringList();
1159 
1160    QListWidgetItem *currentItem            = m_listWidget->currentItem();
1161 
1162    if (currentItem)
1163       currentPlaylistPath = currentItem->data(Qt::UserRole).toString();
1164 
1165    getPlaylistFiles();
1166 
1167    m_listWidget->clear();
1168    m_listWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
1169    m_listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
1170    m_listWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed);
1171 
1172    allPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ALL_PLAYLISTS));
1173    allPlaylistsItem->setData(Qt::UserRole, ALL_PLAYLISTS_TOKEN);
1174 
1175    favoritesPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES_TAB));
1176    favoritesPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_favorites);
1177 
1178    m_historyPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_HISTORY_TAB));
1179    m_historyPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_history);
1180 
1181    imagePlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB));
1182    imagePlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_image_history);
1183 
1184    musicPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB));
1185    musicPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_music_history);
1186 
1187    videoPlaylistsItem = new QListWidgetItem(m_folderIcon, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB));
1188    videoPlaylistsItem->setData(Qt::UserRole, settings->paths.path_content_video_history);
1189 
1190    m_listWidget->addItem(allPlaylistsItem);
1191    m_listWidget->addItem(favoritesPlaylistsItem);
1192    m_listWidget->addItem(m_historyPlaylistsItem);
1193    m_listWidget->addItem(imagePlaylistsItem);
1194    m_listWidget->addItem(musicPlaylistsItem);
1195    m_listWidget->addItem(videoPlaylistsItem);
1196 
1197    if (hiddenPlaylists.contains(ALL_PLAYLISTS_TOKEN))
1198       m_listWidget->setRowHidden(m_listWidget->row(allPlaylistsItem), true);
1199    if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_favorites).fileName()))
1200       m_listWidget->setRowHidden(m_listWidget->row(favoritesPlaylistsItem), true);
1201    if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_history).fileName()))
1202       m_listWidget->setRowHidden(m_listWidget->row(m_historyPlaylistsItem), true);
1203    if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_image_history).fileName()))
1204       m_listWidget->setRowHidden(m_listWidget->row(imagePlaylistsItem), true);
1205    if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_music_history).fileName()))
1206       m_listWidget->setRowHidden(m_listWidget->row(musicPlaylistsItem), true);
1207    if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_video_history).fileName()))
1208       m_listWidget->setRowHidden(m_listWidget->row(videoPlaylistsItem), true);
1209 
1210    for (i = 0; i < m_playlistFiles.count(); i++)
1211    {
1212       QIcon icon;
1213       QString iconPath;
1214       QListWidgetItem   *item = NULL;
1215       const QString     &file = m_playlistFiles.at(i);
1216       QString fileDisplayName = file;
1217       QString        fileName = file;
1218       bool            hasIcon = false;
1219 
1220       fileDisplayName.remove(".lpl");
1221 
1222       iconPath                = QString(
1223             settings->paths.directory_assets)
1224          + ICON_PATH
1225          + fileDisplayName
1226          + ".png";
1227 
1228       hasIcon                 = QFile::exists(iconPath);
1229 
1230       if (hasIcon)
1231          icon                 = QIcon(iconPath);
1232       else
1233          icon                 = m_folderIcon;
1234 
1235       item                    = new QListWidgetItem(icon, fileDisplayName);
1236       item->setFlags(item->flags() | Qt::ItemIsEditable);
1237       item->setData(Qt::UserRole, playlistDir.absoluteFilePath(file));
1238 
1239       m_listWidget->addItem(item);
1240 
1241       if (hiddenPlaylists.contains(fileName))
1242       {
1243          int row = m_listWidget->row(item);
1244 
1245          if (row >= 0)
1246             m_listWidget->setRowHidden(row, true);
1247       }
1248    }
1249 
1250    if (m_listWidget->count() > 0)
1251    {
1252       firstItem = m_listWidget->item(0);
1253 
1254       if (firstItem)
1255       {
1256          bool            foundCurrent = false;
1257          bool            foundInitial = false;
1258          QString      initialPlaylist = m_settings->value("initial_playlist", m_historyPlaylistsItem->data(Qt::UserRole).toString()).toString();
1259          QListWidgetItem *initialItem = NULL;
1260 
1261          for (i = 0; i < m_listWidget->count(); i++)
1262          {
1263             QString path;
1264             QListWidgetItem *item = m_listWidget->item(i);
1265 
1266             if (item)
1267             {
1268                path = item->data(Qt::UserRole).toString();
1269 
1270                if (!path.isEmpty())
1271                {
1272                   /* don't break early here since we want
1273                    * to make sure we've found both initial
1274                    * and current items if they exist */
1275                   if (!foundInitial && path == initialPlaylist)
1276                   {
1277                      foundInitial = true;
1278                      initialItem = item;
1279                   }
1280                   if (     !foundCurrent
1281                         && !currentPlaylistPath.isEmpty()
1282                         && path == currentPlaylistPath)
1283                   {
1284                      foundCurrent = true;
1285                      m_listWidget->setCurrentItem(item);
1286                   }
1287                }
1288             }
1289          }
1290 
1291          if (!foundCurrent)
1292          {
1293             if (foundInitial && initialItem)
1294                m_listWidget->setCurrentItem(initialItem);
1295             else
1296             {
1297                /* the previous playlist must be gone now,
1298                 * just select the first one */
1299                m_listWidget->setCurrentItem(firstItem);
1300             }
1301          }
1302       }
1303    }
1304 
1305 }
1306 
getCurrentPlaylistPath()1307 QString MainWindow::getCurrentPlaylistPath()
1308 {
1309    QString playlistPath;
1310    QListWidgetItem *playlistItem = m_listWidget->currentItem();
1311 
1312    if (!playlistItem)
1313       return playlistPath;
1314 
1315    playlistPath = playlistItem->data(Qt::UserRole).toString();
1316 
1317    return playlistPath;
1318 }
1319 
currentPlaylistIsSpecial()1320 bool MainWindow::currentPlaylistIsSpecial()
1321 {
1322    QFileInfo currentPlaylistFileInfo;
1323    QString currentPlaylistPath;
1324    QString currentPlaylistDirPath;
1325    settings_t *settings                 = config_get_ptr();
1326    QDir playlistDir(settings->paths.directory_playlist);
1327    QString playlistDirAbsPath           = playlistDir.absolutePath();
1328    QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem();
1329 
1330    if (!currentPlaylistItem)
1331       return false;
1332 
1333    currentPlaylistPath     = currentPlaylistItem->data(Qt::UserRole).toString();
1334    currentPlaylistFileInfo = QFileInfo(currentPlaylistPath);
1335    currentPlaylistDirPath  = currentPlaylistFileInfo.absoluteDir().absolutePath();
1336 
1337    /* Don't just compare strings in case there are
1338     * case differences on Windows that should be ignored. */
1339    if (QDir(currentPlaylistDirPath) != QDir(playlistDirAbsPath))
1340       return true;
1341    return false;
1342 }
1343 
currentPlaylistIsAll()1344 bool MainWindow::currentPlaylistIsAll()
1345 {
1346    QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem();
1347    if (
1348             currentPlaylistItem
1349          && currentPlaylistItem->data(Qt::UserRole).toString()
1350          == ALL_PLAYLISTS_TOKEN)
1351       return true;
1352    return false;
1353 }
1354 
deleteCurrentPlaylistItem()1355 void MainWindow::deleteCurrentPlaylistItem()
1356 {
1357    QByteArray playlistArray;
1358    playlist_config_t playlist_config;
1359    QString playlistPath                = getCurrentPlaylistPath();
1360    QHash<QString, QString> contentHash = getCurrentContentHash();
1361    playlist_t *playlist                = NULL;
1362    const char *playlistData            = NULL;
1363    unsigned index                      = 0;
1364    bool ok                             = false;
1365    bool isAllPlaylist                  = currentPlaylistIsAll();
1366    settings_t *settings                = config_get_ptr();
1367 
1368    playlist_config.capacity            = COLLECTION_SIZE;
1369    playlist_config.old_format          = settings->bools.playlist_use_old_format;
1370    playlist_config.compress            = settings->bools.playlist_compression;
1371    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
1372    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
1373 
1374    if (isAllPlaylist)
1375       return;
1376 
1377    if (playlistPath.isEmpty())
1378       return;
1379 
1380    if (contentHash.isEmpty())
1381       return;
1382 
1383    playlistArray = playlistPath.toUtf8();
1384    playlistData = playlistArray.constData();
1385 
1386    index = contentHash.value("index").toUInt(&ok);
1387 
1388    if (!ok)
1389       return;
1390 
1391    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))
1392       return;
1393 
1394    playlist_config_set_path(&playlist_config, playlistData);
1395    playlist = playlist_init(&playlist_config);
1396 
1397    playlist_delete_index(playlist, index);
1398    playlist_write_file(playlist);
1399    playlist_free(playlist);
1400 
1401    reloadPlaylists();
1402 }
1403 
getPlaylistDefaultCore(QString plName)1404 QString MainWindow::getPlaylistDefaultCore(QString plName)
1405 {
1406    playlist_config_t playlist_config;
1407    char playlistPath[PATH_MAX_LENGTH];
1408    QByteArray plNameByteArray          = plName.toUtf8();
1409    const char *plNameCString           = plNameByteArray.data();
1410    playlist_t *cachedPlaylist          = playlist_get_cached();
1411    playlist_t *playlist                = NULL;
1412    bool loadPlaylist                   = true;
1413    QString corePath                    = QString();
1414    settings_t *settings                = config_get_ptr();
1415 
1416    playlist_config.capacity            = COLLECTION_SIZE;
1417    playlist_config.old_format          = settings->bools.playlist_use_old_format;
1418    playlist_config.compress            = settings->bools.playlist_compression;
1419    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
1420    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
1421 
1422    playlistPath[0] = '\0';
1423 
1424    if (!settings || string_is_empty(plNameCString))
1425       return corePath;
1426 
1427    /* Get playlist path */
1428    fill_pathname_join(
1429       playlistPath,
1430       settings->paths.directory_playlist, plNameCString,
1431       sizeof(playlistPath));
1432    strlcat(playlistPath, ".lpl", sizeof(playlistPath));
1433 
1434    /* Load playlist, if required */
1435    if (cachedPlaylist)
1436    {
1437       if (string_is_equal(playlistPath,
1438                playlist_get_conf_path(cachedPlaylist)))
1439       {
1440          playlist     = cachedPlaylist;
1441          loadPlaylist = false;
1442       }
1443    }
1444 
1445    if (loadPlaylist)
1446    {
1447       playlist_config_set_path(&playlist_config, playlistPath);
1448       playlist = playlist_init(&playlist_config);
1449    }
1450 
1451    if (playlist)
1452    {
1453       const char *defaultCorePath = playlist_get_default_core_path(playlist);
1454 
1455       /* Get default core path */
1456       if (!string_is_empty(defaultCorePath) &&
1457           !string_is_equal(defaultCorePath, "DETECT"))
1458          corePath = QString::fromUtf8(defaultCorePath);
1459 
1460       /* Free playlist, if required */
1461       if (loadPlaylist)
1462          playlist_free(playlist);
1463    }
1464 
1465    return corePath;
1466 }
1467 
getPlaylistFiles()1468 void MainWindow::getPlaylistFiles()
1469 {
1470    settings_t *settings = config_get_ptr();
1471    QDir playlistDir(settings->paths.directory_playlist);
1472 
1473    m_playlistFiles = playlistDir.entryList(
1474          QDir::NoDotAndDotDot | QDir::Readable | QDir::Files, QDir::Name);
1475 }
1476 
getPlaylistItems(QString path)1477 void PlaylistModel::getPlaylistItems(QString path)
1478 {
1479    QByteArray pathArray;
1480    playlist_config_t playlist_config;
1481    const char *pathData                = NULL;
1482    const char *playlistName            = NULL;
1483    playlist_t *playlist                = NULL;
1484    unsigned playlistSize               = 0;
1485    unsigned            i               = 0;
1486    settings_t *settings                = config_get_ptr();
1487 
1488    playlist_config.capacity            = COLLECTION_SIZE;
1489    playlist_config.old_format          = settings->bools.playlist_use_old_format;
1490    playlist_config.compress            = settings->bools.playlist_compression;
1491    playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
1492    playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
1493 
1494    pathArray.append(path);
1495    pathData              = pathArray.constData();
1496    if (!string_is_empty(pathData))
1497       playlistName       = path_basename(pathData);
1498 
1499    playlist_config_set_path(&playlist_config, pathData);
1500    playlist              = playlist_init(&playlist_config);
1501    playlistSize          = playlist_get_size(playlist);
1502 
1503    for (i = 0; i < playlistSize; i++)
1504    {
1505       QHash<QString, QString> hash;
1506       const struct playlist_entry *entry  = NULL;
1507 
1508       playlist_get_index(playlist, i, &entry);
1509 
1510       if (string_is_empty(entry->path))
1511          continue;
1512 
1513       hash["path"]           = entry->path;
1514       hash["index"]          = QString::number(i);
1515 
1516       if (string_is_empty(entry->label))
1517       {
1518          hash["label"]       = entry->path;
1519          hash["label_noext"] = entry->path;
1520       }
1521       else
1522       {
1523          hash["label"]       = entry->label;
1524          hash["label_noext"] = entry->label;
1525       }
1526 
1527       if (!string_is_empty(entry->core_path))
1528          hash["core_path"]   = entry->core_path;
1529 
1530       if (!string_is_empty(entry->core_name))
1531          hash["core_name"]   = entry->core_name;
1532 
1533       if (!string_is_empty(entry->crc32))
1534          hash["crc32"]       = entry->crc32;
1535 
1536       if (!string_is_empty(entry->db_name))
1537       {
1538          hash["db_name"]     = entry->db_name;
1539          hash["db_name"].remove(".lpl");
1540       }
1541 
1542       if (!string_is_empty(playlistName))
1543       {
1544          hash["pl_name"]     = playlistName;
1545          hash["pl_name"].remove(".lpl");
1546       }
1547 
1548       m_contents.append(hash);
1549    }
1550 
1551    playlist_free(playlist);
1552    playlist = NULL;
1553 }
1554 
addPlaylistItems(const QStringList & paths,bool add)1555 void PlaylistModel::addPlaylistItems(const QStringList &paths, bool add)
1556 {
1557    int i;
1558 
1559    if (paths.isEmpty())
1560       return;
1561 
1562    beginResetModel();
1563 
1564    m_contents.clear();
1565 
1566    for (i = 0; i < paths.size(); i++)
1567       getPlaylistItems(paths.at(i));
1568 
1569    endResetModel();
1570 }
1571 
addDir(QString path,QFlags<QDir::Filter> showHidden)1572 void PlaylistModel::addDir(QString path, QFlags<QDir::Filter> showHidden)
1573 {
1574    QDir            dir = path;
1575    int               i = 0;
1576    QStringList dirList =
1577       dir.entryList(QDir::NoDotAndDotDot |
1578       QDir::Readable                     |
1579       QDir::Files                        |
1580       showHidden,
1581       QDir::Name);
1582 
1583    if (dirList.count() == 0)
1584       return;
1585 
1586    beginResetModel();
1587 
1588    m_contents.clear();
1589 
1590    for (i = 0; i < dirList.count(); i++)
1591    {
1592       QHash<QString, QString> hash;
1593       QString fileName    = dirList.at(i);
1594       QString filePath(
1595             QDir::toNativeSeparators(dir.absoluteFilePath(fileName)));
1596       QFileInfo fileInfo(filePath);
1597 
1598       hash["path"]        = filePath;
1599       hash["label"]       = hash["path"];
1600       hash["label_noext"] = fileInfo.completeBaseName();
1601       hash["db_name"]     = fileInfo.dir().dirName();
1602 
1603       m_contents.append(hash);
1604    }
1605 
1606    endResetModel();
1607 }
1608 
setAllPlaylistsListMaxCount(int count)1609 void MainWindow::setAllPlaylistsListMaxCount(int count)
1610 {
1611    if (count < 1)
1612       count = 0;
1613 
1614    m_allPlaylistsListMaxCount = count;
1615 }
1616 
setAllPlaylistsGridMaxCount(int count)1617 void MainWindow::setAllPlaylistsGridMaxCount(int count)
1618 {
1619    if (count < 1)
1620       count = 0;
1621 
1622    m_allPlaylistsGridMaxCount = count;
1623 }
1624