实现应用列表和SpkResource

SpkResource现有已知问题:首次使用不会完全进行所有任务。
This commit is contained in:
RigoLigoRLC
2021-09-03 00:48:24 +08:00
parent 774e347957
commit ca57a7ac86
26 changed files with 1053 additions and 274 deletions

View File

@@ -0,0 +1,85 @@
#include <page/spkpageapplist.h>
#include "inc/page/spkpageapplist.h"
#include "spkutils.h"
namespace SpkUi
{
SpkPageAppList::SpkPageAppList(QWidget *parent) : SpkPageBase(parent)
{
mItemLay = new SpkStretchLayout(this);
mItemLay->setContentsMargins(6, 6, 6, 6);
}
void SpkPageAppList::AddApplicationEntry(QString name, QString pkgName, QString description,
QString iconUrl, int appId)
{
auto item = new SpkAppItem(appId, this);
auto id = mAppItemList.size();
item->SetTitle(name);
item->SetDescription(description);
item->setProperty("pkg_name", pkgName);
auto iconRes = RES->RequestResource(id, pkgName, SpkResource::ResourceType::AppIcon,
iconUrl, 0);
QPixmap icon;
if(iconRes.status == SpkResource::ResourceStatus::Ready)
{
if(icon.loadFromData(iconRes.data))
item->SetIcon(icon.scaled(SpkAppItem::IconSize_,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
else
{
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
}
}
//TODO: prepare icons for loading entries
// else
// item->SetIcon(QPixmap(":/icons/loading_icon.svg").scaled(SpkAppItem::IconSize_));
mAppItemList.append(item);
mItemLay->addWidget(item);
}
void SpkPageAppList::ClearAll()
{
QWidget *itm;
QLayoutItem *layitm;
while((layitm = mItemLay->takeAt(0)))
{
itm = layitm->widget();
itm->hide();
itm->deleteLater();
}
mAppItemList.clear();
}
void SpkPageAppList::ResourceAcquisitionFinished(int id, ResourceResult result)
{
QPixmap icon;
auto item = mAppItemList[id];
if(result.status == SpkResource::ResourceStatus::Ready)
{
if(icon.loadFromData(result.data))
item->SetIcon(icon.scaled(SpkAppItem::IconSize_,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
else
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
}
else if(result.status == SpkResource::ResourceStatus::Failed)
{
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
RES->PurgeCachedResource(item->property("pkg_name").toString(),
SpkResource::ResourceType::AppIcon, 0);
}
}
void SpkPageAppList::Activated()
{
RES->Acquire(this, false);
}
}

18
gui/page/spkpagebase.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "page/spkpagebase.h"
SpkPageBase::SpkPageBase(QWidget *parent) : QScrollArea(parent)
{
}
void SpkPageBase::ResourceAcquisitionFinished(int id, ResourceResult result)
{
Q_UNUSED(id);
Q_UNUSED(result);
}
void SpkPageBase::Activated()
{
; // Do nothing
}

View File

@@ -1,7 +1,7 @@
#include <QApplication>
#include "spkabout.h"
#include "spkpageuitest.h"
#include "inc/page/spkpageuitest.h"
#include "spkpopup.h"
#include "spkui_general.h"
@@ -44,9 +44,9 @@ SpkUi::SpkPageUiTest::SpkPageUiTest(QWidget *parent) : QSplitter(parent)
Loading->setObjectName("spk_pg_qsstest_loading");
Loading->start();
AppItem = new SpkAppItem(this);
AppItem = new SpkAppItem(0, this);
AppItem->setObjectName("spk_pg_qsstest_appitem");
AppItem->SetTitle("Lorem Ipsum");
AppItem->SetTitle("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
AppItem->SetDescription("Nam vehicula lacus vitae leo fermentum efficitur. "
"Phasellus finibus risus id aliquam pulvinar.");
AppItem->SetIcon(QIcon::fromTheme("dialog-information").pixmap(72, 72));

133
gui/qt/elidedlabel.cpp Normal file
View File

@@ -0,0 +1,133 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module 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$
**
****************************************************************************/
/*
* Slight modifications has been done to the code to make it fit into the project.
*/
#include "qt/elidedlabel.h"
#include <QPainter>
#include <QSizePolicy>
#include <QTextLayout>
//! [0]
ElidedLabel::ElidedLabel(const QString &text, QWidget *parent)
: QFrame(parent)
, elided(false)
, content(text)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
//! [0]
//! [1]
void ElidedLabel::setText(const QString &newText)
{
content = newText;
update();
}
//! [1]
//! [2]
void ElidedLabel::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
QPainter painter(this);
QFontMetrics fontMetrics = painter.fontMetrics();
bool didElide = false;
int lineSpacing = fontMetrics.lineSpacing();
int y = 0;
QTextLayout textLayout(content, painter.font());
textLayout.beginLayout();
forever {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(width());
int nextLineY = y + lineSpacing;
if (height() >= nextLineY + lineSpacing) {
line.draw(&painter, QPoint(0, y));
y = nextLineY;
//! [2]
//! [3]
} else {
QString lastLine = content.mid(line.textStart());
QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
line = textLayout.createLine();
didElide = line.isValid();
break;
}
}
textLayout.endLayout();
//! [3]
//! [4]
if (didElide != elided) {
elided = didElide;
emit elisionChanged(didElide);
}
}
ElidedLabel::ElidedLabel(QWidget *parent)
: QFrame(parent)
, elided(false)
, content("")
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
//! [4]

View File

@@ -2,9 +2,14 @@
#include <QPainter>
#include <QStyleOption>
#include "spkappitem.h"
#include "qt/elidedlabel.h"
SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
const QSize SpkAppItem::IconSize_;
SpkAppItem::SpkAppItem(int appId, QWidget *parent) : QWidget(parent)
{
mAppId = appId;
mMainLay = new QHBoxLayout(this);
mLayText = new QVBoxLayout;
@@ -12,16 +17,17 @@ SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
mIcon->setFixedSize(IconSize, IconSize);
mIcon->setAutoFillBackground(false);
// NOTE: Make mTitle ElidedTitle too?
mTitle = new QLabel;
mTitle->setWordWrap(false);
mTitle->setObjectName("styAppItmTitle");
mTitle->setAutoFillBackground(true);
mDescription = new QLabel;
mDescription->setWordWrap(true);
mDescription = new ElidedLabel;
// mDescription->setWordWrap(true); // Commented out since ElidedLabel lacks of these methods
mDescription->setObjectName("styAppItmDesc");
mDescription->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDescription->setAlignment(Qt::AlignTop | Qt::AlignLeft);
mDescription->setAutoFillBackground(true);
// mDescription->setAlignment(Qt::AlignTop | Qt::AlignLeft);
// mDescription->setAutoFillBackground(true);
mLayText->addWidget(mTitle);
mLayText->addWidget(mDescription);
mLayText->setAlignment(Qt::AlignTop);
@@ -32,7 +38,7 @@ SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
setMinimumHeight(82);
setMaximumHeight(82);
setMinimumWidth(300);
setMaximumWidth(350);
// setMaximumWidth(350);
}
void SpkAppItem::paintEvent(QPaintEvent *e)

View File

@@ -11,6 +11,7 @@
SpkMainWindow::SpkMainWindow(QWidget *parent) : SpkWindow(parent)
{
ui = new SpkUi::SpkMainWidget(parent);
Initialize();
SetUseTitleBar(false);
SetCentralWidget(ui);
@@ -22,11 +23,23 @@ SpkMainWindow::SpkMainWindow(QWidget *parent) : SpkWindow(parent)
move(size.width(), size.height());
}
void SpkMainWindow::SwitchToPage(SpkUi::SpkStackedPages page)
{
if(mCurrentPage != page)
{
ui->Pager->setCurrentIndex(int(page));
mCurrentPage = page;
// If the page is a SpkPageBase (with a resource context), activate it for resource acquisition
auto tryActivate = qobject_cast<SpkPageBase *>(ui->Pager->currentWidget());
if(tryActivate)
tryActivate->Activated();
}
}
void SpkMainWindow::PopulateCategories(QJsonArray aCategoryData)
{
using SpkUi::SpkSidebarSelector;
QTreeWidgetItem *catg;
auto w = ui->CategoryWidget;
if(ui->CategoryParentItem->childCount()) // Clear all existing children if there is any
foreach(auto &i, ui->CategoryParentItem->takeChildren())
delete i;
@@ -71,10 +84,91 @@ void SpkMainWindow::CategoryDataReceived()
{
sErr(tr("Failed to load categories!"));
// TODO: Switch to an error page
return;
}
PopulateCategories(retval.toArray());
}
void SpkMainWindow::EnterCategoryList(int aCategoryId)
{
// Asynchronously call category API
using namespace SpkUtils;
VerifySingleRequest(mCategoryAppListGetReply);
QJsonObject reqData;
QJsonDocument reqDoc;
reqData.insert("type_id", QJsonValue(aCategoryId));
reqDoc.setObject(reqData);
mCategoryAppListGetReply = STORE->SendApiRequest("application/get_application_list", reqDoc);
DeleteReplyLater(mCategoryAppListGetReply);
connect(mCategoryAppListGetReply, &QNetworkReply::finished,
this, &SpkMainWindow::CategoryListDataReceived);
setCursor(Qt::BusyCursor);
}
void SpkMainWindow::CategoryListDataReceived()
{
QJsonValue retval;
if(!SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval) || !retval.isObject())
{
sErrPop(tr("Failed to load app list of category! Type of retval: %1.").arg(retval.type()));
return;
}
PopulateAppList(retval.toObject());
setCursor(Qt::ArrowCursor);
SwitchToPage(SpkUi::PgAppList);
}
void SpkMainWindow::PopulateAppList(QJsonObject appData)
{
auto w = ui->PageAppList;
w->ClearAll();
if(!appData.contains("data") || !appData.value("data").isArray())
{
sErrPop(tr("Received invalid application list data!"));
return;
}
auto apps = appData.value("data").toArray();
foreach(auto i, apps)
{
if(i.isObject())
{
auto j = i.toObject();
QString pkgName, displayName, description, iconPath;
int appid;
if(j.contains("package") && j.value("package").isString())
pkgName = j.value("package").toString();
else continue;
if(j.contains("application_name_zh") && j.value("application_name_zh").isString())
displayName = j.value("application_name_zh").toString();
else continue;
if(j.contains("description") && j.value("description").isString())
description = j.value("description").toString();
else continue;
if(j.contains("application_id") && j.value("application_id").isDouble())
appid = j.value("application_id").toInt();
else continue;
if(j.contains("icons") && j.value("icons").isString())
iconPath = j.value("icons").toString();
else continue;
w->AddApplicationEntry(displayName, pkgName, description, iconPath, appid);
}
}
}
// ==================== Main Window Initialization ====================
void SpkMainWindow::Initialize()
{
connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToCategory,
this, &SpkMainWindow::EnterCategoryList);
}
// ==================== Main Widget Initialization ====================
SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
{
setObjectName("spk_mainwidget");
@@ -169,9 +263,22 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
HorizontalDivide->addWidget(SideBarRestrictor);
HorizontalDivide->addLayout(VLayMain);
// Red-Black tree based map will be able to sort things
QMap<SpkStackedPages, QWidget*> sorter;
// Initialize pages
PageAppList = new SpkUi::SpkPageAppList(this);
PageAppList->setProperty("spk_pageid", SpkStackedPages::PgAppList);
sorter[PgAppList] = PageAppList;
#ifndef NDEBUG // If only in debug mode should we initialize QSS test page
PageQssTest = new SpkUi::SpkPageUiTest(this);
Pager->addWidget(PageQssTest);
PageQssTest->setProperty("spk_pageid", SpkStackedPages::PgQssTest);
sorter[PgQssTest] = PageQssTest;
#endif
for(auto i : sorter)
Pager->addWidget(i);
setLayout(HorizontalDivide);
}

84
gui/spkstretchlayout.cpp Normal file
View File

@@ -0,0 +1,84 @@
#include "spkstretchlayout.h"
SpkStretchLayout::SpkStretchLayout(QWidget *parent) : QLayout(parent)
{
}
SpkStretchLayout::~SpkStretchLayout()
{
QLayoutItem *item;
while((item = takeAt(0)))
delete item;
}
void SpkStretchLayout::addItem(QLayoutItem *item)
{
mItems.append(item);
}
QSize SpkStretchLayout::sizeHint() const
{
int n = mItems.count();
auto minSize = mItems.isEmpty() ? QSize { 0, 0 } : mItems.first()->minimumSize();
QSize s { 200, 50 }; // s: Hint
if(n)
s = s.expandedTo(QSize { minSize.width(), minSize.height() * n });
return s + n * QSize(spacing(), spacing());
}
QSize SpkStretchLayout::minimumSize() const
{
int n = mItems.count();
auto minSize = mItems.isEmpty() ? QSize { 0, 0 } : mItems.first()->minimumSize();
QSize s(minSize.width(), minSize.height() * n);
return s + n * QSize(spacing(), spacing());
}
int SpkStretchLayout::count() const
{
return mItems.size();
}
QLayoutItem *SpkStretchLayout::itemAt(int i) const
{
return mItems.value(i);
}
QLayoutItem *SpkStretchLayout::takeAt(int i)
{
return i >=0 && i < mItems.size() ? mItems.takeAt(i) : nullptr;
}
void SpkStretchLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
if(mItems.isEmpty())
return;
int spc = spacing(), w = rect.width() - spc;
QSize size;
auto itm = mItems.first();
// All items are considered the same, so we only calculate with the first item.
// Figure out how many at most can we squeeze into one line
int countPerLine = w / (itm->minimumSize().width() + spacing());
if(countPerLine >= mItems.size()) // All items fit in one line
size = itm->minimumSize();
// Won't fit in one line.
else // Stretch items.
size = QSize {(w / countPerLine - spc), itm->maximumSize().height() };
QLayoutItem *o;
for(int i = 0; i < mItems.size(); i++)
{
o = mItems.at(i);
QRect geo;
geo.setSize(size);
geo.moveTo((i % countPerLine) * (size.width() + spc) + spc,
(i / countPerLine) * (size.height() + spacing()) + spc);
o->setGeometry(geo);
}
}

View File

@@ -271,5 +271,4 @@ namespace SpkUi
else
SetGlobalStyle(Light, true);
}
}