diff --git a/Makefile.common b/Makefile.common index 5710d4012f..b2c83a60b8 100644 --- a/Makefile.common +++ b/Makefile.common @@ -334,14 +334,16 @@ OBJ += ui/drivers/ui_qt.o \ ui/drivers/qt/ui_qt_window.o \ ui/drivers/qt/ui_qt_browser_window.o \ ui/drivers/qt/ui_qt_load_core_window.o \ - ui/drivers/qt/ui_qt_msg_window.o + ui/drivers/qt/ui_qt_msg_window.o \ + ui/drivers/qt/flowlayout.o MOC_HEADERS += ui/drivers/ui_qt.h \ - ui/drivers/qt/ui_qt_load_core_window.h + ui/drivers/qt/ui_qt_load_core_window.h \ + ui/drivers/qt/flowlayout.h -DEFINES += $(QT5CORE_CFLAGS) $(QT5GUI_CFLAGS) $(QT5WIDGETS_CFLAGS) -DHAVE_MAIN +DEFINES += $(QT5CORE_CFLAGS) $(QT5GUI_CFLAGS) $(QT5WIDGETS_CFLAGS) $(QT5CONCURRENT_CFLAGS) -DHAVE_MAIN #DEFINES += $(QT5WEBENGINE_CFLAGS) -LIBS += $(QT5CORE_LIBS) $(QT5GUI_LIBS) $(QT5WIDGETS_LIBS) +LIBS += $(QT5CORE_LIBS) $(QT5GUI_LIBS) $(QT5WIDGETS_LIBS) $(QT5CONCURRENT_LIBS) #LIBS += $(QT5WEBENGINE_LIBS) NEED_CXX_LINKER = 1 diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index ced77ebd5b..2756c4d4bf 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -3562,6 +3562,14 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, "ファイルは存在しません。") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_SUGGEST_LOADED_CORE_FIRST, "ロードしたコアを最初に優先する") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ZOOM, + "ズーム") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW, + "表示") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS, + "アイコン") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST, + "一覧") MSG_HASH(MENU_ENUM_LABEL_VALUE_QUICK_MENU_OVERRIDE_OPTIONS, "オーバーライド") MSG_HASH(MENU_ENUM_SUBLABEL_QUICK_MENU_OVERRIDE_OPTIONS, @@ -3622,3 +3630,5 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_SUSTAINED_PERFORMANCE_MODE, "パフォーマンス維持モード") MSG_HASH(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_MPV_SUPPORT, "mpv対応") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PROGRESS, + "進行状況:") diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 506655743d..b96c7ea606 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -3710,6 +3710,14 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, "File does not exist.") MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_SUGGEST_LOADED_CORE_FIRST, "Suggest loaded core first") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_ZOOM, + "Zoom") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW, + "View") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS, + "Icons") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST, + "List") MSG_HASH(MENU_ENUM_LABEL_VALUE_QUICK_MENU_OVERRIDE_OPTIONS, "Overrides") MSG_HASH(MENU_ENUM_SUBLABEL_QUICK_MENU_OVERRIDE_OPTIONS, @@ -3780,3 +3788,5 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_SUSTAINED_PERFORMANCE_MODE, "Sustained Performance Mode") MSG_HASH(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_MPV_SUPPORT, "mpv support") +MSG_HASH(MENU_ENUM_LABEL_VALUE_QT_PROGRESS, + "Progress:") diff --git a/msg_hash.h b/msg_hash.h index 41dfbb57dc..63e952cc07 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1871,6 +1871,11 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY, MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED, MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, + MENU_ENUM_LABEL_VALUE_QT_ZOOM, + MENU_ENUM_LABEL_VALUE_QT_VIEW, + MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS, + MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST, + MENU_ENUM_LABEL_VALUE_QT_PROGRESS, MENU_LABEL(MIDI_INPUT), MENU_LABEL(MIDI_OUTPUT), diff --git a/qb/config.libs.sh b/qb/config.libs.sh index ac4c7cf380..f4499099ff 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -278,14 +278,16 @@ if [ "$HAVE_QT" != 'no' ] && [ "$MOC_PATH" != 'none' ]; then check_pkgconf QT5CORE Qt5Core 5.2 check_pkgconf QT5GUI Qt5Gui 5.2 check_pkgconf QT5WIDGETS Qt5Widgets 5.2 + check_pkgconf QT5CONCURRENT Qt5Concurrent 5.2 #check_pkgconf QT5WEBENGINE Qt5WebEngine 5.4 check_val '' QT5CORE -lQt5Core QT5CORE check_val '' QT5GUI -lQt5Gui QT5GUI check_val '' QT5WIDGETS -lQt5Widgets QT5WIDGETS + check_val '' QT5CONCURRENT -lQt5Widgets QT5CONCURRENT #check_val '' QT5WEBENGINE -lQt5WebEngine QT5WEBENGINE - if [ "$HAVE_QT5CORE" = "no" ] || [ "$HAVE_QT5GUI" = "no" ] || [ "$HAVE_QT5WIDGETS" = "no" ]; then + if [ "$HAVE_QT5CORE" = "no" ] || [ "$HAVE_QT5GUI" = "no" ] || [ "$HAVE_QT5WIDGETS" = "no" ] || [ "$HAVE_QT5CONCURRENT" = "no" ]; then die : 'Notice: Not building Qt support, required libraries were not found.' HAVE_QT=no else diff --git a/ui/drivers/qt/flowlayout.cpp b/ui/drivers/qt/flowlayout.cpp new file mode 100644 index 0000000000..b970e10b10 --- /dev/null +++ b/ui/drivers/qt/flowlayout.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* Original work Copyright (C) 2016 The Qt Company Ltd. + * Modified work Copyright (C) 2018 - Brad Parker + */ + +#include + +#include "flowlayout.h" +#include "../ui_qt.h" + +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); + + connect(this, SIGNAL(signalAddWidgetDeferred(QPointer)), this, SLOT(onAddWidgetDeferred(QPointer)), Qt::QueuedConnection); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem *item = NULL; + + while ((item = takeAt(0)) != NULL) + delete item; +} + +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) + return m_hSpace; + else + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); +} + +int FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) + return m_vSpace; + else + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem* FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem* FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return NULL; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return 0; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + int i = 0; + + if (itemList.isEmpty()) + return size; + + for (i = 0; i < itemList.count(); i++) + { + const QLayoutItem *item = itemList.at(i); + size = size.expandedTo(item->minimumSize()); + } + + size += QSize(2 * margin(), 2 * margin()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + QRect effectiveRect; + int left = 0, top = 0, right = 0, bottom = 0; + int x = 0; + int y = 0; + int lineHeight = 0; + int i = 0; + + getContentsMargins(&left, &top, &right, &bottom); + effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + x = effectiveRect.x(); + y = effectiveRect.y(); + + if (itemList.isEmpty()) + return y + lineHeight - rect.y() + bottom; + + for (i = 0; i < itemList.count(); i++) + { + QLayoutItem *item = itemList.at(i); + const QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + int spaceY = 0; + int nextX = 0; + + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + + spaceY = verticalSpacing(); + + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + + nextX = x + item->sizeHint().width() + spaceX; + + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) + { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + + return y + lineHeight - rect.y() + bottom; +} + +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + const QObject *parentObj = parent(); + + if (!parentObj) + return -1; + else if (parentObj->isWidgetType()) + { + const QWidget *pw = static_cast(parentObj); + return pw->style()->pixelMetric(pm, NULL, pw); + } + else + return static_cast(parentObj)->spacing(); +} + +void FlowLayout::addWidgetDeferred(QPointer widget) +{ + emit signalAddWidgetDeferred(widget); +} + +void FlowLayout::onAddWidgetDeferred(QPointer widget) +{ + /* widget might have been deleted before we got to it since this uses a queued connection, hence the guarded QPointer */ + if (!widget) + return; + + addWidget(widget); +} diff --git a/ui/drivers/qt/flowlayout.h b/ui/drivers/qt/flowlayout.h new file mode 100644 index 0000000000..c9d11e7653 --- /dev/null +++ b/ui/drivers/qt/flowlayout.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* Original work Copyright (C) 2016 The Qt Company Ltd. + * Modified work Copyright (C) 2018 - Brad Parker + */ + +/* bparker: Removed C++11 override keyword from original source + * Changed QList to QVector + */ + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include + +class ThumbnailWidget; + +class FlowLayout : public QLayout +{ + Q_OBJECT +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item); + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const; + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int count() const; + QLayoutItem* itemAt(int index) const; + QSize minimumSize() const; + void setGeometry(const QRect &rect); + QSize sizeHint() const; + QLayoutItem* takeAt(int index); + void addWidgetDeferred(QPointer widget); + +signals: + void signalAddWidgetDeferred(QPointer widget); + +private slots: + void onAddWidgetDeferred(QPointer widget); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QVector itemList; + int m_hSpace; + int m_vSpace; +}; + +#endif // FLOWLAYOUT_H diff --git a/ui/drivers/qt/ui_qt_load_core_window.cpp b/ui/drivers/qt/ui_qt_load_core_window.cpp index 65da0381fc..00f29f38de 100644 --- a/ui/drivers/qt/ui_qt_load_core_window.cpp +++ b/ui/drivers/qt/ui_qt_load_core_window.cpp @@ -266,8 +266,11 @@ void LoadCoreWindow::initCoreList(const QStringList &extensionFilters) if (rowsToHide.size() != m_table->rowCount()) { - foreach (const int &row, rowsToHide) + int i = 0; + + for (i = 0; i < rowsToHide.count() && rowsToHide.count() > 0; i++) { + const int &row = rowsToHide.at(i); m_table->setRowHidden(row, true); } } diff --git a/ui/drivers/qt/ui_qt_themes.h b/ui/drivers/qt/ui_qt_themes.h index d43867def2..89248ab818 100644 --- a/ui/drivers/qt/ui_qt_themes.h +++ b/ui/drivers/qt/ui_qt_themes.h @@ -1,7 +1,15 @@ #include /* %1 is a placeholder for palette(highlight) or the equivalent chosen by the user */ -static const QString qt_theme_default_stylesheet = QStringLiteral(""); +static const QString qt_theme_default_stylesheet = QStringLiteral("" + "QPushButton[flat=\"true\"] {\n" + " min-height:20px;\n" + " min-width:80px;\n" + " padding:1px 3px 1px 3px;\n" + " background-color: transparent;\n" + " border: 1px solid #ddd;\n" + "}\n" +); static const QString qt_theme_dark_stylesheet = QStringLiteral("" "QWidget {\n" @@ -233,6 +241,10 @@ static const QString qt_theme_dark_stylesheet = QStringLiteral("" " border:1px solid %1;\n" " border-radius:4px;\n" "}\n" + "QPushButton[flat=\"true\"] {\n" + " background-color: transparent;\n" + " border: 1px solid #ddd;\n" + "}\n" "QRadioButton::indicator {\n" " width:18px;\n" " height:18px;\n" diff --git a/ui/drivers/qt/ui_qt_window.cpp b/ui/drivers/qt/ui_qt_window.cpp index cf5ee3309f..4b770c723b 100644 --- a/ui/drivers/qt/ui_qt_window.cpp +++ b/ui/drivers/qt/ui_qt_window.cpp @@ -29,10 +29,12 @@ #include #include #include +#include #include "../ui_qt.h" #include "ui_qt_load_core_window.h" #include "ui_qt_themes.h" +#include "flowlayout.h" extern "C" { #include "../../../version.h" @@ -52,6 +54,7 @@ extern "C" { #include #include #include +#include } #define TIMER_MSEC 1000 /* periodic timer for gathering statistics */ @@ -83,6 +86,24 @@ enum CoreSelection CORE_SELECTION_LOAD_CORE }; +static double lerp(double x, double y, double a, double b, double d) { + return a + (b - a) * ((double)(d - x) / (double)(y - x)); +} + +/* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */ +static double expScale(double inputValue, double midValue, double maxValue) +{ + double returnValue = 0; + double M = maxValue / midValue; + double C = log(pow(M - 1, 2)); + double B = maxValue / (exp(C) - 1); + double A = -1 * B; + + returnValue = A + B * exp(C * inputValue); + + return returnValue; +} + #ifdef HAVE_LIBRETRODB static void scan_finished_handler(void *task_data, void *user_data, const char *err) { @@ -90,6 +111,10 @@ static void scan_finished_handler(void *task_data, void *user_data, const char * menu_environ.type = MENU_ENVIRON_RESET_HORIZONTAL_LIST; menu_environ.data = NULL; + (void)task_data; + (void)user_data; + (void)err; + menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); if (!ui_window.qtWindow->settings()->value("scan_finish_confirm", true).toBool()) @@ -100,6 +125,17 @@ static void scan_finished_handler(void *task_data, void *user_data, const char * } #endif +GridItem::GridItem() : + QObject() + ,widget(NULL) + ,label(NULL) + ,hash() + ,image() + ,pixmap() + ,imageWatcher() +{ +} + TreeView::TreeView(QWidget *parent) : QTreeView(parent) { @@ -168,7 +204,7 @@ void CoreInfoDialog::showCoreInfo() int row = 0; int rowCount = m_formLayout->rowCount(); int i = 0; - QList > infoList = m_mainwindow->getCoreInfo(); + QVector > infoList = m_mainwindow->getCoreInfo(); if (rowCount > 0) { @@ -487,13 +523,101 @@ MainWindow::MainWindow(QWidget *parent) : ,m_historyPlaylistsItem(NULL) ,m_folderIcon() ,m_customThemeString() + ,m_gridLayout(NULL) + ,m_gridWidget(new QWidget(this)) + ,m_gridScrollArea(new QScrollArea(m_gridWidget)) + ,m_gridItems() + ,m_gridLayoutWidget(new QWidget()) + ,m_zoomSlider(NULL) + ,m_lastZoomSliderValue(0) + ,m_pendingItemUpdates() + ,m_viewType(VIEW_TYPE_LIST) + ,m_gridProgressBar(NULL) + ,m_gridProgressWidget(NULL) + ,m_currentGridHash() + ,m_lastViewType(m_viewType) { settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); QString configDir = QFileInfo(path_get(RARCH_PATH_CONFIG)).dir().absolutePath(); QToolButton *searchResetButton = NULL; + QWidget *zoomWidget = new QWidget(); + QHBoxLayout *zoomLayout = new QHBoxLayout(); + QLabel *zoomLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ZOOM), zoomWidget); + QPushButton *viewTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW), zoomWidget); + QMenu *viewTypeMenu = new QMenu(viewTypePushButton); + QAction *viewTypeIconsAction = NULL; + QAction *viewTypeListAction = NULL; + QHBoxLayout *gridProgressLayout = new QHBoxLayout(); + QLabel *gridProgressLabel = NULL; + QHBoxLayout *gridFooterLayout = NULL; int i = 0; + qRegisterMetaType >("ThumbnailWidget"); + + m_gridProgressWidget = new QWidget(); + gridProgressLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PROGRESS), m_gridProgressWidget); + + viewTypePushButton->setObjectName("viewTypePushButton"); + viewTypePushButton->setFlat(true); + + viewTypeIconsAction = viewTypeMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS)); + viewTypeListAction = viewTypeMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST)); + + viewTypePushButton->setMenu(viewTypeMenu); + + gridProgressLabel->setObjectName("gridProgressLabel"); + + m_gridProgressBar = new QProgressBar(m_gridProgressWidget); + + m_gridProgressBar->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); + + zoomLabel->setObjectName("zoomLabel"); + + m_zoomSlider = new QSlider(Qt::Horizontal, zoomWidget); + + m_zoomSlider->setMinimum(0); + m_zoomSlider->setMaximum(100); + m_zoomSlider->setValue(50); + m_zoomSlider->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); + + m_lastZoomSliderValue = m_zoomSlider->value(); + + m_gridWidget->setLayout(new QVBoxLayout()); + + m_gridLayout = new FlowLayout(m_gridLayoutWidget); + + m_gridScrollArea->setAlignment(Qt::AlignCenter); + m_gridScrollArea->setFrameShape(QFrame::NoFrame); + m_gridScrollArea->setWidgetResizable(true); + m_gridScrollArea->setWidget(m_gridLayoutWidget); + + m_gridWidget->layout()->addWidget(m_gridScrollArea); + m_gridWidget->layout()->setAlignment(Qt::AlignCenter); + + m_gridProgressWidget->setLayout(gridProgressLayout); + gridProgressLayout->setContentsMargins(0, 0, 0, 0); + gridProgressLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); + gridProgressLayout->addWidget(gridProgressLabel); + gridProgressLayout->addWidget(m_gridProgressBar); + + m_gridWidget->layout()->addWidget(m_gridProgressWidget); + + zoomWidget->setLayout(zoomLayout); + zoomLayout->setContentsMargins(0, 0, 0, 0); + zoomLayout->addWidget(zoomLabel); + zoomLayout->addWidget(m_zoomSlider); + zoomLayout->addWidget(viewTypePushButton); + + gridFooterLayout = new QHBoxLayout(); + gridFooterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); + gridFooterLayout->addWidget(m_gridProgressWidget); + gridFooterLayout->addWidget(zoomWidget); + + static_cast(m_gridWidget->layout())->addLayout(gridFooterLayout); + + m_gridProgressWidget->hide(); + m_tableWidget->setAlternatingRowColors(true); m_logWidget->setObjectName("logWidget"); @@ -610,11 +734,13 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_coreInfoPushButton, SIGNAL(clicked()), m_coreInfoDialog, SLOT(showCoreInfo())); connect(m_runPushButton, SIGNAL(clicked()), this, SLOT(onRunClicked())); connect(m_stopPushButton, SIGNAL(clicked()), this, SLOT(onStopClicked())); - connect(m_browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), this, SLOT(onTabWidgetIndexChanged(int))); connect(m_dirTree, SIGNAL(itemsSelected(QModelIndexList)), this, SLOT(onTreeViewItemsSelected(QModelIndexList))); connect(m_dirTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onFileBrowserTreeContextMenuRequested(const QPoint&))); connect(m_listWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onPlaylistWidgetContextMenuRequested(const QPoint&))); connect(m_launchWithComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onLaunchWithComboBoxIndexChanged(int))); + connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(onZoomValueChanged(int))); + connect(viewTypeIconsAction, SIGNAL(triggered()), this, SLOT(onIconViewClicked())); + connect(viewTypeListAction, SIGNAL(triggered()), this, SLOT(onListViewClicked())); /* make sure these use an auto connection so it will be queued if called from a different thread (some facilities in RA log messages from other threads) */ connect(this, SIGNAL(gotLogMessage(const QString&)), this, SLOT(onGotLogMessage(const QString&)), Qt::AutoConnection); @@ -632,16 +758,6 @@ MainWindow::MainWindow(QWidget *parent) : qApp->processEvents(); QTimer::singleShot(0, this, SLOT(onBrowserStartClicked())); - for (i = 0; i < m_listWidget->count(); i++) - { - /* select the first non-hidden row */ - if (!m_listWidget->isRowHidden(i)) - { - m_listWidget->setCurrentRow(i); - break; - } - } - m_searchLineEdit->setFocus(); m_loadCoreWindow->setWindowModality(Qt::ApplicationModal); @@ -658,6 +774,73 @@ MainWindow::~MainWindow() delete m_thumbnailPixmap2; if (m_thumbnailPixmap3) delete m_thumbnailPixmap3; + + removeGridItems(); +} + +void MainWindow::onGridItemClicked() +{ + QHash hash; + ThumbnailWidget *w = static_cast(sender()); + + if (!w) + return; + + hash = w->property("hash").value >(); + + m_currentGridHash = hash; + + currentItemChanged(hash); +} + +void MainWindow::onGridItemDoubleClicked() +{ + QHash hash; + ThumbnailWidget *w = static_cast(sender()); + + if (!w) + return; + + hash = w->property("hash").value >(); + + loadContent(hash); +} + +void MainWindow::onIconViewClicked() +{ + setCurrentViewType(VIEW_TYPE_ICONS); + onCurrentListItemChanged(m_listWidget->currentItem(), NULL); +} + +void MainWindow::onListViewClicked() +{ + setCurrentViewType(VIEW_TYPE_LIST); + onCurrentListItemChanged(m_listWidget->currentItem(), NULL); +} + +inline void MainWindow::calcGridItemSize(GridItem *item, int zoomValue) +{ + int newSize = 0; + + if (zoomValue < 50) + newSize = expScale(lerp(0, 49, 25, 49, zoomValue) / 50.0, 102, 256); + else + newSize = expScale(zoomValue / 100.0, 256, 1024); + + item->widget->setFixedSize(QSize(newSize, newSize)); +} + +void MainWindow::onZoomValueChanged(int value) +{ + int i = 0; + + for (i = 0; i < m_gridItems.count() && m_gridItems.count() > 0; i++) + { + GridItem *item = m_gridItems.at(i); + calcGridItemSize(item, value); + } + + m_lastZoomSliderValue = value; } void MainWindow::showWelcomeScreen() @@ -746,11 +929,11 @@ void MainWindow::setCustomThemeString(QString qss) bool MainWindow::showMessageBox(QString msg, MessageBoxType msgType, Qt::WindowModality modality) { - QScopedPointer msgBoxPtr; + QPointer msgBoxPtr; QMessageBox *msgBox = NULL; QCheckBox *checkBox = NULL; - msgBoxPtr.reset(new QMessageBox(this)); + msgBoxPtr = new QMessageBox(this); msgBox = msgBoxPtr.data(); checkBox = new QCheckBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DONT_SHOW_AGAIN), msgBox); @@ -787,6 +970,9 @@ bool MainWindow::showMessageBox(QString msg, MessageBoxType msgType, Qt::WindowM msgBox->setText(msg); msgBox->exec(); + if (!msgBoxPtr) + return true; + if (checkBox->isChecked()) return false; @@ -800,7 +986,7 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) QScopedPointer associateMenu; QScopedPointer hiddenPlaylistsMenu; QScopedPointer hideAction; - QAction *selectedAction = NULL; + QPointer selectedAction; QPoint cursorPos = QCursor::pos(); QListWidgetItem *selectedItem = m_listWidget->itemAt(m_listWidget->viewport()->mapFromGlobal(cursorPos)); QDir playlistDir(settings->paths.directory_playlist); @@ -850,20 +1036,17 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) menu->addAction(hideAction.data()); - if (m_listWidget->count() > 0) + for (j = 0; j < m_listWidget->count() && m_listWidget->count() > 0; j++) { - for (j = 0; j < m_listWidget->count(); j++) - { - QListWidgetItem *item = m_listWidget->item(j); - bool hidden = m_listWidget->isItemHidden(item); + 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 (hidden) + { + QAction *action = hiddenPlaylistsMenu->addAction(item->text()); + action->setProperty("row", j); + action->setProperty("core_path", item->data(Qt::UserRole).toString()); + foundHiddenPlaylist = true; } } @@ -888,17 +1071,28 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) core_info_get_list(&core_info_list); - for (i = 0; i < core_info_list->count; i++) + 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; } - foreach (const QString &key, coreList.keys()) { - const core_info_t *core = coreList.value(key); - QAction *action = associateMenu->addAction(core->core_name); - action->setProperty("core_path", core->path); + QMapIterator coreListIterator(coreList); + + while (coreListIterator.hasNext()) + { + QString key; + const core_info_t *core = NULL; + QAction *action = NULL; + + coreListIterator.next(); + + key = coreListIterator.key(); + core = coreList.value(key); + action = associateMenu->addAction(core->core_name); + action->setProperty("core_path", core->path); + } } menu->addMenu(associateMenu.data()); @@ -991,7 +1185,7 @@ end: void MainWindow::onFileBrowserTreeContextMenuRequested(const QPoint&) { #ifdef HAVE_LIBRETRODB - QAction *action = NULL; + QPointer action; QList actions; QScopedPointer scanAction; QDir dir; @@ -1041,6 +1235,8 @@ void MainWindow::onGotStatusMessage(QString msg, unsigned priority, unsigned dur QScreen *screen = qApp->primaryScreen(); QStatusBar *status = statusBar(); + Q_UNUSED(priority) + if (msg.isEmpty()) return; @@ -1082,6 +1278,7 @@ void MainWindow::reloadPlaylists() QDir playlistDir(settings->paths.directory_playlist); QString currentPlaylistPath; QStringList hiddenPlaylists = m_settings->value("hidden_playlists").toStringList(); + int i = 0; currentItem = m_listWidget->currentItem(); @@ -1132,9 +1329,10 @@ void MainWindow::reloadPlaylists() if (hiddenPlaylists.contains(QFileInfo(settings->paths.path_content_video_history).fileName())) m_listWidget->setRowHidden(m_listWidget->row(videoPlaylistsItem), true); - foreach (QString file, m_playlistFiles) + for (i = 0; i < m_playlistFiles.count() && m_playlistFiles.count() > 0; i++) { QListWidgetItem *item = NULL; + const QString &file = m_playlistFiles.at(i); QString fileDisplayName = file; QString fileName = file; bool hasIcon = false; @@ -1172,7 +1370,6 @@ void MainWindow::reloadPlaylists() if (firstItem) { - int i = 0; bool found = false; for (i = 0; i < m_listWidget->count(); i++) @@ -1220,7 +1417,7 @@ void MainWindow::onGotLogMessage(const QString &msg) void MainWindow::onLaunchWithComboBoxIndexChanged(int) { - QList > infoList = getCoreInfo(); + QVector > infoList = getCoreInfo(); QString coreInfoText; QVariantMap coreMap = m_launchWithComboBox->currentData(Qt::UserRole).value(); CoreSelection coreSelection = static_cast(coreMap.value("core_selection").toInt()); @@ -1325,9 +1522,9 @@ void MainWindow::setTheme(Theme theme) } } -QList > MainWindow::getCoreInfo() +QVector > MainWindow::getCoreInfo() { - QList > infoList; + QVector > infoList; QHash currentCore = getSelectedCore(); core_info_list_t *core_info_list = NULL; const core_info_t *core_info = NULL; @@ -1729,9 +1926,12 @@ QHash MainWindow::getSelectedCore() QHash coreHash; QHash contentHash; QTableWidgetItem *contentItem = m_tableWidget->currentItem(); + ViewType viewType = getCurrentViewType(); - if (contentItem) + if (viewType == VIEW_TYPE_LIST && contentItem) contentHash = contentItem->data(Qt::UserRole).value >(); + else if (viewType == VIEW_TYPE_ICONS) + contentHash = m_currentGridHash; switch(coreSelection) { @@ -1752,7 +1952,7 @@ QHash MainWindow::getSelectedCore() } case CORE_SELECTION_PLAYLIST_DEFAULT: { - QList > cores; + QVector > cores; int i = 0; if (!contentItem || contentHash["db_name"].isEmpty()) @@ -1782,12 +1982,19 @@ QHash MainWindow::getSelectedCore() return coreHash; } -void MainWindow::onRunClicked() +/* the hash typically has the following keys: +path - absolute path to the content file +core_path - absolute path to the core, or "DETECT" to ask the user +db_name - the display name of the rdb database this content is from +label - the display name of the content, usually comes from the database +crc32 - an upper-case, 8 byte string representation of the hex CRC32 checksum (e.g. ABCDEF12) followed by "|crc" +core_name - the display name of the core, or "DETECT" if unknown +label_noext - the display name of the content that is guaranteed not to contain a file extension +*/ +void MainWindow::loadContent(const QHash &contentHash) { #ifdef HAVE_MENU content_ctx_info_t content_info; - QHash contentHash; - QTableWidgetItem *item = m_tableWidget->currentItem(); QByteArray corePathArray; QByteArray contentPathArray; QByteArray contentLabelArray; @@ -1797,63 +2004,50 @@ void MainWindow::onRunClicked() QVariantMap coreMap = m_launchWithComboBox->currentData(Qt::UserRole).value(); CoreSelection coreSelection = static_cast(coreMap.value("core_selection").toInt()); - if (!item) - return; - if (m_pendingRun) coreSelection = CORE_SELECTION_CURRENT; - contentHash = item->data(Qt::UserRole).value >(); - if (coreSelection == CORE_SELECTION_ASK) { - QTableWidgetItem *item = m_tableWidget->currentItem(); QStringList extensionFilters; - if (item) + if (contentHash.contains("path")) { - QHash hash; + int lastIndex = contentHash["path"].lastIndexOf('.'); + QString extensionStr; + QByteArray pathArray = contentHash["path"].toUtf8(); + const char *pathData = pathArray.constData(); - hash = item->data(Qt::UserRole).value >(); - - if (hash.contains("path")) + if (lastIndex >= 0) { - int lastIndex = hash["path"].lastIndexOf('.'); - QString extensionStr; - QByteArray pathArray = hash["path"].toUtf8(); - const char *pathData = pathArray.constData(); + extensionStr = contentHash["path"].mid(lastIndex + 1); - if (lastIndex >= 0) + if (!extensionStr.isEmpty()) { - extensionStr = hash["path"].mid(lastIndex + 1); - - if (!extensionStr.isEmpty()) - { - extensionFilters.append(extensionStr.toLower()); - } + extensionFilters.append(extensionStr.toLower()); } + } - if (path_is_compressed_file(pathData)) + if (path_is_compressed_file(pathData)) + { + unsigned i = 0; + struct string_list *list = file_archive_get_file_list(pathData, NULL); + + if (list) { - unsigned i = 0; - struct string_list *list = file_archive_get_file_list(pathData, NULL); - - if (list) + if (list->size > 0) { - if (list->size > 0) + for (i = 0; i < list->size; i++) { - for (i = 0; i < list->size; i++) - { - const char *filePath = list->elems[i].data; - const char *extension = path_get_extension(filePath); + const char *filePath = list->elems[i].data; + const char *extension = path_get_extension(filePath); - if (!extensionFilters.contains(extension, Qt::CaseInsensitive)) - extensionFilters.append(extension); - } + if (!extensionFilters.contains(extension, Qt::CaseInsensitive)) + extensionFilters.append(extension); } - - string_list_free(list); } + + string_list_free(list); } } } @@ -1884,7 +2078,7 @@ void MainWindow::onRunClicked() } case CORE_SELECTION_PLAYLIST_DEFAULT: { - QList > cores = getPlaylistDefaultCores(); + QVector > cores = getPlaylistDefaultCores(); int i = 0; for (i = 0; i < cores.count(); i++) @@ -1929,6 +2123,25 @@ void MainWindow::onRunClicked() #endif } +void MainWindow::onRunClicked() +{ +#ifdef HAVE_MENU + QTableWidgetItem *item = m_tableWidget->currentItem(); + ViewType viewType = getCurrentViewType(); + QHash contentHash; + + if (!item) + return; + + if (viewType == VIEW_TYPE_LIST) + contentHash = item->data(Qt::UserRole).value >(); + else if (viewType == VIEW_TYPE_ICONS) + contentHash = m_currentGridHash; + + loadContent(contentHash); +#endif +} + bool MainWindow::isContentLessCore() { rarch_system_info_t *system = runloop_get_system_info(); @@ -1949,13 +2162,13 @@ ViewOptionsDialog* MainWindow::viewOptionsDialog() return m_viewOptionsDialog; } -QList > MainWindow::getPlaylistDefaultCores() +QVector > MainWindow::getPlaylistDefaultCores() { settings_t *settings = config_get_ptr(); struct string_list *playlists = string_split(settings->arrays.playlist_names, ";"); struct string_list *cores = string_split(settings->arrays.playlist_cores, ";"); unsigned i = 0; - QList > coreList; + QVector > coreList; if (!playlists || !cores) { @@ -1997,6 +2210,7 @@ void MainWindow::setCoreActions() { QTableWidgetItem *currentContentItem = m_tableWidget->currentItem(); QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); + ViewType viewType = getCurrentViewType(); QHash hash; m_launchWithComboBox->clear(); @@ -2015,8 +2229,10 @@ void MainWindow::setCoreActions() m_launchWithComboBox->addItem(m_currentCore, QVariant::fromValue(comboBoxMap)); } - if (currentContentItem) + if (viewType == VIEW_TYPE_LIST && currentContentItem) hash = currentContentItem->data(Qt::UserRole).value >(); + else if (viewType == VIEW_TYPE_ICONS) + hash = m_currentGridHash; if (m_browserAndPlaylistTabWidget->tabText(m_browserAndPlaylistTabWidget->currentIndex()) == msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS)) { @@ -2066,7 +2282,7 @@ void MainWindow::setCoreActions() if (!hash["db_name"].isEmpty()) { - QList > defaultCores = getPlaylistDefaultCores(); + QVector > defaultCores = getPlaylistDefaultCores(); int i = 0; if (defaultCores.count() > 0) @@ -2174,6 +2390,9 @@ void MainWindow::onTabWidgetIndexChanged(int index) { QModelIndex index = m_dirTree->currentIndex(); + /* force list view for file browser, will set it back to whatever the user had when switching back to playlist tab */ + setCurrentViewType(VIEW_TYPE_LIST); + m_tableWidget->clear(); m_tableWidget->setColumnCount(0); m_tableWidget->setRowCount(0); @@ -2188,6 +2407,9 @@ void MainWindow::onTabWidgetIndexChanged(int index) { QListWidgetItem *item = m_listWidget->currentItem(); + if (m_lastViewType != getCurrentViewType()) + setCurrentViewType(m_lastViewType); + m_tableWidget->clear(); m_tableWidget->setColumnCount(0); m_tableWidget->setRowCount(0); @@ -2308,6 +2530,7 @@ void MainWindow::onViewClosedDocksAboutToShow() QMenu *menu = qobject_cast(sender()); QList dockWidgets; bool found = false; + int i = 0; if (!menu) return; @@ -2322,8 +2545,10 @@ void MainWindow::onViewClosedDocksAboutToShow() return; } - foreach (QDockWidget *dock, dockWidgets) + for (i = 0; i < dockWidgets.count() && dockWidgets.count() > 0; i++) { + const QDockWidget *dock = dockWidgets.at(i); + if (!dock->isVisible()) { QAction *action = menu->addAction(dock->property("menu_text").toString(), this, SLOT(onShowHiddenDockWidgetAction())); @@ -2333,9 +2558,7 @@ void MainWindow::onViewClosedDocksAboutToShow() } if (!found) - { menu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE)); - } } void MainWindow::onShowHiddenDockWidgetAction() @@ -2376,19 +2599,25 @@ void MainWindow::onSearchEnterPressed() void MainWindow::onCurrentTableItemChanged(QTableWidgetItem *current, QTableWidgetItem *) { - settings_t *settings = config_get_ptr(); QHash hash; - QString label; - QString playlist_name; - QByteArray extension; - QString extensionStr; - int lastIndex = -1; if (!current) return; hash = current->data(Qt::UserRole).value >(); + currentItemChanged(hash); +} + +void MainWindow::currentItemChanged(const QHash &hash) +{ + settings_t *settings = config_get_ptr(); + QString label; + QString playlist_name; + QByteArray extension; + QString extensionStr; + int lastIndex = -1; + label = hash["label_noext"]; label.replace(m_fileSanitizerRegex, "_"); @@ -2504,15 +2733,59 @@ void MainWindow::resizeThumbnails(bool one, bool two, bool three) } } +void MainWindow::setCurrentViewType(ViewType viewType) +{ + m_lastViewType = m_viewType; + m_viewType = viewType; + + switch (viewType) + { + case VIEW_TYPE_ICONS: + { + m_tableWidget->hide(); + m_gridWidget->show(); + break; + } + case VIEW_TYPE_LIST: + default: + { + m_gridWidget->hide(); + m_tableWidget->show(); + break; + } + } +} + +MainWindow::ViewType MainWindow::getCurrentViewType() +{ + return m_viewType; +} + void MainWindow::onCurrentListItemChanged(QListWidgetItem *current, QListWidgetItem *previous) { + ViewType viewType = getCurrentViewType(); + Q_UNUSED(current) Q_UNUSED(previous) if (m_browserAndPlaylistTabWidget->tabText(m_browserAndPlaylistTabWidget->currentIndex()) != msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS)) return; - initContentTableWidget(); + switch (viewType) + { + case VIEW_TYPE_ICONS: + { + initContentGridLayout(); + break; + } + case VIEW_TYPE_LIST: + default: + { + initContentTableWidget(); + break; + } + } + setCoreActions(); } @@ -2521,6 +2794,16 @@ TableWidget* MainWindow::contentTableWidget() return m_tableWidget; } +QWidget* MainWindow::contentGridWidget() +{ + return m_gridWidget; +} + +FlowLayout* MainWindow::contentGridLayout() +{ + return m_gridLayout; +} + void MainWindow::onBrowserDownloadsClicked() { settings_t *settings = config_get_ptr(); @@ -2700,6 +2983,265 @@ void MainWindow::onLoadCoreClicked(const QStringList &extensionFilters) m_loadCoreWindow->initCoreList(extensionFilters); } +void MainWindow::removeGridItems() +{ + if (m_gridItems.count() > 0) + { + QMutableVectorIterator > items(m_gridItems); + + m_pendingItemUpdates.clear(); + + while (items.hasNext()) + { + QPointer item = items.next(); + + if (item) + { + item->imageWatcher.waitForFinished(); + + items.remove(); + + m_gridLayout->removeWidget(item->widget); + + delete item->widget; + delete item; + } + } + } +} + +void MainWindow::onDeferredImageLoaded() +{ + const QFutureWatcher *watcher = static_cast*>(sender()); + GridItem *item = NULL; + + if (!watcher) + return; + + item = watcher->result(); + + if (!item) + return; + + if (m_gridItems.contains(item)) + { + if (!item->image.isNull()) + { + m_pendingItemUpdates.append(item); + QTimer::singleShot(0, this, SLOT(onPendingItemUpdates())); + } + } +} + +void MainWindow::onPendingItemUpdates() +{ + QMutableListIterator list(m_pendingItemUpdates); + + while (list.hasNext()) + { + GridItem *item = list.next(); + + if (!item) + continue; + + if (m_gridItems.contains(item)) + onUpdateGridItemPixmapFromImage(item); + + list.remove(); + } +} + +void MainWindow::onUpdateGridItemPixmapFromImage(GridItem *item) +{ + if (!item) + return; + + if (!m_gridItems.contains(item)) + return; + + item->label->setPixmap(QPixmap::fromImage(item->image)); + item->label->update(); +} + +void MainWindow::loadImageDeferred(GridItem *item, QString path) +{ + connect(&item->imageWatcher, SIGNAL(finished()), this, SLOT(onDeferredImageLoaded()), Qt::QueuedConnection); + item->imageWatcher.setFuture(QtConcurrent::run(this, &MainWindow::doDeferredImageLoad, item, path)); +} + +GridItem* MainWindow::doDeferredImageLoad(GridItem *item, QString path) +{ + /* this runs in another thread */ + if (!item) + return NULL; + + /* While we are indeed writing across thread boundaries here, the image is never accessed until after + * its thread finishes, and the item is never deleted without first waiting for the thread to finish. + */ + item->image = QImage(path); + + return item; +} + +void MainWindow::addPlaylistItemsToGrid(const QString &pathString, bool setProgress) +{ + QVector > items = getPlaylistItems(pathString); + QScreen *screen = qApp->primaryScreen(); + QSize screenSize = screen->size(); + QListWidgetItem *currentItem = m_listWidget->currentItem(); + settings_t *settings = config_get_ptr(); + int i = 0; + int zoomValue = m_zoomSlider->value(); + + /* setProgress means we are resetting the range of the progress bar as we are only loading a single playlist. If false, just increment by 1 since instead we're loading multiple playlists and we only track the progress of each entire playlist that is loaded, instead of every single entry. */ + if (setProgress) + { + m_gridProgressBar->setMinimum(0); + m_gridProgressBar->setMaximum(items.count() - 1); + m_gridProgressBar->setValue(0); + } + + for (i = 0; i < items.count(); i++) + { + const QHash &hash = items.at(i); + QPointer item; + QPointer label; + QString thumbnailFileNameNoExt; + QLabel *newLabel = NULL; + QSize thumbnailWidgetSizeHint(screenSize.width() / 8, screenSize.height() / 8); + QByteArray extension; + QString extensionStr; + QString imagePath; + int lastIndex = -1; + + if (m_listWidget->currentItem() != currentItem) + { + /* user changed the current playlist before we finished loading... abort */ + m_gridProgressWidget->hide(); + break; + } + + item = new GridItem(); + + lastIndex = hash["path"].lastIndexOf('.'); + + if (lastIndex >= 0) + { + extensionStr = hash["path"].mid(lastIndex + 1); + + if (!extensionStr.isEmpty()) + { + extension = extensionStr.toLower().toUtf8(); + } + } + + if (!extension.isEmpty() && m_imageFormats.contains(extension)) + { + /* use thumbnail widgets to show regular image files */ + imagePath = hash["path"]; + } + else + { + thumbnailFileNameNoExt = hash["label_noext"]; + thumbnailFileNameNoExt.replace(m_fileSanitizerRegex, "_"); + imagePath = QString(settings->paths.directory_thumbnails) + "/" + hash.value("db_name") + "/" + THUMBNAIL_BOXART + "/" + thumbnailFileNameNoExt + ".png"; + } + + item->hash = hash; + item->widget = new ThumbnailWidget(); + item->widget->setSizeHint(thumbnailWidgetSizeHint); + item->widget->setFixedSize(item->widget->sizeHint()); + item->widget->setLayout(new QVBoxLayout()); + item->widget->setStyleSheet("background-color: #555555"); + + item->widget->setProperty("hash", QVariant::fromValue >(hash)); + + connect(item->widget, SIGNAL(mouseDoubleClicked()), this, SLOT(onGridItemDoubleClicked())); + connect(item->widget, SIGNAL(mousePressed()), this, SLOT(onGridItemClicked())); + + label = new ThumbnailLabel(item->widget); + + item->label = label; + + calcGridItemSize(item, zoomValue); + + item->widget->layout()->addWidget(label); + + newLabel = new QLabel(hash.value("label"), item->widget); + newLabel->setAlignment(Qt::AlignCenter); + + item->widget->layout()->addWidget(newLabel); + qobject_cast(item->widget->layout())->setStretchFactor(label, 1); + + m_gridLayout->addWidgetDeferred(item->widget); + m_gridItems.append(item); + + loadImageDeferred(item, imagePath); + + if (i % 25 == 0) + qApp->processEvents(); + + if (setProgress) + m_gridProgressBar->setValue(i); + } + + if (!setProgress && m_gridProgressBar->value() < m_gridProgressBar->maximum()) + m_gridProgressBar->setValue(m_gridProgressBar->value() + 1); + + /* If there's only one entry, a min/max/value of all zero would make an indeterminate progress bar that never ends... so just hide it when we are done. */ + if (m_gridProgressBar->value() == m_gridProgressBar->maximum()) + m_gridProgressWidget->hide(); +} + +void MainWindow::initContentGridLayout() +{ + QListWidgetItem *item = m_listWidget->currentItem(); + QString path; + + if (!item) + return; + + m_gridProgressBar->setMinimum(0); + m_gridProgressBar->setMaximum(0); + m_gridProgressBar->setValue(0); + m_gridProgressWidget->show(); + + removeGridItems(); + + path = item->data(Qt::UserRole).toString(); + + if (path == ALL_PLAYLISTS_TOKEN) + { + settings_t *settings = config_get_ptr(); + QDir playlistDir(settings->paths.directory_playlist); + int i = 0; + + m_gridProgressBar->setMinimum(0); + m_gridProgressBar->setMaximum(m_playlistFiles.count() - 1); + m_gridProgressBar->setValue(0); + + for (i = 0; i < m_playlistFiles.count() && m_playlistFiles.count() > 0; i++) + { + const QString &playlist = m_playlistFiles.at(i); + m_gridProgressBar->setValue(i); + addPlaylistItemsToGrid(playlistDir.absoluteFilePath(playlist), false); + } + } + else + addPlaylistItemsToGrid(path); + + QTimer::singleShot(0, this, SLOT(onContentGridInited())); +} + +void MainWindow::onContentGridInited() +{ + m_gridLayoutWidget->resize(m_gridScrollArea->viewport()->size()); + + onZoomValueChanged(m_zoomSlider->value()); + + onSearchEnterPressed(); +} + void MainWindow::initContentTableWidget() { QListWidgetItem *item = m_listWidget->currentItem(); @@ -2730,9 +3272,11 @@ void MainWindow::initContentTableWidget() { settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); + int i = 0; - foreach (QString playlist, m_playlistFiles) + for (i = 0; i < m_playlistFiles.count() && m_playlistFiles.count() > 0; i++) { + const QString &playlist = m_playlistFiles.at(i); addPlaylistItemsToTable(playlistDir.absoluteFilePath(playlist)); } } @@ -2759,14 +3303,14 @@ void MainWindow::initContentTableWidget() onSearchEnterPressed(); } -void MainWindow::addPlaylistItemsToTable(QString pathString) +QVector > MainWindow::getPlaylistItems(QString pathString) { QByteArray pathArray; + QVector > items; const char *pathData = NULL; playlist_t *playlist = NULL; unsigned playlistSize = 0; unsigned i = 0; - int oldRowCount = m_tableWidget->rowCount(); pathArray.append(pathString); pathData = pathArray.constData(); @@ -2774,8 +3318,6 @@ void MainWindow::addPlaylistItemsToTable(QString pathString) playlist = playlist_init(pathData, COLLECTION_SIZE); playlistSize = playlist_get_size(playlist); - m_tableWidget->setRowCount(oldRowCount + playlistSize); - for (i = 0; i < playlistSize; i++) { const char *path = NULL; @@ -2784,7 +3326,6 @@ void MainWindow::addPlaylistItemsToTable(QString pathString) const char *core_name = NULL; const char *crc32 = NULL; const char *db_name = NULL; - QTableWidgetItem *labelItem = NULL; QHash hash; playlist_get_index(playlist, i, @@ -2822,15 +3363,34 @@ void MainWindow::addPlaylistItemsToTable(QString pathString) hash["db_name"].remove(file_path_str(FILE_PATH_LPL_EXTENSION)); } - labelItem = new QTableWidgetItem(hash["label"]); + items.append(hash); + } + + playlist_free(playlist); + playlist = NULL; + + return items; +} + +void MainWindow::addPlaylistItemsToTable(QString pathString) +{ + QVector > items = getPlaylistItems(pathString); + int i = 0; + int oldRowCount = m_tableWidget->rowCount(); + + m_tableWidget->setRowCount(oldRowCount + items.count()); + + for (i = 0; i < items.count(); i++) + { + QTableWidgetItem *labelItem = NULL; + const QHash &hash = items.at(i); + + labelItem = new QTableWidgetItem(hash.value("label")); labelItem->setData(Qt::UserRole, QVariant::fromValue >(hash)); labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsEditable); m_tableWidget->setItem(oldRowCount + i, 0, labelItem); } - - playlist_free(playlist); - playlist = NULL; } void MainWindow::keyPressEvent(QKeyEvent *event) @@ -2852,6 +3412,24 @@ QSettings* MainWindow::settings() return m_settings; } +QString MainWindow::getCurrentViewTypeString() +{ + switch (m_viewType) + { + case VIEW_TYPE_ICONS: + { + return QStringLiteral("icons"); + } + case VIEW_TYPE_LIST: + default: + { + return QStringLiteral("list"); + } + } + + return QStringLiteral("list"); +} + void MainWindow::closeEvent(QCloseEvent *event) { if (m_settings->value("save_geometry", false).toBool()) @@ -2861,6 +3439,8 @@ void MainWindow::closeEvent(QCloseEvent *event) if (m_settings->value("save_last_tab", false).toBool()) m_settings->setValue("last_tab", m_browserAndPlaylistTabWidget->currentIndex()); + m_settings->setValue("view_type", getCurrentViewTypeString()); + QMainWindow::closeEvent(event); } diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 6a5272b39b..ba0644b285 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -57,20 +57,35 @@ typedef struct ui_companion_qt ThumbnailWidget::ThumbnailWidget(QWidget *parent) : QWidget(parent) + ,m_sizeHint(QSize(256, 256)) { } +void ThumbnailWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + + emit mousePressed(); +} + +void ThumbnailWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QWidget::mouseDoubleClickEvent(event); + + emit mouseDoubleClicked(); +} + void ThumbnailWidget::paintEvent(QPaintEvent *event) { - QStyleOption o; - QPainter p; - o.initFrom(this); - p.begin(this); - style()->drawPrimitive( - QStyle::PE_Widget, &o, &p, this); - p.end(); + QStyleOption o; + QPainter p; + o.initFrom(this); + p.begin(this); + style()->drawPrimitive( + QStyle::PE_Widget, &o, &p, this); + p.end(); - QWidget::paintEvent(event); + QWidget::paintEvent(event); } void ThumbnailWidget::resizeEvent(QResizeEvent *event) @@ -80,7 +95,12 @@ void ThumbnailWidget::resizeEvent(QResizeEvent *event) QSize ThumbnailWidget::sizeHint() const { - return QSize(256, 256); + return m_sizeHint; +} + +void ThumbnailWidget::setSizeHint(QSize size) +{ + m_sizeHint = size; } ThumbnailLabel::ThumbnailLabel(QWidget *parent) : @@ -231,6 +251,8 @@ static void* ui_companion_qt_init(void) QAction *exitAction = NULL; QComboBox *launchWithComboBox = NULL; QSettings *qsettings = NULL; + QListWidget *listWidget = NULL; + int i = 0; if (!handle) return NULL; @@ -251,11 +273,14 @@ static void* ui_companion_qt_init(void) mainwindow->setWindowTitle("RetroArch"); mainwindow->setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | GROUPED_DRAGGING); + listWidget = mainwindow->playlistListWidget(); + widget = new QWidget(mainwindow); widget->setObjectName("tableWidget"); layout = new QVBoxLayout(); layout->addWidget(mainwindow->contentTableWidget()); + layout->addWidget(mainwindow->contentGridWidget()); widget->setLayout(layout); @@ -286,6 +311,10 @@ static void* ui_companion_qt_init(void) QObject::connect(viewClosedDocksMenu, SIGNAL(aboutToShow()), mainwindow, SLOT(onViewClosedDocksAboutToShow())); + viewMenu->addSeparator(); + viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS), mainwindow, SLOT(onIconViewClicked())); + viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST), mainwindow, SLOT(onListViewClicked())); + viewMenu->addSeparator(); viewMenu->addAction(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS), mainwindow->viewOptionsDialog(), SLOT(showDialog())); playlistWidget = new QWidget(); @@ -455,17 +484,6 @@ static void* ui_companion_qt_init(void) if (qsettings->contains("dock_positions")) mainwindow->restoreState(qsettings->value("dock_positions").toByteArray()); - if (qsettings->contains("save_last_tab")) - { - if (qsettings->contains("last_tab")) - { - int lastTabIndex = qsettings->value("last_tab", 0).toInt(); - - if (lastTabIndex >= 0 && browserAndPlaylistTabWidget->count() > lastTabIndex) - browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex); - } - } - if (qsettings->contains("theme")) { QString themeStr = qsettings->value("theme").toString(); @@ -483,6 +501,58 @@ static void* ui_companion_qt_init(void) else mainwindow->setTheme(); + if (qsettings->contains("view_type")) + { + QString viewType = qsettings->value("view_type", "list").toString(); + + if (viewType == "list") + mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); + else if (viewType == "icons") + mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_ICONS); + else + mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); + + /* we set it to the same thing a second time so that m_lastViewType is also equal to the startup view type */ + mainwindow->setCurrentViewType(mainwindow->getCurrentViewType()); + } + else + mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); + + /* We make sure to hook up the tab widget callback only after the tabs themselves have been added, + * but before changing to a specific one, to avoid the callback firing before the view type is set. + */ + QObject::connect(browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), mainwindow, SLOT(onTabWidgetIndexChanged(int))); + + /* setting the last tab must come after setting the view type */ + if (qsettings->contains("save_last_tab")) + { + if (qsettings->contains("last_tab")) + { + int lastTabIndex = qsettings->value("last_tab", 0).toInt(); + + if (lastTabIndex >= 0 && browserAndPlaylistTabWidget->count() > lastTabIndex) + { + browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex); + mainwindow->onTabWidgetIndexChanged(lastTabIndex); + } + } + } + else + { + browserAndPlaylistTabWidget->setCurrentIndex(0); + mainwindow->onTabWidgetIndexChanged(0); + } + + for (i = 0; i < listWidget->count() && listWidget->count() > 0; i++) + { + /* select the first non-hidden row */ + if (!listWidget->isRowHidden(i)) + { + listWidget->setCurrentRow(i); + break; + } + } + return handle; } diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 5ebd251212..941a49ba5e 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -28,6 +28,11 @@ #include #include #include +#include +#include +#include +#include +#include extern "C" { #include @@ -55,8 +60,26 @@ class QCheckBox; class QFormLayout; class QStyle; class QScrollArea; +class QSlider; class LoadCoreWindow; class MainWindow; +class ThumbnailWidget; +class ThumbnailLabel; +class FlowLayout; + +class GridItem : public QObject +{ + Q_OBJECT +public: + GridItem(); + + QPointer widget; + QPointer label; + QHash hash; + QImage image; + QPixmap pixmap; + QFutureWatcher imageWatcher; +}; class ThumbnailWidget : public QWidget { @@ -64,9 +87,17 @@ class ThumbnailWidget : public QWidget public: ThumbnailWidget(QWidget *parent = 0); QSize sizeHint() const; + void setSizeHint(QSize size); +signals: + void mouseDoubleClicked(); + void mousePressed(); +private: + QSize m_sizeHint; protected: void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); }; class ThumbnailLabel : public QWidget @@ -204,6 +235,12 @@ class MainWindow : public QMainWindow Q_OBJECT public: + enum ViewType + { + VIEW_TYPE_ICONS, + VIEW_TYPE_LIST + }; + enum Theme { THEME_SYSTEM_DEFAULT, @@ -223,6 +260,8 @@ public: TreeView* dirTreeView(); QListWidget* playlistListWidget(); TableWidget* contentTableWidget(); + FlowLayout* contentGridLayout(); + QWidget* contentGridWidget(); QWidget* searchWidget(); QLineEdit* searchLineEdit(); QComboBox* launchWithComboBox(); @@ -231,10 +270,10 @@ public: QToolButton* runPushButton(); QToolButton* stopPushButton(); QTabWidget* browserAndPlaylistTabWidget(); - QList > getPlaylistDefaultCores(); + QVector > getPlaylistDefaultCores(); ViewOptionsDialog* viewOptionsDialog(); QSettings* settings(); - QList > getCoreInfo(); + QVector > getCoreInfo(); void setTheme(Theme theme = THEME_SYSTEM_DEFAULT); Theme theme(); Theme getThemeFromString(QString themeString); @@ -245,6 +284,10 @@ public: bool setCustomThemeFile(QString filePath); void setCustomThemeString(QString qss); const QString& customThemeString() const; + GridItem* doDeferredImageLoad(GridItem *item, QString path); + void setCurrentViewType(ViewType viewType); + QString getCurrentViewTypeString(); + ViewType getCurrentViewType(); signals: void thumbnailChanged(const QPixmap &pixmap); @@ -259,10 +302,12 @@ public slots: void onBrowserUpClicked(); void onBrowserStartClicked(); void initContentTableWidget(); + void initContentGridLayout(); void onViewClosedDocksAboutToShow(); void onShowHiddenDockWidgetAction(); void setCoreActions(); void onRunClicked(); + void loadContent(const QHash &contentHash); void onStartCoreClicked(); void onTableWidgetEnterPressed(); void selectBrowserDir(QString path); @@ -277,6 +322,9 @@ public slots: void deferReloadPlaylists(); void onGotReloadPlaylists(); void showWelcomeScreen(); + void onIconViewClicked(); + void onListViewClicked(); + void onTabWidgetIndexChanged(int index); private slots: void onLoadCoreClicked(const QStringList &extensionFilters = QStringList()); @@ -285,24 +333,36 @@ private slots: void onCoreLoaded(); void onCurrentListItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void onCurrentTableItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous); + void currentItemChanged(const QHash &hash); void onSearchEnterPressed(); void onSearchLineEditEdited(const QString &text); void addPlaylistItemsToTable(QString path); + void addPlaylistItemsToGrid(const QString &path, bool setProgress = true); void onContentItemDoubleClicked(QTableWidgetItem *item); void onCoreLoadWindowClosed(); - void onTabWidgetIndexChanged(int index); void onTreeViewItemsSelected(QModelIndexList selectedIndexes); void onSearchResetClicked(); void onLaunchWithComboBoxIndexChanged(int index); void onFileBrowserTreeContextMenuRequested(const QPoint &pos); void onPlaylistWidgetContextMenuRequested(const QPoint &pos); void onStopClicked(); + void onDeferredImageLoaded(); + void onZoomValueChanged(int value); + void onContentGridInited(); + void onUpdateGridItemPixmapFromImage(GridItem *item); + void onPendingItemUpdates(); + void onGridItemDoubleClicked(); + void onGridItemClicked(); private: void setCurrentCoreLabel(); void getPlaylistFiles(); bool isCoreLoaded(); bool isContentLessCore(); + void removeGridItems(); + void loadImageDeferred(GridItem *item, QString path); + void calcGridItemSize(GridItem *item, int zoomValue); + QVector > getPlaylistItems(QString pathString); LoadCoreWindow *m_loadCoreWindow; QTimer *m_timer; @@ -344,12 +404,27 @@ private: QListWidgetItem *m_historyPlaylistsItem; QIcon m_folderIcon; QString m_customThemeString; + FlowLayout *m_gridLayout; + QWidget *m_gridWidget; + QScrollArea *m_gridScrollArea; + QVector > m_gridItems; + QWidget *m_gridLayoutWidget; + QSlider *m_zoomSlider; + int m_lastZoomSliderValue; + QList m_pendingItemUpdates; + ViewType m_viewType; + QProgressBar *m_gridProgressBar; + QWidget *m_gridProgressWidget; + QHash m_currentGridHash; + ViewType m_lastViewType; protected: void closeEvent(QCloseEvent *event); void keyPressEvent(QKeyEvent *event); }; +Q_DECLARE_METATYPE(QPointer) + RETRO_BEGIN_DECLS typedef struct ui_application_qt