mirror of
https://gitee.com/spark-store-project/spark-store
synced 2025-11-14 04:52:20 +08:00
实现应用列表和SpkResource
SpkResource现有已知问题:首次使用不会完全进行所有任务。
This commit is contained in:
parent
774e347957
commit
ca57a7ac86
@ -56,6 +56,8 @@ set(SOURCE_FILES
|
||||
inc/telemetry/collectid.h
|
||||
inc/spkqsshelper.h
|
||||
|
||||
inc/qt/elidedlabel.h gui/qt/elidedlabel.cpp
|
||||
|
||||
inc/spkwindow.h gui/spkwindow.cpp
|
||||
inc/spktitlebar.h gui/spktitlebar.cpp
|
||||
inc/spkui_general.h gui/spkui_general.cpp
|
||||
@ -63,11 +65,15 @@ set(SOURCE_FILES
|
||||
inc/spkmsgbox.h gui/spkmsgbox.cpp
|
||||
inc/spkdialog.h gui/spkdialog.cpp
|
||||
inc/spkabout.h gui/spkabout.cpp
|
||||
inc/spkpageuitest.h gui/spkpageuitest.cpp
|
||||
inc/spkloading.h gui/spkloading.cpp
|
||||
inc/spksidebartree.h gui/spksidebartree.cpp
|
||||
inc/spkappitem.h gui/spkappitem.cpp
|
||||
inc/spkpopup.h gui/spkpopup.cpp
|
||||
inc/spkstretchlayout.h gui/spkstretchlayout.cpp
|
||||
|
||||
inc/page/spkpagebase.h gui/page/spkpagebase.cpp
|
||||
inc/page/spkpageuitest.h gui/page/spkpageuitest.cpp
|
||||
inc/page/spkpageapplist.h gui/page/spkpageapplist.cpp
|
||||
|
||||
inc/spkstore.h src/spkstore.cpp
|
||||
inc/spkuimsg.h src/spkuimsg.cpp
|
||||
|
||||
43
docs/structure_and_naming.md
Normal file
43
docs/structure_and_naming.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 项目结构和命名规范
|
||||
|
||||
## 文件夹结构
|
||||
|
||||
### cmake
|
||||
|
||||
主要用于 CMake 配置时需要用到的 CMake 模块脚本。
|
||||
|
||||
### gui
|
||||
|
||||
主要用于显示界面的 C++ 代码文件(即包含 Qt Widgets 代码),无论包含多少与其他组件的联系,都在这个目录内。
|
||||
这包括 SpkUi 的自定义界面组件、商店主界面自身的 Page 类等等。
|
||||
|
||||
### inc
|
||||
|
||||
项目中所有以 `#include "xxx.h"` 方式包含到代码中的标头文件,全部放入此目录。
|
||||
标头文件并不强求目录结构明确,文件用途或来源特殊的除外。
|
||||
|
||||
### src
|
||||
|
||||
商店主体逻辑。包含基础的 `SpkStore` 类、`main.cpp` 等核心逻辑。
|
||||
新加入的逻辑如单个功能多于一个cpp文件,则必须分装到一个目录内。
|
||||
|
||||
### plugin
|
||||
|
||||
适配 Deepin 以及其他平台的平台相关插件。
|
||||
|
||||
## 命名规范
|
||||
|
||||
### 通用规范
|
||||
|
||||
类名、成员、方法名一律采用大驼峰形式。例如,`SpkTitleBar::SetTitle` 方法。
|
||||
C++ 规范要求的和第三方库有较大差异的除外。例如,`SpkUiMessage::_notify`成员。
|
||||
临时变量采用小驼峰形式或全小写。
|
||||
|
||||
名称要对功能要有基础的描述,例如,`SpkUi::SpkCategorySelector` 类。抽象核心逻辑除外。
|
||||
|
||||
### 类名
|
||||
|
||||
如非第三方代码,一般以 `Spk` 前缀标明。
|
||||
实现 UI 的类都应归于 `SpkUi` 命名空间。
|
||||
|
||||
<!--TODO-->
|
||||
85
gui/page/spkpageapplist.cpp
Normal file
85
gui/page/spkpageapplist.cpp
Normal 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
18
gui/page/spkpagebase.cpp
Normal 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
|
||||
}
|
||||
@ -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
133
gui/qt/elidedlabel.cpp
Normal 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]
|
||||
@ -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)
|
||||
|
||||
@ -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
84
gui/spkstretchlayout.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -271,5 +271,4 @@ namespace SpkUi
|
||||
else
|
||||
SetGlobalStyle(Light, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
38
inc/page/spkpageapplist.h
Normal file
38
inc/page/spkpageapplist.h
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QList>
|
||||
#include "spkresource.h"
|
||||
#include "spkappitem.h"
|
||||
#include "page/spkpagebase.h"
|
||||
#include "spkstretchlayout.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPageAppList : public SpkPageBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageAppList(QWidget *parent = nullptr);
|
||||
|
||||
void AddApplicationEntry(QString name, QString pkgName, QString description, QString iconUrl,
|
||||
int appId);
|
||||
void ClearAll();
|
||||
|
||||
private:
|
||||
|
||||
public:
|
||||
|
||||
private:
|
||||
SpkStretchLayout *mItemLay;
|
||||
QList<SpkAppItem *> mAppItemList;
|
||||
|
||||
signals:
|
||||
void ApplicationClicked(QString name, QString pkgName);
|
||||
|
||||
public slots:
|
||||
void ResourceAcquisitionFinished(int id, ResourceResult result);
|
||||
void Activated();
|
||||
};
|
||||
}
|
||||
27
inc/page/spkpagebase.h
Normal file
27
inc/page/spkpagebase.h
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <spkresource.h>
|
||||
|
||||
class SpkPageBase : public QScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageBase(QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief This signal is emitted by resource manager when a resource acquisition requested
|
||||
* has finished.
|
||||
* @param id The request ID
|
||||
* @param result The data retrieved
|
||||
*/
|
||||
virtual void ResourceAcquisitionFinished(int id, ResourceResult result);
|
||||
|
||||
/**
|
||||
* @brief This is an optional signal for Resource Context objects, mainly used for notifying the
|
||||
* context that it is now activated (therefore it needs to acquire the resource manager).
|
||||
*/
|
||||
virtual void Activated();
|
||||
};
|
||||
87
inc/qt/elidedlabel.h
Normal file
87
inc/qt/elidedlabel.h
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 <QFrame>
|
||||
#include <QString>
|
||||
|
||||
//! [0]
|
||||
class ElidedLabel : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString text READ text WRITE setText)
|
||||
Q_PROPERTY(bool isElided READ isElided)
|
||||
|
||||
public:
|
||||
explicit ElidedLabel(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
explicit ElidedLabel(QWidget *parent = nullptr);
|
||||
|
||||
void setText(const QString &text);
|
||||
const QString & text() const { return content; }
|
||||
bool isElided() const { return elided; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void elisionChanged(bool elided);
|
||||
|
||||
private:
|
||||
bool elided;
|
||||
QString content;
|
||||
};
|
||||
//! [0]
|
||||
@ -4,13 +4,14 @@
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include "qt/elidedlabel.h"
|
||||
|
||||
class SpkAppItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkAppItem(QWidget *parent = nullptr);
|
||||
|
||||
public:
|
||||
SpkAppItem(int appId, QWidget *parent = nullptr);
|
||||
void SetIcon(QPixmap p) { mIcon->setPixmap(p); }
|
||||
void SetTitle(QString s) { mTitle->setText(s); }
|
||||
void SetDescription(QString s) { mDescription->setText(s); }
|
||||
@ -18,14 +19,17 @@ class SpkAppItem : public QWidget
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e);
|
||||
|
||||
public:
|
||||
static constexpr int IconSize = 72;
|
||||
static constexpr QSize IconSize_ = { IconSize, IconSize };
|
||||
|
||||
private:
|
||||
QLabel *mIcon;
|
||||
QLabel *mTitle;
|
||||
QLabel *mDescription;
|
||||
ElidedLabel *mDescription;
|
||||
int mAppId;
|
||||
|
||||
QVBoxLayout *mLayText;
|
||||
QHBoxLayout *mMainLay;
|
||||
|
||||
static constexpr int IconSize = 72;
|
||||
};
|
||||
|
||||
|
||||
@ -5,13 +5,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "spkwindow.h"
|
||||
#include <vector>
|
||||
#include <QTextEdit>
|
||||
#include <QStackedWidget>
|
||||
#include <QButtonGroup>
|
||||
#include <QJsonObject>
|
||||
#include "spksidebartree.h" // In place of #include <QTreeWidget>
|
||||
#include <QPointer>
|
||||
#include "spkpageuitest.h"
|
||||
#include "inc/page/spkpageuitest.h"
|
||||
#include "inc/page/spkpageapplist.h"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
@ -19,9 +21,16 @@ namespace SpkUi
|
||||
{
|
||||
enum SpkStackedPages
|
||||
{
|
||||
PgInvalid = -1,
|
||||
PgAppList,
|
||||
PgQssTest // Must be at last
|
||||
};
|
||||
|
||||
const std::vector<SpkStackedPages> ResourceContexts
|
||||
{
|
||||
PgAppList
|
||||
};
|
||||
|
||||
class SpkSidebarSelector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -139,6 +148,7 @@ namespace SpkUi
|
||||
|
||||
//Pages
|
||||
SpkPageUiTest *PageQssTest;
|
||||
SpkPageAppList *PageAppList;
|
||||
};
|
||||
}
|
||||
|
||||
@ -154,11 +164,24 @@ class SpkMainWindow : public SpkWindow
|
||||
void PopulateCategories(QJsonArray);
|
||||
|
||||
private:
|
||||
QPointer<QNetworkReply> mCategoryGetReply;
|
||||
void Initialize();
|
||||
|
||||
private:
|
||||
QPointer<QNetworkReply> mCategoryGetReply,
|
||||
mCategoryAppListGetReply;
|
||||
SpkUi::SpkStackedPages mCurrentPage = SpkUi::PgInvalid;
|
||||
|
||||
public slots:
|
||||
void RefreshCategoryData();
|
||||
|
||||
private slots:
|
||||
void SwitchToPage(SpkUi::SpkStackedPages page);
|
||||
|
||||
void CategoryDataReceived();
|
||||
|
||||
void EnterCategoryList(int id);
|
||||
void CategoryListDataReceived();
|
||||
|
||||
private:
|
||||
void PopulateAppList(QJsonObject appData);
|
||||
};
|
||||
|
||||
@ -23,6 +23,7 @@ namespace SpkUi
|
||||
LightCtrlsGradDarker,
|
||||
DarkCtrlsGradLight,
|
||||
DarkCtrlsGradDark,
|
||||
DarkCtrlsGradDarker,
|
||||
TextOnSelection, TextOnAccentColor = TextOnSelection,
|
||||
TextOnGlobalBgnd,
|
||||
TextOnControlsBgnd,
|
||||
@ -50,6 +51,7 @@ namespace SpkUi
|
||||
{ LightCtrlsGradDarker, "LCTL3" },
|
||||
{ DarkCtrlsGradLight, "DCTL1" },
|
||||
{ DarkCtrlsGradDark, "DCTL2" },
|
||||
{ DarkCtrlsGradDarker, "DCTL3" },
|
||||
{ TextOnSelection, "TXACC" },
|
||||
{ TextOnGlobalBgnd, "TXGBG" },
|
||||
{ TextOnControlsBgnd, "TXCBG" },
|
||||
@ -71,6 +73,7 @@ namespace SpkUi
|
||||
{ LightCtrlsGradDarker, 0x606060 },
|
||||
{ DarkCtrlsGradLight, 0x404040 },
|
||||
{ DarkCtrlsGradDark, 0x383838 },
|
||||
{ DarkCtrlsGradDarker, 0x323232 },
|
||||
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
|
||||
{ TextOnGlobalBgnd, ColorTextOnBackground(0x282828) },
|
||||
{ TextOnControlsBgnd, ColorTextOnBackground(0x282828) },
|
||||
@ -91,12 +94,13 @@ namespace SpkUi
|
||||
{ LightCtrlsGradDarker, 0xebebeb },
|
||||
{ DarkCtrlsGradLight, 0xe4e4e4 },
|
||||
{ DarkCtrlsGradDark, 0xcecece },
|
||||
{ DarkCtrlsGradDarker, 0xb8b8b8 },
|
||||
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
|
||||
{ TextOnGlobalBgnd, ColorTextOnBackground(0xf8f8f8) },
|
||||
{ TextOnControlsBgnd, ColorTextOnBackground(0xf8f8f8) },
|
||||
{ TextLighter, 0x2a2a2a },
|
||||
{ GlossyEdge, 0x9d9d9d },
|
||||
{ ShadesEdge, 0xc5c5c5 }
|
||||
{ GlossyEdge, 0xc5c5c5 },
|
||||
{ ShadesEdge, 0x9d9d9d }
|
||||
};
|
||||
|
||||
using ColorSet = std::map<Qss::ColorSetIndex, QColor>;
|
||||
|
||||
@ -2,13 +2,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QSemaphore>
|
||||
#include <QMap>
|
||||
#include <QNetworkReply>
|
||||
#include <QVariant>
|
||||
|
||||
struct ResourceResult;
|
||||
class SpkResourceContext;
|
||||
class SpkPageBase;
|
||||
|
||||
class SpkResource : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class ResourceType { HomeImage, AppIcon, TagIcon, AppScreenshot, };
|
||||
enum class ResourceStatus
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkResource(QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
|
||||
Ready, ///< The resource is ready to be read now and the file link is included
|
||||
Deferred, ///< The resource is not in cache or outdated, and is being downloaded
|
||||
Failed, ///< The requested resource can not be downloaded and thus can not be loaded.
|
||||
};
|
||||
|
||||
private:
|
||||
struct ResourceTask
|
||||
{
|
||||
QString pkgName, path;
|
||||
ResourceType type;
|
||||
QVariant info;
|
||||
int id;
|
||||
};
|
||||
|
||||
static const QMap<ResourceType, QString> ResourceName;
|
||||
|
||||
public:
|
||||
SpkResource(QObject *parent = nullptr);
|
||||
ResourceResult
|
||||
RequestResource(const int aId, const QString &aPkgName, ResourceType aType, const QString &aPath,
|
||||
const QVariant &aInfo = 0);
|
||||
void PurgeCachedResource(const QString &aPkgName, SpkResource::ResourceType aType,
|
||||
const QVariant &aInfo);
|
||||
|
||||
inline QString GetCachePath(const ResourceTask &task);
|
||||
|
||||
/**
|
||||
* @brief When the resource context was changed, the new context needs to acquire the resource
|
||||
* manager, so the resource manager can download resource for the new context.
|
||||
*
|
||||
* A resource context, in Spark Store, is likely to be a "page" of the UI. AppList is
|
||||
* a page, Homepage is a page, AppDetails is a page. But each category is not a separated
|
||||
* context; they're all from AppList page.
|
||||
* @param dest Pass a SpkResourceContext as the resource acquisition destination. Signals will
|
||||
* be connected in this function internally. When a resource was downloaded, this
|
||||
* object will be notified.
|
||||
* @param clearQueue determines if the awaiting tasks needs to be cleared.
|
||||
* @param stopOngoing determines if ongoing tasks needs to be terminated.
|
||||
*/
|
||||
void Acquire(SpkPageBase *dest, bool stopOngoing, bool clearQueue = true);
|
||||
|
||||
public:
|
||||
static SpkResource* Instance;
|
||||
const int mMaximumConcurrent; ///< Maximum number of concurrent resource downloads
|
||||
const QString mCacheDirectory;///< Where caches were stored
|
||||
|
||||
private slots:
|
||||
void ResourceDownloaded();
|
||||
void TryBeginAwaitingTasks();
|
||||
|
||||
private:
|
||||
ResourceResult LocateCachedResource(const ResourceTask &task);
|
||||
|
||||
signals:
|
||||
void AcquisitionFinish(int id, ResourceResult result);
|
||||
|
||||
private:
|
||||
// Operations to mAwaitingRequests and mWorkingRequests must be all made synchronously.
|
||||
// When connecting signal/slot pairs regarding operations to them, use QueuedConnection.
|
||||
QQueue<ResourceTask> mAwaitingRequests;
|
||||
QSemaphore *mRequestSemaphore;
|
||||
QMap<QNetworkReply*, int> mWorkingRequests;
|
||||
};
|
||||
|
||||
|
||||
struct ResourceResult
|
||||
{
|
||||
SpkResource::ResourceStatus status;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ResourceResult);
|
||||
|
||||
/**
|
||||
* @brief SpkResourceContext is meant to be used by connecting signals from it. An object inheriting
|
||||
* it would be theoretically a resource context. A resource context can acquire a SpkResource
|
||||
* management object for downloading resources. More details at SpkResource::Acquire.
|
||||
*/
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
#include "spklogging.h"
|
||||
#include "spkmainwindow.h"
|
||||
#include "spkresource.h"
|
||||
|
||||
/**
|
||||
* @brief SpkStore class is the core of the store client side program, it is constructed first and
|
||||
@ -27,13 +28,19 @@ class SpkStore : public QObject
|
||||
|
||||
SpkMainWindow* GetRootWindow() { return mMainWindow; }
|
||||
|
||||
void SetApiResuestUrl(QString aUrlStr) { mApiRequestUrl = aUrlStr; }
|
||||
void SetApiRequestUrl(QString aUrlStr) { mApiRequestUrl = aUrlStr; }
|
||||
QString GetApiRequestUrl() { return mApiRequestUrl; }
|
||||
QNetworkReply *SendApiRequest(QString path, QJsonDocument param = QJsonDocument());
|
||||
QNetworkReply *SendResourceRequest(QString path); ///< WARNING: Only intended for SpkResource!
|
||||
|
||||
private:
|
||||
SpkLogger *mLogger;
|
||||
SpkMainWindow *mMainWindow = nullptr;
|
||||
SpkResource *mResMgr = nullptr;
|
||||
QNetworkAccessManager *mNetMgr = nullptr;
|
||||
QString mDistroName, mApiRequestUrl, mUserAgentStr, mConfigPath;
|
||||
QString mDistroName,
|
||||
mApiRequestUrl,
|
||||
mResourceRequestUrl,
|
||||
mUserAgentStr,
|
||||
mConfigPath;
|
||||
};
|
||||
|
||||
27
inc/spkstretchlayout.h
Normal file
27
inc/spkstretchlayout.h
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QVector>
|
||||
|
||||
/**
|
||||
* @brief SpkStretchLayout is meant solely for use with app list, where each layout item is
|
||||
* considered the same size.
|
||||
*/
|
||||
class SpkStretchLayout : public QLayout
|
||||
{
|
||||
public:
|
||||
SpkStretchLayout(QWidget *parent = nullptr);
|
||||
~SpkStretchLayout();
|
||||
|
||||
void addItem(QLayoutItem *item) override;
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSize() const override;
|
||||
int count() const override;
|
||||
QLayoutItem* itemAt(int) const override;
|
||||
QLayoutItem* takeAt(int) override;
|
||||
void setGeometry(const QRect &rect) override;
|
||||
|
||||
private:
|
||||
QVector<QLayoutItem*> mItems;
|
||||
};
|
||||
@ -10,8 +10,9 @@
|
||||
#include "spkstore.h"
|
||||
#include "spklogging.h"
|
||||
|
||||
#define STORE SpkStore::Instance
|
||||
#define STORE (SpkStore::Instance)
|
||||
#define CFG (SpkStore::Instance->mCfg)
|
||||
#define RES (SpkResource::Instance)
|
||||
|
||||
namespace SpkUtils
|
||||
{
|
||||
@ -22,4 +23,7 @@ namespace SpkUtils
|
||||
void DeleteReplyLater(QNetworkReply *aReply);
|
||||
|
||||
bool VerifyReplyJson(QNetworkReply *aReply, QJsonValue& aRetDoc);
|
||||
|
||||
QString CutFileName(QString);
|
||||
QString CutPath(QString);
|
||||
}
|
||||
|
||||
240
inc/xcb/xcbint.h
240
inc/xcb/xcbint.h
@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2001-2004 Bart Massey and Jamey Sharp.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the names of the authors or their
|
||||
* institutions shall not be used in advertising or otherwise to promote the
|
||||
* sale, use or other dealings in this Software without prior written
|
||||
* authorization from the authors.
|
||||
*/
|
||||
|
||||
#ifndef __XCBINT_H
|
||||
#define __XCBINT_H
|
||||
|
||||
#include "bigreq.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef GCC_HAS_VISIBILITY
|
||||
#pragma GCC visibility push(hidden)
|
||||
#endif
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with older compilers. */
|
||||
#endif
|
||||
|
||||
#if __has_attribute(fallthrough)
|
||||
# define XCB_ALLOW_FALLTHRU __attribute__ ((fallthrough));
|
||||
#else
|
||||
# define XCB_ALLOW_FALLTHRU /* FALLTHRU */
|
||||
#endif
|
||||
|
||||
enum workarounds {
|
||||
WORKAROUND_NONE,
|
||||
WORKAROUND_GLX_GET_FB_CONFIGS_BUG,
|
||||
WORKAROUND_EXTERNAL_SOCKET_OWNER
|
||||
};
|
||||
|
||||
enum lazy_reply_tag
|
||||
{
|
||||
LAZY_NONE = 0,
|
||||
LAZY_COOKIE,
|
||||
LAZY_FORCED
|
||||
};
|
||||
|
||||
#define XCB_PAD(i) (-(i) & 3)
|
||||
|
||||
#define XCB_SEQUENCE_COMPARE(a,op,b) ((int64_t) ((a) - (b)) op 0)
|
||||
|
||||
#ifndef offsetof
|
||||
#define offsetof(type,member) ((size_t) &((type *)0)->member)
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
#define container_of(pointer,type,member) ((type *)(((char *)(pointer)) - offsetof(type, member)))
|
||||
|
||||
/* xcb_list.c */
|
||||
|
||||
typedef void (*xcb_list_free_func_t)(void *);
|
||||
|
||||
typedef struct _xcb_map _xcb_map;
|
||||
|
||||
_xcb_map *_xcb_map_new(void);
|
||||
void _xcb_map_delete(_xcb_map *q, xcb_list_free_func_t do_free);
|
||||
int _xcb_map_put(_xcb_map *q, unsigned int key, void *data);
|
||||
void *_xcb_map_remove(_xcb_map *q, unsigned int key);
|
||||
|
||||
|
||||
/* xcb_out.c */
|
||||
|
||||
#if HAVE_SENDMSG
|
||||
#define XCB_MAX_PASS_FD 16
|
||||
|
||||
typedef struct _xcb_fd {
|
||||
int fd[XCB_MAX_PASS_FD];
|
||||
int nfd;
|
||||
int ifd;
|
||||
} _xcb_fd;
|
||||
#endif
|
||||
|
||||
typedef struct _xcb_out {
|
||||
pthread_cond_t cond;
|
||||
int writing;
|
||||
|
||||
pthread_cond_t socket_cond;
|
||||
void (*return_socket)(void *closure);
|
||||
void *socket_closure;
|
||||
int socket_moving;
|
||||
|
||||
char queue[XCB_QUEUE_BUFFER_SIZE];
|
||||
int queue_len;
|
||||
|
||||
uint64_t request;
|
||||
uint64_t request_written;
|
||||
uint64_t total_written;
|
||||
|
||||
pthread_mutex_t reqlenlock;
|
||||
enum lazy_reply_tag maximum_request_length_tag;
|
||||
union {
|
||||
xcb_big_requests_enable_cookie_t cookie;
|
||||
uint32_t value;
|
||||
} maximum_request_length;
|
||||
#if HAVE_SENDMSG
|
||||
_xcb_fd out_fd;
|
||||
#endif
|
||||
} _xcb_out;
|
||||
|
||||
int _xcb_out_init(_xcb_out *out);
|
||||
void _xcb_out_destroy(_xcb_out *out);
|
||||
|
||||
int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count);
|
||||
void _xcb_out_send_sync(xcb_connection_t *c);
|
||||
int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request);
|
||||
|
||||
|
||||
/* xcb_in.c */
|
||||
|
||||
typedef struct _xcb_in {
|
||||
pthread_cond_t event_cond;
|
||||
int reading;
|
||||
|
||||
char queue[4096];
|
||||
int queue_len;
|
||||
|
||||
uint64_t request_expected;
|
||||
uint64_t request_read;
|
||||
uint64_t request_completed;
|
||||
uint64_t total_read;
|
||||
struct reply_list *current_reply;
|
||||
struct reply_list **current_reply_tail;
|
||||
|
||||
_xcb_map *replies;
|
||||
struct event_list *events;
|
||||
struct event_list **events_tail;
|
||||
struct reader_list *readers;
|
||||
struct special_list *special_waiters;
|
||||
|
||||
struct pending_reply *pending_replies;
|
||||
struct pending_reply **pending_replies_tail;
|
||||
#if HAVE_SENDMSG
|
||||
_xcb_fd in_fd;
|
||||
#endif
|
||||
struct xcb_special_event *special_events;
|
||||
} _xcb_in;
|
||||
|
||||
int _xcb_in_init(_xcb_in *in);
|
||||
void _xcb_in_destroy(_xcb_in *in);
|
||||
|
||||
void _xcb_in_wake_up_next_reader(xcb_connection_t *c);
|
||||
|
||||
int _xcb_in_expect_reply(xcb_connection_t *c, uint64_t request, enum workarounds workaround, int flags);
|
||||
void _xcb_in_replies_done(xcb_connection_t *c);
|
||||
|
||||
int _xcb_in_read(xcb_connection_t *c);
|
||||
int _xcb_in_read_block(xcb_connection_t *c, void *buf, int nread);
|
||||
|
||||
|
||||
/* xcb_xid.c */
|
||||
|
||||
typedef struct _xcb_xid {
|
||||
pthread_mutex_t lock;
|
||||
uint32_t last;
|
||||
uint32_t base;
|
||||
uint32_t max;
|
||||
uint32_t inc;
|
||||
} _xcb_xid;
|
||||
|
||||
int _xcb_xid_init(xcb_connection_t *c);
|
||||
void _xcb_xid_destroy(xcb_connection_t *c);
|
||||
|
||||
|
||||
/* xcb_ext.c */
|
||||
|
||||
typedef struct _xcb_ext {
|
||||
pthread_mutex_t lock;
|
||||
struct lazyreply *extensions;
|
||||
int extensions_size;
|
||||
} _xcb_ext;
|
||||
|
||||
int _xcb_ext_init(xcb_connection_t *c);
|
||||
void _xcb_ext_destroy(xcb_connection_t *c);
|
||||
|
||||
|
||||
/* xcb_conn.c */
|
||||
|
||||
struct xcb_connection_t {
|
||||
/* This must be the first field; see _xcb_conn_ret_error(). */
|
||||
int has_error;
|
||||
|
||||
/* constant data */
|
||||
xcb_setup_t *setup;
|
||||
int fd;
|
||||
|
||||
/* I/O data */
|
||||
pthread_mutex_t iolock;
|
||||
_xcb_in in;
|
||||
_xcb_out out;
|
||||
|
||||
/* misc data */
|
||||
_xcb_ext ext;
|
||||
_xcb_xid xid;
|
||||
};
|
||||
|
||||
void _xcb_conn_shutdown(xcb_connection_t *c, int err);
|
||||
|
||||
xcb_connection_t *_xcb_conn_ret_error(int err);
|
||||
|
||||
int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count);
|
||||
|
||||
|
||||
/* xcb_auth.c */
|
||||
|
||||
int _xcb_get_auth_info(int fd, xcb_auth_info_t *info, int display);
|
||||
|
||||
#ifdef GCC_HAS_VISIBILITY
|
||||
#pragma GCC visibility pop
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -56,7 +56,7 @@ QPushButton
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
border-radius: 8px;
|
||||
border-color: LCTL2;
|
||||
border-color: GLS;
|
||||
border-top-color: SHD;
|
||||
border-style: solid;
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 LCTL1, stop:1 LCTL2)
|
||||
@ -90,6 +90,11 @@ QScrollBar::add-line, QScrollBar::sub-line
|
||||
|
||||
/* Custom widgets */
|
||||
|
||||
ElidedLabel
|
||||
{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#styAboutDesc
|
||||
{
|
||||
color: TXL
|
||||
@ -104,7 +109,11 @@ SpkAppItem
|
||||
SpkAppItem::hover
|
||||
{
|
||||
background: DCTL1;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
SpkAppItem::pressed
|
||||
{
|
||||
background: DCTL3;
|
||||
}
|
||||
|
||||
#styAppItmTitle
|
||||
|
||||
@ -0,0 +1,199 @@
|
||||
|
||||
#include <QDir>
|
||||
#include "page/spkpagebase.h"
|
||||
#include "spkutils.h"
|
||||
#include "spkresource.h"
|
||||
|
||||
SpkResource* SpkResource::Instance = nullptr;
|
||||
|
||||
// If you want to iterate all keys in a QMap, there's no good way to do that, nor does Clazy have.
|
||||
// Clazy here is just a piece of shit nagging you like crazy and never gives you the actual
|
||||
// solution.
|
||||
// clazy:excludeall=container-anti-pattern
|
||||
|
||||
SpkResource::SpkResource(QObject *parent) : QObject(parent),
|
||||
mMaximumConcurrent(CFG->value("download/resource_concurrent_count", 5).toInt()),
|
||||
mCacheDirectory(CFG->value("cache_directory", "%1/.cache/spark-store/res/")
|
||||
.toString()
|
||||
.arg(QDir::homePath()))
|
||||
{
|
||||
Q_ASSERT(!Instance);
|
||||
qRegisterMetaType<ResourceResult>();
|
||||
Instance = this;
|
||||
mRequestSemaphore = new QSemaphore(mMaximumConcurrent);
|
||||
|
||||
QString path = mCacheDirectory.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
if(!QDir().exists(path))
|
||||
{
|
||||
if(!QDir().mkpath(path))
|
||||
{
|
||||
sErr(tr("Cache directory \"%1\" cannot be created.").arg(path));
|
||||
return;
|
||||
}
|
||||
else
|
||||
sLog(tr("Created cache directory \"%1\".").arg(path));
|
||||
}
|
||||
}
|
||||
|
||||
ResourceResult
|
||||
SpkResource::RequestResource(const int aId, const QString &aPkgName, ResourceType aType,
|
||||
const QString &aPath, const QVariant &aInfo)
|
||||
{
|
||||
auto ret = LocateCachedResource(ResourceTask
|
||||
{ .pkgName = aPkgName, .path = aPath, .type = aType,
|
||||
.info = aInfo, .id = aId });
|
||||
|
||||
if(ret.status == ResourceStatus::Ready)
|
||||
return ret;
|
||||
|
||||
mAwaitingRequests.enqueue(ResourceTask
|
||||
{ .pkgName = aPkgName, .path = aPath, .type = aType, .info = aInfo,
|
||||
.id = aId });
|
||||
TryBeginAwaitingTasks();
|
||||
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
}
|
||||
|
||||
void SpkResource::ResourceDownloaded()
|
||||
{
|
||||
auto reply = qobject_cast<QNetworkReply*>(sender());
|
||||
auto id = mWorkingRequests.value(reply);
|
||||
mWorkingRequests.remove(reply); // Remove in time
|
||||
mRequestSemaphore->release(); // Release semaphore resource
|
||||
|
||||
ResourceResult ret;
|
||||
ret.status = ResourceStatus::Ready;
|
||||
ret.data = reply->readAll();
|
||||
|
||||
// Save cache to disk
|
||||
auto cacheFile = reply->property("dest_file").toString();
|
||||
QFile writeCache(cacheFile);
|
||||
QString path = cacheFile.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
if(!QDir().exists(path))
|
||||
{
|
||||
if(!QDir().mkpath(path))
|
||||
{
|
||||
sWarn(tr("Cache directory \"%1\" cannot be created.").arg(path));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(writeCache.open(QFile::WriteOnly))
|
||||
{
|
||||
writeCache.write(ret.data);
|
||||
writeCache.close();
|
||||
}
|
||||
else
|
||||
sWarn("Save cache to \"" + cacheFile + "\" failed! Msg: " + writeCache.errorString());
|
||||
|
||||
// Tell ResourceContext
|
||||
AcquisitionFinish(id, ret);
|
||||
// Start next possible mission
|
||||
TryBeginAwaitingTasks();
|
||||
}
|
||||
|
||||
void SpkResource::Acquire(SpkPageBase *dest, bool stopOngoing, bool clearQueue)
|
||||
{
|
||||
if(stopOngoing)
|
||||
{
|
||||
for(auto &i : mWorkingRequests.keys())
|
||||
{
|
||||
// Don't let forced abort falsely report a finish signal. Disconnect them first.
|
||||
i->disconnect(i, &QNetworkReply::finished, this, &SpkResource::ResourceDownloaded);
|
||||
i->abort();
|
||||
delete i;
|
||||
}
|
||||
mWorkingRequests.clear();
|
||||
|
||||
mRequestSemaphore->release(mMaximumConcurrent); // Release all semaphore users
|
||||
}
|
||||
|
||||
if(clearQueue)
|
||||
mAwaitingRequests.clear();
|
||||
|
||||
disconnect(this, SLOT(AcquisitionFinish(int, ResourceResult)));
|
||||
|
||||
connect(this, &SpkResource::AcquisitionFinish,
|
||||
dest, &SpkPageBase::ResourceAcquisitionFinished,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
ResourceResult SpkResource::LocateCachedResource(const ResourceTask &task)
|
||||
{
|
||||
// TODO: Test overhead of all these?
|
||||
auto dir = QDir(mCacheDirectory + task.pkgName + '/', ResourceName[task.type] + '*');
|
||||
auto list = dir.entryList();
|
||||
|
||||
// If there's not even a file of the desired type, then tell invoker it's deferred
|
||||
if(list.isEmpty())
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
|
||||
// If there is the desired file, then we return the resource in binary
|
||||
auto cacheFullPath = GetCachePath(task);
|
||||
if(list.contains(SpkUtils::CutFileName(cacheFullPath)))
|
||||
{
|
||||
// qInfo() << "Cache hit:" << GetCachePath(task);
|
||||
QFile cacheFile(cacheFullPath);
|
||||
if(cacheFile.open(QFile::ReadOnly))
|
||||
{
|
||||
ResourceResult ret { .status = ResourceStatus::Ready,
|
||||
.data = cacheFile.readAll()};
|
||||
cacheFile.close();
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache file is unreadable, error and will be deleted
|
||||
sErr(tr("Desired cache file \"%1\" is unreadable by you!"));
|
||||
}
|
||||
}
|
||||
|
||||
// If above two cases are not met, then we have outdated resources. Remove them.
|
||||
for(auto &i : list)
|
||||
QFile::remove(i);
|
||||
|
||||
// Nothing to give you. Wait for completion.
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
}
|
||||
|
||||
void SpkResource::TryBeginAwaitingTasks()
|
||||
{
|
||||
if(mAwaitingRequests.isEmpty())
|
||||
return;
|
||||
while(!mAwaitingRequests.isEmpty() && mRequestSemaphore->tryAcquire())
|
||||
{
|
||||
auto task = mAwaitingRequests.dequeue();
|
||||
// If no cached items exist, go download it
|
||||
auto reply = STORE->SendResourceRequest(task.path);
|
||||
// Store the destination cache file location in the property
|
||||
reply->setProperty("dest_file", GetCachePath(task));
|
||||
mWorkingRequests[reply] = task.id;
|
||||
connect(reply, &QNetworkReply::finished, this, &SpkResource::ResourceDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
void SpkResource::PurgeCachedResource(const QString &aPkgName, SpkResource::ResourceType aType,
|
||||
const QVariant &aInfo)
|
||||
{
|
||||
auto dir = QDir(mCacheDirectory + aPkgName + '/', ResourceName[aType] + '*');
|
||||
auto list = dir.entryList();
|
||||
|
||||
if(list.isEmpty())
|
||||
return;
|
||||
for(auto &i : list)
|
||||
if(!QFile::remove(dir.absolutePath() + '/' + i))
|
||||
qInfo() << "Fail to remove broken cache " << dir.absolutePath() + '/' + i;
|
||||
}
|
||||
|
||||
QString SpkResource::GetCachePath(const ResourceTask &task)
|
||||
{
|
||||
return mCacheDirectory + task.pkgName + '/' + ResourceName.value(task.type) + '.' +
|
||||
task.info.toString() + '.' + SpkUtils::CutFileName(task.path);
|
||||
}
|
||||
|
||||
const QMap<SpkResource::ResourceType, QString> SpkResource::ResourceName
|
||||
{
|
||||
{ SpkResource::ResourceType::AppIcon, "icon" },
|
||||
{ SpkResource::ResourceType::AppScreenshot, "scrshot" },
|
||||
{ SpkResource::ResourceType::HomeImage, "img" },
|
||||
{ SpkResource::ResourceType::TagIcon, "tag" },
|
||||
};
|
||||
@ -56,7 +56,12 @@ SpkStore::SpkStore(bool aCli, QString &aLogPath)
|
||||
mNetMgr = new QNetworkAccessManager(this);
|
||||
mNetMgr->setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); // FIXME
|
||||
mDistroName = SpkUtils::GetDistroName();
|
||||
mApiRequestUrl = "https://store.deepinos.org/api/"; // TODO: CHECK BEFORE 4.0 RELEASE
|
||||
|
||||
// Initialize URL
|
||||
mApiRequestUrl = mCfg->value("url/api", "https://store.deepinos.org/api/").toString();
|
||||
mResourceRequestUrl = mCfg->value("url/res", "http://img.store.deepinos.org.cn/").toString();
|
||||
|
||||
|
||||
mUserAgentStr = QString("Spark-Store/%1 Distro/%2")
|
||||
.arg(GitVer::DescribeTags())
|
||||
.arg(mDistroName);
|
||||
@ -66,6 +71,7 @@ SpkStore::SpkStore(bool aCli, QString &aLogPath)
|
||||
return;
|
||||
|
||||
// UI Initialization
|
||||
mResMgr = new SpkResource(this); // Resource manager must be created before the windows
|
||||
SpkUi::Initialize();
|
||||
mMainWindow = new SpkMainWindow;
|
||||
SpkUi::Popup = new SpkUi::SpkPopup(mMainWindow);
|
||||
@ -88,6 +94,14 @@ QNetworkReply *SpkStore::SendApiRequest(QString aPath, QJsonDocument aParam)
|
||||
return mNetMgr->post(request, aParam.isEmpty() ? "{}" : aParam.toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
QNetworkReply *SpkStore::SendResourceRequest(QString aPath)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setUrl(mResourceRequestUrl + aPath);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, mUserAgentStr);
|
||||
return mNetMgr->get(request);
|
||||
}
|
||||
|
||||
static void InstallDefaultConfigs()
|
||||
{
|
||||
//TODO:STUB
|
||||
|
||||
@ -78,3 +78,13 @@ void SpkUtils::DeleteReplyLater(QNetworkReply *aReply)
|
||||
{
|
||||
QObject::connect(aReply, &QNetworkReply::finished, aReply, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
QString SpkUtils::CutFileName(QString path)
|
||||
{
|
||||
return path.section('/', -1);
|
||||
}
|
||||
|
||||
QString SpkUtils::CutPath(QString path)
|
||||
{
|
||||
return path.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user