diff --git a/Makefile.common b/Makefile.common index 16e902f259..91d297038b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -340,7 +340,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 \ @@ -355,7 +355,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 9e374066b5..6854693894 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7679,6 +7679,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" diff --git a/msg_hash.h b/msg_hash.h index bdd5a4a8b0..9680e52eda 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1988,6 +1988,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..9d53c8c506 --- /dev/null +++ b/ui/drivers/qt/gridview.cpp @@ -0,0 +1,481 @@ +#include +#include + +#include "gridview.h" +#include "../ui_qt.h" + +/* http://www.informit.com/articles/article.aspx?p=1613548 */ + +ThumbnailDelegate::ThumbnailDelegate(const GridItem &gridItem, QObject* parent) : + QStyledItemDelegate(parent), m_style(gridItem) +{ +} +void ThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const +{ + QStyleOptionViewItem opt = option; + const QWidget *widget = opt.widget; + QStyle *style = widget->style(); + int padding = m_style.padding; + int textTopMargin = 4; /* Qt seemingly reports -4 the actual line height. */ + int textHeight = painter->fontMetrics().height() + padding + padding; + QRect rect = opt.rect; + QRect adjusted = rect.adjusted(padding, padding, -padding, -textHeight + textTopMargin); + QPixmap pixmap = index.data(PlaylistModel::THUMBNAIL).value(); + + painter->save(); + + initStyleOption(&opt, index); + + /* 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 | m_style.thumbnailVerticalAlignmentFlag, pixmapScaled); + } + + /* draw the text */ + if (!opt.text.isEmpty()) + { + QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + QRect textRect = QRect(rect.x() + padding, rect.y() + adjusted.height() - textTopMargin + padding, rect.width() - 2 * padding, textHeight); + QString elidedText = painter->fontMetrics().elidedText(opt.text, opt.textElideMode, textRect.width(), Qt::TextShowMnemonic); + + 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)); + + 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; + } + } + 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; + } + } + 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_idealHeight = y + m_size + m_spacing; + 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 +{ + QRectF rect; + calculateRectsIfNecessary(); + 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(); +} + +/* TODO: Make this more efficient by changing m_rectForRow for another data structure. Look at how Qt's own views do it. */ +QModelIndex GridView::indexAt(const QPoint &point_) const +{ + QPoint point(point_); + QHash::const_iterator i; + point.rx() += horizontalScrollBar()->value(); + point.ry() += verticalScrollBar()->value(); + + calculateRectsIfNecessary(); + + i = m_rectForRow.constBegin(); + + while (i != m_rectForRow.constEnd()) + { + if (i.value().contains(point)) + return model()->index(i.key(), 0, rootIndex()); + i++; + } + 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() +{ + m_visibleIndexes.clear(); + 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()); +} + +/* TODO: Maybe add a way to get the previous/next visible indexes. */ +QVector GridView::visibleIndexes() const { + return m_visibleIndexes; +} + +void GridView::setSelection(const QRect &rect, QFlags flags) +{ + QRect rectangle; + QHash::const_iterator i; + int firstRow = model()->rowCount(); + int lastRow = -1; + + calculateRectsIfNecessary(); + + rectangle = rect.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized(); + + i = m_rectForRow.constBegin(); + while (i != m_rectForRow.constEnd()) + { + if (i.value().intersects(rectangle)) + { + firstRow = firstRow < i.key() ? firstRow : i.key(); + lastRow = lastRow > i.key() ? lastRow : i.key(); + } + i++; + } + 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; + QItemSelectionRange range; + int i; + + for (i = 0; i < selection.size(); i++) + { + range = selection.at(i); + int row; + for (row = range.top(); row <= range.bottom(); row++) + { + int column; + for (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); + QStyleOptionViewItem option = viewOptions(); + + if (!rect.isValid() || rect.bottom() < 0 || rect.y() > viewport()->height()) + continue; + + m_visibleIndexes.append(index); + 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()); +} + +QString GridView::getLayout() const +{ + switch (m_viewMode) + { + case Simple: + return "simple"; + case Anchored: + return "anchored"; + case Centered: + default: + return "centered"; + } +} + +void GridView::setLayout(QString layout) +{ + if (layout == "anchored") + m_viewMode = Anchored; + else if (layout == "centered") + m_viewMode = Centered; + else if (layout == "fixed") + m_viewMode = Simple; +} + +int GridView::getSpacing() const +{ + return m_spacing; +} + +void GridView::setSpacing(const int spacing) +{ + m_spacing = spacing; +} + +GridItem::GridItem(QWidget* parent) : QWidget(parent) +, thumbnailVerticalAlignmentFlag(Qt::AlignBottom) +, padding(11) +{ +} + +int GridItem::getPadding() const +{ + return padding; +} + +void GridItem::setPadding(const int value) +{ + padding = value; +} + +QString GridItem::getThumbnailVerticalAlign() const +{ + switch (thumbnailVerticalAlignmentFlag) + { + case Qt::AlignTop: + return "top"; + case Qt::AlignVCenter: + return "center"; + case Qt::AlignBottom: + default: + return "bottom"; + } +} + +void GridItem::setThumbnailVerticalAlign(const QString valign) +{ + if (valign == "top") + thumbnailVerticalAlignmentFlag = Qt::AlignTop; + else if (valign == "center") + thumbnailVerticalAlignmentFlag = Qt::AlignVCenter; + else if (valign == "bottom") + thumbnailVerticalAlignmentFlag = Qt::AlignBottom; +} diff --git a/ui/drivers/qt/gridview.h b/ui/drivers/qt/gridview.h new file mode 100644 index 0000000000..165781d91e --- /dev/null +++ b/ui/drivers/qt/gridview.h @@ -0,0 +1,91 @@ +#ifndef GRIDVIEW_H +#define GRIDVIEW_H + +#include +#include + +#define DEFAULT_GRID_ITEM_MARGIN 11 +#define DEFAULT_GRID_ITEM_THUMBNAIL_ALIGNMENT "bottom" +#define DEFAULT_GRID_SPACING 7 +#define DEFAULT_GRID_LAYOUT "centered" + +class GridItem; + +class ThumbnailDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + ThumbnailDelegate(const GridItem &gridItem, QObject* parent = 0); + void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const; + +private: + const GridItem &m_style; +}; + +class GridView : public QAbstractItemView +{ + Q_OBJECT + + Q_PROPERTY(QString layout READ getLayout WRITE setLayout DESIGNABLE true SCRIPTABLE true) + Q_PROPERTY(int spacing READ getSpacing WRITE setSpacing DESIGNABLE true SCRIPTABLE true) + +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); + QString getLayout() const; + void setLayout(QString layout); + int getSpacing() const; + void setSpacing(const int spacing); + +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(); + +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 = DEFAULT_GRID_SPACING; + QVector m_visibleIndexes; + ViewMode m_viewMode = Centered; + mutable int m_idealHeight; + mutable QHash m_rectForRow; + mutable bool m_hashIsDirty; +}; + +#endif diff --git a/ui/drivers/qt/playlist.cpp b/ui/drivers/qt/playlist.cpp index 7217f19f5a..ce3742b413 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,13 +1172,11 @@ void MainWindow::reloadPlaylists() } } - connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); } QString MainWindow::getCurrentPlaylistPath() { QListWidgetItem *playlistItem = m_listWidget->currentItem(); - QHash contentHash; QString playlistPath; if (!playlistItem) @@ -1116,151 +1318,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 +1379,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 +1457,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..8780e2f9d4 100644 --- a/ui/drivers/qt/thumbnailpackdownload.cpp +++ b/ui/drivers/qt/thumbnailpackdownload.cpp @@ -191,7 +191,6 @@ void MainWindow::onThumbnailPackDownloadFinished() reply->disconnect(); reply->close(); - reply->deleteLater(); } void MainWindow::onThumbnailPackDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) @@ -309,6 +308,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_load_core_window.cpp b/ui/drivers/qt/ui_qt_load_core_window.cpp index 8179b82570..7cd344cd2b 100644 --- a/ui/drivers/qt/ui_qt_load_core_window.cpp +++ b/ui/drivers/qt/ui_qt_load_core_window.cpp @@ -31,7 +31,7 @@ extern "C" { #include #include #include -}; +} #define CORE_NAME_COLUMN 0 #define CORE_VERSION_COLUMN 1 diff --git a/ui/drivers/qt/ui_qt_themes.h b/ui/drivers/qt/ui_qt_themes.h index 803cf8a37a..7525730219 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,18 @@ 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; + qproperty-layout: "fixed"; + } + GridItem { + qproperty-thumbnailvalign: "center"; } )"); diff --git a/ui/drivers/qt/ui_qt_window.cpp b/ui/drivers/qt/ui_qt_window.cpp index 712137f68d..7444dd7499 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) { @@ -283,7 +253,7 @@ MainWindow::MainWindow(QWidget *parent) : ,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,19 +281,17 @@ 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_thumbnailType(THUMBNAIL_TYPE_BOXART) ,m_gridProgressBar(NULL) ,m_gridProgressWidget(NULL) ,m_currentGridHash() @@ -354,6 +322,8 @@ MainWindow::MainWindow(QWidget *parent) : ,m_failedThumbnails(0) ,m_playlistThumbnailDownloadWasCanceled(false) ,m_pendingDirScrollPath() + ,m_thumbnailTimer(new QTimer(this)) + ,m_gridItem(this) { settings_t *settings = config_get_ptr(); QDir playlistDir(settings->paths.directory_playlist); @@ -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,18 @@ 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_tableView->setSortingEnabled(true); + m_tableView->verticalHeader()->setVisible(false); + + m_gridView->setItemDelegate(new ThumbnailDelegate(m_gridItem, this)); + m_gridView->setModel(m_proxyModel); m_logWidget->setObjectName("logWidget"); @@ -557,10 +533,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 +562,26 @@ 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())); + + /* TODO: Handle scroll and resize differently. */ + 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 +640,38 @@ 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(); + int i; + for (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 +714,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 +736,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 +748,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() @@ -1212,6 +1137,8 @@ void MainWindow::setTheme(Theme theme) { m_currentTheme = theme; + setDefaultCustomProperties(); + switch(theme) { case THEME_SYSTEM_DEFAULT: @@ -1237,6 +1164,21 @@ void MainWindow::setTheme(Theme theme) } } +void MainWindow::setDefaultCustomProperties() +{ + m_gridView->setLayout(QString(DEFAULT_GRID_LAYOUT)); + m_gridView->setSpacing(DEFAULT_GRID_SPACING); + m_gridItem.setThumbnailVerticalAlign(QString(DEFAULT_GRID_ITEM_THUMBNAIL_ALIGNMENT)); + m_gridItem.setPadding(DEFAULT_GRID_ITEM_MARGIN); +} + +void MainWindow::changeThumbnailType(ThumbnailType type) +{ + m_playlistModel->setThumbnailType(type); + updateVisibleItems(); + m_gridView->viewport()->update(); +} + QVector > MainWindow::getCoreInfo() { QVector > infoList; @@ -1545,66 +1487,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,45 +1495,40 @@ 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*) +void MainWindow::onContentItemDoubleClicked(const QModelIndex &index) { + Q_UNUSED(index); onRunClicked(); } @@ -1677,13 +1555,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 +1757,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 +1793,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 +1813,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 +1968,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 +1981,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 +2014,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 +2042,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 +2132,21 @@ 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)); + 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 +2221,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 +2230,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 +2356,7 @@ void MainWindow::setCurrentViewType(ViewType viewType) { case VIEW_TYPE_ICONS: { - m_tableWidget->hide(); + m_tableView->hide(); m_gridWidget->show(); break; } @@ -2627,48 +2365,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 +2414,9 @@ QWidget* MainWindow::contentGridWidget() return m_gridWidget; } -FlowLayout* MainWindow::contentGridLayout() +GridView* MainWindow::contentGridView() { - return m_gridLayout; + return m_gridView; } void MainWindow::onBrowserDownloadsClicked() @@ -2883,184 +2621,12 @@ 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(); QStringList horizontal_header_labels; QString path; + QModelIndex index; int i = 0; if (!item) @@ -3077,23 +2643,11 @@ 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 +2664,23 @@ void MainWindow::initContentTableWidget() playlists.append(playlistDir.absoluteFilePath(playlist)); } - addPlaylistItemsToTable(playlists, true); + m_playlistModel->addPlaylistItems(playlists, true); } else - addPlaylistItemsToTable(QStringList() << path); - - m_tableWidget->setSortingEnabled(true); + m_playlistModel->addPlaylistItems(QStringList() << path); if (item != m_historyPlaylistsItem) - m_tableWidget->sortByColumn(0, Qt::AscendingOrder); + m_tableView->sortByColumn(0, Qt::AscendingOrder); + else + m_proxyModel->sort(-1); - 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*))); + 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 +2720,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..b0fe0b30b6 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 @@ -233,6 +271,26 @@ public slots: void appendMessage(const QString& text); }; +/* Used to store styling since delegates don't inherit QWidget. */ +class GridItem : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QString thumbnailvalign READ getThumbnailVerticalAlign WRITE setThumbnailVerticalAlign) + Q_PROPERTY(int padding READ getPadding WRITE setPadding) + +public: + GridItem(QWidget* parent); + + Qt::AlignmentFlag thumbnailVerticalAlignmentFlag; + int padding; + + int getPadding() const; + void setPadding(const int value); + QString getThumbnailVerticalAlign() const; + void setThumbnailVerticalAlign(const QString valign); +}; + class MainWindow : public QMainWindow { Q_OBJECT @@ -263,9 +321,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,20 +348,26 @@ 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); QVector > getPlaylists(); QString getScrubbedString(QString str); + void setDefaultCustomProperties(); signals: void thumbnailChanged(const QPixmap &pixmap); @@ -317,6 +382,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 +393,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,24 +429,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(QTableWidgetItem *item); + void onContentItemDoubleClicked(const QModelIndex &index); void onCoreLoadWindowClosed(); void onTreeViewItemsSelected(QModelIndexList selectedIndexes); void onSearchResetClicked(); @@ -390,13 +450,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 +458,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 +492,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 +507,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 +518,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 +550,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 +594,9 @@ private: bool m_playlistThumbnailDownloadWasCanceled; QString m_pendingDirScrollPath; + QTimer *m_thumbnailTimer; + GridItem m_gridItem; + protected: void closeEvent(QCloseEvent *event); void keyPressEvent(QKeyEvent *event);