mirror of
https://github.com/libretro/RetroArch
synced 2025-03-02 19:13:34 +00:00
Qt: Implement custom playlist model and grid view.
Only load images when they become visible and cache them. Add option to change thumbnail type displayed in grid view. Add option to change thumbnail cache limit.
This commit is contained in:
parent
e981afcca8
commit
a868ef29e8
@ -342,7 +342,7 @@ ifeq ($(HAVE_QT), 1)
|
||||
ui/drivers/qt/ui_qt_browser_window.o \
|
||||
ui/drivers/qt/ui_qt_load_core_window.o \
|
||||
ui/drivers/qt/ui_qt_msg_window.o \
|
||||
ui/drivers/qt/flowlayout.o \
|
||||
ui/drivers/qt/gridview.o \
|
||||
ui/drivers/qt/shaderparamsdialog.o \
|
||||
ui/drivers/qt/coreoptionsdialog.o \
|
||||
ui/drivers/qt/filedropwidget.o \
|
||||
@ -357,7 +357,7 @@ ifeq ($(HAVE_QT), 1)
|
||||
|
||||
MOC_HEADERS += ui/drivers/ui_qt.h \
|
||||
ui/drivers/qt/ui_qt_load_core_window.h \
|
||||
ui/drivers/qt/flowlayout.h \
|
||||
ui/drivers/qt/gridview.h \
|
||||
ui/drivers/qt/shaderparamsdialog.h \
|
||||
ui/drivers/qt/coreoptionsdialog.h \
|
||||
ui/drivers/qt/filedropwidget.h \
|
||||
|
@ -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"
|
||||
|
@ -7544,6 +7544,14 @@ MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_STARTUP_PLAYLIST,
|
||||
"Start on playlist:"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE,
|
||||
"Icon view thumbnail type:"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_CACHE_LIMIT,
|
||||
"Thumbnail cache limit:"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_ALL_THUMBNAILS,
|
||||
"Download All Thumbnails"
|
||||
@ -7779,4 +7787,4 @@ MSG_HASH(
|
||||
MSG_HASH(
|
||||
MSG_MISSING_ASSETS,
|
||||
"Warning: Missing assets, use the Online Updater if available"
|
||||
)
|
||||
)
|
||||
|
@ -1967,6 +1967,8 @@ enum msg_hash_enums
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_LIST_MAX_COUNT,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_ALL_PLAYLISTS_GRID_MAX_COUNT,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_STARTUP_PLAYLIST,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_CACHE_LIMIT,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_TOOLS,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_HELP,
|
||||
MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER,
|
||||
|
@ -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();
|
||||
|
@ -15,6 +15,7 @@ public:
|
||||
FileDropWidget(QWidget *parent = 0);
|
||||
signals:
|
||||
void filesDropped(QStringList files);
|
||||
void enterPressed();
|
||||
void deletePressed();
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
|
@ -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 <QtWidgets>
|
||||
|
||||
#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<ThumbnailWidget>)), this, SLOT(onAddWidgetDeferred(QPointer<ThumbnailWidget>)), 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<const QWidget*>(parentObj);
|
||||
return pw->style()->pixelMetric(pm, NULL, pw);
|
||||
}
|
||||
else
|
||||
return static_cast<const QLayout*>(parentObj)->spacing();
|
||||
}
|
||||
|
||||
void FlowLayout::addWidgetDeferred(QPointer<ThumbnailWidget> widget)
|
||||
{
|
||||
emit signalAddWidgetDeferred(widget);
|
||||
}
|
||||
|
||||
void FlowLayout::onAddWidgetDeferred(QPointer<ThumbnailWidget> 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);
|
||||
}
|
@ -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 <QLayout>
|
||||
#include <QRect>
|
||||
#include <QStyle>
|
||||
|
||||
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<ThumbnailWidget> widget);
|
||||
|
||||
signals:
|
||||
void signalAddWidgetDeferred(QPointer<ThumbnailWidget> widget);
|
||||
|
||||
private slots:
|
||||
void onAddWidgetDeferred(QPointer<ThumbnailWidget> widget);
|
||||
|
||||
private:
|
||||
int doLayout(const QRect &rect, bool testOnly) const;
|
||||
int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
QVector<QLayoutItem*> itemList;
|
||||
int m_hSpace;
|
||||
int m_vSpace;
|
||||
};
|
||||
|
||||
#endif // FLOWLAYOUT_H
|
392
ui/drivers/qt/gridview.cpp
Normal file
392
ui/drivers/qt/gridview.cpp
Normal file
@ -0,0 +1,392 @@
|
||||
#include <QScrollBar>
|
||||
#include <QPainter>
|
||||
|
||||
#include "gridview.h"
|
||||
#include "../ui_qt.h"
|
||||
|
||||
/* http://www.informit.com/articles/article.aspx?p=1613548 */
|
||||
|
||||
ThumbnailDelegate::ThumbnailDelegate(QObject* parent) :
|
||||
QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const
|
||||
{
|
||||
painter->save();
|
||||
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
const QWidget *widget = opt.widget;
|
||||
|
||||
QStyle *style = widget->style();
|
||||
|
||||
int margin = 11;
|
||||
int textMargin = 4;
|
||||
QRect rect = opt.rect;
|
||||
int textHeight = painter->fontMetrics().height() + margin + margin;
|
||||
QRect adjusted = rect.adjusted(margin, margin, -margin, -textHeight + textMargin);
|
||||
QPixmap pixmap = index.data(PlaylistModel::THUMBNAIL).value<QPixmap>();
|
||||
|
||||
// draw the background
|
||||
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);
|
||||
|
||||
// draw the image
|
||||
if (!pixmap.isNull())
|
||||
{
|
||||
QPixmap pixmapScaled = pixmap.scaled(adjusted.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
style->drawItemPixmap(painter, adjusted, Qt::AlignHCenter | Qt::AlignBottom, pixmapScaled);
|
||||
}
|
||||
|
||||
// draw the text
|
||||
if (!opt.text.isEmpty())
|
||||
{
|
||||
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
|
||||
|
||||
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
|
||||
cg = QPalette::Inactive;
|
||||
|
||||
if (opt.state & QStyle::State_Selected)
|
||||
painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
|
||||
else
|
||||
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
||||
|
||||
QRect textRect = QRect(rect.x() + margin, rect.y() + adjusted.height() - textMargin + margin, rect.width() - 2 * margin, textHeight);
|
||||
QString elidedText = painter->fontMetrics().elidedText(opt.text, opt.textElideMode, textRect.width(), Qt::TextShowMnemonic);
|
||||
|
||||
painter->setFont(opt.font);
|
||||
painter->drawText(textRect, Qt::AlignCenter, elidedText);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
GridView::GridView(QWidget *parent) : QAbstractItemView(parent), m_idealHeight(0), m_hashIsDirty(false)
|
||||
{
|
||||
setFocusPolicy(Qt::WheelFocus);
|
||||
horizontalScrollBar()->setRange(0, 0);
|
||||
verticalScrollBar()->setRange(0, 0);
|
||||
}
|
||||
|
||||
void GridView::setModel(QAbstractItemModel *newModel)
|
||||
{
|
||||
if (model())
|
||||
disconnect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
|
||||
|
||||
QAbstractItemView::setModel(newModel);
|
||||
|
||||
connect(newModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
|
||||
|
||||
m_hashIsDirty = true;
|
||||
}
|
||||
|
||||
void GridView::setviewMode(ViewMode mode)
|
||||
{
|
||||
m_viewMode = mode;
|
||||
}
|
||||
|
||||
void GridView::calculateRectsIfNecessary() const
|
||||
{
|
||||
if (!m_hashIsDirty)
|
||||
return;
|
||||
|
||||
int x = m_spacing;
|
||||
int y = m_spacing;
|
||||
int row;
|
||||
int nextX;
|
||||
|
||||
const int maxWidth = viewport()->width();
|
||||
|
||||
switch (m_viewMode) {
|
||||
case Anchored:
|
||||
{
|
||||
int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
|
||||
if (columns > 0)
|
||||
{
|
||||
const int actualSpacing = (maxWidth - m_spacing - m_size - (columns - 1) * m_size) / columns;
|
||||
for (row = 0; row < model()->rowCount(); ++row)
|
||||
{
|
||||
nextX = x + m_size + actualSpacing;
|
||||
if (nextX > maxWidth)
|
||||
{
|
||||
x = m_spacing;
|
||||
y += m_size + m_spacing;
|
||||
nextX = x + m_size + actualSpacing;
|
||||
}
|
||||
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
||||
x = nextX;
|
||||
}
|
||||
m_idealHeight = y + m_size + m_spacing;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Centered:
|
||||
{
|
||||
int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
|
||||
if (columns > 0)
|
||||
{
|
||||
const int actualSpacing = (maxWidth - columns * m_size) / (columns + 1);
|
||||
x = actualSpacing;
|
||||
for (row = 0; row < model()->rowCount(); ++row)
|
||||
{
|
||||
nextX = x + m_size + actualSpacing;
|
||||
if (nextX > maxWidth)
|
||||
{
|
||||
x = actualSpacing;
|
||||
y += m_size + m_spacing;
|
||||
nextX = x + m_size + actualSpacing;
|
||||
}
|
||||
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
||||
x = nextX;
|
||||
}
|
||||
m_idealHeight = y + m_size + m_spacing;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Simple:
|
||||
for (row = 0; row < model()->rowCount(); ++row)
|
||||
{
|
||||
nextX = x + m_size + m_spacing;
|
||||
if (nextX > maxWidth)
|
||||
{
|
||||
x = m_spacing;
|
||||
y += m_size + m_spacing;
|
||||
nextX = x + m_size + m_spacing;
|
||||
}
|
||||
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
||||
x = nextX;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_hashIsDirty = false;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
QRect GridView::visualRect(const QModelIndex &index) const
|
||||
{
|
||||
QRect rect;
|
||||
if (index.isValid())
|
||||
rect = viewportRectForRow(index.row()).toRect();
|
||||
return rect;
|
||||
}
|
||||
|
||||
QRectF GridView::viewportRectForRow(int row) const
|
||||
{
|
||||
calculateRectsIfNecessary();
|
||||
QRectF rect = m_rectForRow.value(row).toRect();
|
||||
if (!rect.isValid())
|
||||
return rect;
|
||||
return QRectF(rect.x() - horizontalScrollBar()->value(), rect.y() - verticalScrollBar()->value(), rect.width(), rect.height());
|
||||
}
|
||||
|
||||
void GridView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint)
|
||||
{
|
||||
QRect viewRect = viewport()->rect();
|
||||
QRect itemRect = visualRect(index);
|
||||
|
||||
if (itemRect.left() < viewRect.left())
|
||||
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + itemRect.left() - viewRect.left());
|
||||
else if (itemRect.right() > viewRect.right())
|
||||
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + qMin(itemRect.right() - viewRect.right(), itemRect.left() - viewRect.left()));
|
||||
if (itemRect.top() < viewRect.top())
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->value() + itemRect.top() - viewRect.top());
|
||||
else if (itemRect.bottom() > viewRect.bottom())
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->value() + qMin(itemRect.bottom() - viewRect.bottom(), itemRect.top() - viewRect.top()));
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
QModelIndex GridView::indexAt(const QPoint &point_) const
|
||||
{
|
||||
QPoint point(point_);
|
||||
point.rx() += horizontalScrollBar()->value();
|
||||
point.ry() += verticalScrollBar()->value();
|
||||
calculateRectsIfNecessary();
|
||||
QHashIterator<int, QRectF> i(m_rectForRow);
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
if (i.value().contains(point))
|
||||
return model()->index(i.key(), 0, rootIndex());
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void GridView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
||||
{
|
||||
m_hashIsDirty = true;
|
||||
QAbstractItemView::dataChanged(topLeft, bottomRight);
|
||||
}
|
||||
|
||||
void GridView::refresh()
|
||||
{
|
||||
m_hashIsDirty = true;
|
||||
calculateRectsIfNecessary();
|
||||
updateGeometries();
|
||||
}
|
||||
|
||||
void GridView::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
QAbstractItemView::rowsInserted(parent, start, end);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void GridView::rowsRemoved(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
||||
void GridView::setGridSize(const int newSize)
|
||||
{
|
||||
if (newSize != m_size)
|
||||
{
|
||||
m_size = newSize;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void GridView::resizeEvent(QResizeEvent*)
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
||||
void GridView::reset()
|
||||
{
|
||||
QAbstractItemView::reset();
|
||||
refresh();
|
||||
}
|
||||
|
||||
QModelIndex GridView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers)
|
||||
{
|
||||
QModelIndex index = currentIndex();
|
||||
if (index.isValid())
|
||||
{
|
||||
if ((cursorAction == MoveLeft && index.row() > 0) || (cursorAction == MoveRight && index.row() + 1 < model()->rowCount()))
|
||||
{
|
||||
const int offset = (cursorAction == MoveLeft ? -1 : 1);
|
||||
index = model()->index(index.row() + offset, index.column(), index.parent());
|
||||
}
|
||||
else if ((cursorAction == MoveUp && index.row() > 0) || (cursorAction == MoveDown && index.row() + 1 < model()->rowCount()))
|
||||
{
|
||||
const int offset = ((m_size + m_spacing) * (cursorAction == MoveUp ? -1 : 1));
|
||||
QRect rect = viewportRectForRow(index.row()).toRect();
|
||||
QPoint point(rect.center().x(), rect.center().y() + offset);
|
||||
index = indexAt(point);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
int GridView::horizontalOffset() const
|
||||
{
|
||||
return horizontalScrollBar()->value();
|
||||
}
|
||||
|
||||
int GridView::verticalOffset() const
|
||||
{
|
||||
return verticalScrollBar()->value();
|
||||
}
|
||||
|
||||
void GridView::scrollContentsBy(int dx, int dy)
|
||||
{
|
||||
scrollDirtyRegion(dx, dy);
|
||||
viewport()->scroll(dx, dy);
|
||||
emit(visibleItemsChangedMaybe());
|
||||
}
|
||||
|
||||
QVector<QModelIndex> GridView::visibleIndexes() const {
|
||||
return m_visibleIndexes;
|
||||
}
|
||||
|
||||
void GridView::setSelection(const QRect &rect, QFlags<QItemSelectionModel::SelectionFlag> flags)
|
||||
{
|
||||
QRect rectangle = rect.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized();
|
||||
calculateRectsIfNecessary();
|
||||
QHashIterator<int, QRectF> i(m_rectForRow);
|
||||
int firstRow = model()->rowCount();
|
||||
int lastRow = -1;
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
if (i.value().intersects(rectangle))
|
||||
{
|
||||
firstRow = firstRow < i.key() ? firstRow : i.key();
|
||||
lastRow = lastRow > i.key() ? lastRow : i.key();
|
||||
}
|
||||
}
|
||||
if (firstRow != model()->rowCount() && lastRow != -1)
|
||||
{
|
||||
QItemSelection selection( model()->index(firstRow, 0, rootIndex()), model()->index(lastRow, 0, rootIndex()));
|
||||
selectionModel()->select(selection, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
QModelIndex invalid;
|
||||
QItemSelection selection(invalid, invalid);
|
||||
selectionModel()->select(selection, flags);
|
||||
}
|
||||
}
|
||||
|
||||
QRegion GridView::visualRegionForSelection(const QItemSelection &selection) const
|
||||
{
|
||||
QRegion region;
|
||||
foreach(const QItemSelectionRange &range, selection)
|
||||
{
|
||||
for (int row = range.top(); row <= range.bottom(); ++row)
|
||||
{
|
||||
for (int column = range.left(); column < range.right(); ++column)
|
||||
{
|
||||
QModelIndex index = model()->index(row, column, rootIndex());
|
||||
region += visualRect(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
void GridView::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(viewport());
|
||||
int row;
|
||||
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
|
||||
m_visibleIndexes.clear();
|
||||
|
||||
for (row = 0; row < model()->rowCount(rootIndex()); ++row)
|
||||
{
|
||||
QModelIndex index = model()->index(row, 0, rootIndex());
|
||||
QRectF rect = viewportRectForRow(row);
|
||||
|
||||
if (!rect.isValid() || rect.bottom() < 0 || rect.y() > viewport()->height())
|
||||
continue;
|
||||
|
||||
m_visibleIndexes.append(index);
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
option.rect = rect.toRect();
|
||||
|
||||
if (selectionModel()->isSelected(index))
|
||||
option.state |= QStyle::State_Selected;
|
||||
|
||||
if (currentIndex() == index)
|
||||
option.state |= QStyle::State_HasFocus;
|
||||
|
||||
itemDelegate()->paint(&painter, option, index);
|
||||
}
|
||||
}
|
||||
|
||||
void GridView::updateGeometries()
|
||||
{
|
||||
const int RowHeight = m_size + m_spacing;
|
||||
|
||||
QAbstractItemView::updateGeometries();
|
||||
|
||||
verticalScrollBar()->setSingleStep(RowHeight);
|
||||
verticalScrollBar()->setPageStep(viewport()->height());
|
||||
verticalScrollBar()->setRange(0, qMax(0, m_idealHeight - viewport()->height()));
|
||||
|
||||
horizontalScrollBar()->setPageStep(viewport()->width());
|
||||
horizontalScrollBar()->setRange(0, qMax(0, RowHeight - viewport()->width()));
|
||||
|
||||
emit(visibleItemsChangedMaybe());
|
||||
}
|
74
ui/drivers/qt/gridview.h
Normal file
74
ui/drivers/qt/gridview.h
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef GRIDVIEW_H
|
||||
#define GRIDVIEW_H
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
class ThumbnailDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ThumbnailDelegate(QObject* parent = 0);
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const;
|
||||
};
|
||||
|
||||
class GridView : public QAbstractItemView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ViewMode
|
||||
{
|
||||
Simple,
|
||||
Centered,
|
||||
Anchored
|
||||
};
|
||||
|
||||
GridView(QWidget *parent = 0);
|
||||
~GridView() {}
|
||||
|
||||
QModelIndex indexAt(const QPoint &point_) const;
|
||||
QVector<QModelIndex> visibleIndexes() const;
|
||||
QRect visualRect(const QModelIndex &index) const;
|
||||
void setModel(QAbstractItemModel *model);
|
||||
void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint);
|
||||
void setGridSize(const int newSize);
|
||||
void setviewMode(ViewMode mode);
|
||||
|
||||
signals:
|
||||
void visibleItemsChangedMaybe() const;
|
||||
|
||||
protected slots:
|
||||
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
void rowsInserted(const QModelIndex &parent, int start, int end);
|
||||
void rowsRemoved(const QModelIndex &parent, int start, int end);
|
||||
void updateGeometries();
|
||||
void reset() override;
|
||||
|
||||
protected:
|
||||
QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers);
|
||||
QRegion visualRegionForSelection(const QItemSelection &selection) const;
|
||||
bool isIndexHidden(const QModelIndex&) const { return false; }
|
||||
int horizontalOffset() const;
|
||||
int verticalOffset() const;
|
||||
void scrollContentsBy(int dx, int dy);
|
||||
void setSelection(const QRect &rect, QFlags<QItemSelectionModel::SelectionFlag> flags);
|
||||
void paintEvent(QPaintEvent*);
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
private:
|
||||
QRectF viewportRectForRow(int row) const;
|
||||
void calculateRectsIfNecessary() const;
|
||||
void refresh();
|
||||
|
||||
int m_size = 255;
|
||||
int m_spacing = 7;
|
||||
QVector<QModelIndex> m_visibleIndexes;
|
||||
ViewMode m_viewMode = Centered;
|
||||
mutable int m_idealHeight;
|
||||
mutable QHash<int, QRectF> m_rectForRow;
|
||||
mutable bool m_hashIsDirty;
|
||||
};
|
||||
|
||||
#endif // GRIDVIEW_H
|
@ -9,9 +9,10 @@
|
||||
#include <QLayout>
|
||||
#include <QScreen>
|
||||
#include <QRegularExpression>
|
||||
#include <QImageReader>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#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<QByteArray>::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<QString, QString> 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<QString, QString> &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<QString> keys = m_cache.keys();
|
||||
QList<QString> 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<QString, QString> &lhs, const QHash<QString, QString> &rhs)
|
||||
{
|
||||
return lhs.value("name").toLower() < rhs.value("name").toLower();
|
||||
@ -38,7 +246,6 @@ inline static bool comp_hash_label_key_lower(const QHash<QString, QString> &lhs,
|
||||
return lhs.value("label").toLower() < rhs.value("label").toLower();
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/questions/7246622/how-to-create-a-slider-with-a-non-linear-scale */
|
||||
bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions)
|
||||
{
|
||||
PlaylistEntryDialog *playlistDialog = playlistEntryDialog();
|
||||
@ -834,9 +1041,6 @@ void MainWindow::reloadPlaylists()
|
||||
|
||||
getPlaylistFiles();
|
||||
|
||||
/* block this signal because setData() would trigger an infinite loop */
|
||||
disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*)));
|
||||
|
||||
m_listWidget->clear();
|
||||
m_listWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
@ -968,7 +1172,6 @@ void MainWindow::reloadPlaylists()
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*)));
|
||||
}
|
||||
|
||||
QString MainWindow::getCurrentPlaylistPath()
|
||||
@ -1116,151 +1319,15 @@ void MainWindow::getPlaylistFiles()
|
||||
m_playlistFiles = playlistDir.entryList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Files, QDir::Name);
|
||||
}
|
||||
|
||||
void MainWindow::addPlaylistItemsToGrid(const QStringList &paths, bool add)
|
||||
{
|
||||
QVector<QHash<QString, QString> > items;
|
||||
int i;
|
||||
|
||||
if (paths.isEmpty())
|
||||
return;
|
||||
|
||||
for (i = 0; i < paths.size(); i++)
|
||||
{
|
||||
int j;
|
||||
QVector<QHash<QString, QString> > 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<QHash<QString, QString> > &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<QString, QString> &hash = items.at(i);
|
||||
QPointer<GridItem> item;
|
||||
QPointer<ThumbnailLabel> 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<QHash<QString, QString> >(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<QVBoxLayout*>(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<QHash<QString, QString> > MainWindow::getPlaylistItems(QString pathString)
|
||||
void PlaylistModel::getPlaylistItems(QString path)
|
||||
{
|
||||
QByteArray pathArray;
|
||||
QVector<QHash<QString, QString> > items;
|
||||
const char *pathData = NULL;
|
||||
playlist_t *playlist = NULL;
|
||||
unsigned playlistSize = 0;
|
||||
unsigned i = 0;
|
||||
|
||||
pathArray.append(pathString);
|
||||
pathArray.append(path);
|
||||
pathData = pathArray.constData();
|
||||
|
||||
playlist = playlist_init(pathData, COLLECTION_SIZE);
|
||||
@ -1313,58 +1380,67 @@ QVector<QHash<QString, QString> > 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<QHash<QString, QString> > items;
|
||||
int i;
|
||||
|
||||
if (paths.isEmpty())
|
||||
return;
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_contents.clear();
|
||||
|
||||
for (i = 0; i < paths.size(); i++)
|
||||
{
|
||||
int j;
|
||||
QVector<QHash<QString, QString> > 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<QHash<QString, QString> > &items)
|
||||
void PlaylistModel::addDir(QString path, QFlags<QDir::Filter> 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<QString, QString> &hash = items.at(i);
|
||||
QString fileName = dirList.at(i);
|
||||
QHash<QString, QString> hash;
|
||||
QString filePath(QDir::toNativeSeparators(dir.absoluteFilePath(fileName)));
|
||||
QFileInfo fileInfo(filePath);
|
||||
|
||||
labelItem = new QTableWidgetItem(hash.value("label"));
|
||||
labelItem->setData(Qt::UserRole, QVariant::fromValue<QHash<QString, QString> >(hash));
|
||||
labelItem->setFlags(labelItem->flags() | Qt::ItemIsEditable);
|
||||
hash["path"] = filePath;
|
||||
hash["label"] = hash["path"];
|
||||
hash["label_noext"] = fileInfo.completeBaseName();
|
||||
hash["db_name"] = fileInfo.dir().dirName();
|
||||
|
||||
m_tableWidget->setItem(oldRowCount + i, 0, labelItem);
|
||||
m_contents.append(hash);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MainWindow::setAllPlaylistsListMaxCount(int count)
|
||||
@ -1382,4 +1458,3 @@ void MainWindow::setAllPlaylistsGridMaxCount(int count)
|
||||
|
||||
m_allPlaylistsGridMaxCount = count;
|
||||
}
|
||||
|
||||
|
@ -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<QString, QString> 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<QHash<QString, QString> > 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<QString, QString> &itemHash = playlistItems.at(i);
|
||||
const QHash<QString, QString> &itemHash = m_playlistModel->index(i, 0).data(PlaylistModel::HASH).value< QHash<QString, QString> >();
|
||||
QHash<QString, QString> hash;
|
||||
QHash<QString, QString> hash2;
|
||||
QHash<QString, QString> hash3;
|
||||
|
@ -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
|
||||
|
@ -191,7 +191,7 @@ void MainWindow::onThumbnailPackDownloadFinished()
|
||||
|
||||
reply->disconnect();
|
||||
reply->close();
|
||||
reply->deleteLater();
|
||||
//reply->deleteLater();
|
||||
}
|
||||
|
||||
void MainWindow::onThumbnailPackDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
@ -309,6 +309,11 @@ void MainWindow::onThumbnailPackExtractFinished(bool success)
|
||||
|
||||
emit showInfoMessageDeferred(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_PACK_DOWNLOADED_SUCCESSFULLY));
|
||||
|
||||
QNetworkReply *reply = m_thumbnailPackDownloadReply.data();
|
||||
|
||||
m_playlistModel->reloadSystemThumbnails(reply->property("system").toString());
|
||||
reply->deleteLater();
|
||||
updateVisibleItems();
|
||||
/* reload thumbnail image */
|
||||
emit itemChanged();
|
||||
}
|
||||
|
@ -313,7 +313,7 @@ static const QString qt_theme_dark_stylesheet = QStringLiteral(R"(
|
||||
padding-left:5px;
|
||||
padding-right:5px;
|
||||
}
|
||||
QTableWidget {
|
||||
QTableView {
|
||||
background-color:rgb(25,25,25);
|
||||
alternate-background-color:rgb(40,40,40);
|
||||
}
|
||||
@ -422,14 +422,14 @@ static const QString qt_theme_dark_stylesheet = QStringLiteral(R"(
|
||||
QSizeGrip {
|
||||
background-color:solid;
|
||||
}
|
||||
ThumbnailWidget#thumbnailWidget, ThumbnailLabel#thumbnailGridLabel, QLabel#thumbnailQLabel {
|
||||
GridView::item {
|
||||
background-color:rgb(40,40,40);
|
||||
}
|
||||
ThumbnailWidget#thumbnailWidgetSelected {
|
||||
background-color:rgb(40,40,40);
|
||||
GridView::item:selected {
|
||||
border:3px solid %1;
|
||||
}
|
||||
QWidget#gridLayoutWidget {
|
||||
GridView {
|
||||
background-color:rgb(25,25,25);
|
||||
selection-color: white;
|
||||
}
|
||||
)");
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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<ThumbnailType>(m_thumbnailComboBox->currentData().value<int>());
|
||||
m_mainwindow->setCurrentThumbnailType(type);
|
||||
}
|
||||
|
||||
void ViewOptionsDialog::onThemeComboBoxIndexChanged(int)
|
||||
{
|
||||
MainWindow::Theme theme = static_cast<MainWindow::Theme>(m_themeComboBox->currentData(Qt::UserRole).toInt());
|
||||
@ -138,6 +156,7 @@ void ViewOptionsDialog::loadViewOptions()
|
||||
QVector<QPair<QString, QString> > 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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include <QMainWindow>
|
||||
#include <QTreeView>
|
||||
#include <QListWidget>
|
||||
#include <QTableWidget>
|
||||
#include <QTableView>
|
||||
#include <QFrame>
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
@ -38,6 +38,10 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QSslError>
|
||||
#include <QNetworkReply>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QCache>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QDir>
|
||||
|
||||
extern "C" {
|
||||
#include <retro_assert.h>
|
||||
@ -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<ThumbnailWidget> widget;
|
||||
QPointer<ThumbnailLabel> label;
|
||||
QHash<QString, QString> hash;
|
||||
QImage image;
|
||||
QPixmap pixmap;
|
||||
QFutureWatcher<GridItem*> 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<QDir::Filter> 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<QHash<QString, QString> > m_contents;
|
||||
QCache<QString, QPixmap> m_cache;
|
||||
QSet<QString> m_pendingImages;
|
||||
QVector<QByteArray> m_imageFormats;
|
||||
QRegularExpression m_fileSanitizerRegex;
|
||||
ThumbnailType m_thumbnailType = THUMBNAIL_TYPE_BOXART;
|
||||
QString getThumbnailPath(const QModelIndex &index, QString type) const;
|
||||
QString getCurrentTypeThumbnailPath(const QModelIndex &index) const;
|
||||
void getPlaylistItems(QString path);
|
||||
void loadImage(const QModelIndex &index, const QString &path);
|
||||
};
|
||||
|
||||
class ThumbnailWidget : public QFrame
|
||||
@ -164,17 +207,12 @@ protected slots:
|
||||
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
|
||||
};
|
||||
|
||||
class TableWidget : public QTableWidget
|
||||
class TableView : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TableWidget(QWidget *parent = 0);
|
||||
TableView(QWidget *parent = 0);
|
||||
bool isEditorOpen();
|
||||
signals:
|
||||
void enterPressed();
|
||||
void deletePressed();
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
};
|
||||
|
||||
class ListWidget : public QListWidget
|
||||
@ -263,9 +301,10 @@ public:
|
||||
MainWindow(QWidget *parent = NULL);
|
||||
~MainWindow();
|
||||
TreeView* dirTreeView();
|
||||
PlaylistModel* playlistModel();
|
||||
ListWidget* playlistListWidget();
|
||||
TableWidget* contentTableWidget();
|
||||
FlowLayout* contentGridLayout();
|
||||
TableView* contentTableView();
|
||||
GridView* contentGridView();
|
||||
QWidget* contentGridWidget();
|
||||
QWidget* searchWidget();
|
||||
QLineEdit* searchLineEdit();
|
||||
@ -289,15 +328,20 @@ public:
|
||||
bool setCustomThemeFile(QString filePath);
|
||||
void setCustomThemeString(QString qss);
|
||||
const QString& customThemeString() const;
|
||||
GridItem* doDeferredImageLoad(GridItem *item, QString path);
|
||||
void setCurrentViewType(ViewType viewType);
|
||||
QString getCurrentViewTypeString();
|
||||
ViewType getCurrentViewType();
|
||||
void setCurrentThumbnailType(ThumbnailType thumbnailType);
|
||||
QString getCurrentThumbnailTypeString();
|
||||
ThumbnailType getCurrentThumbnailType();
|
||||
ThumbnailType getThumbnailTypeFromString(QString thumbnailType);
|
||||
void setAllPlaylistsListMaxCount(int count);
|
||||
void setAllPlaylistsGridMaxCount(int count);
|
||||
void setThumbnailCacheLimit(int count);
|
||||
PlaylistEntryDialog* playlistEntryDialog();
|
||||
void addFilesToPlaylist(QStringList files);
|
||||
QString getCurrentPlaylistPath();
|
||||
QModelIndex getCurrentContentIndex();
|
||||
QHash<QString, QString> getCurrentContentHash();
|
||||
static double lerp(double x, double y, double a, double b, double d);
|
||||
QString getSpecialPlaylistPath(SpecialPlaylist playlist);
|
||||
@ -317,6 +361,7 @@ signals:
|
||||
void showInfoMessageDeferred(QString msg);
|
||||
void extractArchiveDeferred(QString path, QString extractionDir, QString tempExtension, retro_task_callback_t cb);
|
||||
void itemChanged();
|
||||
void updateThumbnails();
|
||||
void gridItemChanged(QString title);
|
||||
void gotThumbnailDownload(QString system, QString title);
|
||||
void scrollToDownloads(QString path);
|
||||
@ -327,15 +372,13 @@ public slots:
|
||||
void onBrowserUpClicked();
|
||||
void onBrowserStartClicked();
|
||||
void initContentTableWidget();
|
||||
void initContentGridLayout();
|
||||
void onViewClosedDocksAboutToShow();
|
||||
void onShowHiddenDockWidgetAction();
|
||||
void setCoreActions();
|
||||
void onRunClicked();
|
||||
void loadContent(const QHash<QString, QString> &contentHash);
|
||||
void onStartCoreClicked();
|
||||
void onTableWidgetEnterPressed();
|
||||
void onTableWidgetDeletePressed();
|
||||
void onDropWidgetEnterPressed();
|
||||
void selectBrowserDir(QString path);
|
||||
void resizeThumbnails(bool one, bool two, bool three);
|
||||
void onResizeThumbnailOne();
|
||||
@ -365,23 +408,20 @@ public slots:
|
||||
void downloadAllThumbnails(QString system, QUrl url = QUrl());
|
||||
void downloadPlaylistThumbnails(QString playlistPath);
|
||||
void downloadNextPlaylistThumbnail(QString system, QString title, QString type, QUrl url = QUrl());
|
||||
void changeThumbnailType(ThumbnailType type);
|
||||
|
||||
private slots:
|
||||
void onLoadCoreClicked(const QStringList &extensionFilters = QStringList());
|
||||
void onUnloadCoreMenuAction();
|
||||
void onTimeout();
|
||||
void onCoreLoaded();
|
||||
void onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &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<QString, QString> &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<QHash<QString, QString> > &items);
|
||||
void addPlaylistItemsToGrid(const QStringList &paths, bool all = false);
|
||||
void addPlaylistHashToGrid(const QVector<QHash<QString, QString> > &items);
|
||||
void onContentItemDoubleClicked(const QModelIndex &index);
|
||||
void onContentItemDoubleClicked(QTableWidgetItem *item);
|
||||
void onCoreLoadWindowClosed();
|
||||
void onTreeViewItemsSelected(QModelIndexList selectedIndexes);
|
||||
@ -390,13 +430,7 @@ private slots:
|
||||
void onFileBrowserTreeContextMenuRequested(const QPoint &pos);
|
||||
void onPlaylistWidgetContextMenuRequested(const QPoint &pos);
|
||||
void onStopClicked();
|
||||
void onDeferredImageLoaded();
|
||||
void onZoomValueChanged(int value);
|
||||
void onContentGridInited();
|
||||
void onUpdateGridItemPixmapFromImage(GridItem *item);
|
||||
void onPendingItemUpdates();
|
||||
void onGridItemDoubleClicked();
|
||||
void onGridItemClicked(ThumbnailWidget *thumbnailWidget = NULL);
|
||||
void onPlaylistFilesDropped(QStringList files);
|
||||
void onShaderParamsClicked();
|
||||
void onCoreOptionsClicked();
|
||||
@ -404,7 +438,6 @@ private slots:
|
||||
void onShowInfoMessage(QString msg);
|
||||
void onContributorsClicked();
|
||||
void onItemChanged();
|
||||
void onGridItemChanged(QString title);
|
||||
void onFileSystemDirLoaded(const QString &path);
|
||||
void onDownloadScroll(QString path);
|
||||
void onDownloadScrollAgain(QString path);
|
||||
@ -439,14 +472,14 @@ private slots:
|
||||
void onPlaylistThumbnailDownloadReadyRead();
|
||||
void onPlaylistThumbnailDownloadCanceled();
|
||||
|
||||
void startTimer();
|
||||
void updateVisibleItems();
|
||||
|
||||
private:
|
||||
void setCurrentCoreLabel();
|
||||
void getPlaylistFiles();
|
||||
bool isCoreLoaded();
|
||||
bool isContentLessCore();
|
||||
void removeGridItems();
|
||||
void loadImageDeferred(GridItem *item, QString path);
|
||||
void calcGridItemSize(GridItem *item, int zoomValue);
|
||||
bool updateCurrentPlaylistEntry(const QHash<QString, QString> &contentHash);
|
||||
int extractArchive(QString path);
|
||||
void removeUpdateTempFiles();
|
||||
@ -454,8 +487,9 @@ private:
|
||||
void renamePlaylistItem(QListWidgetItem *item, QString newName);
|
||||
bool currentPlaylistIsSpecial();
|
||||
bool currentPlaylistIsAll();
|
||||
QVector<QHash<QString, QString> > getPlaylistItems(QString pathString);
|
||||
|
||||
PlaylistModel *m_playlistModel;
|
||||
QSortFilterProxyModel *m_proxyModel;
|
||||
LoadCoreWindow *m_loadCoreWindow;
|
||||
QTimer *m_timer;
|
||||
QString m_currentCore;
|
||||
@ -464,7 +498,7 @@ private:
|
||||
TreeView *m_dirTree;
|
||||
QFileSystemModel *m_dirModel;
|
||||
ListWidget *m_listWidget;
|
||||
TableWidget *m_tableWidget;
|
||||
TableView *m_tableView;
|
||||
QWidget *m_searchWidget;
|
||||
QLineEdit *m_searchLineEdit;
|
||||
QDockWidget *m_searchDock;
|
||||
@ -496,19 +530,19 @@ private:
|
||||
QListWidgetItem *m_historyPlaylistsItem;
|
||||
QIcon m_folderIcon;
|
||||
QString m_customThemeString;
|
||||
FlowLayout *m_gridLayout;
|
||||
GridView *m_gridView;
|
||||
QWidget *m_gridWidget;
|
||||
QScrollArea *m_gridScrollArea;
|
||||
QVector<QPointer<GridItem> > m_gridItems;
|
||||
QWidget *m_gridLayoutWidget;
|
||||
QSlider *m_zoomSlider;
|
||||
int m_lastZoomSliderValue;
|
||||
QList<GridItem*> m_pendingItemUpdates;
|
||||
ViewType m_viewType;
|
||||
ThumbnailType m_thumbnailType;
|
||||
QProgressBar *m_gridProgressBar;
|
||||
QWidget *m_gridProgressWidget;
|
||||
QHash<QString, QString> m_currentGridHash;
|
||||
ViewType m_lastViewType;
|
||||
ThumbnailType m_lastThumbnailType;
|
||||
QPointer<ThumbnailWidget> m_currentGridWidget;
|
||||
int m_allPlaylistsListMaxCount;
|
||||
int m_allPlaylistsGridMaxCount;
|
||||
@ -540,6 +574,8 @@ private:
|
||||
bool m_playlistThumbnailDownloadWasCanceled;
|
||||
QString m_pendingDirScrollPath;
|
||||
|
||||
QTimer *m_thumbnailTimer;
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
|
Loading…
x
Reference in New Issue
Block a user