From a868ef29e85cbd85c1f1835826d07b8605406de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20L=C3=B3pez?= Date: Wed, 12 Dec 2018 20:45:19 +0100 Subject: [PATCH] Qt: Implement custom playlist model and grid view. Only load images when they become visible and cache them. Add option to change thumbnail type displayed in grid view. Add option to change thumbnail cache limit. --- Makefile.common | 4 +- griffin/griffin_cpp.cpp | 4 +- intl/msg_hash_us.h | 10 +- msg_hash.h | 2 + ui/drivers/qt/filedropwidget.cpp | 7 +- ui/drivers/qt/filedropwidget.h | 1 + ui/drivers/qt/flowlayout.cpp | 248 ------ ui/drivers/qt/flowlayout.h | 105 --- ui/drivers/qt/gridview.cpp | 392 +++++++++ ui/drivers/qt/gridview.h | 74 ++ ui/drivers/qt/playlist.cpp | 419 ++++++---- ui/drivers/qt/playlistthumbnaildownload.cpp | 18 +- ui/drivers/qt/thumbnaildownload.cpp | 2 + ui/drivers/qt/thumbnailpackdownload.cpp | 7 +- ui/drivers/qt/ui_qt_themes.h | 10 +- ui/drivers/qt/ui_qt_window.cpp | 838 +++++--------------- ui/drivers/qt/viewoptionsdialog.cpp | 33 +- ui/drivers/qt/viewoptionsdialog.h | 3 + ui/drivers/ui_qt.cpp | 27 +- ui/drivers/ui_qt.h | 130 +-- 20 files changed, 1105 insertions(+), 1229 deletions(-) delete mode 100644 ui/drivers/qt/flowlayout.cpp delete mode 100644 ui/drivers/qt/flowlayout.h create mode 100644 ui/drivers/qt/gridview.cpp create mode 100644 ui/drivers/qt/gridview.h diff --git a/Makefile.common b/Makefile.common index ae3d10b3e4..5bf02a93ab 100644 --- a/Makefile.common +++ b/Makefile.common @@ -342,7 +342,7 @@ ifeq ($(HAVE_QT), 1) 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/flowlayout.o \ + ui/drivers/qt/gridview.o \ ui/drivers/qt/shaderparamsdialog.o \ ui/drivers/qt/coreoptionsdialog.o \ ui/drivers/qt/filedropwidget.o \ @@ -357,7 +357,7 @@ ifeq ($(HAVE_QT), 1) MOC_HEADERS += ui/drivers/ui_qt.h \ ui/drivers/qt/ui_qt_load_core_window.h \ - ui/drivers/qt/flowlayout.h \ + ui/drivers/qt/gridview.h \ ui/drivers/qt/shaderparamsdialog.h \ ui/drivers/qt/coreoptionsdialog.h \ ui/drivers/qt/filedropwidget.h \ diff --git a/griffin/griffin_cpp.cpp b/griffin/griffin_cpp.cpp index fd3673afa0..e34561659f 100644 --- a/griffin/griffin_cpp.cpp +++ b/griffin/griffin_cpp.cpp @@ -43,7 +43,7 @@ UI #include "../ui/drivers/qt/ui_qt_browser_window.cpp" #include "../ui/drivers/qt/ui_qt_msg_window.cpp" #include "../ui/drivers/qt/ui_qt_application.cpp" -#include "../ui/drivers/qt/flowlayout.cpp" +#include "../ui/drivers/qt/gridview.cpp" #include "../ui/drivers/qt/shaderparamsdialog.cpp" #include "../ui/drivers/qt/coreoptionsdialog.cpp" #include "../ui/drivers/qt/filedropwidget.cpp" @@ -59,7 +59,7 @@ UI #include "../ui/drivers/qt/moc_coreinfodialog.cpp" #include "../ui/drivers/qt/moc_coreoptionsdialog.cpp" #include "../ui/drivers/qt/moc_filedropwidget.cpp" -#include "../ui/drivers/qt/moc_flowlayout.cpp" +#include "../ui/drivers/qt/moc_gridview.cpp" #include "../ui/drivers/qt/moc_playlistentrydialog.cpp" #include "../ui/drivers/qt/moc_shaderparamsdialog.cpp" #include "../ui/drivers/qt/moc_ui_qt_load_core_window.cpp" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 9a4ae85a39..bf9b3fe806 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7544,6 +7544,14 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_STARTUP_PLAYLIST, "Start on playlist:" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE, + "Icon view thumbnail type:" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_CACHE_LIMIT, + "Thumbnail cache limit:" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS, "Download All Thumbnails" @@ -7779,4 +7787,4 @@ MSG_HASH( MSG_HASH( MSG_MISSING_ASSETS, "Warning: Missing assets, use the Online Updater if available" - ) \ No newline at end of file + ) diff --git a/msg_hash.h b/msg_hash.h index 08fba9705c..93cac5f1ce 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1967,6 +1967,8 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_LIST_MAX_COUNT, MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_GRID_MAX_COUNT, MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_STARTUP_PLAYLIST, + MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE, + MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_CACHE_LIMIT, MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS, MENU_ENUM_LABEL_VALUE_QT_MENU_HELP, MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER, diff --git a/ui/drivers/qt/filedropwidget.cpp b/ui/drivers/qt/filedropwidget.cpp index a53b588e7e..8a336288b2 100644 --- a/ui/drivers/qt/filedropwidget.cpp +++ b/ui/drivers/qt/filedropwidget.cpp @@ -41,7 +41,12 @@ void FileDropWidget::paintEvent(QPaintEvent *event) void FileDropWidget::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Delete) + if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + { + event->accept(); + emit enterPressed(); + } + else if (event->key() == Qt::Key_Delete) { event->accept(); emit deletePressed(); diff --git a/ui/drivers/qt/filedropwidget.h b/ui/drivers/qt/filedropwidget.h index 9301d948df..3a5d420e5a 100644 --- a/ui/drivers/qt/filedropwidget.h +++ b/ui/drivers/qt/filedropwidget.h @@ -15,6 +15,7 @@ public: FileDropWidget(QWidget *parent = 0); signals: void filesDropped(QStringList files); + void enterPressed(); void deletePressed(); protected: void dragEnterEvent(QDragEnterEvent *event); diff --git a/ui/drivers/qt/flowlayout.cpp b/ui/drivers/qt/flowlayout.cpp deleted file mode 100644 index b970e10b10..0000000000 --- a/ui/drivers/qt/flowlayout.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/**************************************************************************** -** -** 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 deleted file mode 100644 index c9d11e7653..0000000000 --- a/ui/drivers/qt/flowlayout.h +++ /dev/null @@ -1,105 +0,0 @@ -/**************************************************************************** -** -** 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/gridview.cpp b/ui/drivers/qt/gridview.cpp new file mode 100644 index 0000000000..5ba83ba1a8 --- /dev/null +++ b/ui/drivers/qt/gridview.cpp @@ -0,0 +1,392 @@ +#include +#include + +#include "gridview.h" +#include "../ui_qt.h" + +/* http://www.informit.com/articles/article.aspx?p=1613548 */ + +ThumbnailDelegate::ThumbnailDelegate(QObject* parent) : + QStyledItemDelegate(parent) +{ +} + +void ThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const +{ + painter->save(); + + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + const QWidget *widget = opt.widget; + + QStyle *style = widget->style(); + + int margin = 11; + int textMargin = 4; + QRect rect = opt.rect; + int textHeight = painter->fontMetrics().height() + margin + margin; + QRect adjusted = rect.adjusted(margin, margin, -margin, -textHeight + textMargin); + QPixmap pixmap = index.data(PlaylistModel::THUMBNAIL).value(); + + // draw the background + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget); + + // draw the image + if (!pixmap.isNull()) + { + QPixmap pixmapScaled = pixmap.scaled(adjusted.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + style->drawItemPixmap(painter, adjusted, Qt::AlignHCenter | Qt::AlignBottom, pixmapScaled); + } + + // draw the text + if (!opt.text.isEmpty()) + { + QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if (opt.state & QStyle::State_Selected) + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + else + painter->setPen(opt.palette.color(cg, QPalette::Text)); + + QRect textRect = QRect(rect.x() + margin, rect.y() + adjusted.height() - textMargin + margin, rect.width() - 2 * margin, textHeight); + QString elidedText = painter->fontMetrics().elidedText(opt.text, opt.textElideMode, textRect.width(), Qt::TextShowMnemonic); + + painter->setFont(opt.font); + painter->drawText(textRect, Qt::AlignCenter, elidedText); + } + + painter->restore(); +} + +GridView::GridView(QWidget *parent) : QAbstractItemView(parent), m_idealHeight(0), m_hashIsDirty(false) +{ + setFocusPolicy(Qt::WheelFocus); + horizontalScrollBar()->setRange(0, 0); + verticalScrollBar()->setRange(0, 0); +} + +void GridView::setModel(QAbstractItemModel *newModel) +{ + if (model()) + disconnect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int))); + + QAbstractItemView::setModel(newModel); + + connect(newModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int))); + + m_hashIsDirty = true; +} + +void GridView::setviewMode(ViewMode mode) +{ + m_viewMode = mode; +} + +void GridView::calculateRectsIfNecessary() const +{ + if (!m_hashIsDirty) + return; + + int x = m_spacing; + int y = m_spacing; + int row; + int nextX; + + const int maxWidth = viewport()->width(); + + switch (m_viewMode) { + case Anchored: + { + int columns = (maxWidth - m_spacing) / (m_size + m_spacing); + if (columns > 0) + { + const int actualSpacing = (maxWidth - m_spacing - m_size - (columns - 1) * m_size) / columns; + for (row = 0; row < model()->rowCount(); ++row) + { + nextX = x + m_size + actualSpacing; + if (nextX > maxWidth) + { + x = m_spacing; + y += m_size + m_spacing; + nextX = x + m_size + actualSpacing; + } + m_rectForRow[row] = QRectF(x, y, m_size, m_size); + x = nextX; + } + m_idealHeight = y + m_size + m_spacing; + } + break; + } + case Centered: + { + int columns = (maxWidth - m_spacing) / (m_size + m_spacing); + if (columns > 0) + { + const int actualSpacing = (maxWidth - columns * m_size) / (columns + 1); + x = actualSpacing; + for (row = 0; row < model()->rowCount(); ++row) + { + nextX = x + m_size + actualSpacing; + if (nextX > maxWidth) + { + x = actualSpacing; + y += m_size + m_spacing; + nextX = x + m_size + actualSpacing; + } + m_rectForRow[row] = QRectF(x, y, m_size, m_size); + x = nextX; + } + m_idealHeight = y + m_size + m_spacing; + } + break; + } + case Simple: + for (row = 0; row < model()->rowCount(); ++row) + { + nextX = x + m_size + m_spacing; + if (nextX > maxWidth) + { + x = m_spacing; + y += m_size + m_spacing; + nextX = x + m_size + m_spacing; + } + m_rectForRow[row] = QRectF(x, y, m_size, m_size); + x = nextX; + } + break; + } + + m_hashIsDirty = false; + viewport()->update(); +} + +QRect GridView::visualRect(const QModelIndex &index) const +{ + QRect rect; + if (index.isValid()) + rect = viewportRectForRow(index.row()).toRect(); + return rect; +} + +QRectF GridView::viewportRectForRow(int row) const +{ + calculateRectsIfNecessary(); + QRectF rect = m_rectForRow.value(row).toRect(); + if (!rect.isValid()) + return rect; + return QRectF(rect.x() - horizontalScrollBar()->value(), rect.y() - verticalScrollBar()->value(), rect.width(), rect.height()); +} + +void GridView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint) +{ + QRect viewRect = viewport()->rect(); + QRect itemRect = visualRect(index); + + if (itemRect.left() < viewRect.left()) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + itemRect.left() - viewRect.left()); + else if (itemRect.right() > viewRect.right()) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + qMin(itemRect.right() - viewRect.right(), itemRect.left() - viewRect.left())); + if (itemRect.top() < viewRect.top()) + verticalScrollBar()->setValue(verticalScrollBar()->value() + itemRect.top() - viewRect.top()); + else if (itemRect.bottom() > viewRect.bottom()) + verticalScrollBar()->setValue(verticalScrollBar()->value() + qMin(itemRect.bottom() - viewRect.bottom(), itemRect.top() - viewRect.top())); + viewport()->update(); +} + +QModelIndex GridView::indexAt(const QPoint &point_) const +{ + QPoint point(point_); + point.rx() += horizontalScrollBar()->value(); + point.ry() += verticalScrollBar()->value(); + calculateRectsIfNecessary(); + QHashIterator i(m_rectForRow); + while (i.hasNext()) + { + i.next(); + if (i.value().contains(point)) + return model()->index(i.key(), 0, rootIndex()); + } + return QModelIndex(); +} + +void GridView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + m_hashIsDirty = true; + QAbstractItemView::dataChanged(topLeft, bottomRight); +} + +void GridView::refresh() +{ + m_hashIsDirty = true; + calculateRectsIfNecessary(); + updateGeometries(); +} + +void GridView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + QAbstractItemView::rowsInserted(parent, start, end); + refresh(); +} + +void GridView::rowsRemoved(const QModelIndex &parent, int start, int end) +{ + refresh(); +} + +void GridView::setGridSize(const int newSize) +{ + if (newSize != m_size) + { + m_size = newSize; + refresh(); + } +} + +void GridView::resizeEvent(QResizeEvent*) +{ + refresh(); +} + +void GridView::reset() +{ + QAbstractItemView::reset(); + refresh(); +} + +QModelIndex GridView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers) +{ + QModelIndex index = currentIndex(); + if (index.isValid()) + { + if ((cursorAction == MoveLeft && index.row() > 0) || (cursorAction == MoveRight && index.row() + 1 < model()->rowCount())) + { + const int offset = (cursorAction == MoveLeft ? -1 : 1); + index = model()->index(index.row() + offset, index.column(), index.parent()); + } + else if ((cursorAction == MoveUp && index.row() > 0) || (cursorAction == MoveDown && index.row() + 1 < model()->rowCount())) + { + const int offset = ((m_size + m_spacing) * (cursorAction == MoveUp ? -1 : 1)); + QRect rect = viewportRectForRow(index.row()).toRect(); + QPoint point(rect.center().x(), rect.center().y() + offset); + index = indexAt(point); + } + } + return index; +} + +int GridView::horizontalOffset() const +{ + return horizontalScrollBar()->value(); +} + +int GridView::verticalOffset() const +{ + return verticalScrollBar()->value(); +} + +void GridView::scrollContentsBy(int dx, int dy) +{ + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); + emit(visibleItemsChangedMaybe()); +} + +QVector GridView::visibleIndexes() const { + return m_visibleIndexes; +} + +void GridView::setSelection(const QRect &rect, QFlags flags) +{ + QRect rectangle = rect.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized(); + calculateRectsIfNecessary(); + QHashIterator i(m_rectForRow); + int firstRow = model()->rowCount(); + int lastRow = -1; + while (i.hasNext()) + { + i.next(); + if (i.value().intersects(rectangle)) + { + firstRow = firstRow < i.key() ? firstRow : i.key(); + lastRow = lastRow > i.key() ? lastRow : i.key(); + } + } + if (firstRow != model()->rowCount() && lastRow != -1) + { + QItemSelection selection( model()->index(firstRow, 0, rootIndex()), model()->index(lastRow, 0, rootIndex())); + selectionModel()->select(selection, flags); + } + else + { + QModelIndex invalid; + QItemSelection selection(invalid, invalid); + selectionModel()->select(selection, flags); + } +} + +QRegion GridView::visualRegionForSelection(const QItemSelection &selection) const +{ + QRegion region; + foreach(const QItemSelectionRange &range, selection) + { + for (int row = range.top(); row <= range.bottom(); ++row) + { + for (int column = range.left(); column < range.right(); ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); + } + } + } + return region; +} + +void GridView::paintEvent(QPaintEvent*) +{ + QPainter painter(viewport()); + int row; + + painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); + m_visibleIndexes.clear(); + + for (row = 0; row < model()->rowCount(rootIndex()); ++row) + { + QModelIndex index = model()->index(row, 0, rootIndex()); + QRectF rect = viewportRectForRow(row); + + if (!rect.isValid() || rect.bottom() < 0 || rect.y() > viewport()->height()) + continue; + + m_visibleIndexes.append(index); + QStyleOptionViewItem option = viewOptions(); + option.rect = rect.toRect(); + + if (selectionModel()->isSelected(index)) + option.state |= QStyle::State_Selected; + + if (currentIndex() == index) + option.state |= QStyle::State_HasFocus; + + itemDelegate()->paint(&painter, option, index); + } +} + +void GridView::updateGeometries() +{ + const int RowHeight = m_size + m_spacing; + + QAbstractItemView::updateGeometries(); + + verticalScrollBar()->setSingleStep(RowHeight); + verticalScrollBar()->setPageStep(viewport()->height()); + verticalScrollBar()->setRange(0, qMax(0, m_idealHeight - viewport()->height())); + + horizontalScrollBar()->setPageStep(viewport()->width()); + horizontalScrollBar()->setRange(0, qMax(0, RowHeight - viewport()->width())); + + emit(visibleItemsChangedMaybe()); +} diff --git a/ui/drivers/qt/gridview.h b/ui/drivers/qt/gridview.h new file mode 100644 index 0000000000..06537ec254 --- /dev/null +++ b/ui/drivers/qt/gridview.h @@ -0,0 +1,74 @@ +#ifndef GRIDVIEW_H +#define GRIDVIEW_H + +#include +#include + +class ThumbnailDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + ThumbnailDelegate(QObject* parent = 0); + void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const; +}; + +class GridView : public QAbstractItemView +{ + Q_OBJECT + +public: + enum ViewMode + { + Simple, + Centered, + Anchored + }; + + GridView(QWidget *parent = 0); + ~GridView() {} + + QModelIndex indexAt(const QPoint &point_) const; + QVector visibleIndexes() const; + QRect visualRect(const QModelIndex &index) const; + void setModel(QAbstractItemModel *model); + void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint); + void setGridSize(const int newSize); + void setviewMode(ViewMode mode); + +signals: + void visibleItemsChangedMaybe() const; + +protected slots: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsRemoved(const QModelIndex &parent, int start, int end); + void updateGeometries(); + void reset() override; + +protected: + QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + bool isIndexHidden(const QModelIndex&) const { return false; } + int horizontalOffset() const; + int verticalOffset() const; + void scrollContentsBy(int dx, int dy); + void setSelection(const QRect &rect, QFlags flags); + void paintEvent(QPaintEvent*); + void resizeEvent(QResizeEvent*); + +private: + QRectF viewportRectForRow(int row) const; + void calculateRectsIfNecessary() const; + void refresh(); + + int m_size = 255; + int m_spacing = 7; + QVector m_visibleIndexes; + ViewMode m_viewMode = Centered; + mutable int m_idealHeight; + mutable QHash m_rectForRow; + mutable bool m_hashIsDirty; +}; + +#endif // GRIDVIEW_H diff --git a/ui/drivers/qt/playlist.cpp b/ui/drivers/qt/playlist.cpp index 7217f19f5a..7021c85d47 100644 --- a/ui/drivers/qt/playlist.cpp +++ b/ui/drivers/qt/playlist.cpp @@ -9,9 +9,10 @@ #include #include #include +#include +#include #include "../ui_qt.h" -#include "flowlayout.h" #include "playlistentrydialog.h" extern "C" { @@ -28,6 +29,213 @@ extern "C" { #include "../../../verbosity.h" } +PlaylistModel::PlaylistModel(QObject *parent) + : QAbstractListModel(parent) +{ + m_imageFormats = QVector::fromList(QImageReader::supportedImageFormats()); + m_fileSanitizerRegex = QRegularExpression("[&*/:`<>?\\|]"); + setThumbnailCacheLimit(500); + connect(this, &PlaylistModel::imageLoaded, this, &PlaylistModel::onImageLoaded); +} + +int PlaylistModel::rowCount(const QModelIndex & /* parent */) const +{ + return m_contents.count(); +} + +int PlaylistModel::columnCount(const QModelIndex & /* parent */) const +{ + return 1; +} + +QVariant PlaylistModel::data(const QModelIndex &index, int role) const +{ + if (index.column() == 0) + { + if (!index.isValid()) + return QVariant(); + + if (index.row() >= m_contents.size() || index.row() < 0) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::ToolTipRole: + return m_contents.at(index.row())["label_noext"]; + case HASH: + return QVariant::fromValue(m_contents.at(index.row())); + case THUMBNAIL: + { + QPixmap *cachedPreview = m_cache.object(getCurrentTypeThumbnailPath(index)); + if (cachedPreview) + return *cachedPreview; + return QVariant(); + } + } + } + return QVariant(); +} + +Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + + return QAbstractListModel::flags(index) | Qt::ItemIsEditable; +} + +bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) { + QHash hash = m_contents.at(index.row()); + + hash["label"] = value.toString(); + hash["label_noext"] = QFileInfo(value.toString()).completeBaseName(); + + m_contents.replace(index.row(), hash); + emit dataChanged(index, index, { role }); + return true; + } + return false; +} + +QVariant PlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME); + else + return section + 1; +} + +void PlaylistModel::setThumbnailType(const ThumbnailType type) +{ + m_thumbnailType = type; +} + +void PlaylistModel::setThumbnailCacheLimit(int limit) +{ + m_cache.setMaxCost(limit * 1024); +} + +QString PlaylistModel::getThumbnailPath(const QModelIndex &index, QString type) const +{ + QByteArray extension; + QString extensionStr; + + QString thumbnailFileNameNoExt; + int lastIndex = -1; + + const QHash &hash = m_contents.at(index.row()); + 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 */ + return hash["path"]; + } + else + { + thumbnailFileNameNoExt = hash["label_noext"]; + thumbnailFileNameNoExt.replace(m_fileSanitizerRegex, "_"); + return QDir::cleanPath(QString(config_get_ptr()->paths.directory_thumbnails)) + "/" + hash.value("db_name") + "/" + type + "/" + thumbnailFileNameNoExt + ".png"; + } +} + +QString PlaylistModel::getCurrentTypeThumbnailPath(const QModelIndex &index) const +{ + switch (m_thumbnailType) + { + case THUMBNAIL_TYPE_BOXART: + return getThumbnailPath(index, THUMBNAIL_BOXART); + case THUMBNAIL_TYPE_SCREENSHOT: + return getThumbnailPath(index, THUMBNAIL_SCREENSHOT); + case THUMBNAIL_TYPE_TITLE_SCREEN: + return getThumbnailPath(index, THUMBNAIL_TITLE); + default: + return QString(); + } +} + +void PlaylistModel::reloadThumbnail(const QModelIndex &index) +{ + if (index.isValid()) { + reloadThumbnailPath(getCurrentTypeThumbnailPath(index)); + loadThumbnail(index); + } +} + +void PlaylistModel::reloadSystemThumbnails(const QString system) +{ + int i = 0; + QString key; + QString path = QDir::cleanPath(QString(config_get_ptr()->paths.directory_thumbnails)) + "/" + system; + QList keys = m_cache.keys(); + QList pending = m_pendingImages.values(); + + for (i = 0; i < keys.size(); i++) + { + key = keys.at(i); + if (key.startsWith(path)) + m_cache.remove(key); + } + + for (i = 0; i < pending.size(); i++) + { + key = pending.at(i); + if (key.startsWith(path)) + m_pendingImages.remove(key); + } +} + +void PlaylistModel::reloadThumbnailPath(const QString path) +{ + m_cache.remove(path); + m_pendingImages.remove(path); +} + +void PlaylistModel::loadThumbnail(const QModelIndex &index) +{ + QString path = getCurrentTypeThumbnailPath(index); + + if (!m_pendingImages.contains(path) && !m_cache.contains(path)) + { + m_pendingImages.insert(path); + QtConcurrent::run(this, &PlaylistModel::loadImage, index, path); + } +} + +void PlaylistModel::loadImage(const QModelIndex &index, const QString &path) +{ + const QImage image = QImage(path); + if (!image.isNull()) + emit imageLoaded(image, index, path); +} + +void PlaylistModel::onImageLoaded(const QImage image, const QModelIndex &index, const QString &path) +{ + QPixmap *pixmap = new QPixmap(QPixmap::fromImage(image)); + const int cost = pixmap->width() * pixmap->height() * pixmap->depth() / (8 * 1024); + m_cache.insert(path, pixmap, cost); + if (index.isValid()) + emit dataChanged(index, index, { THUMBNAIL }); + m_pendingImages.remove(path); +} + inline static bool comp_hash_name_key_lower(const QHash &lhs, const QHash &rhs) { return lhs.value("name").toLower() < rhs.value("name").toLower(); @@ -38,7 +246,6 @@ inline static bool comp_hash_label_key_lower(const QHash &lhs, return lhs.value("label").toLower() < rhs.value("label").toLower(); } -/* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */ bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions) { PlaylistEntryDialog *playlistDialog = playlistEntryDialog(); @@ -834,9 +1041,6 @@ void MainWindow::reloadPlaylists() getPlaylistFiles(); - /* block this signal because setData() would trigger an infinite loop */ - disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); - m_listWidget->clear(); m_listWidget->setSelectionBehavior(QAbstractItemView::SelectRows); m_listWidget->setSelectionMode(QAbstractItemView::SingleSelection); @@ -968,7 +1172,6 @@ void MainWindow::reloadPlaylists() } } - connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); } QString MainWindow::getCurrentPlaylistPath() @@ -1116,151 +1319,15 @@ void MainWindow::getPlaylistFiles() m_playlistFiles = playlistDir.entryList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Files, QDir::Name); } -void MainWindow::addPlaylistItemsToGrid(const QStringList &paths, bool add) -{ - QVector > items; - int i; - - if (paths.isEmpty()) - return; - - for (i = 0; i < paths.size(); i++) - { - int j; - QVector > vec = getPlaylistItems(paths.at(i)); - /* QVector::append() wasn't added until 5.5, so just do it the old fashioned way */ - for (j = 0; j < vec.size(); j++) - { - if (add && m_allPlaylistsGridMaxCount > 0 && items.size() >= m_allPlaylistsGridMaxCount) - goto finish; - - items.append(vec.at(j)); - } - } -finish: - std::sort(items.begin(), items.end(), comp_hash_label_key_lower); - - addPlaylistHashToGrid(items); -} - -void MainWindow::addPlaylistHashToGrid(const QVector > &items) -{ - 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(); - - m_gridProgressBar->setMinimum(0); - m_gridProgressBar->setMaximum(qMax(0, 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->setObjectName("thumbnailWidget"); - item->widget->setProperty("hash", QVariant::fromValue >(hash)); - item->widget->setProperty("image_path", imagePath); - - connect(item->widget, SIGNAL(mouseDoubleClicked()), this, SLOT(onGridItemDoubleClicked())); - connect(item->widget, SIGNAL(mousePressed()), this, SLOT(onGridItemClicked())); - - label = new ThumbnailLabel(item->widget); - label->setObjectName("thumbnailGridLabel"); - - item->label = label; - item->labelText = hash.value("label"); - - newLabel = new QLabel(item->labelText, item->widget); - newLabel->setObjectName("thumbnailQLabel"); - newLabel->setAlignment(Qt::AlignCenter); - newLabel->setToolTip(item->labelText); - - calcGridItemSize(item, zoomValue); - - item->widget->layout()->addWidget(label); - - 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) - { - /* Needed to update progress dialog while doing a lot of stuff on the main thread. */ - qApp->processEvents(); - } - - m_gridProgressBar->setValue(i); - } - - /* 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(); -} - -QVector > MainWindow::getPlaylistItems(QString pathString) +void PlaylistModel::getPlaylistItems(QString path) { QByteArray pathArray; - QVector > items; const char *pathData = NULL; playlist_t *playlist = NULL; unsigned playlistSize = 0; unsigned i = 0; - pathArray.append(pathString); + pathArray.append(path); pathData = pathArray.constData(); playlist = playlist_init(pathData, COLLECTION_SIZE); @@ -1313,58 +1380,67 @@ QVector > MainWindow::getPlaylistItems(QString pathStrin hash["db_name"].remove(file_path_str(FILE_PATH_LPL_EXTENSION)); } - items.append(hash); + m_contents.append(hash); } playlist_free(playlist); playlist = NULL; - - return items; } -void MainWindow::addPlaylistItemsToTable(const QStringList &paths, bool add) +void PlaylistModel::addPlaylistItems(const QStringList &paths, bool add) { - QVector > items; int i; if (paths.isEmpty()) return; + beginResetModel(); + + m_contents.clear(); + for (i = 0; i < paths.size(); i++) { - int j; - QVector > vec = getPlaylistItems(paths.at(i)); - /* QVector::append() wasn't added until 5.5, so just do it the old fashioned way */ - for (j = 0; j < vec.size(); j++) - { - if (add && m_allPlaylistsListMaxCount > 0 && items.size() >= m_allPlaylistsListMaxCount) - goto finish; - - items.append(vec.at(j)); - } + getPlaylistItems(paths.at(i)); } -finish: - addPlaylistHashToTable(items); + + endResetModel(); } -void MainWindow::addPlaylistHashToTable(const QVector > &items) +void PlaylistModel::addDir(QString path, QFlags showHidden) { + QDir dir = path; + QStringList dirList; int i = 0; - int oldRowCount = m_tableWidget->rowCount(); - m_tableWidget->setRowCount(oldRowCount + items.count()); + dirList = dir.entryList(QDir::NoDotAndDotDot | + QDir::Readable | + QDir::Files | + showHidden, + QDir::Name); - for (i = 0; i < items.count(); i++) + if (dirList.count() == 0) + return; + + beginResetModel(); + + m_contents.clear(); + + for (i = 0; i < dirList.count(); i++) { - QTableWidgetItem *labelItem = NULL; - const QHash &hash = items.at(i); + QString fileName = dirList.at(i); + QHash hash; + QString filePath(QDir::toNativeSeparators(dir.absoluteFilePath(fileName))); + QFileInfo fileInfo(filePath); - labelItem = new QTableWidgetItem(hash.value("label")); - labelItem->setData(Qt::UserRole, QVariant::fromValue >(hash)); - labelItem->setFlags(labelItem->flags() | Qt::ItemIsEditable); + hash["path"] = filePath; + hash["label"] = hash["path"]; + hash["label_noext"] = fileInfo.completeBaseName(); + hash["db_name"] = fileInfo.dir().dirName(); - m_tableWidget->setItem(oldRowCount + i, 0, labelItem); + m_contents.append(hash); } + + endResetModel(); } void MainWindow::setAllPlaylistsListMaxCount(int count) @@ -1382,4 +1458,3 @@ void MainWindow::setAllPlaylistsGridMaxCount(int count) m_allPlaylistsGridMaxCount = count; } - diff --git a/ui/drivers/qt/playlistthumbnaildownload.cpp b/ui/drivers/qt/playlistthumbnaildownload.cpp index e14558ea9d..3dab4cd02f 100644 --- a/ui/drivers/qt/playlistthumbnaildownload.cpp +++ b/ui/drivers/qt/playlistthumbnaildownload.cpp @@ -150,14 +150,14 @@ void MainWindow::onPlaylistThumbnailDownloadFinished() emit showErrorMessageDeferred(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR)) + ": Code " + QString::number(code) + ": " + errorData);*/ } + m_playlistModel->reloadThumbnailPath(m_playlistThumbnailDownloadFile.fileName()); + if (!m_playlistThumbnailDownloadWasCanceled && m_pendingPlaylistThumbnails.count() > 0) { QHash nextThumbnail = m_pendingPlaylistThumbnails.takeAt(0); ViewType viewType = getCurrentViewType(); - if (viewType == VIEW_TYPE_ICONS) - emit gridItemChanged(reply->property("title").toString()); - + updateVisibleItems(); downloadNextPlaylistThumbnail(nextThumbnail.value("db_name"), nextThumbnail.value("label_noext"), nextThumbnail.value("type")); } else @@ -236,7 +236,7 @@ void MainWindow::downloadNextPlaylistThumbnail(QString system, QString title, QS if (!m_playlistThumbnailDownloadFile.open(QIODevice::WriteOnly)) { m_failedThumbnails++; - + RARCH_ERR("[Qt]: Could not open file for writing: %s\n", fileNameData); if (m_pendingPlaylistThumbnails.count() > 0) @@ -290,9 +290,9 @@ void MainWindow::downloadPlaylistThumbnails(QString playlistPath) QString system; QString title; QString type; - QVector > playlistItems = getPlaylistItems(playlistPath); settings_t *settings = config_get_ptr(); int i; + int count; if (!settings || !playlistFile.exists()) return; @@ -302,12 +302,14 @@ void MainWindow::downloadPlaylistThumbnails(QString playlistPath) m_failedThumbnails = 0; m_playlistThumbnailDownloadWasCanceled = false; - if (playlistItems.count() == 0) + count = m_playlistModel->rowCount(); + + if (count == 0) return; - for (i = 0; i < playlistItems.count(); i++) + for (i = 0; i < count; i++) { - const QHash &itemHash = playlistItems.at(i); + const QHash &itemHash = m_playlistModel->index(i, 0).data(PlaylistModel::HASH).value< QHash >(); QHash hash; QHash hash2; QHash hash3; diff --git a/ui/drivers/qt/thumbnaildownload.cpp b/ui/drivers/qt/thumbnaildownload.cpp index d83c8e186b..3e6a90ccb0 100644 --- a/ui/drivers/qt/thumbnaildownload.cpp +++ b/ui/drivers/qt/thumbnaildownload.cpp @@ -154,6 +154,8 @@ void MainWindow::onThumbnailDownloadFinished() { RARCH_LOG("[Qt]: Thumbnail download finished successfully.\n"); /* reload thumbnail image */ + m_playlistModel->reloadThumbnailPath(m_thumbnailDownloadFile.fileName()); + updateVisibleItems(); emit itemChanged(); } else diff --git a/ui/drivers/qt/thumbnailpackdownload.cpp b/ui/drivers/qt/thumbnailpackdownload.cpp index ac29d22fe0..48528ded06 100644 --- a/ui/drivers/qt/thumbnailpackdownload.cpp +++ b/ui/drivers/qt/thumbnailpackdownload.cpp @@ -191,7 +191,7 @@ void MainWindow::onThumbnailPackDownloadFinished() reply->disconnect(); reply->close(); - reply->deleteLater(); + //reply->deleteLater(); } void MainWindow::onThumbnailPackDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) @@ -309,6 +309,11 @@ void MainWindow::onThumbnailPackExtractFinished(bool success) emit showInfoMessageDeferred(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_PACK_DOWNLOADED_SUCCESSFULLY)); + QNetworkReply *reply = m_thumbnailPackDownloadReply.data(); + + m_playlistModel->reloadSystemThumbnails(reply->property("system").toString()); + reply->deleteLater(); + updateVisibleItems(); /* reload thumbnail image */ emit itemChanged(); } diff --git a/ui/drivers/qt/ui_qt_themes.h b/ui/drivers/qt/ui_qt_themes.h index 803cf8a37a..d4c3d327a9 100644 --- a/ui/drivers/qt/ui_qt_themes.h +++ b/ui/drivers/qt/ui_qt_themes.h @@ -313,7 +313,7 @@ static const QString qt_theme_dark_stylesheet = QStringLiteral(R"( padding-left:5px; padding-right:5px; } - QTableWidget { + QTableView { background-color:rgb(25,25,25); alternate-background-color:rgb(40,40,40); } @@ -422,14 +422,14 @@ static const QString qt_theme_dark_stylesheet = QStringLiteral(R"( QSizeGrip { background-color:solid; } - ThumbnailWidget#thumbnailWidget, ThumbnailLabel#thumbnailGridLabel, QLabel#thumbnailQLabel { + GridView::item { background-color:rgb(40,40,40); } - ThumbnailWidget#thumbnailWidgetSelected { - background-color:rgb(40,40,40); + GridView::item:selected { border:3px solid %1; } - QWidget#gridLayoutWidget { + GridView { background-color:rgb(25,25,25); + selection-color: white; } )"); diff --git a/ui/drivers/qt/ui_qt_window.cpp b/ui/drivers/qt/ui_qt_window.cpp index 712137f68d..e7e05bf41b 100644 --- a/ui/drivers/qt/ui_qt_window.cpp +++ b/ui/drivers/qt/ui_qt_window.cpp @@ -41,7 +41,7 @@ #include "invader_png.h" #include "ui_qt_load_core_window.h" #include "ui_qt_themes.h" -#include "flowlayout.h" +#include "gridview.h" #include "shaderparamsdialog.h" #include "coreoptionsdialog.h" #include "filedropwidget.h" @@ -139,6 +139,7 @@ static void scan_finished_handler(void *task_data, void *user_data, const char * } #endif +/* 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; @@ -152,27 +153,6 @@ static double expScale(double inputValue, double midValue, double maxValue) return returnValue; } -/* https://gist.github.com/andrey-str/0f9c7709cbf0c9c49ef9 */ -static void setElidedText(QLabel *label, QWidget *clipWidget, int padding, const QString &text) -{ - QFontMetrics metrix(label->font()); - int width = clipWidget->width() - padding; - QString clippedText = metrix.elidedText(text, Qt::ElideRight, width); - label->setText(clippedText); -} - -GridItem::GridItem() : - QObject() - ,widget(NULL) - ,label(NULL) - ,hash() - ,image() - ,pixmap() - ,imageWatcher() - ,labelText() -{ -} - TreeView::TreeView(QWidget *parent) : QTreeView(parent) { @@ -192,26 +172,16 @@ void TreeView::selectionChanged(const QItemSelection &selected, const QItemSelec emit itemsSelected(list); } -TableWidget::TableWidget(QWidget *parent) : - QTableWidget(parent) +TableView::TableView(QWidget *parent) : + QTableView(parent) { } -bool TableWidget::isEditorOpen() +bool TableView::isEditorOpen() { return (state() == QAbstractItemView::EditingState); } -void TableWidget::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) - emit enterPressed(); - else if (event->key() == Qt::Key_Delete) - emit deletePressed(); - - QTableWidget::keyPressEvent(event); -} - ListWidget::ListWidget(QWidget *parent) : QListWidget(parent) { @@ -277,13 +247,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) ,m_loadCoreWindow(new LoadCoreWindow(this)) ,m_timer(new QTimer(this)) + ,m_thumbnailTimer(new QTimer(this)) ,m_currentCore() ,m_currentCoreVersion() ,m_statusLabel(new QLabel(this)) ,m_dirTree(new TreeView(this)) ,m_dirModel(new QFileSystemModel(m_dirTree)) ,m_listWidget(new ListWidget(this)) - ,m_tableWidget(new TableWidget(this)) + ,m_tableView(new TableView(this)) ,m_searchWidget(new QWidget(this)) ,m_searchLineEdit(new QLineEdit(this)) ,m_searchDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_EDIT_SEARCH), this)) @@ -311,18 +282,15 @@ MainWindow::MainWindow(QWidget *parent) : ,m_logDock(new QDockWidget(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG), this)) ,m_logWidget(new QWidget(this)) ,m_logTextEdit(new LogTextEdit(m_logWidget)) - ,m_imageFormats() ,m_historyPlaylistsItem(NULL) ,m_folderIcon() ,m_customThemeString() - ,m_gridLayout(NULL) + ,m_gridView(new GridView(this)) ,m_gridWidget(new QWidget(this)) ,m_gridScrollArea(new QScrollArea(m_gridWidget)) - ,m_gridItems() ,m_gridLayoutWidget(new FileDropWidget()) ,m_zoomSlider(NULL) ,m_lastZoomSliderValue(0) - ,m_pendingItemUpdates() ,m_viewType(VIEW_TYPE_LIST) ,m_gridProgressBar(NULL) ,m_gridProgressWidget(NULL) @@ -370,6 +338,8 @@ MainWindow::MainWindow(QWidget *parent) : QLabel *gridProgressLabel = NULL; QHBoxLayout *gridFooterLayout = NULL; + //QApplication::setStyle(QStyleFactory::create("windowsvista")); + qRegisterMetaType >("ThumbnailWidget"); qRegisterMetaType("retro_task_callback_t"); @@ -409,15 +379,10 @@ MainWindow::MainWindow(QWidget *parent) : m_gridWidget->setLayout(new QVBoxLayout()); - m_gridLayout = new FlowLayout(m_gridLayoutWidget); - m_gridLayoutWidget->setObjectName("gridLayoutWidget"); + m_gridView->setSelectionMode(QAbstractItemView::SingleSelection); + m_gridView->setEditTriggers(QAbstractItemView::NoEditTriggers); - 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()->addWidget(m_gridView); m_gridWidget->layout()->setAlignment(Qt::AlignCenter); m_gridWidget->layout()->setContentsMargins(0, 0, 0, 0); @@ -444,7 +409,16 @@ MainWindow::MainWindow(QWidget *parent) : m_gridProgressWidget->hide(); - m_tableWidget->setAlternatingRowColors(true); + m_playlistModel = new PlaylistModel(this); + m_proxyModel = new QSortFilterProxyModel(this); + m_proxyModel->setSourceModel(m_playlistModel); + m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + + m_tableView->setAlternatingRowColors(true); + m_tableView->setModel(m_proxyModel); + + m_gridView->setItemDelegate(new ThumbnailDelegate(this)); + m_gridView->setModel(m_proxyModel); m_logWidget->setObjectName("logWidget"); @@ -557,10 +531,6 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_loadCoreWindow, SIGNAL(coreLoaded()), this, SLOT(onCoreLoaded())); connect(m_loadCoreWindow, SIGNAL(windowClosed()), this, SLOT(onCoreLoadWindowClosed())); connect(m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(onCurrentListItemChanged(QListWidgetItem*, QListWidgetItem*))); - connect(m_tableWidget, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)), this, SLOT(onCurrentTableItemChanged(QTableWidgetItem*, QTableWidgetItem*))); - connect(m_tableWidget, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this, SLOT(onContentItemDoubleClicked(QTableWidgetItem*))); - connect(m_tableWidget, SIGNAL(enterPressed()), this, SLOT(onTableWidgetEnterPressed())); - connect(m_tableWidget, SIGNAL(deletePressed()), this, SLOT(onTableWidgetDeletePressed())); connect(m_startCorePushButton, SIGNAL(clicked()), this, SLOT(onStartCoreClicked())); connect(m_coreInfoPushButton, SIGNAL(clicked()), m_coreInfoDialog, SLOT(showCoreInfo())); connect(m_runPushButton, SIGNAL(clicked()), this, SLOT(onRunClicked())); @@ -590,9 +560,25 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_thumbnailPackDownloadProgressDialog, SIGNAL(canceled()), this, SLOT(onThumbnailPackDownloadCanceled())); connect(this, SIGNAL(itemChanged()), this, SLOT(onItemChanged())); - connect(this, SIGNAL(gridItemChanged(QString)), this, SLOT(onGridItemChanged(QString))); connect(this, SIGNAL(gotThumbnailDownload(QString,QString)), this, SLOT(onDownloadThumbnail(QString,QString))); + m_thumbnailTimer->setSingleShot(true); + connect(m_thumbnailTimer, SIGNAL(timeout()), this, SLOT(updateVisibleItems())); + connect(this, SIGNAL(updateThumbnails()), this, SLOT(updateVisibleItems())); + + connect(m_gridView, SIGNAL(visibleItemsChangedMaybe()), this, SLOT(startTimer())); + + connect(m_gridView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(currentItemChanged(const QModelIndex&))); + connect(m_tableView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(currentItemChanged(const QModelIndex&))); + + connect(m_gridView->selectionModel(), SIGNAL(currentChanged(const QModelIndex& , const QModelIndex&)), this, SLOT(currentItemChanged(const QModelIndex&))); + connect(m_tableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(currentItemChanged(const QModelIndex&))); + + connect(m_gridView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&))); + connect(m_tableView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onContentItemDoubleClicked(const QModelIndex&))); + + connect(m_playlistModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)), this, SLOT(onCurrentTableItemDataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); + /* 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); connect(this, SIGNAL(gotStatusMessage(QString,unsigned,unsigned,bool)), this, SLOT(onGotStatusMessage(QString,unsigned,unsigned,bool)), Qt::AutoConnection); @@ -651,8 +637,37 @@ MainWindow::~MainWindow() delete m_thumbnailPixmap2; if (m_thumbnailPixmap3) delete m_thumbnailPixmap3; +} - removeGridItems(); +void MainWindow::startTimer() { + if (m_thumbnailTimer->isActive()) + { + m_thumbnailTimer->stop(); + m_thumbnailTimer->start(50); + } + else + { + m_thumbnailTimer->start(50); + } +} + +void MainWindow::updateVisibleItems() { + if (m_viewType == VIEW_TYPE_ICONS) + { + QVector indexes = m_gridView->visibleIndexes(); + for (int i = 0; i < indexes.size(); i++) + { + m_playlistModel->loadThumbnail(m_proxyModel->mapToSource(indexes.at(i))); + } + } +} + +void MainWindow::setThumbnailCacheLimit(int count) +{ + if (count < 1) + count = 0; + + m_playlistModel->setThumbnailCacheLimit(count); } void MainWindow::onFileSystemDirLoaded(const QString &path) @@ -695,44 +710,11 @@ QVector > MainWindow::getPlaylists() return playlists; } -void MainWindow::onGridItemChanged(QString title) -{ - int i; - - for (i = 0; i < m_gridItems.count(); i++) - { - const QPointer &item = m_gridItems.at(i); - const QHash &hash = item->hash; - - if (hash.value("label_noext") == title) - { - loadImageDeferred(item.data(), item->widget->property("image_path").toString()); - break; - } - } -} - void MainWindow::onItemChanged() { - ViewType viewType = getCurrentViewType(); - - currentItemChanged(getCurrentContentHash()); - - if (viewType == VIEW_TYPE_ICONS) - { - int i; - - for (i = 0; i < m_gridItems.count(); i++) - { - const QPointer &item = m_gridItems.at(i); - - if (item->widget == m_currentGridWidget) - { - loadImageDeferred(item.data(), m_currentGridWidget->property("image_path").toString()); - break; - } - } - } + QModelIndex index = getCurrentContentIndex(); + m_playlistModel->reloadThumbnail(index); + currentItemChanged(index); } QString MainWindow::getSpecialPlaylistPath(SpecialPlaylist playlist) @@ -750,50 +732,6 @@ double MainWindow::lerp(double x, double y, double a, double b, double d) { return a + (b - a) * ((double)(d - x) / (double)(y - x)); } -void MainWindow::onGridItemClicked(ThumbnailWidget *widget) -{ - QHash hash; - ThumbnailWidget *w = static_cast(sender()); - - if (!w) - { - if (widget) - w = widget; - else - return; - } - - if (m_currentGridWidget) - { - m_currentGridWidget->setObjectName("thumbnailWidget"); - m_currentGridWidget->style()->unpolish(m_currentGridWidget); - m_currentGridWidget->style()->polish(m_currentGridWidget); - } - - hash = w->property("hash").value >(); - w->setObjectName("thumbnailWidgetSelected"); - w->style()->unpolish(w); - w->style()->polish(w); - - m_currentGridWidget = w; - 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); @@ -806,35 +744,18 @@ void MainWindow::onListViewClicked() onCurrentListItemChanged(m_listWidget->currentItem(), NULL); } -void MainWindow::calcGridItemSize(GridItem *item, int zoomValue) +void MainWindow::onZoomValueChanged(int zoomValue) { int newSize = 0; - QLabel *label = NULL; 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)); + m_gridView->setGridSize(newSize); - label = item->widget->findChild("thumbnailQLabel"); - - if (label) - setElidedText(label, item->widget, item->widget->layout()->contentsMargins().left() + item->widget->layout()->spacing() + 2, item->labelText); -} - -void MainWindow::onZoomValueChanged(int value) -{ - int i; - - for (i = 0; i < m_gridItems.count(); i++) - { - GridItem *item = m_gridItems.at(i); - calcGridItemSize(item, value); - } - - m_lastZoomSliderValue = value; + m_lastZoomSliderValue = zoomValue; } void MainWindow::showWelcomeScreen() @@ -1237,6 +1158,13 @@ void MainWindow::setTheme(Theme theme) } } +void MainWindow::changeThumbnailType(ThumbnailType type) +{ + m_playlistModel->setThumbnailType(type); + updateVisibleItems(); + m_gridView->viewport()->update(); +} + QVector > MainWindow::getCoreInfo() { QVector > infoList; @@ -1545,66 +1473,7 @@ void MainWindow::onTreeViewItemsSelected(QModelIndexList selectedIndexes) void MainWindow::selectBrowserDir(QString path) { - QStringList horizontal_header_labels; - QDir dir = path; - QStringList dirList; - int i = 0; - - horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME); - - /* block this signal because setData() called in addPlaylistHashToTable() would trigger an infinite loop */ - disconnect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); - - m_tableWidget->clear(); - m_tableWidget->setColumnCount(0); - m_tableWidget->setRowCount(0); - m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); - m_tableWidget->setSortingEnabled(false); - m_tableWidget->setColumnCount(1); - m_tableWidget->setRowCount(0); - m_tableWidget->setHorizontalHeaderLabels(horizontal_header_labels); - m_tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_tableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - - dirList = dir.entryList(QDir::NoDotAndDotDot | - QDir::Readable | - QDir::Files | - (m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast(0)), - QDir::Name); - - if (dirList.count() == 0) - return; - - m_tableWidget->setRowCount(dirList.count()); - - for (i = 0; i < dirList.count(); i++) - { - QString fileName = dirList.at(i); - QTableWidgetItem *item = new QTableWidgetItem(fileName); - QHash hash; - QString filePath(QDir::toNativeSeparators(dir.absoluteFilePath(fileName))); - QFileInfo fileInfo(filePath); - - hash["path"] = filePath; - hash["label"] = hash["path"]; - hash["label_noext"] = fileInfo.completeBaseName(); - hash["db_name"] = fileInfo.dir().dirName(); - - item->setData(Qt::UserRole, QVariant::fromValue >(hash)); - item->setFlags(item->flags() & ~Qt::ItemIsEditable); - - m_tableWidget->setItem(i, 0, item); - } - - m_tableWidget->setSortingEnabled(true); - m_tableWidget->resizeColumnsToContents(); - m_tableWidget->sortByColumn(0, Qt::AscendingOrder); - m_tableWidget->selectRow(0); - - onSearchEnterPressed(); - - connect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); + m_playlistModel->addDir(path, m_settings->value("show_hidden_files", true).toBool() ? (QDir::Hidden | QDir::System) : static_cast(0)); } QTabWidget* MainWindow::browserAndPlaylistTabWidget() @@ -1612,41 +1481,35 @@ QTabWidget* MainWindow::browserAndPlaylistTabWidget() return m_browserAndPlaylistTabWidget; } -void MainWindow::onTableWidgetEnterPressed() +void MainWindow::onDropWidgetEnterPressed() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) /* entry is being renamed, ignore this enter press */ - if (m_tableWidget->isPersistentEditorOpen(m_tableWidget->currentIndex())) + if (m_tableView->isPersistentEditorOpen(m_tableView->currentIndex())) #else /* we can only check if any editor at all is open */ - if (m_tableWidget->isEditorOpen()) + if (m_tableView->isEditorOpen()) #endif return; onRunClicked(); } -void MainWindow::onTableWidgetDeletePressed() +QModelIndex MainWindow::getCurrentContentIndex() { - deleteCurrentPlaylistItem(); + if (m_viewType == VIEW_TYPE_LIST) + { + return m_tableView->currentIndex(); + } + else if (m_viewType == VIEW_TYPE_ICONS) + { + return m_gridView->currentIndex(); + } + return QModelIndex(); } QHash MainWindow::getCurrentContentHash() { - QTableWidgetItem *contentItem = m_tableWidget->currentItem(); - QHash contentHash; - ViewType viewType = getCurrentViewType(); - - if (viewType == VIEW_TYPE_LIST) - { - if (!contentItem) - return contentHash; - - contentHash = contentItem->data(Qt::UserRole).value >(); - } - else if (viewType == VIEW_TYPE_ICONS) - contentHash = m_currentGridHash; - - return contentHash; + return getCurrentContentIndex().data(PlaylistModel::HASH).value >(); } void MainWindow::onContentItemDoubleClicked(QTableWidgetItem*) @@ -1654,6 +1517,12 @@ void MainWindow::onContentItemDoubleClicked(QTableWidgetItem*) onRunClicked(); } +void MainWindow::onContentItemDoubleClicked(const QModelIndex &index) +{ + Q_UNUSED(index); + onRunClicked(); +} + void MainWindow::onStartCoreClicked() { content_ctx_info_t content_info; @@ -1677,13 +1546,12 @@ QHash MainWindow::getSelectedCore() CoreSelection coreSelection = static_cast(coreMap.value("core_selection").toInt()); QHash coreHash; QHash contentHash; - QTableWidgetItem *contentItem = m_tableWidget->currentItem(); ViewType viewType = getCurrentViewType(); - if (viewType == VIEW_TYPE_LIST && contentItem) - contentHash = contentItem->data(Qt::UserRole).value >(); + if (viewType == VIEW_TYPE_LIST) + contentHash = m_tableView->currentIndex().data(PlaylistModel::HASH).value >(); else if (viewType == VIEW_TYPE_ICONS) - contentHash = m_currentGridHash; + contentHash = m_gridView->currentIndex().data(PlaylistModel::HASH).value >(); else return coreHash; @@ -1880,25 +1748,9 @@ void MainWindow::loadContent(const QHash &contentHash) void MainWindow::onRunClicked() { #ifdef HAVE_MENU - QTableWidgetItem *item = m_tableWidget->currentItem(); - ViewType viewType = getCurrentViewType(); - QHash contentHash; + QHash contentHash = getCurrentContentHash(); - if (viewType == VIEW_TYPE_LIST) - { - if (!item) - return; - - contentHash = item->data(Qt::UserRole).value >(); - } - else if (viewType == VIEW_TYPE_ICONS) - { - contentHash = m_currentGridHash; - - if (contentHash.isEmpty()) - return; - } - else + if (contentHash.isEmpty()) return; loadContent(contentHash); @@ -1932,10 +1784,9 @@ ViewOptionsDialog* MainWindow::viewOptionsDialog() void MainWindow::setCoreActions() { - QTableWidgetItem *currentContentItem = m_tableWidget->currentItem(); QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); ViewType viewType = getCurrentViewType(); - QHash hash; + QHash hash = getCurrentContentHash(); m_launchWithComboBox->clear(); @@ -1953,11 +1804,6 @@ void MainWindow::setCoreActions() m_launchWithComboBox->addItem(m_currentCore, QVariant::fromValue(comboBoxMap)); } - 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)) { if (!hash.isEmpty()) @@ -2113,10 +1959,6 @@ void MainWindow::onTabWidgetIndexChanged(int index) /* 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); - if (index.isValid()) { m_dirTree->clearSelection(); @@ -2130,10 +1972,6 @@ void MainWindow::onTabWidgetIndexChanged(int index) if (m_lastViewType != getCurrentViewType()) setCurrentViewType(m_lastViewType); - m_tableWidget->clear(); - m_tableWidget->setColumnCount(0); - m_tableWidget->setRowCount(0); - if (item) { m_listWidget->setCurrentItem(NULL); @@ -2167,8 +2005,6 @@ QComboBox* MainWindow::launchWithComboBox() void MainWindow::onSearchLineEditEdited(const QString &text) { int i = 0; - QList items; - QVector > gridItems; QVector textUnicode = text.toUcs4(); QVector textHiraToKata; QVector textKataToHira; @@ -2197,112 +2033,21 @@ void MainWindow::onSearchLineEditEdited(const QString &text) } } - switch(viewType) + if (!foundHira && !foundKata) { - case VIEW_TYPE_LIST: - { - if (text.isEmpty()) - { - for (i = 0; i < m_tableWidget->rowCount(); i++) - { - m_tableWidget->setRowHidden(i, false); - } - return; - } - - items.append(m_tableWidget->findItems(text, Qt::MatchContains)); - - if (foundHira) - { - items.append(m_tableWidget->findItems(QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()), Qt::MatchContains)); - } - - if (foundKata) - { - items.append(m_tableWidget->findItems(QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::MatchContains)); - } - - if (items.isEmpty()) - { - for (i = 0; i < m_tableWidget->rowCount(); i++) - { - m_tableWidget->setRowHidden(i, true); - } - - return; - } - else - { - for (i = 0; i < m_tableWidget->rowCount(); i++) - { - if (items.contains(m_tableWidget->item(i, 0))) - m_tableWidget->setRowHidden(i, false); - else - m_tableWidget->setRowHidden(i, true); - } - } - - break; - } - case VIEW_TYPE_ICONS: - { - int i; - - if (text.isEmpty()) - { - for (i = 0; i < m_gridItems.size(); i++) - { - m_gridItems.at(i)->widget->show(); - } - return; - } - - for (i = 0; i < m_gridItems.count(); i++) - { - const QPointer &item = m_gridItems.at(i); - - if (item->hash.value("label").contains(text, Qt::CaseInsensitive)) - gridItems.append(item); - - if (foundHira) - { - if (item->hash.value("label").contains(QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()), Qt::CaseInsensitive)) - gridItems.append(item); - } - - if (foundKata) - { - if (item->hash.value("label").contains(QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::CaseInsensitive)) - gridItems.append(item); - } - } - - if (gridItems.isEmpty()) - { - for (i = 0; i < m_gridItems.size(); i++) - { - m_gridItems.at(i)->widget->hide(); - } - - return; - } - else - { - for (i = 0; i < m_gridItems.size(); i++) - { - const QPointer &item = m_gridItems.at(i); - - if (gridItems.contains(item)) - item->widget->show(); - else - item->widget->hide(); - } - } - - break; - } - default: - break; + m_proxyModel->setFilterRegExp(QRegExp(text, Qt::CaseInsensitive)); + } + else if (foundHira && !foundKata) + { + m_proxyModel->setFilterRegExp(QRegExp(text + "|" + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()), Qt::CaseInsensitive)); + } + else if (!foundHira && foundKata) + { + m_proxyModel->setFilterRegExp(QRegExp(text + "|" + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::CaseInsensitive)); + } + else + { + m_proxyModel->setFilterRegExp(QRegExp(text + "|" + QString::fromUcs4(textHiraToKata.constData(), textHiraToKata.size()) + "|" + QString::fromUcs4(textKataToHira.constData(), textKataToHira.size()), Qt::CaseInsensitive)); } } @@ -2378,39 +2123,19 @@ void MainWindow::onSearchEnterPressed() onSearchLineEditEdited(m_searchLineEdit->text()); } -void MainWindow::onCurrentTableItemChanged(QTableWidgetItem *current, QTableWidgetItem *) +void MainWindow::onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - QHash hash; - - if (!current) + if (!roles.contains(Qt::EditRole)) return; - hash = current->data(Qt::UserRole).value >(); - - currentItemChanged(hash); -} - -void MainWindow::onCurrentTableItemDataChanged(QTableWidgetItem *item) -{ - QHash hash; - - if (!item) + if (topLeft != bottomRight) return; - /* block this signal because setData() would trigger an infinite loop here */ - disconnect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); - - hash = item->data(Qt::UserRole).value >(); - hash["label"] = item->text(); - hash["label_noext"] = QFileInfo(item->text()).completeBaseName(); - - item->setData(Qt::UserRole, QVariant::fromValue(hash)); + QHash hash = topLeft.data(PlaylistModel::HASH).value>(); updateCurrentPlaylistEntry(hash); - currentItemChanged(hash); - - connect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); + currentItemChanged(topLeft); } void MainWindow::onCurrentListItemDataChanged(QListWidgetItem *item) @@ -2485,7 +2210,7 @@ void MainWindow::renamePlaylistItem(QListWidgetItem *item, QString newName) connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); } -void MainWindow::currentItemChanged(const QHash &hash) +void MainWindow::currentItemChanged(const QModelIndex &index) { settings_t *settings = config_get_ptr(); QString label; @@ -2494,6 +2219,8 @@ void MainWindow::currentItemChanged(const QHash &hash) QString extensionStr; int lastIndex = -1; + const QHash &hash = index.data(PlaylistModel::HASH).value>(); + label = hash["label_noext"]; label.replace(m_fileSanitizerRegex, "_"); @@ -2618,7 +2345,7 @@ void MainWindow::setCurrentViewType(ViewType viewType) { case VIEW_TYPE_ICONS: { - m_tableWidget->hide(); + m_tableView->hide(); m_gridWidget->show(); break; } @@ -2627,48 +2354,48 @@ void MainWindow::setCurrentViewType(ViewType viewType) { m_viewType = VIEW_TYPE_LIST; m_gridWidget->hide(); - m_tableWidget->show(); + m_tableView->show(); break; } } } +void MainWindow::setCurrentThumbnailType(ThumbnailType thumbnailType) +{ + m_lastThumbnailType = m_thumbnailType; + m_thumbnailType = thumbnailType; + + m_playlistModel->setThumbnailType(thumbnailType); + updateVisibleItems(); + m_gridView->viewport()->update(); +} + MainWindow::ViewType MainWindow::getCurrentViewType() { return m_viewType; } +ThumbnailType MainWindow::getCurrentThumbnailType() +{ + return m_thumbnailType; +} + 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; - switch (viewType) - { - case VIEW_TYPE_ICONS: - { - initContentGridLayout(); - break; - } - case VIEW_TYPE_LIST: - default: - { - initContentTableWidget(); - break; - } - } + initContentTableWidget(); setCoreActions(); } -TableWidget* MainWindow::contentTableWidget() +TableView* MainWindow::contentTableView() { - return m_tableWidget; + return m_tableView; } QWidget* MainWindow::contentGridWidget() @@ -2676,9 +2403,9 @@ QWidget* MainWindow::contentGridWidget() return m_gridWidget; } -FlowLayout* MainWindow::contentGridLayout() +GridView* MainWindow::contentGridView() { - return m_gridLayout; + return m_gridView; } void MainWindow::onBrowserDownloadsClicked() @@ -2883,179 +2610,6 @@ 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::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(); - - m_currentGridHash.clear(); - - if (m_currentGridWidget) - { - m_currentGridWidget->setObjectName("thumbnailWidget"); - m_currentGridWidget->style()->unpolish(m_currentGridWidget); - m_currentGridWidget->style()->polish(m_currentGridWidget); - } - - m_currentGridWidget = NULL; - - path = item->data(Qt::UserRole).toString(); - - if (path == ALL_PLAYLISTS_TOKEN) - { - settings_t *settings = config_get_ptr(); - QDir playlistDir(settings->paths.directory_playlist); - QStringList playlists; - int i = 0; - - for (i = 0; i < m_playlistFiles.count(); i++) - { - const QString &playlist = m_playlistFiles.at(i); - playlists.append(playlistDir.absoluteFilePath(playlist)); - } - - addPlaylistItemsToGrid(playlists, true); - } - else - addPlaylistItemsToGrid(QStringList() << path); - - QTimer::singleShot(0, this, SLOT(onContentGridInited())); -} - -void MainWindow::onContentGridInited() -{ - ThumbnailWidget *thumbnailWidget = NULL; - - m_gridLayoutWidget->resize(m_gridScrollArea->viewport()->size()); - - onZoomValueChanged(m_zoomSlider->value()); - - onSearchEnterPressed(); - - if (m_gridItems.count() > 0) - { - GridItem *gridItem = m_gridItems.at(0); - - if (gridItem) - { - thumbnailWidget = m_gridItems.at(0)->widget.data(); - - if (thumbnailWidget) - onGridItemClicked(thumbnailWidget); - } - } -} - void MainWindow::initContentTableWidget() { QListWidgetItem *item = m_listWidget->currentItem(); @@ -3077,23 +2631,12 @@ void MainWindow::initContentTableWidget() m_currentGridWidget = NULL; - horizontal_header_labels << msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NAME); - /* block this signal because setData() called in addPlaylistHashToTable() would trigger an infinite loop */ - disconnect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); - - m_tableWidget->clear(); - m_tableWidget->setColumnCount(0); - m_tableWidget->setRowCount(0); - m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); - m_tableWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); - m_tableWidget->setSortingEnabled(false); - m_tableWidget->setColumnCount(1); - m_tableWidget->setRowCount(0); - m_tableWidget->setHorizontalHeaderLabels(horizontal_header_labels); - m_tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_tableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); + m_tableView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); + m_tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); path = item->data(Qt::UserRole).toString(); @@ -3110,31 +2653,24 @@ void MainWindow::initContentTableWidget() playlists.append(playlistDir.absoluteFilePath(playlist)); } - addPlaylistItemsToTable(playlists, true); + m_playlistModel->addPlaylistItems(playlists, true); } else - addPlaylistItemsToTable(QStringList() << path); + m_playlistModel->addPlaylistItems(QStringList() << path); - m_tableWidget->setSortingEnabled(true); + //TODO delete? + //m_tableView->setSortingEnabled(true); if (item != m_historyPlaylistsItem) - m_tableWidget->sortByColumn(0, Qt::AscendingOrder); + m_tableView->sortByColumn(0, Qt::AscendingOrder); - m_tableWidget->resizeColumnsToContents(); + m_tableView->resizeColumnsToContents(); - for (i = 0; i < m_tableWidget->rowCount(); i++) - { - /* select the first non-hidden row */ - if (!m_tableWidget->isRowHidden(i)) - { - m_tableWidget->selectRow(i); - break; - } - } - - onSearchEnterPressed(); - - connect(m_tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onCurrentTableItemDataChanged(QTableWidgetItem*))); + QModelIndex index = m_proxyModel->index(0, 0); + m_gridView->scrollToTop(); + m_gridView->setCurrentIndex(index); + m_tableView->setCurrentIndex(index); + currentItemChanged(index); } void MainWindow::keyPressEvent(QKeyEvent *event) @@ -3174,6 +2710,40 @@ QString MainWindow::getCurrentViewTypeString() return QStringLiteral("list"); } +QString MainWindow::getCurrentThumbnailTypeString() +{ + switch (m_thumbnailType) + { + case THUMBNAIL_TYPE_SCREENSHOT: + { + return QStringLiteral("screenshot"); + } + case THUMBNAIL_TYPE_TITLE_SCREEN: + { + return QStringLiteral("title"); + } + case THUMBNAIL_TYPE_BOXART: + default: + { + return QStringLiteral("boxart"); + } + } + + return QStringLiteral("list"); +} + +ThumbnailType MainWindow::getThumbnailTypeFromString(QString thumbnailType) +{ + if (thumbnailType == "boxart") + return THUMBNAIL_TYPE_BOXART; + else if (thumbnailType == "screenshot") + return THUMBNAIL_TYPE_SCREENSHOT; + else if (thumbnailType == "title") + return THUMBNAIL_TYPE_TITLE_SCREEN; + + return THUMBNAIL_TYPE_BOXART; +} + void MainWindow::closeEvent(QCloseEvent *event) { if (m_settings->value("save_geometry", false).toBool()) diff --git a/ui/drivers/qt/viewoptionsdialog.cpp b/ui/drivers/qt/viewoptionsdialog.cpp index 5d9bce0466..dcfa80782d 100644 --- a/ui/drivers/qt/viewoptionsdialog.cpp +++ b/ui/drivers/qt/viewoptionsdialog.cpp @@ -19,7 +19,7 @@ extern "C" { } ViewOptionsDialog::ViewOptionsDialog(MainWindow *mainwindow, QWidget *parent) : - QDialog(parent) + QDialog(mainwindow) ,m_mainwindow(mainwindow) ,m_settings(mainwindow->settings()) ,m_saveGeometryCheckBox(new QCheckBox(this)) @@ -27,6 +27,8 @@ ViewOptionsDialog::ViewOptionsDialog(MainWindow *mainwindow, QWidget *parent) : ,m_saveLastTabCheckBox(new QCheckBox(this)) ,m_showHiddenFilesCheckBox(new QCheckBox(this)) ,m_themeComboBox(new QComboBox(this)) + ,m_thumbnailComboBox(new QComboBox(this)) + ,m_thumbnailCacheSpinBox(new QSpinBox(this)) ,m_startupPlaylistComboBox(new QComboBox(this)) ,m_highlightColorPushButton(new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CHOOSE), this)) ,m_highlightColor() @@ -45,6 +47,13 @@ ViewOptionsDialog::ViewOptionsDialog(MainWindow *mainwindow, QWidget *parent) : m_themeComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THEME_DARK), MainWindow::THEME_DARK); m_themeComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THEME_CUSTOM), MainWindow::THEME_CUSTOM); + m_thumbnailComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART), THUMBNAIL_TYPE_BOXART); + m_thumbnailComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT), THUMBNAIL_TYPE_SCREENSHOT); + m_thumbnailComboBox->addItem(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN), THUMBNAIL_TYPE_TITLE_SCREEN); + + m_thumbnailCacheSpinBox->setSuffix(" MB"); + m_thumbnailCacheSpinBox->setRange(0, 99999); + m_allPlaylistsListMaxCountSpinBox->setRange(0, 99999); m_allPlaylistsGridMaxCountSpinBox->setRange(0, 99999); @@ -67,6 +76,8 @@ ViewOptionsDialog::ViewOptionsDialog(MainWindow *mainwindow, QWidget *parent) : form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_LIST_MAX_COUNT), m_allPlaylistsListMaxCountSpinBox); form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_GRID_MAX_COUNT), m_allPlaylistsGridMaxCountSpinBox); form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_STARTUP_PLAYLIST), m_startupPlaylistComboBox); + form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE), m_thumbnailComboBox); + form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_CACHE_LIMIT), m_thumbnailCacheSpinBox); form->addRow(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THEME), m_themeComboBox); form->addRow(m_highlightColorLabel, m_highlightColorPushButton); @@ -77,9 +88,16 @@ ViewOptionsDialog::ViewOptionsDialog(MainWindow *mainwindow, QWidget *parent) : loadViewOptions(); connect(m_themeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onThemeComboBoxIndexChanged(int))); + connect(m_thumbnailComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onThumbnailComboBoxIndexChanged(int))); connect(m_highlightColorPushButton, SIGNAL(clicked()), this, SLOT(onHighlightColorChoose())); } +void ViewOptionsDialog::onThumbnailComboBoxIndexChanged(int index) +{ + ThumbnailType type = static_cast(m_thumbnailComboBox->currentData().value()); + m_mainwindow->setCurrentThumbnailType(type); +} + void ViewOptionsDialog::onThemeComboBoxIndexChanged(int) { MainWindow::Theme theme = static_cast(m_themeComboBox->currentData(Qt::UserRole).toInt()); @@ -138,6 +156,7 @@ void ViewOptionsDialog::loadViewOptions() QVector > playlists = m_mainwindow->getPlaylists(); QString initialPlaylist = m_settings->value("initial_playlist", m_mainwindow->getSpecialPlaylistPath(SPECIAL_PLAYLIST_HISTORY)).toString(); int themeIndex = 0; + int thumbnailIndex = 0; int playlistIndex = 0; int i; @@ -148,12 +167,18 @@ void ViewOptionsDialog::loadViewOptions() m_suggestLoadedCoreFirstCheckBox->setChecked(m_settings->value("suggest_loaded_core_first", false).toBool()); m_allPlaylistsListMaxCountSpinBox->setValue(m_settings->value("all_playlists_list_max_count", 0).toInt()); m_allPlaylistsGridMaxCountSpinBox->setValue(m_settings->value("all_playlists_grid_max_count", 5000).toInt()); + m_thumbnailCacheSpinBox->setValue(m_settings->value("thumbnail_cache_limit", 512).toInt()); themeIndex = m_themeComboBox->findData(m_mainwindow->getThemeFromString(m_settings->value("theme", "default").toString())); if (m_themeComboBox->count() > themeIndex) m_themeComboBox->setCurrentIndex(themeIndex); + thumbnailIndex = m_thumbnailComboBox->findData(m_mainwindow->getThumbnailTypeFromString(m_settings->value("icon_view_thumbnail_type", "boxart").toString())); + + if (m_thumbnailComboBox->count() > thumbnailIndex) + m_thumbnailComboBox->setCurrentIndex(thumbnailIndex); + if (highlightColor.isValid()) { m_highlightColor = highlightColor; @@ -204,12 +229,15 @@ void ViewOptionsDialog::saveViewOptions() m_settings->setValue("all_playlists_list_max_count", m_allPlaylistsListMaxCountSpinBox->value()); m_settings->setValue("all_playlists_grid_max_count", m_allPlaylistsGridMaxCountSpinBox->value()); m_settings->setValue("initial_playlist", m_startupPlaylistComboBox->currentData(Qt::UserRole).toString()); + m_settings->setValue("icon_view_thumbnail_type", m_mainwindow->getCurrentThumbnailTypeString()); + m_settings->setValue("thumbnail_cache_limit", m_thumbnailCacheSpinBox->value()); if (!m_mainwindow->customThemeString().isEmpty()) m_settings->setValue("custom_theme", m_customThemePath); m_mainwindow->setAllPlaylistsListMaxCount(m_allPlaylistsListMaxCountSpinBox->value()); m_mainwindow->setAllPlaylistsGridMaxCount(m_allPlaylistsGridMaxCountSpinBox->value()); + m_mainwindow->setThumbnailCacheLimit(m_thumbnailCacheSpinBox->value()); } void ViewOptionsDialog::onAccepted() @@ -229,11 +257,12 @@ void ViewOptionsDialog::onRejected() void ViewOptionsDialog::showDialog() { loadViewOptions(); + setWindowFlags(windowFlags() | Qt::Tool); show(); + activateWindow(); } void ViewOptionsDialog::hideDialog() { reject(); } - diff --git a/ui/drivers/qt/viewoptionsdialog.h b/ui/drivers/qt/viewoptionsdialog.h index faf0f143df..4e9ff4c76a 100644 --- a/ui/drivers/qt/viewoptionsdialog.h +++ b/ui/drivers/qt/viewoptionsdialog.h @@ -24,6 +24,7 @@ public slots: void onRejected(); private slots: void onThemeComboBoxIndexChanged(int index); + void onThumbnailComboBoxIndexChanged(int index); void onHighlightColorChoose(); private: void loadViewOptions(); @@ -37,6 +38,8 @@ private: QCheckBox *m_saveLastTabCheckBox; QCheckBox *m_showHiddenFilesCheckBox; QComboBox *m_themeComboBox; + QComboBox *m_thumbnailComboBox; + QSpinBox *m_thumbnailCacheSpinBox; QComboBox *m_startupPlaylistComboBox; QPushButton *m_highlightColorPushButton; QColor m_highlightColor; diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 1c494f1e99..306143527a 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -293,11 +293,12 @@ static void* ui_companion_qt_init(void) widget->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(widget, SIGNAL(filesDropped(QStringList)), mainwindow, SLOT(onPlaylistFilesDropped(QStringList))); + QObject::connect(widget, SIGNAL(enterPressed()), mainwindow, SLOT(onDropWidgetEnterPressed())); QObject::connect(widget, SIGNAL(deletePressed()), mainwindow, SLOT(deleteCurrentPlaylistItem())); QObject::connect(widget, SIGNAL(customContextMenuRequested(const QPoint&)), mainwindow, SLOT(onFileDropWidgetContextMenuRequested(const QPoint&))); layout = new QVBoxLayout(); - layout->addWidget(mainwindow->contentTableWidget()); + layout->addWidget(mainwindow->contentTableView()); layout->addWidget(mainwindow->contentGridWidget()); widget->setLayout(layout); @@ -513,6 +514,11 @@ static void* ui_companion_qt_init(void) if (qsettings->contains("all_playlists_grid_max_count")) mainwindow->setAllPlaylistsGridMaxCount(qsettings->value("all_playlists_grid_max_count", 5000).toInt()); + if (qsettings->contains("thumbnail_cache_limit")) + mainwindow->setThumbnailCacheLimit(qsettings->value("thumbnail_cache_limit", 500).toInt()); + else + mainwindow->setThumbnailCacheLimit(500); + if (qsettings->contains("geometry")) if (qsettings->contains("save_geometry")) mainwindow->restoreGeometry(qsettings->value("geometry").toByteArray()); @@ -555,6 +561,25 @@ static void* ui_companion_qt_init(void) else mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); + if (qsettings->contains("icon_view_thumbnail_type")) + { + QString thumbnailType = qsettings->value("icon_view_thumbnail_type", "boxart").toString(); + + if (thumbnailType == "boxart") + mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); + else if (thumbnailType == "screenshot") + mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT); + else if (thumbnailType == "title") + mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN); + else + mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); + + /* we set it to the same thing a second time so that m_lastThumbnailType is also equal to the startup view type */ + mainwindow->setCurrentThumbnailType(mainwindow->getCurrentThumbnailType()); + } + 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. */ diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 7c050d075b..70eb1810e1 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -38,6 +38,10 @@ #include #include #include +#include +#include +#include +#include extern "C" { #include @@ -84,7 +88,7 @@ class LoadCoreWindow; class MainWindow; class ThumbnailWidget; class ThumbnailLabel; -class FlowLayout; +class GridView; class ShaderParamsDialog; class CoreOptionsDialog; class CoreInfoDialog; @@ -96,19 +100,58 @@ enum SpecialPlaylist SPECIAL_PLAYLIST_HISTORY }; -class GridItem : public QObject +enum ThumbnailType +{ + THUMBNAIL_TYPE_BOXART, + THUMBNAIL_TYPE_SCREENSHOT, + THUMBNAIL_TYPE_TITLE_SCREEN, +}; + +class PlaylistModel : public QAbstractListModel { Q_OBJECT -public: - GridItem(); - QPointer widget; - QPointer label; - QHash hash; - QImage image; - QPixmap pixmap; - QFutureWatcher imageWatcher; - QString labelText; +public: + enum Roles + { + HASH = Qt::UserRole + 1, + THUMBNAIL + }; + + PlaylistModel(QObject *parent = 0); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + void addPlaylistItems(const QStringList &paths, bool add = false); + void addDir(QString path, QFlags showHidden); + void setThumbnailType(const ThumbnailType type); + void loadThumbnail(const QModelIndex &index); + void reloadThumbnail(const QModelIndex &index); + void reloadThumbnailPath(const QString path); + void reloadSystemThumbnails(const QString system); + void setThumbnailCacheLimit(int limit); + +signals: + void imageLoaded(const QImage image, const QModelIndex &index, const QString &path); + +private slots: + void onImageLoaded(const QImage image, const QModelIndex &index, const QString &path); + +private: + QVector > m_contents; + QCache m_cache; + QSet m_pendingImages; + QVector m_imageFormats; + QRegularExpression m_fileSanitizerRegex; + ThumbnailType m_thumbnailType = THUMBNAIL_TYPE_BOXART; + QString getThumbnailPath(const QModelIndex &index, QString type) const; + QString getCurrentTypeThumbnailPath(const QModelIndex &index) const; + void getPlaylistItems(QString path); + void loadImage(const QModelIndex &index, const QString &path); }; class ThumbnailWidget : public QFrame @@ -164,17 +207,12 @@ protected slots: void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); }; -class TableWidget : public QTableWidget +class TableView : public QTableView { Q_OBJECT public: - TableWidget(QWidget *parent = 0); + TableView(QWidget *parent = 0); bool isEditorOpen(); -signals: - void enterPressed(); - void deletePressed(); -protected: - void keyPressEvent(QKeyEvent *event); }; class ListWidget : public QListWidget @@ -263,9 +301,10 @@ public: MainWindow(QWidget *parent = NULL); ~MainWindow(); TreeView* dirTreeView(); + PlaylistModel* playlistModel(); ListWidget* playlistListWidget(); - TableWidget* contentTableWidget(); - FlowLayout* contentGridLayout(); + TableView* contentTableView(); + GridView* contentGridView(); QWidget* contentGridWidget(); QWidget* searchWidget(); QLineEdit* searchLineEdit(); @@ -289,15 +328,20 @@ 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(); + void setCurrentThumbnailType(ThumbnailType thumbnailType); + QString getCurrentThumbnailTypeString(); + ThumbnailType getCurrentThumbnailType(); + ThumbnailType getThumbnailTypeFromString(QString thumbnailType); void setAllPlaylistsListMaxCount(int count); void setAllPlaylistsGridMaxCount(int count); + void setThumbnailCacheLimit(int count); PlaylistEntryDialog* playlistEntryDialog(); void addFilesToPlaylist(QStringList files); QString getCurrentPlaylistPath(); + QModelIndex getCurrentContentIndex(); QHash getCurrentContentHash(); static double lerp(double x, double y, double a, double b, double d); QString getSpecialPlaylistPath(SpecialPlaylist playlist); @@ -317,6 +361,7 @@ signals: void showInfoMessageDeferred(QString msg); void extractArchiveDeferred(QString path, QString extractionDir, QString tempExtension, retro_task_callback_t cb); void itemChanged(); + void updateThumbnails(); void gridItemChanged(QString title); void gotThumbnailDownload(QString system, QString title); void scrollToDownloads(QString path); @@ -327,15 +372,13 @@ 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 onTableWidgetDeletePressed(); + void onDropWidgetEnterPressed(); void selectBrowserDir(QString path); void resizeThumbnails(bool one, bool two, bool three); void onResizeThumbnailOne(); @@ -365,23 +408,20 @@ public slots: void downloadAllThumbnails(QString system, QUrl url = QUrl()); void downloadPlaylistThumbnails(QString playlistPath); void downloadNextPlaylistThumbnail(QString system, QString title, QString type, QUrl url = QUrl()); + void changeThumbnailType(ThumbnailType type); private slots: void onLoadCoreClicked(const QStringList &extensionFilters = QStringList()); void onUnloadCoreMenuAction(); void onTimeout(); void onCoreLoaded(); + void onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); void onCurrentListItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void onCurrentTableItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous); - void onCurrentTableItemDataChanged(QTableWidgetItem *item); void onCurrentListItemDataChanged(QListWidgetItem *item); - void currentItemChanged(const QHash &hash); + void currentItemChanged(const QModelIndex &index); void onSearchEnterPressed(); void onSearchLineEditEdited(const QString &text); - void addPlaylistItemsToTable(const QStringList &paths, bool all = false); - void addPlaylistHashToTable(const QVector > &items); - void addPlaylistItemsToGrid(const QStringList &paths, bool all = false); - void addPlaylistHashToGrid(const QVector > &items); + void onContentItemDoubleClicked(const QModelIndex &index); void onContentItemDoubleClicked(QTableWidgetItem *item); void onCoreLoadWindowClosed(); void onTreeViewItemsSelected(QModelIndexList selectedIndexes); @@ -390,13 +430,7 @@ private slots: 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(ThumbnailWidget *thumbnailWidget = NULL); void onPlaylistFilesDropped(QStringList files); void onShaderParamsClicked(); void onCoreOptionsClicked(); @@ -404,7 +438,6 @@ private slots: void onShowInfoMessage(QString msg); void onContributorsClicked(); void onItemChanged(); - void onGridItemChanged(QString title); void onFileSystemDirLoaded(const QString &path); void onDownloadScroll(QString path); void onDownloadScrollAgain(QString path); @@ -439,14 +472,14 @@ private slots: void onPlaylistThumbnailDownloadReadyRead(); void onPlaylistThumbnailDownloadCanceled(); + void startTimer(); + void updateVisibleItems(); + private: void setCurrentCoreLabel(); void getPlaylistFiles(); bool isCoreLoaded(); bool isContentLessCore(); - void removeGridItems(); - void loadImageDeferred(GridItem *item, QString path); - void calcGridItemSize(GridItem *item, int zoomValue); bool updateCurrentPlaylistEntry(const QHash &contentHash); int extractArchive(QString path); void removeUpdateTempFiles(); @@ -454,8 +487,9 @@ private: void renamePlaylistItem(QListWidgetItem *item, QString newName); bool currentPlaylistIsSpecial(); bool currentPlaylistIsAll(); - QVector > getPlaylistItems(QString pathString); + PlaylistModel *m_playlistModel; + QSortFilterProxyModel *m_proxyModel; LoadCoreWindow *m_loadCoreWindow; QTimer *m_timer; QString m_currentCore; @@ -464,7 +498,7 @@ private: TreeView *m_dirTree; QFileSystemModel *m_dirModel; ListWidget *m_listWidget; - TableWidget *m_tableWidget; + TableView *m_tableView; QWidget *m_searchWidget; QLineEdit *m_searchLineEdit; QDockWidget *m_searchDock; @@ -496,19 +530,19 @@ private: QListWidgetItem *m_historyPlaylistsItem; QIcon m_folderIcon; QString m_customThemeString; - FlowLayout *m_gridLayout; + GridView *m_gridView; QWidget *m_gridWidget; QScrollArea *m_gridScrollArea; - QVector > m_gridItems; QWidget *m_gridLayoutWidget; QSlider *m_zoomSlider; int m_lastZoomSliderValue; - QList m_pendingItemUpdates; ViewType m_viewType; + ThumbnailType m_thumbnailType; QProgressBar *m_gridProgressBar; QWidget *m_gridProgressWidget; QHash m_currentGridHash; ViewType m_lastViewType; + ThumbnailType m_lastThumbnailType; QPointer m_currentGridWidget; int m_allPlaylistsListMaxCount; int m_allPlaylistsGridMaxCount; @@ -540,6 +574,8 @@ private: bool m_playlistThumbnailDownloadWasCanceled; QString m_pendingDirScrollPath; + QTimer *m_thumbnailTimer; + protected: void closeEvent(QCloseEvent *event); void keyPressEvent(QKeyEvent *event);