mirror of
https://gitee.com/spark-store-project/spark-store
synced 2025-12-15 13:22:04 +08:00
Compare commits
280 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
144103d8da | ||
|
|
ee876856c2 | ||
|
|
3bd76b865d | ||
|
|
8d40a0598e | ||
|
|
904cc3cb1c | ||
|
|
b0a2d64b71 | ||
|
|
393b3f73d5 | ||
|
|
7b2312f8ce | ||
|
|
26a0c8133b | ||
|
|
aa58da05db | ||
|
|
3ecd3310ad | ||
|
|
90ab4cb87f | ||
|
|
d33e28b024 | ||
|
|
8f32141726 | ||
|
|
03f157f620 | ||
|
|
8f8f479873 | ||
|
|
2481adf29c | ||
|
|
2bba8f57ba | ||
|
|
c71582997b | ||
|
|
55795601e0 | ||
|
|
0a351ad40d | ||
|
|
ed233e2625 | ||
|
|
ae987f9542 | ||
|
|
f655d0d94d | ||
|
|
28e2c0d082 | ||
|
|
2221d5c816 | ||
|
|
f2e417e02a | ||
|
|
58a0336a23 | ||
|
|
7756bda006 | ||
|
|
89e326e69e | ||
|
|
0697acd9e4 | ||
|
|
96ba6770b0 | ||
|
|
908dd7d7a7 | ||
|
|
af40213c5a | ||
|
|
aa22cd7ff2 | ||
|
|
02530de7da | ||
|
|
974d0032e3 | ||
|
|
179e57b9b5 | ||
|
|
f5a31affff | ||
|
|
523640ba58 | ||
|
|
cefd309097 | ||
|
|
2e04d05b34 | ||
|
|
6491b19f6e | ||
|
|
acf013d8ab | ||
|
|
2b12c38f50 | ||
|
|
d2ab7cfbf7 | ||
|
|
c0789ca72f | ||
|
|
90d44b85c1 | ||
|
|
91292f6550 | ||
|
|
f96aa8150a | ||
|
|
f5649a121f | ||
|
|
4ede620e71 | ||
|
|
ca57a7ac86 | ||
|
|
774e347957 | ||
|
|
764f6eee73 | ||
|
|
8ac76b668e | ||
|
|
5b946bb91a | ||
|
|
6ddf44516a | ||
|
|
f50b014b77 | ||
|
|
5cbd758f67 | ||
|
|
04ebc6ed20 | ||
|
|
dd00465d83 | ||
|
|
f6baf41306 | ||
|
|
d49917c6fb | ||
|
|
e2821b13e0 | ||
|
|
2b2bb06d02 | ||
|
|
b3a9485bac | ||
|
|
4f0990293a | ||
|
|
3eb2a0d63a | ||
|
|
8198dc48c5 | ||
|
|
f7becc1a66 | ||
|
|
677cde3682 | ||
|
|
ac9c725dfc | ||
|
|
270bf66b32 | ||
|
|
99083d2bcb | ||
|
|
e3c43198b9 | ||
|
|
48c9046993 | ||
|
|
09c3f8f67c | ||
|
|
d8b4a031e2 | ||
|
|
0a6e86dba7 | ||
|
|
6536d3230f | ||
|
|
0885b7ca13 | ||
|
|
0729cfffb7 | ||
|
|
fc4f2ddcc5 | ||
|
|
4df8bf7d0a | ||
|
|
8991043127 | ||
|
|
9c080f8efb | ||
|
|
8579e901d4 | ||
|
|
4cf2f46866 | ||
|
|
479d218651 | ||
| 0ab9f4dda6 | |||
| 5a97f6caef | |||
| 3da25b0904 | |||
|
|
116c824365 | ||
|
|
44edb08518 | ||
|
|
9a3d32ee11 | ||
|
|
e5fe80cb76 | ||
|
|
9314acd7e1 | ||
|
|
e64e7fcae2 | ||
| 536efeedfa | |||
|
|
6fd3c40e97 | ||
|
|
90513a8925 | ||
|
|
ed3d869b99 | ||
|
|
c80737a458 | ||
|
|
ebf30e67f9 | ||
|
|
90b684af87 | ||
|
|
3ff363d7b1 | ||
|
|
f7ced7739c | ||
|
|
e12f617f59 | ||
|
|
d164aec86d | ||
|
|
96cd1b9918 | ||
|
|
393c8220f5 | ||
|
|
01d1543cc4 | ||
|
|
4ccc8c0dae | ||
|
|
815036e28f | ||
|
|
9cc68fac86 | ||
|
|
2f8c11a30b | ||
| 1a4b1176fb | |||
| 3101f1fe70 | |||
| 58f32c119a | |||
| ccdcf407cc | |||
| 1a18a51d3c | |||
|
|
ab88af006b | ||
|
|
d02900cb10 | ||
|
|
3e473c091a | ||
|
|
84541d0c22 | ||
|
|
51f84bed1b | ||
| 4a0acf0575 | |||
|
|
e28d1c39ac | ||
|
|
f58201a612 | ||
| 405d3b6986 | |||
| b619d3cc7b | |||
|
|
14e3e7f9a2 | ||
|
|
1d0e0cc65c | ||
|
|
7a5b982dea | ||
|
|
91fcab56df | ||
|
|
4315f04023 | ||
|
|
736ede0742 | ||
|
|
a73a4416fc | ||
|
|
62f0dd097c | ||
|
|
6f3e4398df | ||
|
|
3cca0d87fe | ||
| b7e038bd88 | |||
|
|
4f0e00ad76 | ||
|
|
579008e8b2 | ||
|
|
ccb405c983 | ||
|
|
66ef37c1ca | ||
|
|
8972425c7c | ||
|
|
2ae6e80785 | ||
| f5788efb47 | |||
| 32bc272791 | |||
| c5f04b5675 | |||
| cf208d0736 | |||
| fdc7cb4cb6 | |||
| 8e89ce3ae3 | |||
| ee51f59874 | |||
| 3d09a28794 | |||
| 4b05758479 | |||
| 74d9d0b563 | |||
|
|
f1a4f7acb5 | ||
| 93be66e871 | |||
| 7b46cca1d4 | |||
| c529367998 | |||
|
|
beae3e3efa | ||
|
|
9706480931 | ||
|
|
ed64eb6f5b | ||
| 8f6e5408ae | |||
| d5783458fc | |||
| 6e05066dd9 | |||
| 58aa5a3787 | |||
| 6087c8a5c8 | |||
| ff758946be | |||
| d1cbf95447 | |||
|
|
187ce7e277 | ||
| d3e4f75254 | |||
|
|
949eb33511 | ||
|
|
3f65002dc9 | ||
|
|
cfd4c7689a | ||
|
|
367c8d857c | ||
| b41f846ea4 | |||
| 1dcd7d1c5c | |||
| 01ac388f50 | |||
| cfbbd34695 | |||
|
|
b7afc5ec8a | ||
| b911158274 | |||
| 67c28d0224 | |||
| 1d65bfc66d | |||
| 0e595db328 | |||
| 7a16028e38 | |||
|
|
1b56d3ad52 | ||
| 431e7b555f | |||
|
|
1cbabe7cc5 | ||
| d027d513a3 | |||
|
|
eff8878e8b | ||
| f0bf0e5aec | |||
|
|
678a4229f7 | ||
| e89c14b5ca | |||
|
|
d4c734627a | ||
| 8830b14e10 | |||
| 8c9f5b8ba6 | |||
| badc3aa089 | |||
| 9c9317dea0 | |||
| 594f71f98a | |||
| c45bf8ffaf | |||
|
|
c9c0357646 | ||
|
|
bd8f660fe9 | ||
| 538c742303 | |||
|
|
c23e816017 | ||
| 2be0e1b523 | |||
| eacc56eaf3 | |||
| 9458bbf2c9 | |||
|
|
e472ba76f1 | ||
|
|
74bd3ae018 | ||
|
|
ccc2bb8898 | ||
| a59e2e0527 | |||
| 2c205bf5db | |||
| efc6c50f88 | |||
| ce263c3cb4 | |||
| 78ac339629 | |||
| 58359aa63a | |||
|
|
e839f7836f | ||
|
|
b34c50d2e0 | ||
|
|
4d4e3e3e3a | ||
|
|
10741a1d92 | ||
|
|
b78ca0e103 | ||
|
|
3a0ed16a21 | ||
|
|
0a3d583d76 | ||
|
|
f5121a0405 | ||
|
|
e568ddafba | ||
|
|
111174a46f | ||
|
|
6de25a299f | ||
|
|
cbd57a3e25 | ||
|
|
6e083f295b | ||
|
|
b82a821d01 | ||
|
|
6578af935e | ||
|
|
fe4143a3f7 | ||
|
|
8952be33c4 | ||
|
|
4e4f55995e | ||
|
|
7af2af64d8 | ||
|
|
e7276662f4 | ||
| 4138c5d634 | |||
| 2bb4feec43 | |||
|
|
18c3965742 | ||
|
|
647fb20f1e | ||
|
|
a75ecd3a9a | ||
|
|
f9b8faa638 | ||
|
|
f39890240f | ||
|
|
3ca8fac6f9 | ||
|
|
9fbe0032b7 | ||
|
|
3ba66f8f8e | ||
|
|
cf1322bab4 | ||
|
|
135d16d068 | ||
|
|
44201a8bed | ||
|
|
8fafd7b3ee | ||
|
|
070788d50d | ||
|
|
0e387acfb6 | ||
|
|
c043542c04 | ||
|
|
25b37ec0f2 | ||
|
|
a732269b62 | ||
|
|
6d63ef60ac | ||
|
|
25456a413c | ||
|
|
ec1c91f021 | ||
|
|
46c1609d58 | ||
|
|
9c5421133f | ||
|
|
114489c3d6 | ||
|
|
101a6bc370 | ||
|
|
9b47e4dd70 | ||
|
|
6a555b06b9 | ||
|
|
145b1b8e30 | ||
|
|
23d8e9ea82 | ||
|
|
f723007166 | ||
|
|
e0639a5855 | ||
|
|
514be30e04 | ||
|
|
817d497f4a | ||
| 8158159bf9 | |||
| 82f49c4aa9 | |||
| b1c2b1bbc6 | |||
| 892766470c | |||
| a3ca04ddc5 | |||
| 147258a7ac |
2
.gitee/Dockerfile
Normal file
2
.gitee/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM python:3
|
||||
RUN pip3 install requests
|
||||
31
.gitee/callback.py
Normal file
31
.gitee/callback.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
# sha=os.system("git rev-parse HEAD")
|
||||
sha = os.getenv("GIT_COMMIT")
|
||||
# sha = '48fed26c51a8c42554e45f72f43e49703e04c97f'
|
||||
#get sha from environment
|
||||
url = "https://gitee.com/api/v5/repos/deepin-community-store/spark-store/commits/{}/comments".format(sha)
|
||||
|
||||
token = os.getenv("gitee_token")
|
||||
|
||||
# process = os.popen("git symbolic-ref --short -q HEAD")
|
||||
|
||||
body = "构建详情请见" + os.getenv("JENKINS_URL") + "blue/organizations/jenkins/" + os.getenv("JOB_NAME").replace("/", "/detail/") + "/" + str(os.getenv("BUILD_ID"))
|
||||
|
||||
# process.close()
|
||||
|
||||
d = {
|
||||
'access_token': token,
|
||||
"body": body
|
||||
}
|
||||
|
||||
h = {
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
}
|
||||
|
||||
res = requests.post(url,headers=h, data=json.dumps(d))
|
||||
# print(res.status_code)
|
||||
# print(res.content)
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pro.user*
|
||||
build/
|
||||
.vscode/
|
||||
Lib/
|
||||
143
CMakeLists.txt
Normal file
143
CMakeLists.txt
Normal file
@@ -0,0 +1,143 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(spark_store)
|
||||
set(EXECUTABLE_NAME spark-store)
|
||||
|
||||
# Begin Compilation Options
|
||||
|
||||
# When set to ON, DTK Plugin for DDE platform will be built.
|
||||
# Note that only machines with DTK and dev packages installed can build the plugin.
|
||||
set(SPARK_BUILD_DTK_PLUGIN ON)
|
||||
|
||||
# When set to ON, store will transmit telemetry even in Debug builds.
|
||||
set(SPARK_FORCE_TELEMETRY OFF)
|
||||
|
||||
# End Compilation Options
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
#set(CMAKE_AUTOUIC ON)
|
||||
|
||||
set(QT_VERSION 5)
|
||||
set(REQUIRED_LIBS
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
Network
|
||||
Concurrent
|
||||
LinguistTools)
|
||||
set(REQUIRED_LIBS_QUALIFIED
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::Network
|
||||
Qt5::Concurrent)
|
||||
|
||||
include_directories(inc)
|
||||
include_directories(plugin)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(PRE_CONFIGURE_FILE "src/gitver.cpp.in")
|
||||
set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/src/gitver.cpp")
|
||||
include(cmake/git_watcher.cmake)
|
||||
add_library(gitver STATIC ${POST_CONFIGURE_FILE})
|
||||
#target_include_directories(gitver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_dependencies(gitver check_git)
|
||||
|
||||
set(SOURCE_FILES
|
||||
src/main.cpp
|
||||
|
||||
resource/resource.qrc
|
||||
|
||||
gui/page/ui/settings.ui
|
||||
gui/page/ui/homepage.ui
|
||||
${WRAPPED_UI_FILES}
|
||||
|
||||
inc/gitver.h
|
||||
inc/deepinplatform.h
|
||||
inc/dtk/spkdtkplugin.h
|
||||
inc/spkutils.h src/spkutils.cpp
|
||||
inc/telemetry/collectid.h
|
||||
inc/spkqsshelper.h gui/spkqsshelper.cpp
|
||||
|
||||
inc/qt/elidedlabel.h gui/qt/elidedlabel.cpp
|
||||
|
||||
inc/spkwindow.h gui/spkwindow.cpp
|
||||
inc/spktitlebar.h gui/spktitlebar.cpp
|
||||
inc/spkiconbutton.h gui/spkiconbutton.cpp
|
||||
inc/spkui_general.h gui/spkui_general.cpp
|
||||
inc/spkmainwindow.h gui/spkmainwindow.cpp
|
||||
inc/spkmsgbox.h gui/spkmsgbox.cpp
|
||||
inc/spkdialog.h gui/spkdialog.cpp
|
||||
inc/spkabout.h gui/spkabout.cpp
|
||||
inc/spkloading.h gui/spkloading.cpp
|
||||
inc/spksidebartree.h gui/spksidebartree.cpp
|
||||
inc/spkappitem.h gui/spkappitem.cpp
|
||||
inc/spkdownloadentry.h gui/spkdownloadentry.cpp
|
||||
inc/spkpopup.h gui/spkpopup.cpp
|
||||
inc/spkstretchlayout.h gui/spkstretchlayout.cpp
|
||||
inc/spkfocuslineedit.h
|
||||
inc/spknotifydot.h gui/spknotifydot.cpp
|
||||
inc/spkimgviewer.h gui/spkimgviewer.cpp
|
||||
|
||||
inc/page/spkpagebase.h gui/page/spkpagebase.cpp
|
||||
inc/page/spkpagehome.h gui/page/spkpagehome.cpp
|
||||
inc/page/spkpageuitest.h gui/page/spkpageuitest.cpp
|
||||
inc/page/spkpageapplist.h gui/page/spkpageapplist.cpp
|
||||
inc/page/spkpageappdetails.h gui/page/spkpageappdetails.cpp
|
||||
inc/page/spkpagedownloads.h gui/page/spkpagedownloads.cpp
|
||||
inc/page/spkpagesettings.h gui/page/spkpagesettings.cpp
|
||||
|
||||
inc/pkgs/spkpkgmgrbase.h
|
||||
inc/pkgs/spkpkgmgrpacman.h src/pkgs/spkpkgmgrpacman.cpp
|
||||
inc/pkgs/spkpkgmgrapt.h src/pkgs/spkpkgmgrapt.cpp
|
||||
|
||||
inc/spkstore.h src/spkstore.cpp
|
||||
inc/spkuimsg.h src/spkuimsg.cpp
|
||||
inc/spklogging.h src/spklogging.cpp
|
||||
inc/spkresource.h src/spkresource.cpp
|
||||
inc/spkdownload.h src/spkdownload.cpp
|
||||
inc/spkconfig.h src/spkconfig.cpp
|
||||
)
|
||||
|
||||
include(cmake/FindLibNotify.cmake)
|
||||
include(cmake/FindGlib.cmake)
|
||||
include(cmake/FindGdk3.cmake)
|
||||
|
||||
include_directories(${GLIB_INCLUDE_DIRS})
|
||||
include_directories(${GDK3_INCLUDE_DIRS})
|
||||
set(LIBLINKING ${LIBLINKING}
|
||||
gitver
|
||||
${LIBNOTIFY_LIBRARIES}
|
||||
${GLIB_LIBRARIES}
|
||||
${GDK3_LIBRARIES}
|
||||
${CMAKE_DL_LIBS})
|
||||
|
||||
# Required for a good backtrace
|
||||
add_compile_options(-g)
|
||||
add_link_options(-rdynamic)
|
||||
|
||||
# Find Qt before adding other build targets
|
||||
find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED)
|
||||
|
||||
QT5_WRAP_UI(WRAPPED_UI_FILES
|
||||
gui/page/ui/settings.ui
|
||||
gui/page/ui/homepage.ui)
|
||||
|
||||
add_custom_target(run_lupdate
|
||||
COMMAND lupdate . -ts lang/zh.ts
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
|
||||
qt5_add_translation(QM_FILES lang/zh.ts)
|
||||
|
||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES} ${QM_FILES})
|
||||
|
||||
if(SPARK_BUILD_DTK_PLUGIN)
|
||||
add_subdirectory(plugin/dtkplugin)
|
||||
add_dependencies(${EXECUTABLE_NAME} spkdtkplugin)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${EXECUTABLE_NAME} ${REQUIRED_LIBS_QUALIFIED} ${LIBLINKING})
|
||||
|
||||
install(TARGETS ${EXECUTABLE_NAME} RUNTIME DESTINATION bin)
|
||||
33
Jenkinsfile
vendored
Normal file
33
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('build') {
|
||||
agent {
|
||||
docker {
|
||||
image 'jerry979/dtke:5.11'
|
||||
}
|
||||
|
||||
}
|
||||
steps {
|
||||
sh 'mkdir build && cd build && cmake .. && make -j'
|
||||
archiveArtifacts(artifacts: 'build/src/spark-store', allowEmptyArchive: true, defaultExcludes: true)
|
||||
}
|
||||
}
|
||||
|
||||
stage('send') {
|
||||
agent {
|
||||
dockerfile {
|
||||
filename '.gitee/Dockerfile'
|
||||
}
|
||||
|
||||
}
|
||||
environment {
|
||||
gitee_token = credentials('1')
|
||||
}
|
||||
steps {
|
||||
sh "python3 .gitee/callback.py"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
BIN
Logo-Spark.png
Normal file
BIN
Logo-Spark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
Logo-Spark.png.bak
Normal file
BIN
Logo-Spark.png.bak
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
70
README.md
70
README.md
@@ -1,27 +1,73 @@
|
||||
# 深度社区应用商店
|
||||
# 星火应用商店
|
||||
|
||||
|
||||
众所周知,国内的Linux应用比较少,wine应用难以获取,优质工具分散在民间各大论坛,无法形成合力,难以改善生态
|
||||
|
||||
生态构建需要的不是某一方的单打独斗,而是人人行动起来,汇聚星火,产生燎原之势
|
||||
|
||||
我们创建了这个应用商店,广泛收录大家需要的软件包,搜集优质小工具,主动适配wine应用,存放到储存库供大家获取
|
||||
|
||||
我们支持:Deepin 20 ; Ubuntu 20.04 LTS ; UOS Home 20
|
||||
|
||||
希望看到这里的人也可以加入我们的队伍,开发或者投递应用都很欢迎,共同构建Linux应用生态
|
||||
|
||||
### [在这里投稿](http://upload.spark-app.store)
|
||||
|
||||
#### 介绍
|
||||
deepin社区商店,由社区维护
|
||||
web页面部分正在开发当中,详情请见[web仓库](https://gitee.com/deepin-community-store/DCSAPP_WEB)
|
||||
|
||||
|
||||
#### 说明
|
||||
|
||||
当前服务器线路列表(项目中包含):
|
||||
|
||||
```
|
||||
http://dcstore.shenmo.tech/
|
||||
http://store.shenmo.tech/
|
||||
http://store2.shenmo.tech/
|
||||
https://d.store.deepinos.org.cn/
|
||||
https://store.deepinos.org.cn/
|
||||
```
|
||||
|
||||
#### 目录结构
|
||||
#### 调用参数(spk规则)
|
||||
|
||||
安装软件过程中产生的包,图标,截图等被储存到`/tmp/deepin-community-store/`中。
|
||||
参数只有一个Url,该url应当遵循这种格式:`spk://<任意合法字符>/web分类/包名`
|
||||
|
||||
配置文件被储存到`~/.config/deepin-community-store/`中。
|
||||
例如:
|
||||
|
||||
线路文件:新版的线路文件被放置于源服务器中,可随时刷新更新源列表
|
||||
[spk://abcdefg/games/store.spark-app.hmcl](spk://abcdefg/games/store.spark-app.hmcl)
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
可选的web分类:
|
||||
|
||||
| 分类名称 | web分类 |
|
||||
| -------- | -------------- |
|
||||
| 网络应用 | network |
|
||||
| 社交沟通 | chat |
|
||||
| 音乐欣赏 | music |
|
||||
| 视频播放 | video |
|
||||
| 图形图像 | graphics |
|
||||
| 游戏娱乐 | games |
|
||||
| 办公学习 | office |
|
||||
| 阅读翻译 | reading |
|
||||
| 编程开发 | development |
|
||||
| 系统工具 | tools |
|
||||
| 主题美化 | beautify |
|
||||
| 其他应用 | others |
|
||||
|
||||
|
||||
|
||||
#### 如何编译
|
||||
|
||||
Deepin V20/UOS 系统下, 安装依赖
|
||||
|
||||
```shell
|
||||
sudo apt install qt5-default libdtkcore-dev libdtkwidget-dev qtwebengine5-dev libnotify-dev
|
||||
```
|
||||
|
||||
```shell
|
||||
git clone https://gitee.com/deepin-community-store/spark-store.git
|
||||
cd spark-store
|
||||
mkdir build
|
||||
cd build
|
||||
qmake ..
|
||||
make -j
|
||||
```
|
||||
|
||||
./build文件下的spark-store即为可执行文件
|
||||
|
||||
52
cmake/FindGdk3.cmake
Normal file
52
cmake/FindGdk3.cmake
Normal file
@@ -0,0 +1,52 @@
|
||||
# - Try to find GDK 3
|
||||
# Once done, this will define
|
||||
#
|
||||
# GDK3_FOUND - system has GDK 3
|
||||
# GDK3_INCLUDE_DIRS - the GDK 3 include directories
|
||||
# GDK3_LIBRARIES - link these to use GDK 3
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
# Copyright (C) 2013 Igalia S.L.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. 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.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS 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 HOLDER OR ITS
|
||||
# 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.
|
||||
|
||||
# Source from:
|
||||
# https://sources.debian.org/src/qtwebkit-opensource-src/5.212.0%7Ealpha2-21/Source/cmake/FindGDK3.cmake/
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(GDK3 gdk-3.0)
|
||||
|
||||
set(VERSION_OK TRUE)
|
||||
if (GDK3_VERSION)
|
||||
if (GDK3_FIND_VERSION_EXACT)
|
||||
if (NOT("${GDK3_FIND_VERSION}" VERSION_EQUAL "${GDK3_VERSION}"))
|
||||
set(VERSION_OK FALSE)
|
||||
endif ()
|
||||
else ()
|
||||
if ("${GDK3_VERSION}" VERSION_LESS "${GDK3_FIND_VERSION}")
|
||||
set(VERSION_OK FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GDK3 DEFAULT_MSG GDK3_INCLUDE_DIRS GDK3_LIBRARIES VERSION_OK)
|
||||
125
cmake/FindGlib.cmake
Normal file
125
cmake/FindGlib.cmake
Normal file
@@ -0,0 +1,125 @@
|
||||
# - Try to find Glib and its components (gio, gobject etc)
|
||||
# Once done, this will define
|
||||
#
|
||||
# GLIB_FOUND - system has Glib
|
||||
# GLIB_INCLUDE_DIRS - the Glib include directories
|
||||
# GLIB_LIBRARIES - link these to use Glib
|
||||
#
|
||||
# Optionally, the COMPONENTS keyword can be passed to find_package()
|
||||
# and Glib components can be looked for. Currently, the following
|
||||
# components can be used, and they define the following variables if
|
||||
# found:
|
||||
#
|
||||
# gio: GLIB_GIO_LIBRARIES
|
||||
# gobject: GLIB_GOBJECT_LIBRARIES
|
||||
# gmodule: GLIB_GMODULE_LIBRARIES
|
||||
# gthread: GLIB_GTHREAD_LIBRARIES
|
||||
#
|
||||
# Note that the respective _INCLUDE_DIR variables are not set, since
|
||||
# all headers are in the same directory as GLIB_INCLUDE_DIRS.
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. 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.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS 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 HOLDER OR ITS
|
||||
# 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.
|
||||
|
||||
# Source from:
|
||||
# https://sources.debian.org/src/qtwebkit-opensource-src/5.212.0%7Ealpha2-21/Source/cmake/FindGLIB.cmake/
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_GLIB QUIET glib-2.0)
|
||||
|
||||
find_library(GLIB_LIBRARIES
|
||||
NAMES glib-2.0
|
||||
HINTS ${PC_GLIB_LIBDIR}
|
||||
${PC_GLIB_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# Files in glib's main include path may include glibconfig.h, which,
|
||||
# for some odd reason, is normally in $LIBDIR/glib-2.0/include.
|
||||
get_filename_component(_GLIB_LIBRARY_DIR ${GLIB_LIBRARIES} PATH)
|
||||
find_path(GLIBCONFIG_INCLUDE_DIR
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_LIBDIR} ${PC_LIBRARY_DIRS} ${_GLIB_LIBRARY_DIR}
|
||||
${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES glib-2.0/include
|
||||
)
|
||||
|
||||
find_path(GLIB_INCLUDE_DIR
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB_INCLUDEDIR}
|
||||
${PC_GLIB_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES glib-2.0
|
||||
)
|
||||
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR} ${GLIBCONFIG_INCLUDE_DIR})
|
||||
|
||||
# Version detection
|
||||
if (EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h")
|
||||
file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS)
|
||||
string(REGEX MATCH "#define GLIB_MAJOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define GLIB_MINOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define GLIB_MICRO_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}")
|
||||
set(GLIB_VERSION "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}")
|
||||
endif ()
|
||||
|
||||
# Additional Glib components. We only look for libraries, as not all of them
|
||||
# have corresponding headers and all headers are installed alongside the main
|
||||
# glib ones.
|
||||
foreach (_component ${GLIB_FIND_COMPONENTS})
|
||||
if (${_component} STREQUAL "gio")
|
||||
find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gobject")
|
||||
find_library(GLIB_GOBJECT_LIBRARIES NAMES gobject-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GOBJECT_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gmodule")
|
||||
find_library(GLIB_GMODULE_LIBRARIES NAMES gmodule-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GMODULE_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gthread")
|
||||
find_library(GLIB_GTHREAD_LIBRARIES NAMES gthread-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GTHREAD_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gio-unix")
|
||||
# gio-unix is compiled as part of the gio library, but the include paths
|
||||
# are separate from the shared glib ones. Since this is currently only used
|
||||
# by WebKitGTK+ we don't go to extraordinary measures beyond pkg-config.
|
||||
pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLIB REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS}
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
|
||||
mark_as_advanced(
|
||||
GLIBCONFIG_INCLUDE_DIR
|
||||
GLIB_GIO_LIBRARIES
|
||||
GLIB_GIO_UNIX_LIBRARIES
|
||||
GLIB_GMODULE_LIBRARIES
|
||||
GLIB_GOBJECT_LIBRARIES
|
||||
GLIB_GTHREAD_LIBRARIES
|
||||
GLIB_INCLUDE_DIR
|
||||
GLIB_INCLUDE_DIRS
|
||||
GLIB_LIBRARIES
|
||||
)
|
||||
58
cmake/FindLibNotify.cmake
Normal file
58
cmake/FindLibNotify.cmake
Normal file
@@ -0,0 +1,58 @@
|
||||
# - Try to find LibNotify
|
||||
# This module defines the following variables:
|
||||
#
|
||||
# LIBNOTIFY_FOUND - LibNotify was found
|
||||
# LIBNOTIFY_INCLUDE_DIRS - the LibNotify include directories
|
||||
# LIBNOTIFY_LIBRARIES - link these to use LibNotify
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
# Copyright (C) 2014 Collabora Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. 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.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS 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 HOLDER OR ITS
|
||||
# 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.
|
||||
|
||||
# Source from:
|
||||
# https://sources.debian.org/src/qtwebkit-opensource-src/5.212.0~alpha2-21/Source/cmake/FindLibNotify.cmake/
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBNOTIFY QUIET libnotify)
|
||||
|
||||
find_path(LIBNOTIFY_INCLUDE_DIRS
|
||||
NAMES notify.h
|
||||
HINTS ${LIBNOTIFY_INCLUDEDIR}
|
||||
${LIBNOTIFY_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES libnotify
|
||||
)
|
||||
|
||||
find_library(LIBNOTIFY_LIBRARIES
|
||||
NAMES notify
|
||||
HINTS ${LIBNOTIFY_LIBDIR}
|
||||
${LIBNOTIFY_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibNotify REQUIRED_VARS LIBNOTIFY_INCLUDE_DIRS LIBNOTIFY_LIBRARIES
|
||||
VERSION_VAR LIBNOTIFY_VERSION)
|
||||
|
||||
mark_as_advanced(
|
||||
LIBNOTIFY_INCLUDE_DIRS
|
||||
LIBNOTIFY_LIBRARIES
|
||||
)
|
||||
51
cmake/FindXCB.cmake
Normal file
51
cmake/FindXCB.cmake
Normal file
@@ -0,0 +1,51 @@
|
||||
# - FindXCB
|
||||
#
|
||||
# Copyright 2015 Valve Coporation
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
if(NOT XCB_FIND_COMPONENTS)
|
||||
set(XCB_FIND_COMPONENTS xcb)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
set(XCB_FOUND true)
|
||||
set(XCB_INCLUDE_DIRS "")
|
||||
set(XCB_LIBRARIES "")
|
||||
foreach(comp ${XCB_FIND_COMPONENTS})
|
||||
# component name
|
||||
string(TOUPPER ${comp} compname)
|
||||
string(REPLACE "-" "_" compname ${compname})
|
||||
# header name
|
||||
string(REPLACE "xcb-" "" headername xcb/${comp}.h)
|
||||
# library name
|
||||
set(libname ${comp})
|
||||
|
||||
pkg_check_modules(PC_${comp} QUIET ${comp})
|
||||
|
||||
find_path(${compname}_INCLUDE_DIR NAMES ${headername}
|
||||
HINTS
|
||||
${PC_${comp}_INCLUDEDIR}
|
||||
${PC_${comp}_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(${compname}_LIBRARY NAMES ${libname}
|
||||
HINTS
|
||||
${PC_${comp}_LIBDIR}
|
||||
${PC_${comp}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
find_package_handle_standard_args(${comp}
|
||||
FOUND_VAR ${comp}_FOUND
|
||||
REQUIRED_VARS ${compname}_INCLUDE_DIR ${compname}_LIBRARY)
|
||||
mark_as_advanced(${compname}_INCLUDE_DIR ${compname}_LIBRARY)
|
||||
|
||||
list(APPEND XCB_INCLUDE_DIRS ${${compname}_INCLUDE_DIR})
|
||||
list(APPEND XCB_LIBRARIES ${${compname}_LIBRARY})
|
||||
|
||||
if(NOT ${comp}_FOUND)
|
||||
set(XCB_FOUND false)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS)
|
||||
335
cmake/git_watcher.cmake
Normal file
335
cmake/git_watcher.cmake
Normal file
@@ -0,0 +1,335 @@
|
||||
# git_watcher.cmake
|
||||
# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake
|
||||
#
|
||||
# Released under the MIT License.
|
||||
# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE
|
||||
|
||||
|
||||
# This file defines a target that monitors the state of a git repo.
|
||||
# If the state changes (e.g. a commit is made), then a file gets reconfigured.
|
||||
# Here are the primary variables that control script behavior:
|
||||
#
|
||||
# PRE_CONFIGURE_FILE (REQUIRED)
|
||||
# -- The path to the file that'll be configured.
|
||||
#
|
||||
# POST_CONFIGURE_FILE (REQUIRED)
|
||||
# -- The path to the configured PRE_CONFIGURE_FILE.
|
||||
#
|
||||
# GIT_STATE_FILE (OPTIONAL)
|
||||
# -- The path to the file used to store the previous build's git state.
|
||||
# Defaults to the current binary directory.
|
||||
#
|
||||
# GIT_WORKING_DIR (OPTIONAL)
|
||||
# -- The directory from which git commands will be run.
|
||||
# Defaults to the directory with the top level CMakeLists.txt.
|
||||
#
|
||||
# GIT_EXECUTABLE (OPTIONAL)
|
||||
# -- The path to the git executable. It'll automatically be set if the
|
||||
# user doesn't supply a path.
|
||||
#
|
||||
# GIT_FAIL_IF_NONZERO_EXIT (optional)
|
||||
# -- Raise a FATAL_ERROR if any of the git commands return a non-zero
|
||||
# exit code. This is set to TRUE by default. You can set this to FALSE
|
||||
# if you'd like the build to continue even if a git command fails.
|
||||
#
|
||||
# DESIGN
|
||||
# - This script was designed similar to a Python application
|
||||
# with a Main() function. I wanted to keep it compact to
|
||||
# simplify "copy + paste" usage.
|
||||
#
|
||||
# - This script is invoked under two CMake contexts:
|
||||
# 1. Configure time (when build files are created).
|
||||
# 2. Build time (called via CMake -P).
|
||||
# The first invocation is what registers the script to
|
||||
# be executed at build time.
|
||||
#
|
||||
# MODIFICATIONS
|
||||
# You may wish to track other git properties like when the last
|
||||
# commit was made. There are two sections you need to modify,
|
||||
# and they're tagged with a ">>>" header.
|
||||
|
||||
# Short hand for converting paths to absolute.
|
||||
macro(PATH_TO_ABSOLUTE var_name)
|
||||
get_filename_component(${var_name} "${${var_name}}" ABSOLUTE)
|
||||
endmacro()
|
||||
|
||||
# Check that a required variable is set.
|
||||
macro(CHECK_REQUIRED_VARIABLE var_name)
|
||||
if(NOT DEFINED ${var_name})
|
||||
message(FATAL_ERROR "The \"${var_name}\" variable must be defined.")
|
||||
endif()
|
||||
PATH_TO_ABSOLUTE(${var_name})
|
||||
endmacro()
|
||||
|
||||
# Check that an optional variable is set, or, set it to a default value.
|
||||
macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value)
|
||||
if(NOT DEFINED ${var_name})
|
||||
set(${var_name} ${default_value})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Check that an optional variable is set, or, set it to a default value.
|
||||
# Also converts that path to an abspath.
|
||||
macro(CHECK_OPTIONAL_VARIABLE var_name default_value)
|
||||
CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value})
|
||||
PATH_TO_ABSOLUTE(${var_name})
|
||||
endmacro()
|
||||
|
||||
CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE)
|
||||
CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE)
|
||||
CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_BINARY_DIR}/git-state-hash")
|
||||
CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}")
|
||||
CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE)
|
||||
|
||||
# Check the optional git variable.
|
||||
# If it's not set, we'll try to find it using the CMake packaging system.
|
||||
if(NOT DEFINED GIT_EXECUTABLE)
|
||||
find_package(Git QUIET REQUIRED)
|
||||
endif()
|
||||
CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE)
|
||||
|
||||
|
||||
set(_state_variable_names
|
||||
GIT_RETRIEVED_STATE
|
||||
GIT_HEAD_SHA1
|
||||
GIT_IS_DIRTY
|
||||
GIT_AUTHOR_NAME
|
||||
GIT_AUTHOR_EMAIL
|
||||
GIT_COMMIT_DATE_ISO8601
|
||||
GIT_COMMIT_SUBJECT
|
||||
GIT_COMMIT_BODY
|
||||
GIT_DESCRIBE
|
||||
# >>>
|
||||
# 1. Add the name of the additional git variable you're interested in monitoring
|
||||
# to this list.
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Macro: RunGitCommand
|
||||
# Description: short-hand macro for calling a git function. Outputs are the
|
||||
# "exit_code" and "output" variables.
|
||||
macro(RunGitCommand)
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" ${ARGV}
|
||||
WORKING_DIRECTORY "${_working_dir}"
|
||||
RESULT_VARIABLE exit_code
|
||||
OUTPUT_VARIABLE output
|
||||
ERROR_VARIABLE stderr
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT exit_code EQUAL 0)
|
||||
set(ENV{GIT_RETRIEVED_STATE} "false")
|
||||
|
||||
# Issue 26: git info not properly set
|
||||
#
|
||||
# Check if we should fail if any of the exit codes are non-zero.
|
||||
if(GIT_FAIL_IF_NONZERO_EXIT)
|
||||
string(REPLACE ";" " " args_with_spaces "${ARGV}")
|
||||
message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})")
|
||||
endif()
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
|
||||
|
||||
# Function: GetGitState
|
||||
# Description: gets the current state of the git repo.
|
||||
# Args:
|
||||
# _working_dir (in) string; the directory from which git commands will be executed.
|
||||
function(GetGitState _working_dir)
|
||||
|
||||
# This is an error code that'll be set to FALSE if the
|
||||
# RunGitCommand ever returns a non-zero exit code.
|
||||
set(ENV{GIT_RETRIEVED_STATE} "true")
|
||||
|
||||
# Get whether or not the working tree is dirty.
|
||||
RunGitCommand(status --porcelain)
|
||||
if(NOT exit_code EQUAL 0)
|
||||
set(ENV{GIT_IS_DIRTY} "false")
|
||||
else()
|
||||
if(NOT "${output}" STREQUAL "")
|
||||
set(ENV{GIT_IS_DIRTY} "true")
|
||||
else()
|
||||
set(ENV{GIT_IS_DIRTY} "false")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# There's a long list of attributes grabbed from git show.
|
||||
set(object HEAD)
|
||||
RunGitCommand(show -s "--format=%H" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
set(ENV{GIT_HEAD_SHA1} ${output})
|
||||
endif()
|
||||
|
||||
RunGitCommand(show -s "--format=%an" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
set(ENV{GIT_AUTHOR_NAME} "${output}")
|
||||
endif()
|
||||
|
||||
RunGitCommand(show -s "--format=%ae" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
set(ENV{GIT_AUTHOR_EMAIL} "${output}")
|
||||
endif()
|
||||
|
||||
RunGitCommand(show -s "--format=%ci" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}")
|
||||
endif()
|
||||
|
||||
RunGitCommand(show -s "--format=%s" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
# Escape quotes
|
||||
string(REPLACE "\"" "\\\"" output "${output}")
|
||||
set(ENV{GIT_COMMIT_SUBJECT} "${output}")
|
||||
endif()
|
||||
|
||||
RunGitCommand(show -s "--format=%b" ${object})
|
||||
if(exit_code EQUAL 0)
|
||||
if(output)
|
||||
# Escape quotes
|
||||
string(REPLACE "\"" "\\\"" output "${output}")
|
||||
# Escape line breaks in the commit message.
|
||||
string(REPLACE "\r\n" "\\r\\n\\\r\n" safe "${output}")
|
||||
if(safe STREQUAL output)
|
||||
# Didn't have windows lines - try unix lines.
|
||||
string(REPLACE "\n" "\\n\\\n" safe "${output}")
|
||||
endif()
|
||||
else()
|
||||
# There was no commit body - set the safe string to empty.
|
||||
set(safe "")
|
||||
endif()
|
||||
set(ENV{GIT_COMMIT_BODY} "\"${safe}\"")
|
||||
else()
|
||||
set(ENV{GIT_COMMIT_BODY} "\"\"") # empty string.
|
||||
endif()
|
||||
|
||||
# Get output of git describe
|
||||
RunGitCommand(describe --tags ${object})
|
||||
if(NOT exit_code EQUAL 0)
|
||||
set(ENV{GIT_DESCRIBE} "unknown")
|
||||
else()
|
||||
set(ENV{GIT_DESCRIBE} "${output}")
|
||||
endif()
|
||||
|
||||
# >>>
|
||||
# 2. Additional git properties can be added here via the
|
||||
# "execute_process()" command. Be sure to set them in
|
||||
# the environment using the same variable name you added
|
||||
# to the "_state_variable_names" list.
|
||||
|
||||
endfunction()
|
||||
|
||||
|
||||
|
||||
# Function: GitStateChangedAction
|
||||
# Description: this function is executed when the state of the git
|
||||
# repository changes (e.g. a commit is made).
|
||||
function(GitStateChangedAction)
|
||||
foreach(var_name ${_state_variable_names})
|
||||
set(${var_name} $ENV{${var_name}})
|
||||
endforeach()
|
||||
configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY)
|
||||
endfunction()
|
||||
|
||||
|
||||
|
||||
# Function: HashGitState
|
||||
# Description: loop through the git state variables and compute a unique hash.
|
||||
# Args:
|
||||
# _state (out) string; a hash computed from the current git state.
|
||||
function(HashGitState _state)
|
||||
set(ans "")
|
||||
foreach(var_name ${_state_variable_names})
|
||||
string(SHA256 ans "${ans}$ENV{${var_name}}")
|
||||
endforeach()
|
||||
set(${_state} ${ans} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
|
||||
# Function: CheckGit
|
||||
# Description: check if the git repo has changed. If so, update the state file.
|
||||
# Args:
|
||||
# _working_dir (in) string; the directory from which git commands will be ran.
|
||||
# _state_changed (out) bool; whether or no the state of the repo has changed.
|
||||
function(CheckGit _working_dir _state_changed)
|
||||
|
||||
# Get the current state of the repo.
|
||||
GetGitState("${_working_dir}")
|
||||
|
||||
# Convert that state into a hash that we can compare against
|
||||
# the hash stored on-disk.
|
||||
HashGitState(state)
|
||||
|
||||
# Issue 14: post-configure file isn't being regenerated.
|
||||
#
|
||||
# Update the state to include the SHA256 for the pre-configure file.
|
||||
# This forces the post-configure file to be regenerated if the
|
||||
# pre-configure file has changed.
|
||||
file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash)
|
||||
string(SHA256 state "${preconfig_hash}${state}")
|
||||
|
||||
# Check if the state has changed compared to the backup on disk.
|
||||
if(EXISTS "${GIT_STATE_FILE}")
|
||||
file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS)
|
||||
if(OLD_HEAD_CONTENTS STREQUAL "${state}")
|
||||
# State didn't change.
|
||||
set(${_state_changed} "false" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# The state has changed.
|
||||
# We need to update the state file on disk.
|
||||
# Future builds will compare their state to this file.
|
||||
file(WRITE "${GIT_STATE_FILE}" "${state}")
|
||||
set(${_state_changed} "true" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
|
||||
# Function: SetupGitMonitoring
|
||||
# Description: this function sets up custom commands that make the build system
|
||||
# check the state of git before every build. If the state has
|
||||
# changed, then a file is configured.
|
||||
function(SetupGitMonitoring)
|
||||
add_custom_target(check_git
|
||||
ALL
|
||||
DEPENDS ${PRE_CONFIGURE_FILE}
|
||||
BYPRODUCTS
|
||||
${POST_CONFIGURE_FILE}
|
||||
${GIT_STATE_FILE}
|
||||
COMMENT "Checking the git repository for changes..."
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-D_BUILD_TIME_CHECK_GIT=TRUE
|
||||
-DGIT_WORKING_DIR=${GIT_WORKING_DIR}
|
||||
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-DGIT_STATE_FILE=${GIT_STATE_FILE}
|
||||
-DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE}
|
||||
-DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE}
|
||||
-DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT}
|
||||
-P "${CMAKE_CURRENT_LIST_FILE}")
|
||||
endfunction()
|
||||
|
||||
|
||||
|
||||
# Function: Main
|
||||
# Description: primary entry-point to the script. Functions are selected based
|
||||
# on whether it's configure or build time.
|
||||
function(Main)
|
||||
if(_BUILD_TIME_CHECK_GIT)
|
||||
# Check if the repo has changed.
|
||||
# If so, run the change action.
|
||||
CheckGit("${GIT_WORKING_DIR}" changed)
|
||||
if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}")
|
||||
GitStateChangedAction()
|
||||
endif()
|
||||
else()
|
||||
# >> Executes at configure time.
|
||||
SetupGitMonitoring()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# And off we go...
|
||||
Main()
|
||||
@@ -1,45 +0,0 @@
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2019-06-30T12:53:03
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui webkitwidgets network concurrent
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = deepin-community-store
|
||||
TEMPLATE = app
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which as been marked as deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
|
||||
SOURCES += main.cpp\
|
||||
mainwindow.cpp \
|
||||
widget.cpp \
|
||||
downloadlist.cpp \
|
||||
outputwindow.cpp
|
||||
|
||||
HEADERS += mainwindow.h \
|
||||
widget.h \
|
||||
downloadlist.h \
|
||||
outputwindow.h
|
||||
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += dtkwidget
|
||||
|
||||
CONFIG += c++11
|
||||
|
||||
FORMS += \
|
||||
widget.ui \
|
||||
downloadlist.ui \
|
||||
outputwindow.ui
|
||||
52
docs/structure_and_naming.md
Normal file
52
docs/structure_and_naming.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 项目结构和命名规范
|
||||
|
||||
## 文件夹结构
|
||||
|
||||
### 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` 命名空间。
|
||||
|
||||
## 类定义
|
||||
|
||||
### 一般规则
|
||||
|
||||
Qt 类的 `Q_OBJECT` 应置于类定义最顶端。
|
||||
`public` ,`protected` 和 `private` 等标签中只应该包含一类成员,
|
||||
如单个 `public` 标签内只能包含方法,或成员。
|
||||
信号、槽等不得与普通方法混合。
|
||||
|
||||
<!--TODO-->
|
||||
6
docs/translating.md
Normal file
6
docs/translating.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
# 翻译
|
||||
|
||||
由于CMake和Qt的稀烂集成,我们不能使用CMake自动lupdate更新翻译。
|
||||
|
||||
如果需要更新翻译,请使用`make run_lupdate`目标进行。qm文件的编译无需其他操作,只需build即可。
|
||||
155
downloadlist.cpp
155
downloadlist.cpp
@@ -1,155 +0,0 @@
|
||||
#include "downloadlist.h"
|
||||
#include "ui_downloadlist.h"
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QtConcurrent>
|
||||
#include <QProcess>
|
||||
bool downloadlist::isInstall=false;
|
||||
|
||||
downloadlist::downloadlist(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::downloadlist)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->pushButton->setEnabled(false);
|
||||
ui->progressBar->setValue(0);
|
||||
ui->label_filename->hide();
|
||||
ui->pushButton->hide();
|
||||
ui->pushButton_3->hide();
|
||||
ui->label->setStyleSheet("color:#000000");
|
||||
}
|
||||
|
||||
downloadlist::~downloadlist()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void downloadlist::setValue(long long value)
|
||||
{
|
||||
ui->progressBar->setValue(value);
|
||||
ui->label_2->setText(QString::number((double)value/100)+"% ("+speed+")");
|
||||
if(ui->label_2->text()=="100%"){
|
||||
ui->label_2->setText("已完成,等待安装");
|
||||
}
|
||||
}
|
||||
|
||||
void downloadlist::setMax(long long max)
|
||||
{
|
||||
ui->progressBar->setMaximum(max);
|
||||
}
|
||||
|
||||
void downloadlist::setName(QString name)
|
||||
{
|
||||
ui->label->setText(name);
|
||||
}
|
||||
|
||||
QString downloadlist::getName()
|
||||
{
|
||||
return ui->label_filename->text();
|
||||
}
|
||||
|
||||
void downloadlist::readyInstall()
|
||||
{
|
||||
if(!close){
|
||||
ui->progressBar->hide();
|
||||
ui->pushButton->setEnabled(true);
|
||||
ui->pushButton->show();
|
||||
ui->pushButton_2->hide();
|
||||
system("notify-send \""+ui->label->text().toUtf8()+"下载完成,等待安装\"" +" --icon=/tmp/deepin-community-store/icon_"+QString::number(num).toUtf8()+".png");
|
||||
}
|
||||
}
|
||||
|
||||
void downloadlist::choose(bool isChoosed)
|
||||
{
|
||||
if(isChoosed){
|
||||
ui->label->setStyleSheet("color:#FFFFFF");
|
||||
ui->label_2->setStyleSheet("color:#FFFFFF");
|
||||
}else {
|
||||
ui->label->setStyleSheet("color:#000000");
|
||||
ui->label_2->setStyleSheet("color:#000000");
|
||||
}
|
||||
}
|
||||
|
||||
void downloadlist::setFileName(QString fileName)
|
||||
{
|
||||
ui->label_filename->setText(fileName);
|
||||
}
|
||||
|
||||
void downloadlist::seticon(const QPixmap icon)
|
||||
{
|
||||
ui->label_3->setPixmap(icon);
|
||||
}
|
||||
|
||||
void downloadlist::closeDownload()
|
||||
{
|
||||
on_pushButton_2_clicked();
|
||||
}
|
||||
|
||||
void downloadlist::setSpeed(QString s)
|
||||
{
|
||||
speed=s;
|
||||
}
|
||||
|
||||
void downloadlist::on_pushButton_clicked()
|
||||
{
|
||||
if(!isInstall){
|
||||
isInstall=true;
|
||||
ui->pushButton->setEnabled(false);
|
||||
ui->label_2->setText("正在安装,请稍候");
|
||||
QtConcurrent::run([=](){
|
||||
QProcess installer;
|
||||
if(reinstall){
|
||||
installer.start("pkexec apt reinstall -y /tmp/deepin-community-store/"+ui->label_filename->text().toUtf8());
|
||||
}else {
|
||||
installer.start("pkexec apt install -y /tmp/deepin-community-store/"+ui->label_filename->text().toUtf8());
|
||||
}
|
||||
|
||||
installer.waitForFinished();
|
||||
QString error=QString::fromStdString(installer.readAllStandardError().toStdString());
|
||||
out=installer.readAllStandardOutput();
|
||||
QStringList everyError=error.split("\n");
|
||||
bool haveError=false;
|
||||
bool notRoot=false;
|
||||
for (int i=0;i<everyError.size();i++) {
|
||||
qDebug()<<everyError[i].left(2);
|
||||
if(everyError[i].left(2)=="E:"){
|
||||
haveError=true;
|
||||
}
|
||||
if(everyError[i].right(14)=="Not authorized"){
|
||||
notRoot=true;
|
||||
}
|
||||
}
|
||||
if(!haveError && !notRoot){
|
||||
ui->pushButton->hide();
|
||||
ui->label_2->setText("安装完成");
|
||||
ui->pushButton_3->show();
|
||||
}else if(haveError){
|
||||
ui->pushButton->hide();
|
||||
ui->label_2->setText("安装出现错误");
|
||||
ui->pushButton_3->show();
|
||||
|
||||
}else {
|
||||
ui->label_2->setText("安装被终止");
|
||||
ui->pushButton->setEnabled(true);
|
||||
}
|
||||
isInstall=false;
|
||||
});
|
||||
qDebug()<<ui->label_filename->text().toUtf8();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void downloadlist::on_pushButton_2_clicked()
|
||||
{
|
||||
ui->label_2->setText("已取消下载");
|
||||
ui->pushButton_2->setEnabled(false);
|
||||
ui->progressBar->hide();
|
||||
close=true;
|
||||
}
|
||||
|
||||
void downloadlist::on_pushButton_3_clicked()
|
||||
{
|
||||
output_w.setoutput(out);
|
||||
output_w.show();
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#ifndef DOWNLOADLIST_H
|
||||
#define DOWNLOADLIST_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <outputwindow.h>
|
||||
namespace Ui {
|
||||
class downloadlist;
|
||||
}
|
||||
|
||||
class downloadlist : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit downloadlist(QWidget *parent = nullptr);
|
||||
~downloadlist();
|
||||
void setValue(long long);
|
||||
void setMax(long long);
|
||||
void setName(QString);
|
||||
QString getName();
|
||||
void readyInstall();
|
||||
void choose(bool);
|
||||
bool free;
|
||||
void setFileName(QString);
|
||||
void seticon(const QPixmap);
|
||||
void closeDownload();
|
||||
void setSpeed(QString);
|
||||
int num;
|
||||
bool close=false;
|
||||
QString out;
|
||||
outputwindow output_w;
|
||||
bool reinstall=false;
|
||||
private slots:
|
||||
void on_pushButton_clicked();
|
||||
|
||||
void on_pushButton_2_clicked();
|
||||
|
||||
|
||||
void on_pushButton_3_clicked();
|
||||
|
||||
private:
|
||||
Ui::downloadlist *ui;
|
||||
static bool isInstall;
|
||||
QString speed;
|
||||
|
||||
|
||||
};
|
||||
//bool downloadlist::isInstall=false;
|
||||
#endif // DOWNLOADLIST_H
|
||||
239
downloadlist.ui
239
downloadlist.ui
@@ -1,239 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>downloadlist</class>
|
||||
<widget class="QWidget" name="downloadlist">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>786</width>
|
||||
<height>54</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>icon</string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_filename">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>名称</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>350</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>350</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>等待开始下载</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>安装</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>取消</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>详情</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
314
gui/page/spkpageappdetails.cpp
Normal file
314
gui/page/spkpageappdetails.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "page/spkpageappdetails.h"
|
||||
#include "spkutils.h"
|
||||
#include "spkappitem.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
constexpr QSize SpkPageAppDetails::IconSize;
|
||||
|
||||
void SpkPageAppDetails::LoadAppResources(QString aPkgName, QString aIcon, QStringList aScreenshots,
|
||||
QStringList aTags)
|
||||
{
|
||||
QPixmap pic;
|
||||
|
||||
// Load icon
|
||||
auto res = RES->RequestResource(0, aPkgName, SpkResource::ResourceType::AppIcon, aIcon);
|
||||
if(res.status == SpkResource::ResourceStatus::Ready)
|
||||
{
|
||||
if(pic.loadFromData(res.data))
|
||||
mAppIcon->setPixmap(pic.scaled(IconSize,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
else
|
||||
{
|
||||
mAppIcon->setPixmap(mBrokenImg);
|
||||
RES->PurgeCachedResource(aPkgName, SpkResource::ResourceType::AppIcon, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Load screenshots. Screenshots have id starting with 1.
|
||||
if(aScreenshots.isEmpty())
|
||||
return;
|
||||
else
|
||||
{
|
||||
auto count = aScreenshots.size();
|
||||
mImgViewer->SetImageTotal(count);
|
||||
if(count > mScreenshotPreviews.size())
|
||||
{
|
||||
auto from = mScreenshotPreviews.size(), to = count - mScreenshotPreviews.size();
|
||||
for(int i = 0; i <= to; i++)
|
||||
{
|
||||
auto wid = new SpkClickLabel;
|
||||
wid->setProperty("shotId", from + i + 1);
|
||||
wid->setFixedHeight(200);
|
||||
wid->setCursor(Qt::PointingHandCursor);
|
||||
connect(wid, &SpkClickLabel::Pressed, this, &SpkPageAppDetails::ImageClicked);
|
||||
mScreenshotPreviews.append(wid);
|
||||
mScreenshotLay->addWidget(wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int shotId = 0;
|
||||
for(auto &i : aScreenshots)
|
||||
{
|
||||
shotId++;
|
||||
res = RES->RequestResource(shotId, aPkgName, SpkResource::ResourceType::AppScreenshot, i,
|
||||
shotId);
|
||||
auto preview = mScreenshotPreviews[shotId - 1];
|
||||
preview->setVisible(true);
|
||||
if(res.status == SpkResource::ResourceStatus::Ready)
|
||||
{
|
||||
if(pic.loadFromData(res.data))
|
||||
{
|
||||
mAppImages[shotId] = pic;
|
||||
mImgViewer->SetPixmap(shotId, &mAppImages[shotId]);
|
||||
preview->setPixmap(pic.scaledToHeight(200, Qt::SmoothTransformation));
|
||||
}
|
||||
else
|
||||
{
|
||||
mAppImages[shotId] = mBrokenImg;
|
||||
RES->PurgeCachedResource(aPkgName, SpkResource::ResourceType::AppScreenshot, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
preview->setPixmap(mIconLoading);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: tags
|
||||
}
|
||||
|
||||
void SpkPageAppDetails::SetWebsiteLink(QString url)
|
||||
{
|
||||
mWebsite->setText(QString("<a href=\"%1\">%2</a>").arg(url, tr("Website link")));
|
||||
}
|
||||
|
||||
void SpkPageAppDetails::SetPackagePath(QString url)
|
||||
{
|
||||
mPkgPath = url;
|
||||
}
|
||||
|
||||
SpkPageAppDetails::SpkPageAppDetails(QWidget *parent) : SpkPageBase(parent),
|
||||
mBrokenImg(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_)),
|
||||
mIconLoading(QIcon(":/icons/loading-icon.svg").pixmap(SpkAppItem::IconSize_))
|
||||
{
|
||||
mMainArea = new QScrollArea;
|
||||
mMainArea->setWidgetResizable(true);
|
||||
mMainArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
mMainLay = new QVBoxLayout(this);
|
||||
mMainLay->addWidget(mMainArea);
|
||||
|
||||
mBottomBar = new QWidget;
|
||||
mMainLay->addWidget(mBottomBar);
|
||||
|
||||
mDetailsLay = new QVBoxLayout(mMainArea);
|
||||
mDetailsLay->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
|
||||
mAppIcon = new QLabel;
|
||||
|
||||
mAppTitle = new QLabel;
|
||||
mAppTitle->setObjectName("styDetTitle");
|
||||
mAppTitle->setWordWrap(true);
|
||||
|
||||
mAppDescription = new QLabel;
|
||||
mAppDescription->setObjectName("styDetDesc");
|
||||
mAppDescription->setWordWrap(true);
|
||||
mAppShortDesc = new QLabel;
|
||||
mAppShortDesc->setObjectName("styDetDesc");
|
||||
mAppShortDesc->setWordWrap(true);
|
||||
// NOTE: Seems Qt have trouble dealing with wrapped text here. Removing the following operations
|
||||
// to mAppShortDesc will result in broken layout. Very possible that it's a Qt bug.
|
||||
mAppShortDesc->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
||||
mAppShortDesc->setMinimumWidth(100);
|
||||
mVersion = new QLabel;
|
||||
mVersion->setWordWrap(true);
|
||||
mWebsite = new QLabel;
|
||||
mPkgName = new QLabel;
|
||||
mPkgName->setObjectName("styDetPkg");
|
||||
mPkgName->setWordWrap(true);
|
||||
|
||||
mTitleLay = new QVBoxLayout;
|
||||
mTitleLay->setAlignment(Qt::AlignTop);
|
||||
mTitleLay->addWidget(mAppTitle);
|
||||
mTitleLay->addWidget(mVersion);
|
||||
mTitleLay->addWidget(mAppShortDesc);
|
||||
mTitleLay->addWidget(mPkgName);
|
||||
mTitleLay->addWidget(mWebsite);
|
||||
mTitleLay->setSpacing(0);
|
||||
|
||||
mIconTitleLay = new QHBoxLayout;
|
||||
mIconTitleLay->setAlignment(Qt::AlignLeft);
|
||||
mIconTitleLay->addWidget(mAppIcon);
|
||||
mIconTitleLay->addSpacing(15);
|
||||
mIconTitleLay->addLayout(mTitleLay);
|
||||
|
||||
mIconTitleWidget = new QWidget;
|
||||
mIconTitleWidget->setLayout(mIconTitleLay);
|
||||
|
||||
mAuthor = new SpkDetailEntry;
|
||||
mAuthor->SetTitle(tr("Author"));
|
||||
mContributor = new SpkDetailEntry;
|
||||
mContributor->SetTitle(tr("Contributor"));
|
||||
// mSite = new SpkDetailEntry;
|
||||
// mSite->SetTitle(tr("Website"));
|
||||
mArch = new SpkDetailEntry;
|
||||
mArch->SetTitle(tr("Architecture"));
|
||||
mSize = new SpkDetailEntry;
|
||||
mSize->SetTitle(tr("Size"));
|
||||
|
||||
mDetailLay = new SpkStretchLayout;
|
||||
mDetailLay->setSpacing(12);
|
||||
mDetailLay->addWidget(mAuthor);
|
||||
mDetailLay->addWidget(mContributor);
|
||||
mDetailLay->addWidget(mSize);
|
||||
mDetailLay->addWidget(mArch);
|
||||
// mDetailLay->addWidget(mSite);
|
||||
|
||||
// mDetailWidget = new QWidget;
|
||||
// mDetailWidget->setLayout(mDetailLay);
|
||||
// mDetailWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
|
||||
mDetailsLay->setAlignment(Qt::AlignTop);
|
||||
mDetailsLay->addWidget(mIconTitleWidget);
|
||||
mDetailsLay->addLayout(mDetailLay);
|
||||
mDetailsLay->addWidget(mAppDescription);
|
||||
// mMainLay->addStretch();
|
||||
|
||||
mScreenshotLay = new QHBoxLayout;
|
||||
mScreenshotArea = new QScrollArea;
|
||||
mWid4ShotArea = new QWidget;
|
||||
mWid4ShotArea->setLayout(mScreenshotLay);
|
||||
mScreenshotArea->setWidget(mWid4ShotArea);
|
||||
mScreenshotArea->setWidgetResizable(true);
|
||||
mScreenshotArea->setFixedHeight(230);
|
||||
mScreenshotArea->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
|
||||
mDetailsLay->addWidget(mScreenshotArea);
|
||||
|
||||
mWid4MainArea = new QWidget;
|
||||
mWid4MainArea->setLayout(mDetailsLay);
|
||||
|
||||
mMainArea->setWidget(mWid4MainArea);
|
||||
|
||||
mWebsite->setTextFormat(Qt::RichText);
|
||||
mWebsite->setOpenExternalLinks(true);
|
||||
|
||||
// Bottom bar buttons
|
||||
mBottomBarLay = new QHBoxLayout;
|
||||
mBottomBar->setLayout(mBottomBarLay);
|
||||
|
||||
mBtnDownload = new QPushButton;
|
||||
mBtnDownload->setText(tr("Download"));
|
||||
|
||||
// mBtnInstall = new QPushButton;
|
||||
// mBtnInstall->setText(tr("Install"));
|
||||
|
||||
mBtnUninstall = new QPushButton;
|
||||
mBtnUninstall->setText(tr("Uninstall"));
|
||||
|
||||
mBtnRequestUpdate = new QPushButton;
|
||||
mBtnRequestUpdate->setText(tr("Request Update"));
|
||||
|
||||
mBtnReport = new QPushButton;
|
||||
mBtnReport->setText(tr("Report"));
|
||||
|
||||
mBottomBarLay->addStretch();
|
||||
mBottomBarLay->addWidget(mBtnDownload);
|
||||
// mBottomBarLay->addWidget(mBtnInstall);
|
||||
mBottomBarLay->addWidget(mBtnUninstall);
|
||||
mBottomBarLay->addWidget(mBtnRequestUpdate);
|
||||
mBottomBarLay->addWidget(mBtnReport);
|
||||
|
||||
mImgViewer = new SpkImgViewer;
|
||||
mImgViewer->setVisible(false);
|
||||
|
||||
connect(mBtnDownload, &QPushButton::clicked,
|
||||
[=](){ emit RequestDownload(mAppTitle->text(), mPkgName->text(),
|
||||
"/store/reading/youdao-dict/youdao-dict_6.0.0-0~ubuntu_amd64.deb");
|
||||
});
|
||||
}
|
||||
|
||||
SpkPageAppDetails::~SpkPageAppDetails()
|
||||
{
|
||||
delete mImgViewer;
|
||||
}
|
||||
|
||||
void SpkPageAppDetails::ResourceAcquisitionFinished(int id, ResourceResult result)
|
||||
{
|
||||
QPixmap img;
|
||||
// qDebug() << "PageAppDetails: Resource" << id << "acquired";
|
||||
if(!id)
|
||||
{
|
||||
// id == 0, icon
|
||||
if(result.status == SpkResource::ResourceStatus::Ready)
|
||||
{
|
||||
if(img.loadFromData(result.data))
|
||||
mAppIcon->setPixmap(img.scaled(SpkAppItem::IconSize_,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
else
|
||||
mAppIcon->setPixmap(mBrokenImg);
|
||||
}
|
||||
else if(result.status == SpkResource::ResourceStatus::Failed)
|
||||
{
|
||||
mAppIcon->setPixmap(mBrokenImg);
|
||||
RES->PurgeCachedResource(mPkgName->text(), SpkResource::ResourceType::AppIcon, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto preview = mScreenshotPreviews[id - 1];
|
||||
preview->setVisible(true);
|
||||
if(result.status == SpkResource::ResourceStatus::Ready)
|
||||
{
|
||||
if(img.loadFromData(result.data))
|
||||
{
|
||||
mAppImages[id] = img;
|
||||
mImgViewer->SetPixmap(id, &mAppImages[id]);
|
||||
mScreenshotPreviews[id - 1]->setPixmap(img.scaledToHeight(200, Qt::SmoothTransformation));
|
||||
}
|
||||
else
|
||||
{
|
||||
mImgViewer->SetPixmap(id, &mBrokenImg);
|
||||
mScreenshotPreviews[id - 1]->setPixmap(mBrokenImg);
|
||||
}
|
||||
}
|
||||
else if(result.status == SpkResource::ResourceStatus::Failed)
|
||||
{
|
||||
mImgViewer->SetPixmap(id, &mBrokenImg);
|
||||
mScreenshotPreviews[id - 1]->setPixmap(mBrokenImg);
|
||||
RES->PurgeCachedResource(mPkgName->text(), SpkResource::ResourceType::AppIcon, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpkPageAppDetails::Activated()
|
||||
{
|
||||
RES->Acquire(this, false);
|
||||
for(auto &i : mScreenshotPreviews)
|
||||
i->setVisible(false);
|
||||
mImgViewer->Clear();
|
||||
}
|
||||
|
||||
void SpkPageAppDetails::ImageClicked()
|
||||
{
|
||||
mImgViewer->ShowWithImage(sender()->property("shotId").toInt());
|
||||
}
|
||||
|
||||
SpkDetailEntry::SpkDetailEntry(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setLayout(&mLay);
|
||||
mLay.addWidget(&mTitle);
|
||||
mLay.addWidget(&mField);
|
||||
mTitle.setAlignment(Qt::AlignLeft);
|
||||
mField.setAlignment(Qt::AlignRight);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
setMinimumWidth(300);
|
||||
setAutoFillBackground(true);
|
||||
}
|
||||
}
|
||||
181
gui/page/spkpageapplist.cpp
Normal file
181
gui/page/spkpageapplist.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
#include "page/spkpageapplist.h"
|
||||
#include "spkutils.h"
|
||||
#include "spkuimsg.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
SpkPageAppList::SpkPageAppList(QWidget *parent) : SpkPageBase(parent)
|
||||
{
|
||||
mLoadingIcon = new QPixmap(QIcon(":/icons/loading-icon.svg").pixmap(SpkAppItem::IconSize_));
|
||||
mBrokenIcon = new QPixmap(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_));
|
||||
|
||||
mAppsWidget = new QWidget;
|
||||
mAppsArea = new QScrollArea(this);
|
||||
mMainLay = new QVBoxLayout(this);
|
||||
mItemLay = new SpkStretchLayout(mAppsWidget);
|
||||
|
||||
mPageSwitchWidget = new QWidget;
|
||||
mPageSwitchLay = new QHBoxLayout(mPageSwitchWidget);
|
||||
mBtnPgUp = new QPushButton;
|
||||
mBtnPgDown = new QPushButton;
|
||||
mBtnGotoPage = new QPushButton;
|
||||
mPageInput = new QLineEdit;
|
||||
mPageValidator = new QIntValidator(mPageInput);
|
||||
mPageIndicator = new QLabel;
|
||||
|
||||
mPageValidator->setRange(1, 99);
|
||||
mPageInput->setFixedWidth(50);
|
||||
mPageInput->setValidator(mPageValidator);
|
||||
mBtnGotoPage->setText(tr("Goto"));
|
||||
mBtnPgUp->setText(tr("Previous"));
|
||||
mBtnPgDown->setText(tr("Next"));
|
||||
mBtnGotoPage->setFocusPolicy(Qt::NoFocus);
|
||||
mBtnPgDown->setFocusPolicy(Qt::NoFocus);
|
||||
mBtnPgUp->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
mPageSwitchLay->addWidget(mPageIndicator);
|
||||
mPageSwitchLay->addStretch();
|
||||
mPageSwitchLay->addWidget(mPageInput);
|
||||
mPageSwitchLay->addWidget(mBtnGotoPage);
|
||||
mPageSwitchLay->addWidget(mBtnPgUp);
|
||||
mPageSwitchLay->addWidget(mBtnPgDown);
|
||||
|
||||
mAppsArea->setWidget(mAppsWidget);
|
||||
mAppsArea->setWidgetResizable(true);
|
||||
|
||||
mMainLay->addWidget(mAppsArea);
|
||||
mMainLay->addWidget(mPageSwitchWidget);
|
||||
setLayout(mMainLay);
|
||||
|
||||
connect(mBtnPgUp, &QPushButton::clicked, this, &SpkPageAppList::PageUp);
|
||||
connect(mBtnPgDown, &QPushButton::clicked, this, &SpkPageAppList::PageDown);
|
||||
connect(mBtnGotoPage, &QPushButton::clicked, this, &SpkPageAppList::GotoPage);
|
||||
}
|
||||
|
||||
void SpkPageAppList::AddApplicationEntry(QString name, QString pkgName, QString description,
|
||||
QString iconUrl, int appId)
|
||||
{
|
||||
auto item = new SpkAppItem(appId, this);
|
||||
auto id = mAppItemList.size();
|
||||
|
||||
connect(item, &SpkAppItem::clicked, this, &SpkPageAppList::ApplicationClicked);
|
||||
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(*mBrokenIcon);
|
||||
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
item->SetIcon(*mLoadingIcon);
|
||||
|
||||
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();
|
||||
mAppsArea->verticalScrollBar()->setValue(0);
|
||||
}
|
||||
|
||||
void SpkPageAppList::ResourceAcquisitionFinished(int id, ResourceResult result)
|
||||
{
|
||||
QPixmap icon;
|
||||
// qDebug() << "PageAppList: Resource" << id << "acquired";
|
||||
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(*mBrokenIcon);
|
||||
}
|
||||
else if(result.status == SpkResource::ResourceStatus::Failed)
|
||||
{
|
||||
item->SetIcon(*mBrokenIcon);
|
||||
RES->PurgeCachedResource(item->property("pkg_name").toString(),
|
||||
SpkResource::ResourceType::AppIcon, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SpkPageAppList::SetPageStatus(int total, int current, int itemCount, QString &keyword)
|
||||
{
|
||||
mCurrentPage = current;
|
||||
mKeyword = keyword;
|
||||
mPageIndicator->setText(tr("Page %1 / %2, %3 apps in total")
|
||||
.arg(current).arg(total).arg(itemCount));
|
||||
mBtnPgUp->setDisabled(current == 1);
|
||||
mBtnPgDown->setDisabled(total == current || total == 1);
|
||||
mBtnGotoPage->setDisabled(total == 1);
|
||||
mPageValidator->setTop(total);
|
||||
}
|
||||
|
||||
void SpkPageAppList::DisablePageSwitchers()
|
||||
{
|
||||
mBtnPgDown->setDisabled(true);
|
||||
mBtnPgUp->setDisabled(true);
|
||||
mBtnGotoPage->setDisabled(true);
|
||||
}
|
||||
|
||||
void SpkPageAppList::PageUp()
|
||||
{
|
||||
DisablePageSwitchers();
|
||||
if(mKeyword.isEmpty())
|
||||
emit SwitchListPage(mCategoryId, mCurrentPage - 1);
|
||||
else
|
||||
emit SwitchSearchPage(mKeyword, mCurrentPage - 1);
|
||||
}
|
||||
|
||||
void SpkPageAppList::PageDown()
|
||||
{
|
||||
DisablePageSwitchers();
|
||||
if(mKeyword.isEmpty())
|
||||
emit SwitchListPage(mCategoryId, mCurrentPage + 1);
|
||||
else
|
||||
emit SwitchSearchPage(mKeyword, mCurrentPage + 1);
|
||||
}
|
||||
|
||||
void SpkPageAppList::GotoPage()
|
||||
{
|
||||
if(mPageInput->text().isEmpty())
|
||||
return SpkUiMessage::SendStoreNotification(tr("Please enter page number to go to!"));
|
||||
int page = mPageInput->text().toInt();
|
||||
if(page > mPageValidator->top())
|
||||
return SpkUiMessage::SendStoreNotification(tr("Page %1 is not a valid page number!")
|
||||
.arg(page));
|
||||
DisablePageSwitchers();
|
||||
if(mKeyword.isEmpty())
|
||||
emit SwitchListPage(mCategoryId, page);
|
||||
else
|
||||
emit SwitchSearchPage(mKeyword, page);
|
||||
}
|
||||
|
||||
void SpkPageAppList::Activated()
|
||||
{
|
||||
RES->Acquire(this, false);
|
||||
}
|
||||
}
|
||||
41
gui/page/spkpagebase.cpp
Normal file
41
gui/page/spkpagebase.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
#include "page/spkpagebase.h"
|
||||
|
||||
/*
|
||||
* Documentation on
|
||||
* How to Add a New Page to Main Window Side Bar
|
||||
*
|
||||
* 1. Derive your page widget class from SpkPageBase (reference implementation
|
||||
* in SpkPageSettings) and add them to CMakeLists
|
||||
*
|
||||
* 2. Add the ID for the page in enum SpkUi::SpkStackedPages (in spkmainwindow.h)
|
||||
* and add it as a resource context if needed
|
||||
*
|
||||
* 3. Include the page's header file in spkmainwindow.h and add a pointer to it in
|
||||
* SpkUi::SpkMainWidget
|
||||
*
|
||||
* 4. Add a tree item or icon button for the corresponding page inside
|
||||
* SpkUi::SpkMainWidget, and initialize it in SpkUi::SpkMainWidget::SpkMainWidget.
|
||||
* Take references of existing entries, and write similar code close to each other
|
||||
* to make the source look nice. Don't forget to add the item to the UI.
|
||||
*
|
||||
* 5. Make the linkage between the page and the sidebar item at the end of
|
||||
* SpkUi::SpkMainWidget::SpkMainWidget().
|
||||
*
|
||||
*/
|
||||
|
||||
SpkPageBase::SpkPageBase(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SpkPageBase::ResourceAcquisitionFinished(int id, ResourceResult result)
|
||||
{
|
||||
Q_UNUSED(id);
|
||||
Q_UNUSED(result);
|
||||
}
|
||||
|
||||
void SpkPageBase::Activated()
|
||||
{
|
||||
; // Do nothing
|
||||
}
|
||||
214
gui/page/spkpagedownloads.cpp
Normal file
214
gui/page/spkpagedownloads.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
|
||||
#include "page/spkpagedownloads.h"
|
||||
#include "pkgs/spkpkgmgrbase.h"
|
||||
#include "spkuimsg.h"
|
||||
#include "spkutils.h"
|
||||
|
||||
SpkUi::SpkPageDownloads::SpkPageDownloads(QWidget *parent) :
|
||||
SpkPageBase(parent)
|
||||
{
|
||||
mMainLay = new QVBoxLayout(this);
|
||||
mLayEntries = new QVBoxLayout;
|
||||
mScrollWidget = new QWidget;
|
||||
mScrollArea = new QScrollArea(this);
|
||||
|
||||
mLayEntries->setAlignment(Qt::AlignTop);
|
||||
mScrollWidget->setLayout(mLayEntries);
|
||||
mScrollArea->setWidget(mScrollWidget);
|
||||
mScrollArea->setWidgetResizable(true);
|
||||
mMainLay->addWidget(mScrollArea);
|
||||
setLayout(mMainLay);
|
||||
|
||||
mDownloadMgr = new SpkDownloadMgr(this);
|
||||
connect(mDownloadMgr, &SpkDownloadMgr::DownloadProgressed,
|
||||
this, &SpkPageDownloads::DownloadProgress);
|
||||
|
||||
mNextDownloadId = 0;
|
||||
mCurrentStatus = Idle;
|
||||
|
||||
connect(mDownloadMgr, &SpkDownloadMgr::DownloadStopped,
|
||||
this, &SpkPageDownloads::DownloadStopped, Qt::QueuedConnection);
|
||||
connect(PKG, &SpkPkgMgrBase::ReportInstallResult,
|
||||
this, &SpkPageDownloads::InstallationEnded);
|
||||
}
|
||||
|
||||
SpkUi::SpkPageDownloads::~SpkPageDownloads()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::DownloadProgress(qint64 downloadedBytes, qint64 totalBytes, int id)
|
||||
{
|
||||
if(!totalBytes)
|
||||
return;
|
||||
|
||||
if(mCurrentStatus == Waiting && totalBytes)
|
||||
{
|
||||
mCurrentStatus = Downloading;
|
||||
mEntries[id]->SetTotalBytes(totalBytes);
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::Downloading);
|
||||
}
|
||||
mEntries[id]->Progress(downloadedBytes);
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::AddDownloadTask(QString name, QString pkgName, QString path)
|
||||
{
|
||||
// Add a new download entry into the UI
|
||||
auto entry = new SpkDownloadEntry;
|
||||
auto iconData = RES->CacheLookup(pkgName, SpkResource::ResourceType::AppIcon, 0);
|
||||
auto id = mNextDownloadId;
|
||||
QPixmap icon;
|
||||
if(iconData.status != SpkResource::ResourceStatus::Ready || !icon.loadFromData(iconData.data))
|
||||
icon.load(":/icons/broken-icon.svg");
|
||||
entry->SetBasicInfo(name, icon, mDownloadMgr->GetDestFilePath(path));
|
||||
entry->SetStatus(SpkDownloadEntry::Waiting);
|
||||
entry->setProperty("entryId", id);
|
||||
entry->setProperty("path", path);
|
||||
|
||||
mNextDownloadId++;
|
||||
|
||||
mEntries[id] = entry;
|
||||
mLayEntries->addWidget(entry);
|
||||
|
||||
connect(entry, &SpkDownloadEntry::Action,
|
||||
this, &SpkPageDownloads::EntryAction);
|
||||
|
||||
NewDownloadTask(id, path);
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::DownloadStopped(SpkDownloadMgr::TaskResult status, int id)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case SpkDownloadMgr::Success:
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::ToBeInstalled);
|
||||
break;
|
||||
|
||||
case SpkDownloadMgr::FailCannotCreateFile:
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::DownloadFailed,
|
||||
tr("Cannot create download file. Download failed."));
|
||||
break;
|
||||
|
||||
case SpkDownloadMgr::FailNoVaibleServer:
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::DownloadFailed,
|
||||
tr("Connection unstable or server failure. Download failed."));
|
||||
break;
|
||||
|
||||
case SpkDownloadMgr::FailCancel:
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::DownloadFailed,
|
||||
tr("This download was cancelled."));
|
||||
break;
|
||||
|
||||
case SpkDownloadMgr::Fail:
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::DownloadFailed,
|
||||
tr("Unknown error. Download failed."));
|
||||
break;
|
||||
}
|
||||
|
||||
if(status == SpkDownloadMgr::Success)
|
||||
SpkUiMessage::SendDesktopNotification(
|
||||
tr("App \"%1\" downloaded, and ready to install.").arg(mEntries[id]->GetTaskName()));
|
||||
else if(status != SpkDownloadMgr::FailCancel)
|
||||
SpkUiMessage::SendDesktopNotification(
|
||||
tr("Error occurred downloading \"%1\".").arg(mEntries[id]->GetTaskName()));
|
||||
|
||||
|
||||
// Continue next download task
|
||||
if(!mWaitingDownloads.isEmpty())
|
||||
{
|
||||
auto nextTask = mWaitingDownloads.dequeue();
|
||||
auto nextEntry = mEntries[nextTask.first];
|
||||
nextEntry->SetStatus(SpkDownloadEntry::Starting);
|
||||
mDownloadMgr->StartNewDownload(nextTask.second, nextTask.first);
|
||||
mCurrentStatus = Waiting;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentStatus = Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::EntryAction(SpkDownloadEntry::EntryAction act)
|
||||
{
|
||||
SpkDownloadEntry *entry = static_cast<SpkDownloadEntry*>(sender());
|
||||
auto id = entry->property("entryId").toInt();
|
||||
switch(act)
|
||||
{
|
||||
case SpkDownloadEntry::AbortDownload:
|
||||
mDownloadMgr->CancelCurrentDownload(); // Only one task at a time so simply abort download
|
||||
break;
|
||||
|
||||
case SpkDownloadEntry::RetryDownload:
|
||||
mLayEntries->removeWidget(entry); // Move to list tail
|
||||
mLayEntries->addWidget(entry);
|
||||
NewDownloadTask(id, entry->property("path").toString());
|
||||
entry->SetStatus(SpkDownloadEntry::Waiting);
|
||||
break;
|
||||
|
||||
case SpkDownloadEntry::StartInstall:
|
||||
switch(PKG->ExecuteInstallation(entry->GetFilePath(), id))
|
||||
{
|
||||
case SpkPkgMgrBase::Succeeded:
|
||||
entry->SetStatus(SpkDownloadEntry::Installing);
|
||||
break;
|
||||
|
||||
case SpkPkgMgrBase::Failed:
|
||||
entry->SetStatus(SpkDownloadEntry::InstallFailed,
|
||||
tr("Failed to start installation."));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SpkDownloadEntry::RemoveEntry:
|
||||
mLayEntries->removeWidget(entry);
|
||||
mEntries.remove(id);
|
||||
for(auto i = mWaitingDownloads.begin(); i != mWaitingDownloads.end(); i++)
|
||||
{
|
||||
if(i->first == id)
|
||||
{
|
||||
mWaitingDownloads.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry->setVisible(false);
|
||||
entry->deleteLater();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::InstallationEnded(int id,
|
||||
SpkPkgMgrBase::PkgInstallResult result,
|
||||
int exitCode)
|
||||
{
|
||||
if(result == SpkPkgMgrBase::Succeeded)
|
||||
{
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::Installed);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEntries[id]->SetStatus(SpkDownloadEntry::InstallFailed,
|
||||
tr("Install failed, exit code: %1.").arg(exitCode));
|
||||
}
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageDownloads::NewDownloadTask(int id, QString downloadPath)
|
||||
{
|
||||
if(mCurrentStatus != Idle)
|
||||
mWaitingDownloads.enqueue({ id, downloadPath }); // Queue download task for future
|
||||
else
|
||||
{
|
||||
auto nextEntry = mEntries[id];
|
||||
nextEntry->SetStatus(SpkDownloadEntry::Starting);
|
||||
mCurrentStatus = Waiting;
|
||||
if(!mDownloadMgr->StartNewDownload(downloadPath, id)) // Initiate a download task when idle
|
||||
{
|
||||
// If fails to start then try next one. Emitting this signal causes
|
||||
// SpkPageDownloads::DownloadStopped to be activated and thus tries next item in queue
|
||||
emit mDownloadMgr->DownloadStopped(SpkDownloadMgr::FailNoVaibleServer, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
gui/page/spkpagehome.cpp
Normal file
50
gui/page/spkpagehome.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "page/spkpagehome.h"
|
||||
#include "spkabout.h"
|
||||
#include "gitver.h"
|
||||
|
||||
SpkUi::SpkPageHome::SpkPageHome(QWidget *parent) :
|
||||
SpkPageBase(parent)
|
||||
{
|
||||
ui = new Ui::SpkHomepage;
|
||||
ui->setupUi(this);
|
||||
|
||||
SetupUi();
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageHome::LinkActivated(QString s)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(s));
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageHome::SetupUi()
|
||||
{
|
||||
ui->lblIcon->setPixmap(QIcon(":/icons/spark-store.svg").pixmap(QSize(128, 128)));
|
||||
|
||||
ui->hlayTitle->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
ui->lblVersion->setText(ui->lblVersion->text().arg(GitVer::DescribeTags()));
|
||||
|
||||
ui->lblNewAnnouncement->setVisible(false);
|
||||
|
||||
// Click event will propagate to the main window and cause the window to move when
|
||||
// mouse enters the zoom & move detection area. Disable mouse event propagation.
|
||||
ui->lblAuthor->setAttribute(Qt::WA_NoMousePropagation, true);
|
||||
|
||||
ui->widReloadCategory->setVisible(false);
|
||||
ui->widReloadCategory->setAttribute(Qt::WA_StyledBackground);
|
||||
ui->lblCategoryErrIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(QSize(32, 32)));
|
||||
|
||||
connect(ui->lblAuthor, &QLabel::linkActivated,
|
||||
this, &SpkPageHome::LinkActivated);
|
||||
connect(ui->btnSubmit, &QPushButton::clicked,
|
||||
[&](){ LinkActivated("https://upload.deepinos.org"); });
|
||||
connect(ui->btnFeedback, &QPushButton::clicked,
|
||||
[&](){ LinkActivated("https://www.deepinos.org/t/spark-feedback"); });
|
||||
connect(ui->btnDonation, &QPushButton::clicked,
|
||||
[&](){ LinkActivated("https://spark.deepinos.org.cn/"); });
|
||||
connect(ui->btnAbout, &QPushButton::clicked,
|
||||
[&](){ SpkAbout::Show(); });
|
||||
}
|
||||
225
gui/page/spkpagesettings.cpp
Normal file
225
gui/page/spkpagesettings.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QMutexLocker>
|
||||
#include <QFutureWatcher>
|
||||
#include "spkutils.h"
|
||||
#include "page/spkpagesettings.h"
|
||||
#include "spkmsgbox.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
SpkPageSettings::SpkPageSettings(QWidget *parent) :
|
||||
SpkPageBase(parent)
|
||||
{
|
||||
mMainArea = new QScrollArea();
|
||||
mMainLay = new QVBoxLayout(this);
|
||||
mSettingsWidget = new QWidget(this);
|
||||
ui = new Ui::SpkUiSettings;
|
||||
|
||||
ui->setupUi(mSettingsWidget);
|
||||
|
||||
mMainLay->addWidget(mMainArea);
|
||||
|
||||
mMainArea->setWidget(mSettingsWidget);
|
||||
mMainArea->setWidgetResizable(true);
|
||||
|
||||
CFG->BindField("url/repo", &this->mRepoListUrl,
|
||||
"https://d.store.deepinos.org.cn/store/server.list");
|
||||
|
||||
mBytesDownloads = mBytesResource = -1;
|
||||
|
||||
connect(&mFwResourceClean, &QFutureWatcher<void>::finished,
|
||||
this, &SpkPageSettings::CleanedResource);
|
||||
connect(&mFwResourceCount, &QFutureWatcher<void>::finished,
|
||||
this, &SpkPageSettings::CountFinishResource);
|
||||
connect(&mFwDownloadClean, &QFutureWatcher<void>::finished,
|
||||
this, &SpkPageSettings::CleanedDownload);
|
||||
connect(&mFwDownloadCount, &QFutureWatcher<void>::finished,
|
||||
this, &SpkPageSettings::CountFinishDownload);
|
||||
connect(ui->btnViewDownloadedContent, &QPushButton::clicked,
|
||||
this, &SpkPageSettings::on_btnViewDownloadedContent_clicked);
|
||||
connect(ui->btnViewResourceCache, &QPushButton::clicked,
|
||||
this, &SpkPageSettings::on_btnViewResourceCache_clicked);
|
||||
connect(ui->btnCleanDownloadedContent, &QPushButton::clicked,
|
||||
this, &SpkPageSettings::on_btnCleanDownloadedContent_clicked);
|
||||
connect(ui->btnCleanResourceCache, &QPushButton::clicked,
|
||||
this, &SpkPageSettings::on_btnCleanResourceCache_clicked);
|
||||
|
||||
SetupUi();
|
||||
}
|
||||
|
||||
SpkPageSettings::~SpkPageSettings()
|
||||
{
|
||||
delete mSettingsWidget;
|
||||
}
|
||||
|
||||
void SpkPageSettings::SetupUi()
|
||||
{
|
||||
ui->lblSettingsTitle->setObjectName("styConfTitle");
|
||||
ui->lblCleanup->setObjectName("styConfTitle");
|
||||
ui->lblAdvanced->setObjectName("styConfTitle");
|
||||
|
||||
connect(ui->btnSave, &QPushButton::clicked,
|
||||
this, &SpkPageSettings::SaveConfiguration);
|
||||
}
|
||||
|
||||
void SpkPageSettings::ReadConfiguration()
|
||||
{
|
||||
ui->spnConcurrentResDownloads->setValue(CFG->ReadField("resource/concurrent", 5).toInt());
|
||||
ui->edtApiUrl->setText(CFG->ReadField("url/api", "").toString());
|
||||
ui->edtResourceUrl->setText(CFG->ReadField("url/res", "").toString());
|
||||
ui->edtResourceCachePath->setText(CFG->ReadField("dirs/cache", "").toString());
|
||||
ui->edtDownloadPath->setText(CFG->ReadField("dirs/download", "").toString());
|
||||
ui->edtDownloadServers->setPlainText(CFG->ReadField("download/servers", "").toString());
|
||||
ui->edtQssPath->setText(CFG->ReadField("internal/qss_path", "").toString());
|
||||
ui->edtRepoListUrl->setText(CFG->ReadField("url/repo", "").toString());
|
||||
ui->cmbLightDarkTheme->setCurrentIndex(CFG->ReadField("ui/theme", 0).toInt());
|
||||
}
|
||||
|
||||
void SpkPageSettings::SaveConfiguration()
|
||||
{
|
||||
CFG->SetSettings("resource/concurrent", ui->spnConcurrentResDownloads->value());
|
||||
CFG->SetField("url/api", ui->edtApiUrl->text());
|
||||
CFG->SetField("url/res", ui->edtResourceUrl->text());
|
||||
CFG->SetSettings("dirs/cache", ui->edtResourceCachePath->text());
|
||||
CFG->SetField("dirs/download", ui->edtDownloadPath->text());
|
||||
CFG->SetSettings("internal/qss_path", ui->edtQssPath->text());
|
||||
CFG->SetField("url/repo", ui->edtRepoListUrl->text());
|
||||
if(!CFG->SetField("download/servers", ui->edtDownloadServers->toPlainText()))
|
||||
SpkMsgBox::StaticExec(tr("Cannot change distribution servers.\n"
|
||||
"There's probably still downloads going on."),
|
||||
tr("Cannot set distribution server"),
|
||||
QMessageBox::Warning);
|
||||
|
||||
if(!CFG->SetField("ui/theme", ui->cmbLightDarkTheme->currentIndex()))
|
||||
SpkMsgBox::StaticExec(tr("Auto mode can only be used when DDE plugin is loaded.\n"
|
||||
"Option change is not applied."),
|
||||
tr("Cannot set theme mode"),
|
||||
QMessageBox::Warning);
|
||||
}
|
||||
|
||||
void SpkPageSettings::CountCleaning()
|
||||
{
|
||||
ui->lblSizeDownloadedContent->setText(tr("Counting..."));
|
||||
ui->lblSizeResourceCache->setText(tr("Counting..."));
|
||||
auto futureDownload = QtConcurrent::run([&]()
|
||||
{
|
||||
QDirIterator itr(ui->edtDownloadPath->text().replace('*', QDir::homePath()),
|
||||
QDirIterator::Subdirectories);
|
||||
if(mMutDownload.tryLock(0))
|
||||
{
|
||||
int64_t size = 0;
|
||||
while(itr.hasNext())
|
||||
{
|
||||
QFile f(itr.next());
|
||||
size += f.size();
|
||||
}
|
||||
mBytesDownloads = size;
|
||||
mMutDownload.unlock();
|
||||
}
|
||||
});
|
||||
auto futureResource = QtConcurrent::run([&]()
|
||||
{
|
||||
QDirIterator itr(ui->edtResourceCachePath->text().replace('*', QDir::homePath()),
|
||||
QDirIterator::Subdirectories);
|
||||
if(mMutResource.tryLock((0)))
|
||||
{
|
||||
int64_t size = 0;
|
||||
while(itr.hasNext())
|
||||
{
|
||||
QFile f(itr.next());
|
||||
size += f.size();
|
||||
}
|
||||
mBytesResource = size;
|
||||
mMutResource.unlock();
|
||||
}
|
||||
});
|
||||
mFwDownloadCount.setFuture(futureDownload);
|
||||
mFwResourceCount.setFuture(futureResource);
|
||||
}
|
||||
|
||||
void SpkPageSettings::CleanedResource()
|
||||
{
|
||||
ui->lblSizeResourceCache->setText(tr("Cleaned"));
|
||||
}
|
||||
|
||||
void SpkPageSettings::CleanedDownload()
|
||||
{
|
||||
ui->lblSizeDownloadedContent->setText(tr("Cleaned"));
|
||||
}
|
||||
|
||||
void SpkPageSettings::Activated()
|
||||
{
|
||||
ReadConfiguration();
|
||||
CountCleaning();
|
||||
}
|
||||
|
||||
void SpkPageSettings::CountFinishResource()
|
||||
{
|
||||
if(mBytesResource >= 0)
|
||||
ui->lblSizeResourceCache->setText(SpkUtils::BytesToSize(mBytesResource));
|
||||
}
|
||||
|
||||
void SpkPageSettings::CountFinishDownload()
|
||||
{
|
||||
if(mBytesDownloads >= 0)
|
||||
ui->lblSizeDownloadedContent->setText(SpkUtils::BytesToSize(mBytesDownloads));
|
||||
}
|
||||
|
||||
void SpkPageSettings::on_btnViewResourceCache_clicked()
|
||||
{
|
||||
QDesktopServices::openUrl(ui->edtResourceCachePath->text().replace('*', QDir::homePath()));
|
||||
}
|
||||
|
||||
void SpkPageSettings::on_btnViewDownloadedContent_clicked()
|
||||
{
|
||||
QDesktopServices::openUrl(ui->edtDownloadPath->text().replace('*', QDir::homePath()));
|
||||
}
|
||||
|
||||
|
||||
void SpkPageSettings::on_btnCleanResourceCache_clicked()
|
||||
{
|
||||
ui->lblSizeResourceCache->setText(tr("Cleaning..."));
|
||||
auto future = QtConcurrent::run([&]()
|
||||
{
|
||||
QDirIterator itr(ui->edtResourceCachePath->text().replace('*', QDir::homePath()),
|
||||
QDirIterator::Subdirectories);
|
||||
if(mMutDownload.tryLock(0))
|
||||
{
|
||||
int64_t size = 0;
|
||||
while(itr.hasNext())
|
||||
{
|
||||
QFile f(itr.next());
|
||||
f.remove();
|
||||
}
|
||||
mBytesDownloads = size;
|
||||
mMutDownload.unlock();
|
||||
}
|
||||
});
|
||||
mFwResourceClean.setFuture(future);
|
||||
}
|
||||
|
||||
|
||||
void SpkPageSettings::on_btnCleanDownloadedContent_clicked()
|
||||
{
|
||||
ui->lblSizeDownloadedContent->setText(tr("Cleaning..."));
|
||||
auto futureDownload = QtConcurrent::run([&]()
|
||||
{
|
||||
QDirIterator itr(ui->edtDownloadPath->text().replace('*', QDir::homePath()),
|
||||
QDirIterator::Subdirectories);
|
||||
if(mMutDownload.tryLock(0))
|
||||
{
|
||||
int64_t size = 0;
|
||||
while(itr.hasNext())
|
||||
{
|
||||
QFile f(itr.next());
|
||||
f.remove();
|
||||
}
|
||||
mBytesDownloads = size;
|
||||
mMutDownload.unlock();
|
||||
}
|
||||
});
|
||||
mFwDownloadClean.setFuture(futureDownload);
|
||||
}
|
||||
|
||||
}
|
||||
165
gui/page/spkpageuitest.cpp
Normal file
165
gui/page/spkpageuitest.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include "spkabout.h"
|
||||
#include "inc/page/spkpageuitest.h"
|
||||
#include "spkpopup.h"
|
||||
#include "spkui_general.h"
|
||||
#include "pkgs/spkpkgmgrbase.h"
|
||||
|
||||
SpkUi::SpkPageUiTest::SpkPageUiTest(QWidget *parent) : QSplitter(parent)
|
||||
{
|
||||
setObjectName("spk_pg_qsstest");
|
||||
|
||||
TextStylesheet = new QTextEdit(this);
|
||||
TextStylesheet->setObjectName("spk_pg_qsstest_qsstext");
|
||||
TextStylesheet->setPlainText(SpkUi::CurrentStylesheet);
|
||||
|
||||
BtnApply = new QPushButton(this);
|
||||
BtnApply->setObjectName("spk_pg_qsstest_btnapply");
|
||||
BtnApply->setText("Apply");
|
||||
connect(BtnApply, &QPushButton::pressed, this, &SpkPageUiTest::SetStylesheet);
|
||||
|
||||
BtnFetch = new QPushButton(this);
|
||||
BtnFetch->setObjectName("spk_pg_qsstest_btnfetch");
|
||||
BtnFetch->setText("Fetch Stylesheet");
|
||||
connect(BtnFetch, &QPushButton::pressed, this, &SpkPageUiTest::FetchStylesheet);
|
||||
|
||||
HLayInputBtns = new QHBoxLayout;
|
||||
HLayInputBtns->setObjectName("spk_pg_qsstest_hlay_inputbtns");
|
||||
HLayInputBtns->addWidget(BtnFetch);
|
||||
HLayInputBtns->addWidget(BtnApply);
|
||||
|
||||
Btn = new QPushButton(this);
|
||||
Btn->setObjectName("spk_pg_qsstest_button");
|
||||
Btn->setText("TestButton");
|
||||
|
||||
Chk = new QCheckBox(this);
|
||||
Chk->setObjectName("spk_pg_qsstest_checkbox");
|
||||
Chk->setText("CheckBox");
|
||||
|
||||
Rad = new QRadioButton(this);
|
||||
Rad->setObjectName("spk_pg_qsstest_radiobtn");
|
||||
Rad->setText("RadioButton");
|
||||
|
||||
Loading = new SpkLoading(this);
|
||||
Loading->setObjectName("spk_pg_qsstest_loading");
|
||||
Loading->start();
|
||||
|
||||
Prog = new QProgressBar(this);
|
||||
Prog->setObjectName("spk_pg_qsstest_prog");
|
||||
Prog->setValue(65);
|
||||
Prog->setRange(0, 100);
|
||||
|
||||
AppItem = new SpkAppItem(0, this);
|
||||
AppItem->setObjectName("spk_pg_qsstest_appitem");
|
||||
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));
|
||||
|
||||
Detail1 = new SpkDetailEntry; Detail1->SetTitle("Foo"); Detail1->SetValue("1");
|
||||
Detail2 = new SpkDetailEntry; Detail2->SetTitle("Foo"); Detail2->SetValue("1");
|
||||
Detail3 = new SpkDetailEntry; Detail3->SetTitle("Foo"); Detail3->SetValue("1");
|
||||
|
||||
DetailsLay = new SpkStretchLayout;
|
||||
DetailsLay->addWidget(Detail1);
|
||||
DetailsLay->addWidget(Detail2);
|
||||
DetailsLay->addWidget(Detail3);
|
||||
|
||||
DetailsWidget = new QWidget;
|
||||
DetailsWidget->setLayout(DetailsLay);
|
||||
|
||||
PopupText = new QLineEdit(this);
|
||||
PopupText->setObjectName("spk_pg_qsstest_poptext");
|
||||
PopupText->setText("Hello, world");
|
||||
|
||||
ShowPopup = new QPushButton(this);
|
||||
ShowPopup->setText("Show Popup");
|
||||
connect(ShowPopup, &QPushButton::clicked, this, &SpkPageUiTest::ShowPopupSlot);
|
||||
|
||||
ShowAbout = new QPushButton(this);
|
||||
ShowAbout->setText("Show About Dialog");
|
||||
connect(ShowAbout, &QPushButton::clicked, [](){ SpkAbout::Show(); });
|
||||
|
||||
ShowPkgmgr = new QPushButton(this);
|
||||
ShowPkgmgr->setText("Show Install Menu");
|
||||
connect(ShowPkgmgr, &QPushButton::clicked, [=](){ SpkPkgMgrBase::Instance()->ExecuteInstallation(PopupText->text(), 0); });
|
||||
|
||||
SlideV = new QSlider(this);
|
||||
SlideV->setObjectName("spk_pg_qsstest_slider_v");
|
||||
SlideV->setOrientation(Qt::Vertical);
|
||||
SlideV->setMaximum(1000);
|
||||
SlideV->setMinimum(0);
|
||||
|
||||
SlideH = new QSlider(this);
|
||||
SlideH->setObjectName("spk_pg_qsstest_slider_h");
|
||||
SlideH->setOrientation(Qt::Horizontal);
|
||||
SlideH->setMaximum(1000);
|
||||
SlideH->setMinimum(0);
|
||||
|
||||
IconBtn = new SpkIconButton(this);
|
||||
IconBtn->setObjectName("spk_pg_qsstest_iconbtn");
|
||||
IconBtn->SetIcon(QIcon(":/icons/settings.svg"), QSize{ 16, 16 });
|
||||
|
||||
VLayTestWidgets = new QVBoxLayout;
|
||||
VLayTestWidgets->setObjectName("spk_pg_qsstest_vlay_btn");
|
||||
VLayTestWidgets->addWidget(Btn);
|
||||
VLayTestWidgets->addWidget(Chk);
|
||||
VLayTestWidgets->addWidget(Rad);
|
||||
VLayTestWidgets->addWidget(IconBtn);
|
||||
VLayTestWidgets->addWidget(Loading);
|
||||
VLayTestWidgets->addWidget(PopupText);
|
||||
VLayTestWidgets->addWidget(ShowPopup);
|
||||
VLayTestWidgets->addWidget(ShowAbout);
|
||||
VLayTestWidgets->addWidget(ShowPkgmgr);
|
||||
VLayTestWidgets->addWidget(AppItem);
|
||||
VLayTestWidgets->addWidget(DetailsWidget);
|
||||
|
||||
Group = new QGroupBox(this);
|
||||
Group->setObjectName("spk_pg_qsstest_groupbox");
|
||||
Group->setTitle("GroupBox");
|
||||
Group->setLayout(VLayTestWidgets);
|
||||
|
||||
VLayInput = new QVBoxLayout;
|
||||
VLayInput->setObjectName("spk_pg_qsstest_inputlay");
|
||||
VLayInput->addWidget(TextStylesheet);
|
||||
VLayInput->addLayout(HLayInputBtns);
|
||||
|
||||
VLayWidgets = new QVBoxLayout;
|
||||
VLayWidgets->setObjectName("spk_pg_qsstest_widgetlay");
|
||||
VLayWidgets->addWidget(Group);
|
||||
VLayWidgets->addWidget(SlideH);
|
||||
VLayWidgets->addWidget(Prog);
|
||||
|
||||
HLay4Slider = new QHBoxLayout;
|
||||
HLay4Slider->setObjectName("spk_pg_qsstest_hlay_for_slider");
|
||||
HLay4Slider->addLayout(VLayWidgets);
|
||||
HLay4Slider->addWidget(SlideV);
|
||||
|
||||
WidL = new QWidget(this);
|
||||
WidL->setObjectName("spk_pg_qsstest_widleft");
|
||||
WidL->setLayout(HLay4Slider);
|
||||
WidL->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
|
||||
WidR = new QWidget(this);
|
||||
WidR->setObjectName("spk_pg_qsstest_widright");
|
||||
WidR->setLayout(VLayInput);
|
||||
|
||||
addWidget(WidL);
|
||||
addWidget(WidR);
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageUiTest::SetStylesheet()
|
||||
{
|
||||
qApp->setStyleSheet(TextStylesheet->toPlainText());
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageUiTest::FetchStylesheet()
|
||||
{
|
||||
TextStylesheet->setPlainText(SpkUi::CurrentStylesheet);
|
||||
}
|
||||
|
||||
void SpkUi::SpkPageUiTest::ShowPopupSlot()
|
||||
{
|
||||
SpkUi::Popup->Show(PopupText->text());
|
||||
}
|
||||
361
gui/page/ui/homepage.ui
Normal file
361
gui/page/ui/homepage.ui
Normal file
@@ -0,0 +1,361 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SpkHomepage</class>
|
||||
<widget class="QWidget" name="SpkHomepage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>648</width>
|
||||
<height>554</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hlayTitle">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblIcon">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>{spark-store-icon}</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblTitle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>40</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Spark Store</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblAuthor">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Brought to you by <a href="https://spark-app.store"><span style=" text-decoration: underline; color:#007af4;">Spark Project</span></a>, an open source software project.</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="vlayBtns">
|
||||
<item row="1" column="1">
|
||||
<widget class="SpkNotifyDot" name="lblNewAnnouncement">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblAboutProject">
|
||||
<property name="text">
|
||||
<string>About this project...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="btnFeedback">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Feedback</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QPushButton" name="btnDonation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Donation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="btnAnnouncements">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Announcements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="btnSubmit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Submit Software</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widReloadCategory" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblCategoryErrIcon">
|
||||
<property name="text">
|
||||
<string>{error-icon}</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblCategoryErr">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Category failed to load. Click the button to retry.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnReloadCategory">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblVersion">
|
||||
<property name="text">
|
||||
<string>Version %1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnAbout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>About</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SpkNotifyDot</class>
|
||||
<extends>QLabel</extends>
|
||||
<header>spknotifydot.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
472
gui/page/ui/settings.ui
Normal file
472
gui/page/ui/settings.ui
Normal file
@@ -0,0 +1,472 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SpkUiSettings</class>
|
||||
<widget class="QWidget" name="SpkUiSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>581</width>
|
||||
<height>896</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layTitle">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblSettingsTitle">
|
||||
<property name="text">
|
||||
<string>Spark Store Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnSave">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="lineTitle">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblRestartHint">
|
||||
<property name="text">
|
||||
<string>Configuration entries marked "*" will only take effect after restarting Spark Store.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="laySettings">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="lblApiUrl">
|
||||
<property name="text">
|
||||
<string>Store API URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="lblResourceUrl">
|
||||
<property name="text">
|
||||
<string>Store resource URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="edtDownloadPath">
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string notr="true">dirs/download</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="lblResourceCachePath">
|
||||
<property name="text">
|
||||
<string>Resource cache path*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="lblAptRepo">
|
||||
<property name="text">
|
||||
<string>APT Repository</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="edtResourceUrl">
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string>url/res</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="layAptRepo">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cmbAptRepo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string notr="true">pkgmgr/apt_repo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnFetchAptRepo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fetch all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnApplyAptRepo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblLightDarkTheme">
|
||||
<property name="text">
|
||||
<string>Light/dark theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="edtResourceCachePath">
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string>dirs/cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPlainTextEdit" name="edtDownloadServers">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string notr="true">download/servers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cmbLightDarkTheme">
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string notr="true">gui/theme</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Auto (DDE only)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always Light</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always Dark</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Manual</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblDownloadPath">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="toolTipDuration">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download path*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="lblDownloadServers">
|
||||
<property name="toolTip">
|
||||
<string>Server addresses are separated with two semicolons (;;).</string>
|
||||
</property>
|
||||
<property name="toolTipDuration">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download servers</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="edtApiUrl">
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string>url/api</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="lblRepoListUrl">
|
||||
<property name="text">
|
||||
<string>APT Repository Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="edtRepoListUrl"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblHomeNotice">
|
||||
<property name="text">
|
||||
<string>Note: character "*" in paths are replaced with the current user's home path.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblCleanup">
|
||||
<property name="text">
|
||||
<string>Cache and Downloads cleanup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblCleanupDescription">
|
||||
<property name="text">
|
||||
<string>Spark Store caches resources such as app icons and screenshots locally. If you want to free up space, you can clear them here or delete them manually.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="layCleanup">
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="btnCleanDownloadedContent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="btnViewResourceCache">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="btnViewDownloadedContent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblDownloadedContent">
|
||||
<property name="text">
|
||||
<string>Downloaded content</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblResourceCache">
|
||||
<property name="text">
|
||||
<string>Resource cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="btnCleanResourceCache">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="lblSizeResourceCache">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblSizeDownloadedContent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblAdvanced">
|
||||
<property name="text">
|
||||
<string>Advanced</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblAdvancedDescription">
|
||||
<property name="text">
|
||||
<string>Advanced settings are low-level configurations that can affect usability and are not meant to be modified by average users.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="layAdvanced">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblConcurrentResDownloads">
|
||||
<property name="text">
|
||||
<string>Concurrent resource downloads*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="spnConcurrentResDownloads">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string>resource/concurrent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblQssPath">
|
||||
<property name="text">
|
||||
<string>Default Qt Style Sheet template*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="edtQssPath">
|
||||
<property name="spkcfg_key" stdset="0">
|
||||
<string>internal/qss_path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
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]
|
||||
73
gui/spkabout.cpp
Normal file
73
gui/spkabout.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include "spkabout.h"
|
||||
#include "gitver.h"
|
||||
|
||||
SpkAbout::SpkAbout(QWidget *parent) : SpkDialog(parent)
|
||||
{
|
||||
setWindowModality(Qt::ApplicationModal);
|
||||
|
||||
// mDialogWidget->setMaximumWidth(600);
|
||||
mDialogWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
setFixedSize(550, 450);
|
||||
SetResizable(false); // Do you like the dilemma of using self created widget?
|
||||
|
||||
mIconLay = new QHBoxLayout;
|
||||
|
||||
mSpkVersion = new QLabel;
|
||||
mSpkVersion->setText(tr("<h1>Spark Store</h1>"
|
||||
"<h3>Version <a href=\"https://www.spark-app.store\">%1</a></h3>"
|
||||
"<t>Built on %2 %3</t>")
|
||||
.arg(GitVer::DescribeTags(),
|
||||
__DATE__, __TIME__));
|
||||
connect(mSpkVersion, &QLabel::linkActivated,
|
||||
[](const QString &link){ QDesktopServices::openUrl(link); });
|
||||
|
||||
mSpkIcon = new QLabel;
|
||||
mSpkIcon->setPixmap(QIcon(":/icons/spark-store.svg").pixmap(QSize(128, 128)));
|
||||
|
||||
auto description = tr(
|
||||
"Spark Store was started when Chinese home-grown Linux operating systems "
|
||||
"had initially hit the market. Because the Linux desktop ecosystem is not "
|
||||
"good enough at the time being, volunteers built this small App Store in "
|
||||
"the hope that users can get useful applications faster.\n\n"
|
||||
"Right now we are not just a Chinese group. We are discovering our way into "
|
||||
"more Debian-based Linux OSes, and build a real community software repository "
|
||||
"for users around the world.");
|
||||
mDescriptionText = new QLabel;
|
||||
mDescriptionText->setObjectName("spk_about_desc");
|
||||
mDescriptionText->setWordWrap(true);
|
||||
mDescriptionText->setText(description);
|
||||
|
||||
mIconLay->addStretch(3);
|
||||
mIconLay->addWidget(mSpkIcon);
|
||||
mIconLay->addStretch(1);
|
||||
mIconLay->addWidget(mSpkVersion);
|
||||
mIconLay->addStretch(3);
|
||||
mIconLay->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
|
||||
|
||||
AddStretch();
|
||||
AddLayout(mIconLay);
|
||||
AddSpacing(18);
|
||||
AddWidget(mDescriptionText);
|
||||
AddStretch();
|
||||
SetMargin(18, 0, 18, 18);
|
||||
|
||||
GetTitleBar()->SetOperationButton(SpkTitleBar::OperationButton::Close);
|
||||
GetTitleBar()->SetTitle(tr("About Spark Store"));
|
||||
}
|
||||
|
||||
SpkAbout::~SpkAbout()
|
||||
{
|
||||
// delete mIconLay;
|
||||
// delete mDescriptionText;
|
||||
}
|
||||
|
||||
void SpkAbout::Show()
|
||||
{
|
||||
SpkAbout *b = new SpkAbout;
|
||||
|
||||
b->Exec();
|
||||
delete b;
|
||||
}
|
||||
63
gui/spkappitem.cpp
Normal file
63
gui/spkappitem.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include "spkappitem.h"
|
||||
#include "qt/elidedlabel.h"
|
||||
|
||||
const QSize SpkAppItem::IconSize_;
|
||||
|
||||
SpkAppItem::SpkAppItem(int appId, QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
mAppId = appId;
|
||||
|
||||
mMainLay = new QHBoxLayout(this);
|
||||
mLayText = new QVBoxLayout;
|
||||
|
||||
mIcon = new QLabel;
|
||||
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 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);
|
||||
mLayText->addWidget(mTitle);
|
||||
mLayText->addWidget(mDescription);
|
||||
mLayText->setAlignment(Qt::AlignTop);
|
||||
|
||||
mMainLay->setMargin(5);
|
||||
mMainLay->addWidget(mIcon);
|
||||
mMainLay->addLayout(mLayText);
|
||||
|
||||
setMinimumHeight(82);
|
||||
setMaximumHeight(82);
|
||||
setMinimumWidth(300);
|
||||
}
|
||||
|
||||
void SpkAppItem::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
Q_UNUSED(e)
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
||||
void SpkAppItem::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
mPressCond = true;
|
||||
}
|
||||
|
||||
void SpkAppItem::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if(mPressCond)
|
||||
emit clicked(mAppId);
|
||||
mPressCond = false;
|
||||
}
|
||||
61
gui/spkcategoryselector.cpp
Normal file
61
gui/spkcategoryselector.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
#include "spkmainwindow.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
SpkCategorySelector::SpkCategorySelector(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
mBtnLayout = new QVBoxLayout(this);
|
||||
mGroup = new QButtonGroup(this);
|
||||
}
|
||||
|
||||
void SpkCategorySelector::AddButton(QString aBtnText, int aCategoryId, QPixmap *aBtnIcon)
|
||||
{
|
||||
auto btn = new SpkCategoryButton(this);
|
||||
btn->SetText(aBtnText);
|
||||
if(aBtnIcon)
|
||||
btn->SetIcon(*aBtnIcon);
|
||||
mBtnList.append(btn);
|
||||
mGroup->addButton(btn, aCategoryId ? aCategoryId : -1);
|
||||
mBtnLayout->addWidget(btn);
|
||||
}
|
||||
|
||||
void SpkCategorySelector::DeleteAllButtons() // TODO: UNTESTED
|
||||
{
|
||||
foreach (auto i, mBtnList)
|
||||
{
|
||||
mBtnLayout->removeWidget(i);
|
||||
mGroup->removeButton(i);
|
||||
i->deleteLater();
|
||||
}
|
||||
mBtnList.clear();
|
||||
}
|
||||
|
||||
SpkCategoryButton::SpkCategoryButton(QWidget *parent) : QPushButton(parent)
|
||||
{
|
||||
mIcon = new QLabel(this);
|
||||
mIcon->setObjectName("spk_categorybtn_label");
|
||||
mText = new QLabel(this);
|
||||
mText->setObjectName("spk_categorybtn_text");
|
||||
mLayout = new QHBoxLayout;
|
||||
mLayout->setObjectName("spk_categorybtn_lay");
|
||||
mLayout->addSpacing(Spacing);
|
||||
mLayout->addWidget(mIcon);
|
||||
mLayout->addSpacing(Spacing);
|
||||
mLayout->addStretch();
|
||||
mLayout->addWidget(mText);
|
||||
mLayout->addStretch();
|
||||
mLayout->addSpacing(Spacing);
|
||||
setLayout(mLayout);
|
||||
}
|
||||
|
||||
void SpkCategoryButton::SetIcon(QPixmap aImage)
|
||||
{
|
||||
mIcon->setPixmap(aImage);
|
||||
}
|
||||
|
||||
void SpkCategoryButton::SetText(QString aText)
|
||||
{
|
||||
mText->setText(aText);
|
||||
}
|
||||
}
|
||||
115
gui/spkdialog.cpp
Normal file
115
gui/spkdialog.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
|
||||
#include "spkdialog.h"
|
||||
#include <QEventLoop>
|
||||
|
||||
SpkDialog::SpkDialog(QWidget *parent) : SpkWindow(parent)
|
||||
{
|
||||
mDialogWidget = new QWidget;
|
||||
mMainVLay = new QVBoxLayout;
|
||||
mWidgetsVLay = new QVBoxLayout();
|
||||
mBtnLay = new QHBoxLayout();
|
||||
mBtnGroup = new QButtonGroup(this);
|
||||
|
||||
mMainVLay->addLayout(mWidgetsVLay);
|
||||
mMainVLay->addLayout(mBtnLay);
|
||||
|
||||
mBtnLay->setAlignment(Qt::AlignCenter);
|
||||
|
||||
SetCentralWidget(mDialogWidget);
|
||||
|
||||
mDialogWidget->setLayout(mMainVLay);
|
||||
|
||||
// idClicked is not available on platforms we support, shouldn't change it
|
||||
connect(mBtnGroup, QOverload<int>::of(&QButtonGroup::buttonClicked),
|
||||
this, &SpkDialog::ButtonPressed);
|
||||
connect(this, &SpkWindow::Closed, this, &SpkDialog::ForceClose);
|
||||
}
|
||||
|
||||
SpkDialog::~SpkDialog()
|
||||
{
|
||||
auto itp = mParentsList.begin();
|
||||
for(auto itw = mWidgetsList.begin(); itw != mWidgetsList.end(); itw++)
|
||||
{
|
||||
(*itw)->setParent(*(itp++));// We shall never take the ownership of these widgets
|
||||
}
|
||||
delete mDialogWidget;
|
||||
}
|
||||
|
||||
void SpkDialog::AddButton(QString text, SpkUi::SpkButtonStyle style)
|
||||
{
|
||||
auto b = new QPushButton();
|
||||
b->setText(text);
|
||||
b->setMinimumWidth(100);
|
||||
b->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
switch(style)
|
||||
{
|
||||
|
||||
case SpkUi::SpkButtonStyle::Recommend:
|
||||
b->setObjectName("sty_recommendbtn");
|
||||
break;
|
||||
case SpkUi::SpkButtonStyle::Warn:
|
||||
b->setObjectName("sty_warnbtn");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mBtnLay->addWidget(b);
|
||||
mBtnGroup->addButton(b);
|
||||
}
|
||||
|
||||
void SpkDialog::AddWidget(QWidget *w)
|
||||
{
|
||||
// Adding a widget does not take the ownership.
|
||||
mWidgetsVLay->addWidget(w);
|
||||
mWidgetsList << w;
|
||||
mParentsList << w->parentWidget();
|
||||
}
|
||||
|
||||
void SpkDialog::AddLayout(QLayout *w)
|
||||
{
|
||||
mWidgetsVLay->addLayout(w);
|
||||
mWidgetsList << w;
|
||||
mParentsList << w->parentWidget();
|
||||
}
|
||||
|
||||
void SpkDialog::AddSpacing(int a)
|
||||
{
|
||||
mWidgetsVLay->addSpacing(a);
|
||||
}
|
||||
|
||||
void SpkDialog::AddStretch(int a)
|
||||
{
|
||||
mWidgetsVLay->addStretch(a);
|
||||
}
|
||||
|
||||
void SpkDialog::SetMargin(int a)
|
||||
{
|
||||
mWidgetsVLay->setMargin(a);
|
||||
}
|
||||
|
||||
int SpkDialog::Exec()
|
||||
{
|
||||
QEventLoop loop;
|
||||
connect(this, &SpkDialog::ExitEventLoop, &loop, &QEventLoop::exit);
|
||||
connect(this, &SpkDialog::CloseWindow, this, &QMainWindow::close);
|
||||
show();
|
||||
return loop.exec();
|
||||
}
|
||||
|
||||
void SpkDialog::ButtonPressed(int aBtnId)
|
||||
{
|
||||
disconnect(this, &SpkWindow::Closed, this, &SpkDialog::ForceClose);
|
||||
emit ExitEventLoop(-aBtnId - 2);
|
||||
emit CloseWindow();
|
||||
}
|
||||
|
||||
void SpkDialog::ForceClose()
|
||||
{
|
||||
disconnect(this, &SpkDialog::CloseWindow, this, &QMainWindow::close);
|
||||
emit ExitEventLoop(-1);
|
||||
}
|
||||
|
||||
void SpkDialog::SetMargin(int left, int top, int right, int bottom)
|
||||
{
|
||||
mWidgetsVLay->setContentsMargins(left, top, right, bottom);
|
||||
}
|
||||
201
gui/spkdownloadentry.cpp
Normal file
201
gui/spkdownloadentry.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
#include "spkdownloadentry.h"
|
||||
#include "spklogging.h"
|
||||
#include "spkutils.h"
|
||||
#include <QDebug>
|
||||
|
||||
constexpr QSize SpkDownloadEntry::IconSize;
|
||||
|
||||
SpkDownloadEntry::SpkDownloadEntry(QWidget *parent)
|
||||
{
|
||||
mIcon = new QLabel;
|
||||
mAppName = new ElidedLabel;
|
||||
mMessage = new QLabel;
|
||||
mProgress = new QProgressBar;
|
||||
mLoading = new SpkLoading;
|
||||
mBtnDelete = new QPushButton;
|
||||
mBtnActions = new QPushButton;
|
||||
|
||||
mLayInfo = new QVBoxLayout;
|
||||
mLayMsgs = new QHBoxLayout;
|
||||
mLayMain = new QHBoxLayout;
|
||||
|
||||
mLoading->setVisible(false);
|
||||
mIcon->setFixedSize(IconSize);
|
||||
mProgress->setRange(0, 1000);
|
||||
|
||||
mLayMsgs->addWidget(mAppName);
|
||||
mLayMsgs->addStretch();
|
||||
mLayMsgs->addWidget(mMessage);
|
||||
|
||||
mLayInfo->addLayout(mLayMsgs);
|
||||
mLayInfo->addWidget(mProgress);
|
||||
mLayInfo->setAlignment(Qt::AlignVCenter);
|
||||
|
||||
mLayMain->addWidget(mIcon);
|
||||
mLayMain->addLayout(mLayInfo);
|
||||
mLayMain->addWidget(mLoading);
|
||||
mLayMain->addWidget(mBtnActions);
|
||||
mLayMain->addWidget(mBtnDelete);
|
||||
|
||||
setLayout(mLayMain);
|
||||
|
||||
connect(mBtnActions, &QPushButton::clicked, this, &SpkDownloadEntry::ActionButton);
|
||||
connect(mBtnDelete, &QPushButton::clicked, this, &SpkDownloadEntry::DeleteButton);
|
||||
|
||||
mStatus = Invalid;
|
||||
|
||||
mLastReportTime = QTime::currentTime();
|
||||
}
|
||||
|
||||
|
||||
SpkDownloadEntry::~SpkDownloadEntry()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::SetTotalBytes(qint64 total)
|
||||
{
|
||||
mTotalBytes = total;
|
||||
mReadableTotalSize = SpkUtils::BytesToSize(total);
|
||||
mLastReportTime = QTime::currentTime();
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::SetBasicInfo(QString name, QPixmap icon, QString filePath)
|
||||
{
|
||||
mAppName->setText(name);
|
||||
mIcon->setPixmap(icon.scaled(IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
mFilePath = filePath;
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::SetStatus(DownloadEntryStatus status, QString msg)
|
||||
{
|
||||
mStatus = status;
|
||||
switch(status)
|
||||
{
|
||||
case Waiting:
|
||||
mMessage->setText(tr("Waiting for download"));
|
||||
mProgress->setVisible(false);
|
||||
mBtnActions->setVisible(false);
|
||||
mBtnDelete->setVisible(true);
|
||||
mBtnDelete->setText(tr("Cancel"));
|
||||
break;
|
||||
|
||||
case Starting:
|
||||
mMessage->setText(tr("Starting download"));
|
||||
mBtnDelete->setVisible(false);
|
||||
break;
|
||||
|
||||
case Downloading:
|
||||
mMessage->setText(tr(""));
|
||||
mProgress->setVisible(true);
|
||||
mBtnActions->setVisible(false);
|
||||
mBtnDelete->setVisible(true);
|
||||
break;
|
||||
|
||||
case DownloadFailed:
|
||||
mMessage->setText(msg);
|
||||
mProgress->setVisible(false);
|
||||
mBtnActions->setVisible(true);
|
||||
mBtnActions->setText(tr("Retry"));
|
||||
|
||||
break;
|
||||
|
||||
case ToBeInstalled:
|
||||
mMessage->setText(tr("Download Finished"));
|
||||
mProgress->setVisible(false);
|
||||
mBtnActions->setVisible(true);
|
||||
mBtnDelete->setVisible(false);
|
||||
mBtnActions->setText(tr("Install"));
|
||||
break;
|
||||
|
||||
case Installing:
|
||||
mMessage->setText("");
|
||||
mProgress->setVisible(false);
|
||||
mBtnActions->setVisible(false);
|
||||
mBtnDelete->setVisible(false);
|
||||
mLoading->setVisible(true);
|
||||
mLoading->Begin();
|
||||
break;
|
||||
|
||||
case Installed:
|
||||
mMessage->setText(tr("Installed"));
|
||||
mLoading->End();
|
||||
mLoading->setVisible(false);
|
||||
mBtnDelete->setVisible(true);
|
||||
mBtnDelete->setText(tr("Delete"));
|
||||
break;
|
||||
|
||||
case InstallFailed:
|
||||
mMessage->setText(msg.isEmpty() ? tr("Install Failed") : msg);
|
||||
mLoading->End();
|
||||
mLoading->setVisible(false);
|
||||
mBtnActions->setVisible(true);
|
||||
mBtnActions->setText(tr("Install"));
|
||||
mBtnDelete->setVisible(true);
|
||||
mBtnDelete->setText(tr("Cancel"));
|
||||
break;
|
||||
|
||||
case Invalid:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::Progress(qint64 bytes)
|
||||
{
|
||||
auto now = QTime::currentTime();
|
||||
auto msecDiff = mLastReportTime.msecsTo(now);
|
||||
|
||||
if(msecDiff != 0)
|
||||
{
|
||||
auto bytesPerSec = (bytes - mDownloadedBytes) / (msecDiff / 1000.0);
|
||||
qDebug() << "Bytes" << bytes - mDownloadedBytes
|
||||
<< "MsDiff" << msecDiff / 1000.0
|
||||
<< "Bytes-Per-Seg" << bytesPerSec;
|
||||
auto speedSize = SpkUtils::BytesToSize(static_cast<size_t>(bytesPerSec));
|
||||
mMessage->setText(QString("%1/%2(%3/s)")
|
||||
.arg(SpkUtils::BytesToSize(bytes), mReadableTotalSize, speedSize));
|
||||
}
|
||||
mDownloadedBytes = bytes;
|
||||
mProgress->setValue(static_cast<int>(((double)bytes) / mTotalBytes * 1000));
|
||||
mLastReportTime = now;
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::ActionButton()
|
||||
{
|
||||
switch(mStatus)
|
||||
{
|
||||
case DownloadFailed:
|
||||
emit Action(RetryDownload);
|
||||
break;
|
||||
|
||||
case ToBeInstalled:
|
||||
case InstallFailed:
|
||||
emit Action(StartInstall);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpkDownloadEntry::DeleteButton()
|
||||
{
|
||||
switch(mStatus)
|
||||
{
|
||||
case Waiting:
|
||||
case DownloadFailed:
|
||||
case Installed:
|
||||
case InstallFailed:
|
||||
case ToBeInstalled:
|
||||
emit Action(RemoveEntry);
|
||||
break;
|
||||
|
||||
case Downloading:
|
||||
emit Action(AbortDownload);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
56
gui/spkiconbutton.cpp
Normal file
56
gui/spkiconbutton.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#include <QMargins>
|
||||
#include <QPainter>
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
#include "spkiconbutton.h"
|
||||
|
||||
SpkIconButton::SpkIconButton(QWidget *parent) :
|
||||
QPushButton(parent)
|
||||
{
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
void SpkIconButton::SetIcon(QIcon i, QSize s)
|
||||
{
|
||||
mPmapPaintedIcon = i.pixmap(s);
|
||||
|
||||
setFixedSize((mPmapSize = s.grownBy(QMargins(IconMargin, IconMargin, IconMargin, IconMargin))));
|
||||
}
|
||||
|
||||
void SpkIconButton::SetIcon(QPixmap m)
|
||||
{
|
||||
mPmapPaintedIcon = m;
|
||||
|
||||
setFixedSize((mPmapSize = m.size().grownBy(QMargins(IconMargin, IconMargin,
|
||||
IconMargin, IconMargin))));
|
||||
}
|
||||
|
||||
void SpkIconButton::SetIconSize(QSize s)
|
||||
{
|
||||
setFixedSize((mPmapSize = s.grownBy(QMargins(IconMargin, IconMargin, IconMargin, IconMargin))));
|
||||
}
|
||||
|
||||
void SpkIconButton::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPushButton::paintEvent(e);
|
||||
|
||||
// Paint the icon mask
|
||||
QPainter p(this), p1(&mPmapPaintedIcon);
|
||||
QBrush b(Qt::SolidPattern);
|
||||
|
||||
p.drawPixmap(IconMargin, IconMargin, mPmapPaintedIcon);
|
||||
if(isDown() || isChecked())
|
||||
{
|
||||
b.setColor(SpkUi::ColorBtnMaskSelected);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.setColor(SpkUi::ColorBtnMaskUnselected);
|
||||
}
|
||||
p1.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
p1.fillRect(0, 0, mPmapSize.width(), mPmapSize.height(), b);
|
||||
p1.end();
|
||||
p.drawPixmap(IconMargin, IconMargin, mPmapPaintedIcon);
|
||||
p.end();
|
||||
}
|
||||
120
gui/spkimgviewer.cpp
Normal file
120
gui/spkimgviewer.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
|
||||
#include "spktitlebar.h"
|
||||
#include "spkimgviewer.h"
|
||||
#include "spkui_general.h"
|
||||
#include <QDebug>
|
||||
#include <QFocusEvent>
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
|
||||
SpkImgViewer::SpkImgViewer(QWidget *parent) :
|
||||
SpkWindow(parent),
|
||||
mIconLoading(QIcon(":/icons/loading-icon.svg").pixmap({ 72, 72 }))
|
||||
{
|
||||
mImgIndict = new QLabel;
|
||||
mImgIndict->setText("%1/%2");
|
||||
mBtnPrev = new QPushButton;
|
||||
mBtnPrev->setText("<");
|
||||
mBtnNext = new QPushButton;
|
||||
mBtnNext->setText(">");
|
||||
|
||||
auto titleBar = GetTitleBar();
|
||||
titleBar->SetUseIcon(false);
|
||||
titleBar->SetTitle(tr("Image Preview"));
|
||||
titleBar->SetOperationButton(SpkTitleBar::OperationButton::Close);
|
||||
|
||||
auto lay = titleBar->GetUserSpace();
|
||||
lay->setAlignment(Qt::AlignVCenter);
|
||||
lay->addStretch();
|
||||
lay->addWidget(mBtnPrev);
|
||||
lay->addWidget(mImgIndict);
|
||||
lay->addWidget(mBtnNext);
|
||||
lay->addStretch();
|
||||
|
||||
mImgArea = new QScrollArea;
|
||||
mImgArea->setWidgetResizable(true);
|
||||
mImgArea->setContentsMargins(10, 10, 10, 10);
|
||||
|
||||
mImgShow = new ImgView;
|
||||
|
||||
mImgArea->setWidget(mImgShow);
|
||||
|
||||
auto w = new QWidget;
|
||||
auto l = new QHBoxLayout;
|
||||
l->setContentsMargins(10, 10, 10, 10);
|
||||
l->addWidget(mImgArea);
|
||||
w->setLayout(l);
|
||||
SetCentralWidget(w);
|
||||
|
||||
connect(mBtnPrev, &QPushButton::clicked,
|
||||
[&](){ if(mCurrentImg > 0) { SwitchToImage(--mCurrentImg); } });
|
||||
connect(mBtnNext, &QPushButton::clicked,
|
||||
[&](){ if(mCurrentImg < mTotalImg) { SwitchToImage(++mCurrentImg); } });
|
||||
}
|
||||
|
||||
void SpkImgViewer::ShowWithImage(int idx)
|
||||
{
|
||||
SwitchToImage(idx);
|
||||
show();
|
||||
}
|
||||
|
||||
void SpkImgViewer::Clear()
|
||||
{
|
||||
mImgMap.clear();
|
||||
mImgShow->SetPixmap(nullptr);
|
||||
mCurrentImg = 1;
|
||||
}
|
||||
|
||||
void SpkImgViewer::SetPixmap(int idx, QPixmap *img)
|
||||
{
|
||||
mImgMap[idx] = img;
|
||||
if(mCurrentImg == idx)
|
||||
{
|
||||
mImgShow->SetPixmap(img);
|
||||
}
|
||||
ResizeToFitImageSize(img->size());
|
||||
}
|
||||
|
||||
void SpkImgViewer::SwitchToImage(int idx)
|
||||
{
|
||||
auto img = mImgMap.value(idx, nullptr);
|
||||
mCurrentImg = idx;
|
||||
mImgShow->SetPixmap(img ? img : &mIconLoading);
|
||||
if(img)
|
||||
ResizeToFitImageSize(img->size());
|
||||
if(idx == 1)
|
||||
{
|
||||
mBtnPrev->setEnabled(false);
|
||||
mBtnNext->setEnabled(true);
|
||||
}
|
||||
else if(idx == mTotalImg)
|
||||
{
|
||||
mBtnPrev->setEnabled(true);
|
||||
mBtnNext->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
mBtnPrev->setEnabled(true);
|
||||
mBtnNext->setEnabled(true);
|
||||
}
|
||||
mImgIndict->setText(QString("%1/%2").arg(mCurrentImg).arg(mTotalImg));
|
||||
}
|
||||
|
||||
bool SpkImgViewer::event(QEvent *e)
|
||||
{
|
||||
if(e->type() == QEvent::WindowDeactivate)
|
||||
close();
|
||||
return SpkWindow::event(e);
|
||||
}
|
||||
|
||||
void SpkImgViewer::ResizeToFitImageSize(QSize s)
|
||||
{
|
||||
auto targetSize = s;
|
||||
targetSize.rheight() += SpkTitleBar::Height;
|
||||
targetSize = s.grownBy(QMargins(10, 10, 10, 10));
|
||||
targetSize = targetSize.boundedTo(SpkUi::PrimaryScreenSize * 0.8);
|
||||
resize(targetSize);
|
||||
targetSize /= 2;
|
||||
auto targetPos = SpkUi::PrimaryScreenSize / 2 - targetSize;
|
||||
move(targetPos.width(), targetPos.height());
|
||||
}
|
||||
79
gui/spkloading.cpp
Normal file
79
gui/spkloading.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include "spkloading.h"
|
||||
#include "spkui_general.h"
|
||||
|
||||
SpkLoading::SpkLoading(QWidget *parent) : QFrame(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
mAnimTimer = new QTimeLine(400, this);
|
||||
mAnimTimer->setFrameRange(10, 30);
|
||||
mAnimTimer->setEasingCurve(QEasingCurve::InCubic);
|
||||
for(int i = 0; i < 5; i++)
|
||||
mSizeList.append(20);
|
||||
connect(mAnimTimer, &QTimeLine::frameChanged, this, &SpkLoading::timer);
|
||||
connect(mAnimTimer, &QTimeLine::finished, this, &SpkLoading::loop);
|
||||
}
|
||||
|
||||
void SpkLoading::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPainter p(this);
|
||||
QPen pen(Qt::NoPen);
|
||||
QBrush b(SpkUi::CurrentColorSet[SpkUi::Qss::AccentColor], Qt::SolidPattern);
|
||||
p.setBrush(b);
|
||||
p.setPen(pen);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
dx = width() / 2 - dh * 2;
|
||||
dy = height() / 2;
|
||||
|
||||
double r;
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
r = dh * mSizeList[i] / 80;
|
||||
p.drawEllipse({ dx, dy }, r, r);
|
||||
dx += dh;
|
||||
}
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void SpkLoading::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
// Calculate size of drawing space
|
||||
if(mUserHeight != 0 && mUserHeight * 5 <= e->size().width())
|
||||
{
|
||||
dw = 5 * mUserHeight;
|
||||
dh = mUserHeight;
|
||||
return;
|
||||
}
|
||||
dh = e->size().height();
|
||||
if(width() < dh * 5)
|
||||
{
|
||||
dw = e->size().width();
|
||||
dh = dw / 5;
|
||||
}
|
||||
else
|
||||
dw = dh * 5;
|
||||
}
|
||||
|
||||
void SpkLoading::timer(int s)
|
||||
{
|
||||
for(int i = 4; i > 0; i--)
|
||||
mSizeList[i] = mSizeList[i - 1];
|
||||
mSizeList[0] = s;
|
||||
update();
|
||||
}
|
||||
|
||||
void SpkLoading::loop()
|
||||
{
|
||||
mAnimTimer->setDirection(mAnimTimer->direction() == QTimeLine::Forward ?
|
||||
QTimeLine::Backward : QTimeLine::Forward);
|
||||
mAnimTimer->start();
|
||||
}
|
||||
|
||||
void SpkLoading::reset()
|
||||
{
|
||||
for(int i = 0; i < 5; i++)
|
||||
mSizeList[i] = 20;
|
||||
update();
|
||||
}
|
||||
570
gui/spkmainwindow.cpp
Normal file
570
gui/spkmainwindow.cpp
Normal file
@@ -0,0 +1,570 @@
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include <QJsonArray>
|
||||
#include "spkmsgbox.h"
|
||||
#include "spkmainwindow.h"
|
||||
#include "spklogging.h"
|
||||
#include "spkutils.h"
|
||||
#include "spkuimsg.h"
|
||||
|
||||
SpkMainWindow::SpkMainWindow(QWidget *parent) : SpkWindow(parent)
|
||||
{
|
||||
ui = new SpkUi::SpkMainWidget(this);
|
||||
Initialize();
|
||||
|
||||
SetCentralWidget(ui);
|
||||
RefreshCategoryData();
|
||||
GetTitleBar()->SetTitle("");
|
||||
GetTitleBar()->SetUseIcon(true);
|
||||
GetTitleBar()->SetIcon(QIcon(":/icons/spark-store.svg").pixmap({ 40, 40 }));
|
||||
GetTitleBar()->setObjectName("spk_mw_titlebar");
|
||||
GetTitleBar()->setAttribute(Qt::WA_StyledBackground);
|
||||
|
||||
auto size = QGuiApplication::primaryScreen()->size() * 0.5;
|
||||
size = size.expandedTo(QSize(900, 600));
|
||||
resize(size);
|
||||
auto pos = QGuiApplication::primaryScreen()->size() * 0.5 - size * 0.5;
|
||||
move(pos.width(), pos.height());
|
||||
}
|
||||
|
||||
void SpkMainWindow::SwitchDayNightTheme()
|
||||
{
|
||||
if(SpkUi::CurrentStyle == SpkUi::Dark)
|
||||
SpkUi::SetGlobalStyle(SpkUi::Light, true);
|
||||
else
|
||||
SpkUi::SetGlobalStyle(SpkUi::Dark, true);
|
||||
ReloadThemedUiIcons();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
ui->BtnBack->setVisible(page == SpkUi::SpkStackedPages::PgAppDetails);
|
||||
ui->BtnBack->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SpkMainWindow::PopulateCategories(QJsonArray aCategoryData)
|
||||
{
|
||||
using SpkUi::SpkSidebarSelector;
|
||||
QTreeWidgetItem *catg;
|
||||
if(ui->CategoryParentItem->childCount()) // Clear all existing children if there is any
|
||||
foreach(auto &i, ui->CategoryParentItem->takeChildren())
|
||||
delete i;
|
||||
|
||||
foreach(auto i, aCategoryData)
|
||||
{
|
||||
if(i.isObject())
|
||||
{
|
||||
auto j = i.toObject();
|
||||
double typeId;
|
||||
QString typeName;
|
||||
if(j.contains("type_id") && j.value("type_id").isDouble())
|
||||
typeId = j.value("type_id").toDouble();
|
||||
else goto WRONG_CATEGORY;
|
||||
if(j.contains("type_name") && j.value("type_name").isString())
|
||||
typeName = j.value("type_name").toString();
|
||||
else goto WRONG_CATEGORY;
|
||||
catg = new QTreeWidgetItem(ui->CategoryParentItem, QStringList(typeName));
|
||||
catg->setData(0, SpkSidebarSelector::RoleItemIsCategory, true);
|
||||
catg->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, typeId);
|
||||
continue;
|
||||
WRONG_CATEGORY:;
|
||||
}
|
||||
ui->CategoryParentItem->setExpanded(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SpkMainWindow::RefreshCategoryData()
|
||||
{
|
||||
// Asynchronously call category API
|
||||
using namespace SpkUtils;
|
||||
VerifySingleRequest(mCategoryGetReply);
|
||||
mCategoryGetReply = STORE->SendApiRequest("type/get_type_list");
|
||||
DeleteReplyLater(mCategoryGetReply);
|
||||
connect(mCategoryGetReply, &QNetworkReply::finished, this, &SpkMainWindow::CategoryDataReceived);
|
||||
}
|
||||
|
||||
void SpkMainWindow::CategoryDataReceived()
|
||||
{
|
||||
QJsonValue retval;
|
||||
auto verify = SpkUtils::VerifyReplyJson(mCategoryGetReply, retval);
|
||||
if(verify || !retval.isArray())
|
||||
{
|
||||
sErr(tr("Failed to load categories! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||
sNotify(tr("Cannot load categories! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||
ui->PageHome->ui->widReloadCategory->setVisible(true);
|
||||
return;
|
||||
}
|
||||
ui->PageHome->ui->widReloadCategory->setVisible(false);
|
||||
PopulateCategories(retval.toArray());
|
||||
}
|
||||
|
||||
void SpkMainWindow::EnterCategoryList(int aCategoryId, int aPage)
|
||||
{
|
||||
// Asynchronously call category API
|
||||
using namespace SpkUtils;
|
||||
VerifySingleRequest(mCategoryAppListGetReply);
|
||||
QJsonObject reqData;
|
||||
QJsonDocument reqDoc;
|
||||
reqData.insert("type_id", QJsonValue(aCategoryId));
|
||||
reqData.insert("page", QJsonValue(aPage));
|
||||
reqDoc.setObject(reqData);
|
||||
mCategoryAppListGetReply = STORE->SendApiRequest("application/get_application_list", reqDoc);
|
||||
DeleteReplyLater(mCategoryAppListGetReply);
|
||||
connect(mCategoryAppListGetReply, &QNetworkReply::finished,
|
||||
this, &SpkMainWindow::CategoryListDataReceived);
|
||||
setCursor(Qt::BusyCursor);
|
||||
ui->PageAppList->SetCurrentCategory(aCategoryId); // AppList needs to remember current category
|
||||
}
|
||||
|
||||
void SpkMainWindow::CategoryListDataReceived()
|
||||
{
|
||||
QJsonValue retval;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
int verify = SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval);
|
||||
if(verify || !retval.isObject())
|
||||
{
|
||||
sErr(tr("Failed to load app list of category! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||
sNotify(tr("Failed to load app list of category! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||
return;
|
||||
}
|
||||
SwitchToPage(SpkUi::PgAppList);
|
||||
PopulateAppList(retval.toObject(), "");
|
||||
}
|
||||
|
||||
void SpkMainWindow::SearchKeyword(QString aKeyword, int aPage)
|
||||
{
|
||||
using namespace SpkUtils;
|
||||
VerifySingleRequest(mCategoryAppListGetReply);
|
||||
QJsonObject reqData;
|
||||
QJsonDocument reqDoc;
|
||||
reqData.insert("application_name", QJsonValue(aKeyword));
|
||||
reqData.insert("page", QJsonValue(aPage));
|
||||
reqDoc.setObject(reqData);
|
||||
mCategoryAppListGetReply = STORE->SendApiRequest("application/get_application_list", reqDoc);
|
||||
mCategoryAppListGetReply->setProperty("keyword", aKeyword);
|
||||
DeleteReplyLater(mCategoryAppListGetReply);
|
||||
connect(mCategoryAppListGetReply, &QNetworkReply::finished,
|
||||
this, &SpkMainWindow::SearchDataReceived);
|
||||
setCursor(Qt::BusyCursor);
|
||||
}
|
||||
|
||||
void SpkMainWindow::SearchDataReceived()
|
||||
{
|
||||
QJsonValue retval;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
auto verify = SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval);
|
||||
if(verify || !retval.isObject())
|
||||
{
|
||||
sErr(tr("Failed to search keyword! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||
sNotify(tr("Failed to search keyword! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||
return;
|
||||
}
|
||||
SwitchToPage(SpkUi::PgAppList);
|
||||
PopulateAppList(retval.toObject(), mCategoryAppListGetReply->property("keyword").toString());
|
||||
}
|
||||
|
||||
void SpkMainWindow::PopulateAppList(QJsonObject appData, QString &&keyword)
|
||||
{
|
||||
auto w = ui->PageAppList;
|
||||
w->ClearAll();
|
||||
static auto err =
|
||||
[](){
|
||||
sErr("Received invalid application list data!");
|
||||
SpkUiMessage::SendStoreNotification(tr("Received an invalid response. Please try again!"));
|
||||
return;
|
||||
};
|
||||
int pgCurrent, pgTotal, totalApps;
|
||||
|
||||
if(appData.contains("currentPage") && appData.value("currentPage").isDouble())
|
||||
pgCurrent = appData.value("currentPage").toInt();
|
||||
else return err();
|
||||
if(appData.contains("totalPages") && appData.value("totalPages").isDouble())
|
||||
pgTotal = appData.value("totalPages").toInt();
|
||||
else return err();
|
||||
if(appData.contains("count") && appData.value("count").isDouble())
|
||||
totalApps = appData.value("count").toInt();
|
||||
else return err();
|
||||
w->SetPageStatus(pgTotal, pgCurrent, totalApps, keyword);
|
||||
|
||||
if(!appData.contains("data") || !appData.value("data").isArray())
|
||||
return err();
|
||||
|
||||
auto apps = appData.value("data").toArray();
|
||||
|
||||
for(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpkMainWindow::EnterAppDetails(int aAppId)
|
||||
{
|
||||
using namespace SpkUtils;
|
||||
VerifySingleRequest(mAppDetailsGetReply);
|
||||
QJsonObject reqData;
|
||||
QJsonDocument reqDoc;
|
||||
reqData.insert("application_id", QJsonValue(aAppId));
|
||||
reqDoc.setObject(reqData);
|
||||
mAppDetailsGetReply = STORE->SendApiRequest("application/get_application_detail", reqDoc);
|
||||
DeleteReplyLater(mAppDetailsGetReply);
|
||||
connect(mAppDetailsGetReply, &QNetworkReply::finished,
|
||||
this, &SpkMainWindow::AppDetailsDataReceived);
|
||||
setCursor(Qt::BusyCursor);
|
||||
}
|
||||
|
||||
void SpkMainWindow::AppDetailsDataReceived()
|
||||
{
|
||||
QJsonValue retval;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
auto verify = SpkUtils::VerifyReplyJson(mAppDetailsGetReply, retval);
|
||||
if(verify || !retval.isObject())
|
||||
{
|
||||
sErr(tr("Failed to open app details page! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||
sNotify(tr("Failed to open app details page! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||
return;
|
||||
}
|
||||
SwitchToPage(SpkUi::PgAppList);
|
||||
PopulateAppDetails(retval.toObject());
|
||||
}
|
||||
|
||||
void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails)
|
||||
{
|
||||
QString pkgName, author, contributor, site, iconPath, arch, version, details, shortDesc, name,
|
||||
pkgPath;
|
||||
QStringList screenshots, tags;
|
||||
int packageSize;
|
||||
static auto err =
|
||||
[](){
|
||||
sErr("Received invalid application details!");
|
||||
SpkUiMessage::SendStoreNotification(tr("Received an invalid response. Please try again!"));
|
||||
return;
|
||||
};
|
||||
|
||||
if(appDetails.contains("package") && appDetails.value("package").isString())
|
||||
pkgName = appDetails.value("package").toString();
|
||||
else return err();
|
||||
if(appDetails.contains("application_name_zh") && appDetails.value("application_name_zh").isString())
|
||||
name = appDetails.value("application_name_zh").toString();
|
||||
else name = pkgName;
|
||||
if(appDetails.contains("version") && appDetails.value("version").isString())
|
||||
version = appDetails.value("version").toString();
|
||||
else return err();
|
||||
if(appDetails.contains("icons") && appDetails.value("icons").isString())
|
||||
iconPath= appDetails.value("icons").toString();
|
||||
if(appDetails.contains("author") && appDetails.value("author").isString())
|
||||
author = appDetails.value("author").toString();
|
||||
if(appDetails.contains("contributor") && appDetails.value("contributor").isString())
|
||||
contributor = appDetails.value("contributor").toString();
|
||||
if(appDetails.contains("website") && appDetails.value("website").isString())
|
||||
site = appDetails.value("website").toString();
|
||||
if(appDetails.contains("description") && appDetails.value("description").isString())
|
||||
shortDesc = appDetails.value("description").toString();
|
||||
if(appDetails.contains("more") && appDetails.value("more").isString())
|
||||
details = appDetails.value("more").toString();
|
||||
if(appDetails.contains("arch") && appDetails.value("arch").isString())
|
||||
arch = appDetails.value("arch").toString();
|
||||
if(appDetails.contains("size") && appDetails.value("size").isDouble())
|
||||
packageSize = appDetails.value("size").toInt();
|
||||
if(appDetails.contains("deb_url") && appDetails.value("deb_url").isString())
|
||||
pkgPath = appDetails.value("deb_url").toString();
|
||||
|
||||
QJsonArray imgs;
|
||||
if(appDetails.contains("img_urls") && appDetails.value("img_urls").isArray())
|
||||
imgs = appDetails.value("img_urls").toArray();
|
||||
if(!imgs.isEmpty())
|
||||
for(auto &&i : imgs)
|
||||
if(i.isString()) screenshots << i.toString();
|
||||
|
||||
QJsonArray tags_j;
|
||||
if(appDetails.contains("tags") && appDetails.value("tags").isArray())
|
||||
imgs = appDetails.value("tags").toArray();
|
||||
if(!tags_j.isEmpty())
|
||||
for(auto &&i : tags_j)
|
||||
if(i.isString()) tags << i.toString();
|
||||
|
||||
// Details string has a strangely appended LF. IDK but still should remove it.
|
||||
shortDesc = shortDesc.trimmed();
|
||||
|
||||
auto w = ui->PageAppDetails;
|
||||
w->mPkgName->setText(pkgName);
|
||||
w->mAppTitle->setText(name);
|
||||
w->mAppShortDesc->setText(shortDesc);
|
||||
w->mAppDescription->setText(details);
|
||||
w->mAuthor->SetValue(author);
|
||||
w->mContributor->SetValue(contributor);
|
||||
// w->mSite->SetValue(site); // Doesn't look good, I disabled it temporarily. Better solution?
|
||||
w->SetWebsiteLink(site);
|
||||
w->mArch->SetValue(arch);
|
||||
w->mSize->SetValue(SpkUtils::BytesToSize(packageSize));
|
||||
w->mVersion->setText(version);
|
||||
w->SetPackagePath(pkgPath);
|
||||
SwitchToPage(SpkUi::PgAppDetails);
|
||||
ui->AppDetailsItem->setHidden(false);
|
||||
ui->CategoryWidget->setCurrentItem(ui->AppDetailsItem);
|
||||
w->LoadAppResources(pkgName, iconPath, screenshots, tags);
|
||||
}
|
||||
|
||||
void SpkMainWindow::ReloadThemedUiIcons()
|
||||
{
|
||||
for(auto &i : mThemedUiIconReferences)
|
||||
i.first->SetIcon(SpkUi::GetThemedIcon(i.second), QSize { 20, 20 });
|
||||
}
|
||||
|
||||
// ==================== Main Window Initialization ====================
|
||||
|
||||
void SpkMainWindow::Initialize()
|
||||
{
|
||||
connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToPage,
|
||||
this, &SpkMainWindow::SwitchToPage);
|
||||
connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToCategory,
|
||||
this, &SpkMainWindow::EnterCategoryList);
|
||||
connect(ui->PageAppList, &SpkUi::SpkPageAppList::SwitchListPage,
|
||||
this, &SpkMainWindow::EnterCategoryList);
|
||||
connect(ui->PageAppList, &SpkUi::SpkPageAppList::SwitchSearchPage,
|
||||
this, &SpkMainWindow::SearchKeyword);
|
||||
connect(ui->SearchEdit, &QLineEdit::returnPressed,
|
||||
[=](){ emit SearchKeyword(ui->SearchEdit->text(), 1); });
|
||||
connect(ui->PageAppList, &SpkUi::SpkPageAppList::ApplicationClicked,
|
||||
this, &SpkMainWindow::EnterAppDetails);
|
||||
connect(ui->BtnDayNight, &QPushButton::clicked,
|
||||
this, &SpkMainWindow::SwitchDayNightTheme);
|
||||
if(SpkUi::States::IsUsingDtkPlugin)
|
||||
{
|
||||
connect(SpkUi::DtkPlugin, &SpkDtkPlugin::DarkLightThemeChanged,
|
||||
this, &SpkMainWindow::ReloadThemedUiIcons);
|
||||
}
|
||||
connect(ui->PageAppDetails, &SpkUi::SpkPageAppDetails::RequestDownload,
|
||||
ui->PageDownloads, &SpkUi::SpkPageDownloads::AddDownloadTask);
|
||||
connect(ui->PageHome->ui->btnReloadCategory, &QPushButton::clicked,
|
||||
this, &SpkMainWindow::RefreshCategoryData);
|
||||
|
||||
connect(&SpkUi::SpkUiMetaObject, &SpkUi::UiMetaObject::SetThemeButtonVisible,
|
||||
[=](bool visible)
|
||||
{
|
||||
ui->BtnDayNight->setVisible(visible);
|
||||
ReloadThemedUiIcons();
|
||||
});
|
||||
|
||||
// Register themed button icons
|
||||
// mThemedUiIconReferences.append({ ui->BtnSettings, "settings" });
|
||||
mThemedUiIconReferences.append({ ui->BtnDayNight, "daynight" });
|
||||
|
||||
ReloadThemedUiIcons();
|
||||
}
|
||||
|
||||
// ==================== Main Widget Initialization ====================
|
||||
|
||||
SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
|
||||
{
|
||||
setObjectName("spk_mainwidget");
|
||||
|
||||
Pager = new QStackedWidget(this);
|
||||
Pager->setObjectName("spk_mw_pager");
|
||||
Pager->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
SidebarMgr = new SpkSidebarSelector(this);
|
||||
SidebarMgr->setObjectName("spk_mw_sidebar_mgr");
|
||||
|
||||
BtnSettings = new SpkIconButton(this);
|
||||
BtnSettings->setObjectName("styPlainChkBtn");
|
||||
BtnSettings->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
BtnSettings->setCheckable(true);
|
||||
BtnSettings->setFixedSize({ 40, 40 });
|
||||
BtnSettings->SetIcon(QIcon(":/icons/settings.svg"), QSize(20, 20));
|
||||
BtnSettings->setProperty("spk_pageno", PgSettings);
|
||||
SidebarMgr->BindPageSwitcherButton(BtnSettings);
|
||||
|
||||
BtnDayNight = new SpkIconButton(this);
|
||||
BtnDayNight->setObjectName("styPlainChkBtn");
|
||||
BtnDayNight->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
BtnDayNight->setFixedSize({ 40, 40 });
|
||||
BtnDayNight->SetIcon(QIcon(":/icons/daynight.svg"), QSize(20, 20));
|
||||
|
||||
BtnBack = new SpkIconButton(this);
|
||||
BtnBack->setFixedSize({ 40, 40 });
|
||||
BtnBack->SetIcon(QIcon(":/icons/back.svg"), QSize(20, 20));
|
||||
BtnBack->setVisible(false);
|
||||
|
||||
using SpkUi::SpkSidebarSelector;
|
||||
CategoryWidget = new SpkSidebarTree(this);
|
||||
CategoryWidget->setObjectName("styMwCateg");
|
||||
CategoryWidget->setAutoFillBackground(true);
|
||||
CategoryWidget->setColumnCount(1);
|
||||
CategoryWidget->setHeaderHidden(true);
|
||||
CategoryWidget->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
|
||||
CategoryWidget->setFixedWidth(200);
|
||||
|
||||
//============ Sidebar entries BEGIN ============
|
||||
HomepageItem = new QTreeWidgetItem(QStringList(tr("Home")));
|
||||
HomepageItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
|
||||
HomepageItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgHomepage);
|
||||
|
||||
AppDetailsItem = new QTreeWidgetItem(QStringList(tr("App Details")));
|
||||
AppDetailsItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
|
||||
AppDetailsItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgAppDetails);
|
||||
|
||||
CategoryParentItem = new QTreeWidgetItem(QStringList(tr("Categories")));
|
||||
CategoryParentItem->setFlags(CategoryParentItem->flags().setFlag(Qt::ItemIsSelectable, false));
|
||||
|
||||
DownloadsItem = new QTreeWidgetItem(QStringList(tr("Downloads")));
|
||||
DownloadsItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
|
||||
DownloadsItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgDownloads);
|
||||
#ifndef NDEBUG
|
||||
UiTestItem = new QTreeWidgetItem(QStringList(tr("UI TEST")));
|
||||
UiTestItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
|
||||
UiTestItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgQssTest);
|
||||
#endif
|
||||
//============ Sidebar entries END ============
|
||||
|
||||
CategoryWidget->addTopLevelItem(HomepageItem);
|
||||
SidebarMgr->AddUnusableItem(CategoryParentItem);
|
||||
CategoryWidget->addTopLevelItem(AppDetailsItem);
|
||||
CategoryWidget->addTopLevelItem(CategoryParentItem);
|
||||
CategoryWidget->addTopLevelItem(DownloadsItem);
|
||||
CategoryWidget->addTopLevelItem(UiTestItem);
|
||||
|
||||
CategoryWidget->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
// Must be done after added into a view.
|
||||
AppDetailsItem->setHidden(true); // Hide until we actually open up a Details page
|
||||
CategoryParentItem->setExpanded(true);
|
||||
|
||||
// FIXMEIFPOSSIBLE: Fusion adds extra gradient.
|
||||
// Details: https://forum.qt.io/topic/128190/fusion-style-kept-adding-an-extra-
|
||||
// layer-of-gradient-to-my-selected-item-of-qtreewidget-even-with-qss
|
||||
if(SpkUi::OldSystemStyle)
|
||||
CategoryWidget->setStyle(SpkUi::OldSystemStyle);
|
||||
SidebarMgr->BindCategoryWidget(CategoryWidget);
|
||||
|
||||
HorizontalDivide = new QHBoxLayout;
|
||||
HorizontalDivide->setObjectName("spk_mw_divide_hlay");
|
||||
HorizontalDivide->setSpacing(0);
|
||||
HorizontalDivide->setContentsMargins(0, 0, 0, 0);
|
||||
HorizontalDivide->setAlignment(Qt::AlignLeft);
|
||||
if(!SpkUi::States::IsUsingDtkPlugin)
|
||||
HorizontalDivide->addSpacing(SpkWindow::BorderWidth);
|
||||
HorizontalDivide->addWidget(CategoryWidget);
|
||||
HorizontalDivide->addWidget(Pager);
|
||||
|
||||
//============ Search Bar ============
|
||||
|
||||
SearchEdit = new SpkFocusLineEdit(this);
|
||||
SearchEdit->setPlaceholderText(tr("Press Enter to search"));
|
||||
SearchEdit->setFixedWidth(30);
|
||||
SearchEdit->setFixedHeight(30);
|
||||
SearchBarAnim = new QTimeLine(300, this);
|
||||
SearchBarAnim->setDuration(300);
|
||||
SearchBarAnim->setEasingCurve(QEasingCurve::OutExpo);
|
||||
SearchBarAnim->setUpdateInterval(20);
|
||||
ActClearSearchBar = SearchEdit->addAction(QIcon(":/icons/clear-input.svg"),
|
||||
QLineEdit::TrailingPosition);
|
||||
ActClearSearchBar->setVisible(false); // Invisible by default
|
||||
ActSearchIcon = SearchEdit->addAction(QIcon(":/icons/search-mini.svg"), QLineEdit::LeadingPosition);
|
||||
connect(SearchEdit, &SpkFocusLineEdit::focusGained,
|
||||
[=](){
|
||||
ActClearSearchBar->setVisible(true);
|
||||
SearchBarAnim->setDirection(QTimeLine::Forward);
|
||||
SearchBarAnim->start();
|
||||
});
|
||||
connect(SearchEdit, &SpkFocusLineEdit::focusLost,
|
||||
[=](){
|
||||
ActClearSearchBar->setVisible(false);
|
||||
SearchBarAnim->setDirection(QTimeLine::Backward);
|
||||
SearchBarAnim->start();
|
||||
});
|
||||
connect(SearchBarAnim, &QTimeLine::valueChanged,
|
||||
[=](qreal v){
|
||||
SearchEdit->setFixedWidth(static_cast<int>(250 * v) + 30);
|
||||
});
|
||||
connect(ActClearSearchBar, &QAction::triggered, [=](){ SearchEdit->clear(); });
|
||||
connect(BtnBack, &QPushButton::clicked,
|
||||
[=](){
|
||||
SidebarMgr->GoBack();
|
||||
BtnBack->setEnabled(false);
|
||||
});
|
||||
|
||||
auto space = static_cast<SpkWindow*>(parent)->GetTitleBar()->GetUserSpace();
|
||||
|
||||
space->addSpacing(50);
|
||||
space->addWidget(BtnDayNight);
|
||||
space->addWidget(BtnSettings);
|
||||
space->addWidget(BtnBack);
|
||||
space->addWidget(SearchEdit);
|
||||
space->addStretch();
|
||||
|
||||
|
||||
//============ Pages =============
|
||||
|
||||
// Red-Black tree based map will be able to sort things. Just for convenience of ordering pages.
|
||||
QMap<SpkStackedPages, QWidget*> sorter;
|
||||
|
||||
// Initialize pages
|
||||
PageAppList = new SpkUi::SpkPageAppList(this);
|
||||
PageAppList->setProperty("spk_pageid", SpkStackedPages::PgAppList);
|
||||
sorter[PgAppList] = PageAppList;
|
||||
|
||||
PageAppDetails = new SpkUi::SpkPageAppDetails(this);
|
||||
PageAppDetails->setProperty("spk_pageid", SpkStackedPages::PgAppDetails);
|
||||
sorter[PgAppDetails] = PageAppDetails;
|
||||
|
||||
PageDownloads = new SpkUi::SpkPageDownloads(this);
|
||||
PageDownloads->setProperty("spk_pageid", SpkStackedPages::PgDownloads);
|
||||
sorter[PgDownloads] = PageDownloads;
|
||||
|
||||
PageSettings = new SpkUi::SpkPageSettings(this);
|
||||
PageSettings->setProperty("spk_pageid", SpkStackedPages::PgSettings);
|
||||
sorter[PgSettings] = PageSettings;
|
||||
|
||||
PageHome = new SpkUi::SpkPageHome(this);
|
||||
PageSettings->setProperty("spk_pageid", SpkStackedPages::PgHomepage);
|
||||
sorter[PgHomepage] = PageHome;
|
||||
|
||||
#ifndef NDEBUG // If only in debug mode should we initialize QSS test page
|
||||
PageQssTest = new SpkUi::SpkPageUiTest(this);
|
||||
PageQssTest->setProperty("spk_pageid", SpkStackedPages::PgQssTest);
|
||||
sorter[PgQssTest] = PageQssTest;
|
||||
#endif
|
||||
|
||||
for(auto i : sorter)
|
||||
Pager->addWidget(i);
|
||||
|
||||
// Default page selection : homepage
|
||||
HomepageItem->setSelected(true);
|
||||
// Manually "activate" the default page item to make the sidebar tree know about default item
|
||||
emit CategoryWidget->itemPressed(HomepageItem, 0);
|
||||
|
||||
setLayout(HorizontalDivide);
|
||||
}
|
||||
139
gui/spkmsgbox.cpp
Normal file
139
gui/spkmsgbox.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include "spkui_general.h"
|
||||
#include "spkmainwindow.h"
|
||||
#include "spkmsgbox.h"
|
||||
#include "spkstore.h"
|
||||
|
||||
// Suppress unwanted Clazy check warnings
|
||||
// clazy:excludeall=connect-3arg-lambda,lambda-in-connect
|
||||
|
||||
const QSize SpkMsgBox::IconSize; // I don't know why I need it, compiler wants that
|
||||
|
||||
SpkMsgBox::SpkMsgBox(QWidget *parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
int SpkMsgBox::StaticExec(QString msg, QString title, QMessageBox::Icon icon,
|
||||
QMessageBox::StandardButtons buttons, QString extra, bool expanded)
|
||||
{
|
||||
SpkMsgBox *b = new SpkMsgBox(SpkStore::Instance->GetRootWindow());
|
||||
QWidget *wMsgWidget = new QWidget;
|
||||
QHBoxLayout *wMsg = new QHBoxLayout(wMsgWidget);
|
||||
QPushButton *wExpandBtn;
|
||||
QScrollArea *wExtraArea;
|
||||
QLabel *wMsgText = new QLabel,
|
||||
*wExtraInfo,
|
||||
*wIcon;
|
||||
int InitialHeight;
|
||||
bool hasextra = extra.length() != 0;
|
||||
|
||||
if(icon)
|
||||
{
|
||||
wIcon = new QLabel;
|
||||
QIcon icon_;
|
||||
switch(icon)
|
||||
{
|
||||
case QMessageBox::Critical:
|
||||
icon_ = QIcon::fromTheme("dialog-error");
|
||||
break;
|
||||
case QMessageBox::Warning:
|
||||
icon_ = QIcon::fromTheme("dialog-warning");
|
||||
break;
|
||||
case QMessageBox::Information:
|
||||
icon_ = QIcon::fromTheme("dialog-information");
|
||||
break;
|
||||
case QMessageBox::Question:
|
||||
icon_ = QIcon::fromTheme("dialog-question");
|
||||
break;
|
||||
case QMessageBox::NoIcon:
|
||||
break;
|
||||
}
|
||||
if(icon)
|
||||
wIcon->setPixmap(icon_.pixmap(IconSize));
|
||||
wMsg->addWidget(wIcon);
|
||||
}
|
||||
wMsgText->setText(msg);
|
||||
wMsgText->setAlignment(Qt::AlignLeft);
|
||||
wMsg->addWidget(wMsgText);
|
||||
wMsg->setSpacing(10);
|
||||
wMsgWidget->setLayout(wMsg);
|
||||
b->AddWidget(wMsgWidget);
|
||||
b->GetTitleBar()->SetTitle(title);
|
||||
b->GetTitleBar()->SetOperationButton(SpkTitleBar::OperationButton::Close);
|
||||
b->SetResizable(false);
|
||||
b->SetCentralMargin(Margin, Margin, Margin, Margin);
|
||||
b->setMaximumSize(SpkUi::PrimaryScreenSize * 0.6);
|
||||
|
||||
if(hasextra)
|
||||
{
|
||||
wExpandBtn = new QPushButton;
|
||||
wExtraInfo = new QLabel;
|
||||
wExtraArea = new QScrollArea;
|
||||
wExtraInfo->setText(extra);
|
||||
wExtraArea->setWidget(wExtraInfo);
|
||||
wExtraArea->setVisible(false);
|
||||
wExtraArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
wExpandBtn->setText(tr("Details"));
|
||||
wExpandBtn->setMaximumWidth(100);
|
||||
wExpandBtn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
wExpandBtn->setCheckable(true);
|
||||
wExpandBtn->setObjectName("styChkBtn");
|
||||
connect(wExpandBtn, &QPushButton::clicked,
|
||||
[&](){ // FIXME: hint doesn't change when visibility changes, this is a quirky hack
|
||||
wExtraArea->setVisible(wExpandBtn->isChecked());
|
||||
if(wExpandBtn->isChecked())
|
||||
b->setFixedHeight(b->sizeHint().height());
|
||||
else
|
||||
b->setFixedHeight(InitialHeight);
|
||||
});
|
||||
b->mBtnLay->addWidget(wExpandBtn);
|
||||
b->mBtnLay->addStretch();
|
||||
b->AddWidget(wExtraArea);
|
||||
}
|
||||
|
||||
b->AddSpacing(3);
|
||||
AddButtons(b, buttons);
|
||||
if(hasextra)
|
||||
{
|
||||
b->mBtnLay->addStretch(); // Keep conventional buttons centered
|
||||
if(expanded)
|
||||
emit wExpandBtn->clicked();
|
||||
}
|
||||
InitialHeight = b->minimumSizeHint().height();
|
||||
auto pos = (SpkUi::PrimaryScreenSize - b->sizeHint()) / 2;
|
||||
b->move(pos.width(), pos.height());
|
||||
b->setWindowModality(Qt::ApplicationModal);
|
||||
b->setFixedSize(b->sizeHint());
|
||||
|
||||
auto r = b->Exec();
|
||||
if(r != -1)
|
||||
r = b->mButtonList[r]; // Retrieve the correct button
|
||||
|
||||
delete b;
|
||||
return r;
|
||||
}
|
||||
|
||||
void SpkMsgBox::AddButtons(SpkMsgBox *me, QMessageBox::StandardButtons b)
|
||||
{
|
||||
// If anyone can do it better, please let me know, I wrote this on the airplane
|
||||
using btn = QMessageBox::StandardButton;
|
||||
if(!b) return;
|
||||
if(b.testFlag(btn::Ok)) { me->AddButton(tr("OK")); me->mButtonList << btn::Ok; };
|
||||
if(b.testFlag(btn::Cancel)) { me->AddButton(tr("Cancel")); me->mButtonList << btn::Cancel; };
|
||||
if(b.testFlag(btn::Yes)) { me->AddButton(tr("Yes")); me->mButtonList << btn::Yes; };
|
||||
if(b.testFlag(btn::No)) { me->AddButton(tr("No")); me->mButtonList << btn::No; };
|
||||
if(b.testFlag(btn::Apply)) { me->AddButton(tr("Apply")); me->mButtonList << btn::Apply; };
|
||||
if(b.testFlag(btn::Reset)) { me->AddButton(tr("Reset")); me->mButtonList << btn::Reset; };
|
||||
if(b.testFlag(btn::Abort)) { me->AddButton(tr("Abort")); me->mButtonList << btn::Abort; };
|
||||
if(b.testFlag(btn::Retry)) { me->AddButton(tr("Retry")); me->mButtonList << btn::Retry; };
|
||||
if(b.testFlag(btn::Ignore)) { me->AddButton(tr("Ignore")); me->mButtonList << btn::Ignore; };
|
||||
if(b.testFlag(btn::Reset)) { me->AddButton(tr("Reset")); me->mButtonList << btn::Reset; };
|
||||
if(b.testFlag(btn::Close)) { me->AddButton(tr("Close")); me->mButtonList << btn::Close; };
|
||||
if(b.testFlag(btn::Open)) { me->AddButton(tr("Open")); me->mButtonList << btn::Open; };
|
||||
}
|
||||
35
gui/spknotifydot.cpp
Normal file
35
gui/spknotifydot.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QBrush>
|
||||
#include "spknotifydot.h"
|
||||
#include "spkstore.h"
|
||||
|
||||
/*
|
||||
* The font size and the actual geometry of this widget is hard coded
|
||||
* If you want to use this widget code please consider improving it
|
||||
*/
|
||||
|
||||
SpkNotifyDot::SpkNotifyDot(QWidget *parent) :
|
||||
QLabel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SpkNotifyDot::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPainter p(this);
|
||||
|
||||
int h = height();
|
||||
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setBrush(QBrush(QColor(255, 70, 50)));
|
||||
p.setPen(Qt::transparent);
|
||||
p.drawEllipse(0, 0, h, h);
|
||||
p.setBrush(QBrush(QColor(Qt::white)));
|
||||
p.setPen(Qt::white);
|
||||
p.setFont(QFont("sansserif", 10, 1000));
|
||||
p.drawText(QRect(0, 0, h, h), Qt::AlignHCenter | Qt::AlignVCenter, text());
|
||||
|
||||
p.end();
|
||||
}
|
||||
82
gui/spkpopup.cpp
Normal file
82
gui/spkpopup.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
#include "spkpopup.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
SpkPopup::SpkPopup(QWidget *parent, int aMillis) : QWidget(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setWindowFlags(Qt::FramelessWindowHint);
|
||||
mText = new QLabel();
|
||||
mText->setStyleSheet("border-radius: 11px;"
|
||||
"background-color: rgba(0,0,0,150);"
|
||||
"color: white;"
|
||||
"padding: 5px;");
|
||||
mText->setText(tr("(No Text)"));
|
||||
mBox = new QHBoxLayout(this);
|
||||
mBox->addWidget(mText);
|
||||
mBox->setContentsMargins(0, 0, 0, 0);
|
||||
// The reason why we contain it in a widget is that, if we want a QLabel have rounded corners,
|
||||
// then it must be able to be displayed with a translucent background. However, setting
|
||||
// Qt::WA_TranslucentBackground will cause the entire background of QLabel transparent.
|
||||
// Therefore we need a container (SpkPopup) with a transparent background as the canvas layer
|
||||
// of the actual displayed text.
|
||||
mAnim = new QSequentialAnimationGroup(this);
|
||||
|
||||
// Disabled as translucency doesn't work well on every platform :(
|
||||
// mAnimFadeIn = new QPropertyAnimation(this, "windowOpacity");
|
||||
// mAnimFadeOut = new QPropertyAnimation(this, "windowOpacity");
|
||||
// mAnimFadeIn->setStartValue(0.0);
|
||||
// mAnimFadeIn->setEndValue(1.0);
|
||||
// mAnimFadeOut->setStartValue(1.0);
|
||||
// mAnimFadeOut->setEndValue(0.0);
|
||||
// Using moving animation instead
|
||||
mAnimFadeIn = new QPropertyAnimation(this, "pos");
|
||||
mAnimFadeOut = new QPropertyAnimation(this, "pos");
|
||||
mAnimFadeIn->setDuration(250);
|
||||
mAnimFadeOut->setDuration(250);
|
||||
mAnimFadeIn->setEasingCurve(QEasingCurve::InQuad);
|
||||
mAnimFadeOut->setEasingCurve(QEasingCurve::InQuad);
|
||||
|
||||
mAnim->addAnimation(mAnimFadeIn);
|
||||
mAnim->addPause(aMillis);
|
||||
mAnim->addAnimation(mAnimFadeOut);
|
||||
setVisible(false);
|
||||
|
||||
connect(mAnim, &QAnimationGroup::stateChanged,
|
||||
[=](QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
|
||||
{
|
||||
// qDebug() << "OldState" << oldState << "NewState" << newState;
|
||||
if(newState == QAbstractAnimation::Stopped)
|
||||
setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
void SpkPopup::Show(QString aText)
|
||||
{
|
||||
if(mAnim->state() == QSequentialAnimationGroup::Running)
|
||||
mAnim->stop();
|
||||
QSize parentSize = parentWidget()->size();
|
||||
mText->setText(aText);
|
||||
adjustSize();
|
||||
move(QPoint((parentSize.width() - width()) / 2, parentSize.height() - height() - 30)/* +
|
||||
parentWidget()->pos()*/);
|
||||
setMaximumWidth(parentSize.width() - 200);
|
||||
setWindowOpacity(1);
|
||||
show();
|
||||
|
||||
mAnimFadeIn->setStartValue(QPoint((parentSize.width() - width()) / 2,
|
||||
parentSize.height() + height()));
|
||||
mAnimFadeIn->setEndValue(QPoint((parentSize.width() - width()) / 2,
|
||||
parentSize.height() - height() - 80));
|
||||
mAnimFadeOut->setStartValue(QPoint((parentSize.width() - width()) / 2,
|
||||
parentSize.height() - height() - 80));
|
||||
mAnimFadeOut->setEndValue(QPoint((parentSize.width() - width()) / 2,
|
||||
parentSize.height() + height()));
|
||||
|
||||
qDebug() << "Popup size " << size() << "position" << pos() << "parent size" << parentWidget()->size();
|
||||
mAnim->start();
|
||||
}
|
||||
}
|
||||
93
gui/spkqsshelper.cpp
Normal file
93
gui/spkqsshelper.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
#include "spkqsshelper.h"
|
||||
|
||||
const std::list<SpkUi::Qss::ColorSetIndex> SpkUi::Qss::AccentColorExceptions
|
||||
{
|
||||
AccentColor,
|
||||
AccentColorHighlighted,
|
||||
TextOnAccentColor,
|
||||
};
|
||||
|
||||
const std::map<SpkUi::Qss::ColorSetIndex, const char *> SpkUi::Qss::ColorSet2Token
|
||||
{
|
||||
{ GlobalBgnd, "GBG_" },
|
||||
{ ControlsBgnd, "CBG_" },
|
||||
{ ControlsBgndHighlighted, "CBGH" },
|
||||
{ SelectionBgnd, "ACC_" },
|
||||
{ SelectionBgndHighlighted, "ACCH" },
|
||||
{ LightCtrlsGradLight, "LCTL1" },
|
||||
{ LightCtrlsGradDark, "LCTL2" },
|
||||
{ LightCtrlsGradDarker, "LCTL3" },
|
||||
{ LightCtrlsDisabledBackground, "LCTLD" },
|
||||
{ DarkCtrlsGradLight, "DCTL1" },
|
||||
{ DarkCtrlsGradDark, "DCTL2" },
|
||||
{ DarkCtrlsGradDarker, "DCTL3" },
|
||||
{ DarkCtrlsDisabledBackground, "DCTLD" },
|
||||
{ TextOnSelection, "TXACC" },
|
||||
{ TextOnGlobalBgnd, "TXGBG" },
|
||||
{ TextOnControlsBgnd, "TXCBG" },
|
||||
{ TextLighter, "TXL1" },
|
||||
{ TextEvenLighter, "TXL2" },
|
||||
{ TextDisabled, "TXD" },
|
||||
{ GlossyEdge, "GLS" },
|
||||
{ ShadesEdge, "SHD" },
|
||||
{ ScrollBarNorm, "SCBN" },
|
||||
{ ScrollBarHover, "SCBH" },
|
||||
{ DivideLine, "DVL" },
|
||||
};
|
||||
|
||||
const std::map<SpkUi::Qss::ColorSetIndex, QColor> SpkUi::Qss::DarkColorSet
|
||||
{
|
||||
{ GlobalBgnd, 0x282828 },
|
||||
{ ControlsBgnd, 0x323232 },
|
||||
{ ControlsBgndHighlighted, 0xff0000 },
|
||||
{ SelectionBgnd, 0x0070ff },
|
||||
{ SelectionBgndHighlighted, QColor(0x0070ff).lighter(120) },
|
||||
{ LightCtrlsGradLight, 0x6b6b6b },
|
||||
{ LightCtrlsGradDark, 0x656565 },
|
||||
{ LightCtrlsGradDarker, 0x606060 },
|
||||
{ LightCtrlsDisabledBackground, 0x808080 },
|
||||
{ DarkCtrlsGradLight, 0x404040 },
|
||||
{ DarkCtrlsGradDark, 0x383838 },
|
||||
{ DarkCtrlsGradDarker, 0x323232 },
|
||||
{ DarkCtrlsDisabledBackground, 0x525252 },
|
||||
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
|
||||
{ TextOnGlobalBgnd, ColorTextOnBackground(0x282828) },
|
||||
{ TextOnControlsBgnd, ColorTextOnBackground(0x282828) },
|
||||
{ TextLighter, 0xd5d5d5 },
|
||||
{ TextEvenLighter, 0x505050 },
|
||||
{ TextDisabled, 0xbebebe },
|
||||
{ GlossyEdge, 0x656565 },
|
||||
{ ShadesEdge, 0x7b7b7b },
|
||||
{ ScrollBarNorm, 0x404040 },
|
||||
{ ScrollBarHover, 0x656565 },
|
||||
{ DivideLine, 0x424242 },
|
||||
};
|
||||
|
||||
const std::map<SpkUi::Qss::ColorSetIndex, QColor> SpkUi::Qss::LightColorSet
|
||||
{
|
||||
{ GlobalBgnd, 0xf8f8f8 },
|
||||
{ ControlsBgnd, 0xf8f8f8 },
|
||||
{ ControlsBgndHighlighted, 0xff0000 },
|
||||
{ SelectionBgnd, 0x0070ff },
|
||||
{ SelectionBgndHighlighted, QColor(0x0070ff).lighter(120) },
|
||||
{ LightCtrlsGradLight, 0xfbfbfb },
|
||||
{ LightCtrlsGradDark, 0xf2f2f2 },
|
||||
{ LightCtrlsGradDarker, 0xebebeb },
|
||||
{ LightCtrlsDisabledBackground, 0xe0e0e0 },
|
||||
{ DarkCtrlsGradLight, 0xe4e4e4 },
|
||||
{ DarkCtrlsGradDark, 0xcecece },
|
||||
{ DarkCtrlsGradDarker, 0xb8b8b8 },
|
||||
{ DarkCtrlsDisabledBackground, 0xababab },
|
||||
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
|
||||
{ TextOnGlobalBgnd, ColorTextOnBackground(0xf8f8f8) },
|
||||
{ TextOnControlsBgnd, ColorTextOnBackground(0xf8f8f8) },
|
||||
{ TextLighter, 0x2a2a2a },
|
||||
{ TextEvenLighter, 0xa0a0a0 },
|
||||
{ TextDisabled, 0x8a8a8a },
|
||||
{ GlossyEdge, 0x9d9d9d },
|
||||
{ ShadesEdge, 0xc5c5c5 },
|
||||
{ ScrollBarNorm, 0xa0a0a0 },
|
||||
{ ScrollBarHover, 0x858585 },
|
||||
{ DivideLine, 0xd5d5d5 },
|
||||
};
|
||||
31
gui/spksidebartree.cpp
Normal file
31
gui/spksidebartree.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include "spksidebartree.h"
|
||||
|
||||
SpkUi::SpkSidebarTree::SpkSidebarTree(QWidget *parent) :
|
||||
QTreeWidget(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SpkUi::SpkSidebarTree::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
// This is solely for forcibly disabling the view to change selection when dragging on the view
|
||||
// and probably the only reason why this class began its existence
|
||||
if((e->buttons() & Qt::LeftButton))
|
||||
setState(NoState);
|
||||
else
|
||||
QTreeWidget::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
void SpkUi::SpkSidebarTree::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
// Prevent anything being deselected
|
||||
if(e->modifiers().testFlag(Qt::ControlModifier) && e->buttons().testFlag(Qt::LeftButton))
|
||||
{
|
||||
auto i = itemAt(e->pos());
|
||||
if(i && i->isSelected())
|
||||
return;
|
||||
}
|
||||
QTreeWidget::mousePressEvent(e);
|
||||
}
|
||||
92
gui/spkstretchlayout.cpp
Normal file
92
gui/spkstretchlayout.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
#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
|
||||
{
|
||||
if(mItems.isEmpty())
|
||||
return { 300, 300 };
|
||||
auto w = geometry().width() - spacing();
|
||||
auto it = mItems.first();
|
||||
int countPerLine = w / (it->minimumSize().width() + spacing());
|
||||
int lines = ceil((double)mItems.size() / countPerLine);
|
||||
auto h = static_cast<int>(it->minimumSize().height() * lines + spacing() * lines);
|
||||
return { w, h };
|
||||
}
|
||||
|
||||
QSize SpkStretchLayout::minimumSize() const
|
||||
{
|
||||
// It works this way, but I honestly have no idea WHY IT WORKS
|
||||
auto r = sizeHint();
|
||||
r.setWidth(300);
|
||||
return r;
|
||||
}
|
||||
|
||||
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 < 1)
|
||||
countPerLine = 1;
|
||||
|
||||
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() };
|
||||
|
||||
auto origin = geometry().topLeft();
|
||||
|
||||
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 + origin.x(),
|
||||
(i / countPerLine) * (size.height() + spacing()) + spc + origin.y());
|
||||
o->setGeometry(geo);
|
||||
}
|
||||
|
||||
// qDebug() << rect;
|
||||
}
|
||||
149
gui/spktitlebar.cpp
Normal file
149
gui/spktitlebar.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include "spkwindow.h"
|
||||
#include "spkui_general.h"
|
||||
#include "spktitlebar.h"
|
||||
|
||||
SpkTitleBar::SpkTitleBar(QWidget *parent) : QFrame(parent)
|
||||
{
|
||||
mLinkedWindow = nullptr;
|
||||
|
||||
setFixedHeight(Height);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
mIcon = new QLabel(this);
|
||||
mTitle = new QLabel(this);
|
||||
mIcon->setFixedSize(40, 40);
|
||||
|
||||
mMainLayout = new QHBoxLayout(this);
|
||||
mUserSpace = new QHBoxLayout();
|
||||
mBtnGroup = new QHBoxLayout();
|
||||
mBtnMin = new SpkTitleBarDefaultButton(this);
|
||||
mBtnMaxRestore = new SpkTitleBarDefaultButton(this);
|
||||
mBtnClose= new SpkTitleBarDefaultButton(this);
|
||||
mMainLayout->setSpacing(8);
|
||||
mBtnGroup->setSpacing(0);
|
||||
|
||||
mMainLayout->addSpacing(12);
|
||||
mMainLayout->addWidget(mIcon);
|
||||
mMainLayout->addWidget(mTitle);
|
||||
mMainLayout->addLayout(mUserSpace);
|
||||
mMainLayout->addLayout(mBtnGroup);
|
||||
mBtnGroup->addWidget(mBtnMin);
|
||||
mBtnGroup->addWidget(mBtnMaxRestore);
|
||||
mBtnGroup->addWidget(mBtnClose);
|
||||
|
||||
mBtnMin->SetRole(OperationButton::Minimize);
|
||||
mBtnMaxRestore->SetRole(OperationButton::MaximizeRestore);
|
||||
mBtnClose->SetRole(OperationButton::Close);
|
||||
|
||||
mMainLayout->setContentsMargins(0, 0, 0, 1);
|
||||
|
||||
setLayout(mMainLayout);
|
||||
|
||||
connect(mBtnClose, &QPushButton::clicked, this, &SpkTitleBar::CloseWindow);
|
||||
connect(mBtnMin, &QPushButton::clicked, this, &SpkTitleBar::MinimizeWindow);
|
||||
connect(mBtnMaxRestore, &QPushButton::clicked, this, &SpkTitleBar::MaximizeRestoreWindow);
|
||||
}
|
||||
|
||||
SpkTitleBar::~SpkTitleBar()
|
||||
{
|
||||
//qDebug() << "Freed title bar!";
|
||||
}
|
||||
|
||||
void SpkTitleBar::SetOperationButton(OperationButton type)
|
||||
{
|
||||
mBtnClose->setVisible(type & OperationButton::Close);
|
||||
mBtnMaxRestore->setVisible(type & OperationButton::MaximizeRestore);
|
||||
mBtnMin->setVisible(type & OperationButton::Minimize);
|
||||
}
|
||||
|
||||
bool SpkTitleBar::event(QEvent *evt)
|
||||
{
|
||||
switch(evt->type())
|
||||
{
|
||||
case QEvent::MouseButtonDblClick:
|
||||
{
|
||||
if(static_cast<QMouseEvent*>(evt)->button())
|
||||
emit mBtnMaxRestore->clicked();
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return QWidget::event(evt);
|
||||
}
|
||||
|
||||
void SpkTitleBar::CloseWindow()
|
||||
{
|
||||
if(mLinkedWindow)
|
||||
mLinkedWindow->close();
|
||||
}
|
||||
|
||||
void SpkTitleBar::MinimizeWindow()
|
||||
{
|
||||
if(mLinkedWindow)
|
||||
mLinkedWindow->setWindowState(Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void SpkTitleBar::MaximizeRestoreWindow()
|
||||
{
|
||||
if(mLinkedWindow)
|
||||
{
|
||||
if(mLinkedWindow->windowState().testFlag(Qt::WindowMaximized))
|
||||
mLinkedWindow->setWindowState(mLinkedWindow->windowState() & ~Qt::WindowMaximized);
|
||||
else
|
||||
mLinkedWindow->setWindowState(Qt::WindowMaximized);
|
||||
}
|
||||
}
|
||||
|
||||
SpkTitleBarDefaultButton::SpkTitleBarDefaultButton(QWidget* parent) : QPushButton(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
setMaximumWidth(ButtonWidth);
|
||||
setMinimumWidth(ButtonWidth);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
void SpkTitleBarDefaultButton::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPushButton::paintEvent(e);
|
||||
QPainter painter;
|
||||
painter.begin(this);
|
||||
PaintSymbol(painter);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void SpkTitleBarDefaultButton::PaintSymbol(QPainter &p)
|
||||
{
|
||||
QPen pen(SpkUi::ColorLine);
|
||||
p.setPen(pen);
|
||||
auto mh = height() / 2, mw = width() / 2, h = height(), w = width();
|
||||
constexpr int fr = 10;
|
||||
switch(Role)
|
||||
{
|
||||
case Minimize:
|
||||
p.drawLine(mw - w / fr, mh, mw + w / fr, mh);
|
||||
break;
|
||||
case MaximizeRestore:
|
||||
p.drawRect(mw - w / fr, mh - h / fr,
|
||||
w / fr * 2, h / fr * 2);
|
||||
break;
|
||||
case Restore:
|
||||
p.drawRect(mw - w / fr - 2, mh - w / fr + 2, w / fr * 2, h / fr * 2);
|
||||
p.drawLine(mw - w / fr, mh - h / fr - 2, mw + w / fr, mh - h / fr - 2);
|
||||
p.drawLine(mw + w / fr, mh - h / fr - 2, mw + w / fr, mh + h / fr - 2);
|
||||
break;
|
||||
case Close:
|
||||
p.drawLine(mw - h / fr * 1.3, mh - h / fr * 1.3,
|
||||
mw + h / fr * 1.3, mh + h / fr * 1.3);
|
||||
p.drawLine(mw - h / fr * 1.3, mh + h / fr * 1.3,
|
||||
mw + h / fr * 1.3, mh - h / fr * 1.3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpkTitleBarDefaultButton::SetRole(OperationButton role)
|
||||
{
|
||||
Role = role;
|
||||
}
|
||||
332
gui/spkui_general.cpp
Normal file
332
gui/spkui_general.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/8.
|
||||
//
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QLibrary>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QScreen>
|
||||
#include <QPluginLoader>
|
||||
#include <QStyleFactory>
|
||||
#include <csignal>
|
||||
#include <execinfo.h>
|
||||
|
||||
#include "spkui_general.h"
|
||||
#include "spkmsgbox.h"
|
||||
#include "spkpopup.h"
|
||||
#include "spklogging.h"
|
||||
#include "spkstore.h"
|
||||
#include "spkutils.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
UiMetaObject SpkUiMetaObject;
|
||||
SpkUiStyle CurrentStyle;
|
||||
QString StylesheetBase, CurrentStylesheet;
|
||||
QColor ColorLine, ColorBack, ColorBtnMaskSelected, ColorBtnMaskUnselected;
|
||||
QSize PrimaryScreenSize;
|
||||
SpkDtkPlugin *DtkPlugin = nullptr;
|
||||
QStyle *OldSystemStyle = nullptr;
|
||||
|
||||
std::map<Qss::ColorSetIndex, QColor> CurrentColorSet;
|
||||
|
||||
SpkPopup *Popup;
|
||||
|
||||
namespace States
|
||||
{
|
||||
bool IsDDE = false, IsUsingDtkPlugin = false;
|
||||
|
||||
bool DoRespondAutoTheme = false;
|
||||
int LightDarkMode = 3; // Default to Manual
|
||||
|
||||
bool ThemeConfigCallback()
|
||||
{
|
||||
switch(LightDarkMode)
|
||||
{
|
||||
case 0:
|
||||
if(!DtkPlugin) return false;
|
||||
SpkUiMetaObject.SetDarkLightTheme(DtkPlugin->GetIsDarkTheme());
|
||||
break;
|
||||
case 1:
|
||||
SetGlobalStyle(Light, true);
|
||||
SpkUiMetaObject.SetThemeButtonVisible(false);
|
||||
break;
|
||||
case 2:
|
||||
SetGlobalStyle(Dark, true);
|
||||
SpkUiMetaObject.SetThemeButtonVisible(false);
|
||||
break;
|
||||
case 3:
|
||||
SpkUiMetaObject.SetThemeButtonVisible(true);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Priv
|
||||
{
|
||||
bool CrashHandlerActivated;
|
||||
}
|
||||
|
||||
// ======================= Static Linkage Private Functions ========================
|
||||
|
||||
static void SetBtnMaskColor()
|
||||
{
|
||||
ColorBtnMaskUnselected = ColorTextOnBackground(CurrentColorSet[Qss::ControlsBgnd]);
|
||||
ColorBtnMaskSelected = ColorTextOnBackground(CurrentColorSet[Qss::AccentColor]);
|
||||
}
|
||||
|
||||
// ======================== Public Functions =========================
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
// Obtain global stylesheets
|
||||
QFile ObtainStylesheet;
|
||||
ObtainStylesheet.setFileName(CFG->ReadField("internal/qss_path",
|
||||
":/stylesheet/stylesheet/default.css")
|
||||
.toString());
|
||||
ObtainStylesheet.open(QIODevice::ReadOnly);
|
||||
StylesheetBase = ObtainStylesheet.readAll();
|
||||
ObtainStylesheet.close();
|
||||
|
||||
CurrentStyle = SpkUiStyle::Invalid;
|
||||
|
||||
#ifdef NDEBUG
|
||||
SetGlobalStyle(Light, false);
|
||||
#else
|
||||
SetGlobalStyle(qEnvironmentVariableIntValue("SPK_FORCE_DARK") ? Dark : Light, false);
|
||||
#endif
|
||||
|
||||
// Initalize crash handler
|
||||
signal(SIGSEGV, SpkUi::CrashSignalHandler);
|
||||
signal(SIGABRT, SpkUi::CrashSignalHandler);
|
||||
signal(SIGFPE, SpkUi::CrashSignalHandler);
|
||||
|
||||
// Prepare theme following for DDE
|
||||
if((States::IsDDE = CheckIsDeepinDesktop()))
|
||||
PrepareForDeepinDesktop();
|
||||
|
||||
// Misc data initialization
|
||||
PrimaryScreenSize = QGuiApplication::primaryScreen()->size();
|
||||
CFG->BindField("ui/theme", &States::LightDarkMode, 3, States::ThemeConfigCallback);
|
||||
}
|
||||
|
||||
void GuessAppropriateTheme()
|
||||
{
|
||||
// FIXME: Too difficult, not implementing it
|
||||
}
|
||||
|
||||
bool CheckIsDeepinDesktop()
|
||||
{
|
||||
QString Desktop(getenv("XDG_CURRENT_DESKTOP"));
|
||||
// This method of checking is from DTK source code.
|
||||
if(Desktop.contains("deepin", Qt::CaseInsensitive) ||
|
||||
Desktop.contains("tablet", Qt::CaseInsensitive))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void PrepareForDeepinDesktop()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
qApp->addLibraryPath(qApp->applicationDirPath() + "/plugin/dtkplugin");
|
||||
#else
|
||||
// You must `make install' before these work in release mode
|
||||
qApp->addLibraryPath("/usr/local/lib");
|
||||
qApp->addLibraryPath("/usr/lib");
|
||||
#endif
|
||||
if(!qEnvironmentVariableIntValue("SPARK_NO_DTK_PLUGIN"))
|
||||
{
|
||||
QPluginLoader p("libspkdtkplugin");
|
||||
if(p.load())
|
||||
{
|
||||
auto i = qobject_cast<SpkDtkPlugin*>(p.instance());
|
||||
if(i)
|
||||
{
|
||||
DtkPlugin = i;
|
||||
States::IsUsingDtkPlugin = true;
|
||||
|
||||
i->Initialize();
|
||||
|
||||
SpkUiMetaObject.SetAccentColor(i->GetAccentColor()); // Match OS accent color
|
||||
SpkUiMetaObject.SetDarkLightTheme(i->GetIsDarkTheme()); // Match OS dark theme type
|
||||
|
||||
QObject::connect(i, &SpkDtkPlugin::AccentColorChanged,
|
||||
&SpkUiMetaObject, &UiMetaObject::SetAccentColor);
|
||||
QObject::connect(i, &SpkDtkPlugin::DarkLightThemeChanged,
|
||||
&SpkUiMetaObject, &UiMetaObject::SetDarkLightTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Chameleon style kept adding unwanted blue focus indication border
|
||||
// to widgets that shouldn't have borders.
|
||||
// We need to eliminate this irritating problem.
|
||||
if(qEnvironmentVariableIntValue("SPARK_NO_QSTYLE_CHANGE"))
|
||||
return;
|
||||
OldSystemStyle = QStyleFactory::create("chameleon"); // TreeWidget doesn't work well with Fusion
|
||||
auto styles = QStyleFactory::keys();
|
||||
styles.removeAll("chameleon");
|
||||
if(styles.contains("Fusion"))
|
||||
{
|
||||
auto style = QStyleFactory::create("Fusion");
|
||||
qApp->setStyle(style);
|
||||
}
|
||||
else if(styles.size()) // What? This shouldn't happen.
|
||||
qApp->setStyle(QStyleFactory::create(styles[0]));
|
||||
else // Duh...
|
||||
sWarn(QObject::tr("Cannot find styles other than 'chameleon'! You may see widgets "
|
||||
"with unwanted blue borders."));
|
||||
}
|
||||
|
||||
void SetGlobalStyle(const SpkUiStyle aStyle, const bool aPreserveAccentColor)
|
||||
{
|
||||
if(aStyle == CurrentStyle) // Don't waste precious CPU time parsing new style sheet!
|
||||
return;
|
||||
CurrentStyle = aStyle;
|
||||
Qss::ColorSet tempset;
|
||||
switch(aStyle)
|
||||
{
|
||||
case Invalid:
|
||||
case Light:
|
||||
tempset = Qss::LightColorSet;
|
||||
ColorLine = Qt::black;
|
||||
break;
|
||||
case Dark:
|
||||
tempset = Qss::DarkColorSet;
|
||||
ColorLine = Qt::white;
|
||||
break;
|
||||
}
|
||||
if(aPreserveAccentColor)
|
||||
{
|
||||
for(auto i : Qss::AccentColorExceptions)
|
||||
tempset[i] = CurrentColorSet[i];
|
||||
}
|
||||
CurrentColorSet = tempset;
|
||||
CurrentStylesheet = StylesheetFromColors(CurrentColorSet);
|
||||
SetBtnMaskColor();
|
||||
qApp->setStyleSheet(CurrentStylesheet);
|
||||
}
|
||||
|
||||
QString WriteStackTrace(const QString &aStackTrace)
|
||||
{
|
||||
QString path = QDir::homePath() + "/.local/share/spark-store/crash/";
|
||||
QFile StackTraceFile;
|
||||
if(!QDir().exists(path))
|
||||
if(!QDir().mkpath(path))
|
||||
return QObject::tr("Stack trace directory %1 cannot be created. "
|
||||
"Stack trace wasn't saved.").arg(path);
|
||||
path += QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
|
||||
while(QFile::exists(path))
|
||||
path += "_";
|
||||
StackTraceFile.setFileName(path);
|
||||
StackTraceFile.open(QIODevice::WriteOnly);
|
||||
if(StackTraceFile.isOpen() && StackTraceFile.isWritable())
|
||||
{
|
||||
QTextStream StackTraceWriter;
|
||||
StackTraceWriter.setDevice(&StackTraceFile);
|
||||
StackTraceWriter << QDateTime::currentDateTime().toLocalTime().toString() << "\n\n";
|
||||
StackTraceWriter << aStackTrace;
|
||||
StackTraceFile.close();
|
||||
return QObject::tr("Stack trace written to \"%1\".").arg(path);
|
||||
}
|
||||
return QObject::tr("Stack trace file %1 cannot be opened. "
|
||||
"Stack trace wasn't saved.").arg(path);
|
||||
}
|
||||
|
||||
void CrashSignalHandler(int sig)
|
||||
{
|
||||
QString msg(QObject::tr("Program has received signal %1 during normal execution.\n\n")),
|
||||
trace;
|
||||
switch(sig)
|
||||
{
|
||||
case SIGSEGV:
|
||||
msg = msg.arg(QObject::tr("\"SIGSEGV\" (Segmentation fault)"));
|
||||
goto CRASH;
|
||||
case SIGFPE:
|
||||
msg = msg.arg(QObject::tr("\"SIGFPE\" (Arithmetic exception)"));
|
||||
goto CRASH;
|
||||
case SIGABRT:
|
||||
msg = msg.arg(QObject::tr("\"SIGABRT\" (Abort)"));
|
||||
CRASH:
|
||||
{
|
||||
if(Priv::CrashHandlerActivated) // If error occured in the handler...
|
||||
{
|
||||
signal(SIGABRT, SIG_DFL); // Return control flow to OS and give up
|
||||
raise(SIGABRT);
|
||||
exit(2);
|
||||
}
|
||||
Priv::CrashHandlerActivated = true;
|
||||
void* TraceStack[StackTraceArraySize];
|
||||
int StackTraceSize = backtrace(TraceStack, StackTraceArraySize);
|
||||
auto TraceTextArray = backtrace_symbols(TraceStack, StackTraceArraySize);
|
||||
trace = QString(QObject::tr("Stack trace:\n"));
|
||||
for(int i = 0; i < StackTraceSize; i++)
|
||||
trace += QString::number(i) + "> " + QString(TraceTextArray[i]) + '\n';
|
||||
msg += QObject::tr("Spark Store cannot continue.\n\n");
|
||||
msg += WriteStackTrace(trace);
|
||||
SpkMsgBox::StaticExec(msg, QObject::tr("Spark Store Crashed"), QMessageBox::Critical,
|
||||
QMessageBox::Ok, trace);
|
||||
exit(2);
|
||||
}
|
||||
default:
|
||||
sErrPop(QObject::tr("Unknown signal %1 received in crash handler. "
|
||||
"Program internals may be corrupted. Please decide if you want "
|
||||
"to continue execution.").arg(sig));
|
||||
}
|
||||
}
|
||||
|
||||
QIcon GetThemedIcon(QString name)
|
||||
{
|
||||
if(CurrentStyle == SpkUiStyle::Dark)
|
||||
name += "-dark";
|
||||
return QIcon(":/icons/" + name + ".svg");
|
||||
}
|
||||
|
||||
QString StylesheetFromColors(Qss::ColorSet aColors)
|
||||
{
|
||||
QString ret = StylesheetBase;
|
||||
foreach(auto &i, aColors)
|
||||
ret = ret.replace(Qss::ColorSet2Token.at(i.first), i.second.name());
|
||||
return ret;
|
||||
}
|
||||
|
||||
QColor ColorTextOnBackground(QColor c)
|
||||
{
|
||||
// From https://github.com/feiyangqingyun/qtkaifajingyan
|
||||
double gray = (0.299 * c.red() + 0.587 * c.green() + 0.114 * c.blue()) / 255;
|
||||
return gray > 0.5 ? Qt::black : Qt::white;
|
||||
}
|
||||
|
||||
// =================== UiMetaObject =======================
|
||||
// UiMetaObject is the signal-slot receiver for DDE plugin, receiving the DDE system level
|
||||
// notifications of UI theme changes
|
||||
// Communications with UI widgets are also done here
|
||||
|
||||
void UiMetaObject::SetAccentColor(QColor aColor)
|
||||
{
|
||||
if(!SpkUi::States::DoRespondAutoTheme)
|
||||
return;
|
||||
CurrentColorSet[Qss::AccentColor] = aColor.lighter(90);
|
||||
CurrentColorSet[Qss::AccentColorHighlighted] = aColor.lighter(105);
|
||||
CurrentColorSet[Qss::TextOnAccentColor] = ColorTextOnBackground(aColor);
|
||||
SetBtnMaskColor();
|
||||
qApp->setStyleSheet(StylesheetFromColors(CurrentColorSet));
|
||||
}
|
||||
|
||||
void UiMetaObject::SetDarkLightTheme(bool isDark)
|
||||
{
|
||||
if(!SpkUi::States::DoRespondAutoTheme)
|
||||
return;
|
||||
if(isDark)
|
||||
SetGlobalStyle(Dark, true);
|
||||
else
|
||||
SetGlobalStyle(Light, true);
|
||||
}
|
||||
}
|
||||
326
gui/spkwindow.cpp
Normal file
326
gui/spkwindow.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QApplication>
|
||||
#include <QStyleHints>
|
||||
#include <QPainterPath>
|
||||
#include <QFile>
|
||||
#include <QPushButton>
|
||||
#include <QMouseEvent>
|
||||
#include "spklogging.h"
|
||||
#include "spkwindow.h"
|
||||
#include "spkui_general.h"
|
||||
#include "spktitlebar.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
SpkWindow::SpkWindow(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
mUseCustomEvents = SpkUi::DtkPlugin == nullptr; // Put to the front, to prevent warnings
|
||||
if(SpkUi::DtkPlugin && !qEnvironmentVariableIntValue("SPARK_NO_DXCB"))
|
||||
SpkUi::DtkPlugin->addWindow(this, parent); // Register window to DXcb so we got Deepin
|
||||
else
|
||||
setWindowFlags(Qt::FramelessWindowHint); // Remove default title bar
|
||||
setAttribute(Qt::WA_Hover);
|
||||
mCornerRadius = 5;
|
||||
mUserCentralWidget = nullptr;
|
||||
mResizable = true;
|
||||
mMoving = mResizing = false;
|
||||
mCloseHook = nullptr;
|
||||
mMaximized = windowState().testFlag(Qt::WindowMaximized);
|
||||
setMouseTracking(true);
|
||||
|
||||
PopulateUi();
|
||||
}
|
||||
|
||||
SpkWindow::~SpkWindow()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool SpkWindow::event(QEvent *evt)
|
||||
{
|
||||
// These custom events weren't useful for Deepin platform
|
||||
if(!mUseCustomEvents)
|
||||
return QWidget::event(evt);
|
||||
|
||||
switch(evt->type())
|
||||
{
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
mMaximized = windowState().testFlag(Qt::WindowMaximized);
|
||||
if(mMaximized)
|
||||
mTitleBarComponent->mBtnMaxRestore->SetRole(SpkTitleBarDefaultButton::Restore);
|
||||
else
|
||||
mTitleBarComponent->mBtnMaxRestore->SetRole(SpkTitleBarDefaultButton::MaximizeRestore);
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseButtonPress:
|
||||
{
|
||||
// if(!mResizable) break;
|
||||
auto e = static_cast<QMouseEvent*>(evt);
|
||||
if(e->button() != Qt::LeftButton) break;
|
||||
auto edge = DetectEdgeOnThis(e->pos());
|
||||
if(edge)
|
||||
{
|
||||
mResizing = true;
|
||||
mEdgesBeingResized = edge;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!QWidget::event(evt) || 1) // Movable property is not implemented, let move anywhere
|
||||
{
|
||||
mMoveOffset = e->globalPos() - pos();
|
||||
mMoving = true;
|
||||
mResizing = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
// if(!mResizable) break;
|
||||
auto e = static_cast<QMouseEvent*>(evt);
|
||||
if(e->button() != Qt::LeftButton) break;
|
||||
mResizing = false;
|
||||
mMoving = false;
|
||||
unsetCursor();
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
case QEvent::HoverMove:
|
||||
{
|
||||
if((mResizing || !mResizable) && !mMoving) break;
|
||||
if(mMaximized)
|
||||
{
|
||||
unsetCursor();
|
||||
break;
|
||||
}
|
||||
if(mResizable && !mMoving)
|
||||
{
|
||||
auto e = static_cast<QHoverEvent*>(evt);
|
||||
auto edge = DetectEdgeOnThis(e->pos());
|
||||
SetMouseCursor(edge);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseMove:
|
||||
{
|
||||
if(mMaximized) break;
|
||||
auto e = static_cast<QMouseEvent*>(evt);
|
||||
if(mResizing && mResizable)
|
||||
{
|
||||
ResizeWindowByCursor(e->globalPos());
|
||||
return true; // Intercept resize movements
|
||||
}
|
||||
else if(mMoving)
|
||||
{
|
||||
move(e->globalPos() - mMoveOffset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
return QWidget::event(evt);
|
||||
}
|
||||
|
||||
Qt::Edges SpkWindow::DetectEdgeOnThis(QPoint p)
|
||||
{
|
||||
Qt::Edges edge;
|
||||
if(p.x() < BorderWidth) edge |= Qt::LeftEdge;
|
||||
if(p.x() > width() - BorderWidth) edge |= Qt::RightEdge;
|
||||
if(p.y() < BorderWidth) edge |= Qt::TopEdge;
|
||||
if(p.y() > height() - BorderWidth) edge |= Qt::BottomEdge;
|
||||
return edge;
|
||||
}
|
||||
|
||||
void SpkWindow::SetMouseCursor(Qt::Edges e)
|
||||
{
|
||||
switch(e)
|
||||
{
|
||||
case Qt::TopEdge:
|
||||
case Qt::BottomEdge:
|
||||
setCursor(Qt::SizeVerCursor);
|
||||
break;
|
||||
case Qt::LeftEdge:
|
||||
case Qt::RightEdge:
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
break;
|
||||
case Qt::TopEdge | Qt::LeftEdge:
|
||||
case Qt::BottomEdge | Qt::RightEdge:
|
||||
setCursor(Qt::SizeFDiagCursor);
|
||||
break;
|
||||
case Qt::TopEdge | Qt::RightEdge:
|
||||
case Qt::BottomEdge | Qt::LeftEdge:
|
||||
setCursor(Qt::SizeBDiagCursor);
|
||||
break;
|
||||
default:
|
||||
unsetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void SpkWindow::ResizeWindowByCursor(QPoint p)
|
||||
{
|
||||
auto r_ = geometry(), r = r_;
|
||||
switch(mEdgesBeingResized)
|
||||
{
|
||||
case Qt::TopEdge | Qt::LeftEdge:
|
||||
r.setLeft(p.x());
|
||||
if(r.width() < minimumWidth()) // If smaller than minimum the window moves, so we stop it
|
||||
r.setLeft(r_.left());
|
||||
case Qt::TopEdge:
|
||||
r.setTop(p.y());
|
||||
if(r.height() < minimumHeight()) // Same
|
||||
r.setTop(r_.top());
|
||||
break;
|
||||
case Qt::BottomEdge | Qt::LeftEdge:
|
||||
r.setLeft(p.x());
|
||||
if(r.width() < minimumWidth())
|
||||
r.setLeft(r_.left());
|
||||
case Qt::BottomEdge:
|
||||
r.setBottom(p.y());
|
||||
if(r.height() < minimumHeight())
|
||||
r.setBottom(r_.bottom());
|
||||
break;
|
||||
case Qt::TopEdge | Qt::RightEdge:
|
||||
r.setTop(p.y());
|
||||
r.setRight(p.x());
|
||||
if(r.height() < minimumHeight())
|
||||
r.setTop(r_.top());
|
||||
if(r.width() < minimumWidth())
|
||||
r.setRight(r_.right());
|
||||
break;
|
||||
case Qt::BottomEdge | Qt::RightEdge:
|
||||
r.setBottom(p.y());
|
||||
if(r.height() < minimumHeight())
|
||||
r.setBottom(r_.bottom());
|
||||
case Qt::RightEdge:
|
||||
r.setRight(p.x());
|
||||
if(r.width() < minimumWidth())
|
||||
r.setRight(r_.right());
|
||||
break;
|
||||
case Qt::LeftEdge:
|
||||
r.setLeft(p.x());
|
||||
if(r.width() < minimumWidth())
|
||||
r.setLeft(r_.left());
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
setGeometry(r);
|
||||
}
|
||||
|
||||
void SpkWindow::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
if(mCloseHook)
|
||||
{
|
||||
if(mCloseHook())
|
||||
e->accept();
|
||||
else
|
||||
e->ignore();
|
||||
emit Closed();
|
||||
return;
|
||||
}
|
||||
e->accept();
|
||||
emit Closed();
|
||||
}
|
||||
|
||||
void SpkWindow::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QWidget::paintEvent(e);
|
||||
if(!mUseCustomEvents)
|
||||
return;
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setBrush(QBrush(Qt::NoBrush));
|
||||
painter.setPen(QPen(SpkUi::ColorLine));
|
||||
QRect rect = this->rect();
|
||||
rect.adjust(0, 0, -1, -1);
|
||||
painter.drawRect(rect);
|
||||
}
|
||||
|
||||
void SpkWindow::SetCornerRadius(int radius)
|
||||
{
|
||||
mCornerRadius = radius;
|
||||
}
|
||||
|
||||
void SpkWindow::PopulateUi()
|
||||
{
|
||||
mMainVLayout = new QVBoxLayout;
|
||||
mTitleBarComponent = new SpkTitleBar(this);
|
||||
|
||||
setLayout(mMainVLayout);
|
||||
|
||||
mMainVLayout->addWidget(mTitleBarComponent);
|
||||
mMainVLayout->setAlignment(Qt::AlignTop);
|
||||
if(mUseCustomEvents)
|
||||
mMainVLayout->setContentsMargins(1, 1, 1, 1);
|
||||
else
|
||||
mMainVLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mMainVLayout->setSpacing(0);
|
||||
|
||||
mTitleBarComponent->SetTitle(qAppName());
|
||||
mTitleBarComponent->SetUseIcon(false);
|
||||
mTitleBarComponent->SetLinkedWindow(this);
|
||||
setWindowFlag(Qt::NoDropShadowWindowHint, false);
|
||||
}
|
||||
|
||||
void SpkWindow::SetCentralWidget(QWidget *widget)
|
||||
{
|
||||
if(mUserCentralWidget)
|
||||
mMainVLayout->removeWidget(mUserCentralWidget);
|
||||
mUserCentralWidget = widget;
|
||||
mMainVLayout->addWidget(widget);
|
||||
}
|
||||
|
||||
void SpkWindow::SetUseTitleBar(bool x)
|
||||
{
|
||||
mTitleBarComponent->setVisible(x);
|
||||
}
|
||||
|
||||
void SpkWindow::SetCentralMargin(int a, int b, int c, int d)
|
||||
{
|
||||
if(mUserCentralWidget)
|
||||
mUserCentralWidget->setContentsMargins(a, b, c, d);
|
||||
}
|
||||
|
||||
void SpkWindow::SetCloseHook(bool (*f)())
|
||||
{
|
||||
mCloseHook = f;
|
||||
}
|
||||
|
||||
void SpkWindow::ClearCloseHook()
|
||||
{
|
||||
mCloseHook = nullptr;
|
||||
}
|
||||
|
||||
void SpkWindow::RecalculateSize()
|
||||
{
|
||||
mMainVLayout->activate();
|
||||
}
|
||||
|
||||
bool SpkWindow::GetUseTitleBar()
|
||||
{
|
||||
return mTitleBarComponent->isVisible();
|
||||
}
|
||||
|
||||
SpkTitleBar *SpkWindow::GetTitleBar()
|
||||
{
|
||||
return mTitleBarComponent;
|
||||
}
|
||||
|
||||
SpkTitleBar *SpkWindow::SetTitleBar(SpkTitleBar *bar, bool replace)
|
||||
{
|
||||
if(!bar)
|
||||
return nullptr;
|
||||
auto ret = mTitleBarComponent;
|
||||
mMainVLayout->removeWidget(mTitleBarComponent);
|
||||
if(replace)
|
||||
mMainVLayout->insertWidget(0, bar);
|
||||
mTitleBarComponent = bar;
|
||||
bar->SetLinkedWindow(this);
|
||||
return ret;
|
||||
}
|
||||
9
inc/deepinplatform.h
Normal file
9
inc/deepinplatform.h
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/8.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// Stub
|
||||
|
||||
#endif //_DEEPINPLATFORM_H_
|
||||
22
inc/dtk/spkdtkplugin.h
Normal file
22
inc/dtk/spkdtkplugin.h
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class SpkDtkPlugin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual ~SpkDtkPlugin() = default;
|
||||
virtual void Initialize() = 0;
|
||||
virtual void addWindow(QWidget* w, QObject* parent) = 0;
|
||||
virtual QColor GetAccentColor() = 0;
|
||||
virtual bool GetIsDarkTheme() = 0;
|
||||
|
||||
signals:
|
||||
void AccentColorChanged(QColor);
|
||||
void DarkLightThemeChanged(bool isDark);
|
||||
};
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_INTERFACE(SpkDtkPlugin, "org.spark-store.client.dtkplugin")
|
||||
QT_END_NAMESPACE
|
||||
8
inc/gitver.h
Normal file
8
inc/gitver.h
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace GitVer
|
||||
{
|
||||
const char *DescribeTags();
|
||||
const char *CommitDate();
|
||||
}
|
||||
86
inc/page/spkpageappdetails.h
Normal file
86
inc/page/spkpageappdetails.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include "page/spkpagebase.h"
|
||||
#include "spkstretchlayout.h"
|
||||
#include "spkimgviewer.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkDetailEntry;
|
||||
|
||||
class SpkClickLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *e) override { emit Pressed(); }
|
||||
signals:
|
||||
void Pressed();
|
||||
};
|
||||
|
||||
class SpkPageAppDetails : public SpkPageBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageAppDetails(QWidget *parent = nullptr);
|
||||
~SpkPageAppDetails();
|
||||
|
||||
void LoadAppResources(QString pkgName, QString icon, QStringList screenshots, QStringList tags);
|
||||
void SetWebsiteLink(QString url);
|
||||
void SetPackagePath(QString url);
|
||||
|
||||
private:
|
||||
QString mPkgPath;
|
||||
QPixmap mBrokenImg, mIconLoading;
|
||||
|
||||
public slots:
|
||||
void ResourceAcquisitionFinished(int id, ResourceResult result);
|
||||
void Activated();
|
||||
|
||||
public:
|
||||
static constexpr QSize IconSize { 144, 144 };
|
||||
|
||||
// Main Area
|
||||
QScrollArea *mMainArea, *mScreenshotArea;
|
||||
QWidget *mDetailWidget, *mIconTitleWidget, *mWid4MainArea, *mWid4ShotArea;
|
||||
QLabel *mAppTitle, *mAppIcon, *mAppDescription, *mAppShortDesc, *mPkgName, *mVersion,
|
||||
*mWebsite;
|
||||
SpkDetailEntry *mAuthor, *mContributor, *mSite, *mArch, *mSize;
|
||||
SpkStretchLayout *mDetailLay;
|
||||
QVBoxLayout *mDetailsLay, *mTitleLay, *mMainLay;
|
||||
QHBoxLayout *mIconTitleLay, *mScreenshotLay;
|
||||
QList<SpkClickLabel*> mScreenshotPreviews;
|
||||
|
||||
QMap<int, QPixmap> mAppImages;
|
||||
SpkImgViewer *mImgViewer;
|
||||
|
||||
// Bottom bar
|
||||
QWidget *mBottomBar;
|
||||
QPushButton *mBtnInstall, *mBtnDownload, *mBtnUninstall, *mBtnRequestUpdate, *mBtnReport;
|
||||
QHBoxLayout *mBottomBarLay;
|
||||
|
||||
signals:
|
||||
void RequestDownload(QString name, QString pkgName, QString path);
|
||||
|
||||
private slots:
|
||||
void ImageClicked();
|
||||
};
|
||||
|
||||
class SpkDetailEntry : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkDetailEntry(QWidget *parent = nullptr);
|
||||
void SetTitle(const QString &s) { mTitle.setText(s); }
|
||||
void SetValue(const QString &s) { mField.setText(s); }
|
||||
|
||||
private:
|
||||
QLabel mTitle, mField;
|
||||
QHBoxLayout mLay;
|
||||
};
|
||||
}
|
||||
63
inc/page/spkpageapplist.h
Normal file
63
inc/page/spkpageapplist.h
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
#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();
|
||||
void SetPageStatus(int total, int current, int itemCount, QString &keyword);
|
||||
void SetCurrentCategory(int categoryId) { mCategoryId = categoryId; }
|
||||
|
||||
private:
|
||||
void DisablePageSwitchers();
|
||||
|
||||
public:
|
||||
|
||||
private:
|
||||
QVBoxLayout *mMainLay;
|
||||
QHBoxLayout *mPageSwitchLay;
|
||||
QPushButton *mBtnPgUp, *mBtnPgDown, *mBtnGotoPage;
|
||||
QLineEdit *mPageInput;
|
||||
QScrollArea *mAppsArea;
|
||||
QLabel *mPageIndicator;
|
||||
QWidget *mAppsWidget, *mPageSwitchWidget;
|
||||
SpkStretchLayout *mItemLay;
|
||||
QList<SpkAppItem *> mAppItemList;
|
||||
|
||||
// Cached icons
|
||||
QPixmap *mLoadingIcon,
|
||||
*mBrokenIcon;
|
||||
|
||||
QIntValidator *mPageValidator;
|
||||
|
||||
int mCategoryId, mCurrentPage;
|
||||
QString mKeyword;
|
||||
|
||||
signals:
|
||||
void ApplicationClicked(int appId);
|
||||
void SwitchListPage(int categoryId, int page);
|
||||
void SwitchSearchPage(QString keyword, int page);
|
||||
|
||||
public slots:
|
||||
void ResourceAcquisitionFinished(int id, ResourceResult result);
|
||||
void Activated();
|
||||
|
||||
private slots:
|
||||
void PageUp();
|
||||
void PageDown();
|
||||
void GotoPage();
|
||||
};
|
||||
}
|
||||
27
inc/page/spkpagebase.h
Normal file
27
inc/page/spkpagebase.h
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <spkresource.h>
|
||||
|
||||
class SpkPageBase : public QWidget
|
||||
{
|
||||
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();
|
||||
};
|
||||
45
inc/page/spkpagedownloads.h
Normal file
45
inc/page/spkpagedownloads.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef SPKPAGEDOWNLOADS_H
|
||||
#define SPKPAGEDOWNLOADS_H
|
||||
|
||||
#include "spkdownload.h"
|
||||
#include "spkdownloadentry.h"
|
||||
#include "page/spkpagebase.h"
|
||||
#include "pkgs/spkpkgmgrbase.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPageDownloads : public SpkPageBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageDownloads(QWidget *parent = nullptr);
|
||||
~SpkPageDownloads();
|
||||
|
||||
public slots:
|
||||
void AddDownloadTask(QString name, QString pkgName, QString path);
|
||||
|
||||
private:
|
||||
// Logic
|
||||
SpkDownloadMgr *mDownloadMgr;
|
||||
QMap<uint, SpkDownloadEntry*> mEntries;
|
||||
uint mNextDownloadId;
|
||||
QQueue<QPair<int, QString>> mWaitingDownloads;
|
||||
enum { Idle, Waiting, Downloading } mCurrentStatus;
|
||||
|
||||
// UI
|
||||
QVBoxLayout *mLayEntries, *mMainLay;
|
||||
QWidget *mScrollWidget;
|
||||
QScrollArea *mScrollArea;
|
||||
|
||||
private slots:
|
||||
void DownloadProgress(qint64 downloadedBytes, qint64 totalBytes, int id);
|
||||
void DownloadStopped(SpkDownloadMgr::TaskResult status, int id);
|
||||
void EntryAction(SpkDownloadEntry::EntryAction);
|
||||
void InstallationEnded(int id, SpkPkgMgrBase::PkgInstallResult, int exitCode);
|
||||
|
||||
private:
|
||||
void NewDownloadTask(int id, QString downloadPath);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SPKPAGEDOWNLOADS_H
|
||||
25
inc/page/spkpagehome.h
Normal file
25
inc/page/spkpagehome.h
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include "page/spkpagebase.h"
|
||||
#include "ui_homepage.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPageHome : public SpkPageBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageHome(QWidget *parent = nullptr);
|
||||
~SpkPageHome() { }
|
||||
|
||||
Ui::SpkHomepage *ui;
|
||||
|
||||
private slots:
|
||||
void LinkActivated(QString);
|
||||
|
||||
private:
|
||||
void SetupUi();
|
||||
};
|
||||
}
|
||||
56
inc/page/spkpagesettings.h
Normal file
56
inc/page/spkpagesettings.h
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QFutureWatcher>
|
||||
#include <QMutex>
|
||||
#include "page/spkpagebase.h"
|
||||
#include "ui_settings.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPageSettings : public SpkPageBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkPageSettings(QWidget *parent = nullptr);
|
||||
~SpkPageSettings();
|
||||
|
||||
void SetupUi();
|
||||
void ReadConfiguration();
|
||||
void SaveConfiguration();
|
||||
|
||||
void CountCleaning();
|
||||
|
||||
virtual void Activated() override;
|
||||
|
||||
private slots:
|
||||
void on_btnCleanDownloadedContent_clicked();
|
||||
void on_btnCleanResourceCache_clicked();
|
||||
void on_btnViewDownloadedContent_clicked();
|
||||
void on_btnViewResourceCache_clicked();
|
||||
|
||||
void CountFinishResource();
|
||||
void CountFinishDownload();
|
||||
void CleanedResource();
|
||||
void CleanedDownload();
|
||||
|
||||
private:
|
||||
QScrollArea *mMainArea;
|
||||
QVBoxLayout *mMainLay;
|
||||
QWidget *mSettingsWidget;
|
||||
Ui::SpkUiSettings *ui;
|
||||
|
||||
QString mRepoListUrl;
|
||||
|
||||
QFutureWatcher<void> mFwResourceCount,
|
||||
mFwDownloadCount,
|
||||
mFwResourceClean,
|
||||
mFwDownloadClean;
|
||||
QMutex mMutResource, mMutDownload;
|
||||
int64_t mBytesResource, mBytesDownloads;
|
||||
|
||||
};
|
||||
}
|
||||
63
inc/page/spkpageuitest.h
Normal file
63
inc/page/spkpageuitest.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSplitter>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QCheckBox>
|
||||
#include <QGroupBox>
|
||||
#include <QTextEdit>
|
||||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QSlider>
|
||||
#include <QProgressBar>
|
||||
#include "spkappitem.h"
|
||||
#include "spkstretchlayout.h"
|
||||
#include "page/spkpageappdetails.h"
|
||||
#include "spkiconbutton.h"
|
||||
|
||||
#include "spkloading.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPageUiTest : public QSplitter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPageUiTest(QWidget *parent = nullptr);
|
||||
QWidget *WidL, *WidR;
|
||||
QVBoxLayout *VLayWidgets,
|
||||
*VLayTestWidgets,
|
||||
*VLayInput;
|
||||
QHBoxLayout *HLay4Slider,
|
||||
*HLayInputBtns;
|
||||
QTextEdit *TextStylesheet;
|
||||
QPushButton *BtnApply;
|
||||
QPushButton *BtnFetch;
|
||||
|
||||
QPushButton *Btn;
|
||||
QCheckBox *Chk;
|
||||
QRadioButton *Rad;
|
||||
QGroupBox *Group;
|
||||
QSlider *SlideH;
|
||||
QSlider *SlideV;
|
||||
SpkIconButton *IconBtn;
|
||||
QProgressBar *Prog;
|
||||
SpkLoading *Loading;
|
||||
SpkAppItem *AppItem;
|
||||
SpkStretchLayout *DetailsLay;
|
||||
SpkDetailEntry *Detail1, *Detail2, *Detail3;
|
||||
QWidget *DetailsWidget;
|
||||
|
||||
QLineEdit *PopupText;
|
||||
QPushButton *ShowPopup,
|
||||
*ShowAbout,
|
||||
*ShowPkgmgr;
|
||||
|
||||
public slots:
|
||||
void SetStylesheet();
|
||||
void FetchStylesheet();
|
||||
void ShowPopupSlot();
|
||||
};
|
||||
}
|
||||
36
inc/pkgs/spkpkgmgrapt.h
Normal file
36
inc/pkgs/spkpkgmgrapt.h
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spkpkgmgrbase.h"
|
||||
#include <QProcess>
|
||||
|
||||
class SpkPkgMgrApt final : public SpkPkgMgrBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkPkgMgrApt(QObject *parent = nullptr);
|
||||
|
||||
static bool DetectRequirements();
|
||||
|
||||
virtual PkgInstallResult ExecuteInstallation(QString pkgPath,
|
||||
int entryId) override;
|
||||
|
||||
// APT backend specific
|
||||
bool ChangeServerRepository(QString content);
|
||||
|
||||
private:
|
||||
void CheckInstallerAvailability();
|
||||
|
||||
private slots:
|
||||
void InstallerExited(int, QProcess::ExitStatus);
|
||||
|
||||
private:
|
||||
QAction *mActAptitudeTerm,
|
||||
*mActAptTerm,
|
||||
*mActGdebi,
|
||||
*mActDeepinPkgInst;
|
||||
|
||||
QProcess mInstaller;
|
||||
|
||||
};
|
||||
91
inc/pkgs/spkpkgmgrbase.h
Normal file
91
inc/pkgs/spkpkgmgrbase.h
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QCursor>
|
||||
#include <QUrl>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "spkutils.h"
|
||||
|
||||
class SpkPkgMgrBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkPkgMgrBase(QObject *parent = nullptr) : QObject(parent)
|
||||
{
|
||||
Q_ASSERT(mInstance == nullptr);
|
||||
mInstance = this;
|
||||
|
||||
mActOpen = new QAction(tr("Open package"), this);
|
||||
mActOpenDir = new QAction(tr("Open containing directory"), this);
|
||||
mMenu = new QMenu(tr("Package Actions"));
|
||||
mMenu->addAction(mActOpen);
|
||||
mMenu->addAction(mActOpenDir);
|
||||
mMenu->setAttribute(Qt::WA_TranslucentBackground);
|
||||
mMenu->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
|
||||
}
|
||||
|
||||
enum PkgInstallResult
|
||||
{
|
||||
Failed,
|
||||
Succeeded,
|
||||
Ignored ///< No installation action is taken, no messages be displayed
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Detects if this system qualified to use this kind of packaging
|
||||
*/
|
||||
static bool DetectRequirements() { return true; }
|
||||
|
||||
/**
|
||||
* @brief Popup a menu at cursor to select installation methods
|
||||
* and do installation accordingly
|
||||
* @param pkgPath Path to the package file
|
||||
* @param entryId ID of the download entry
|
||||
*/
|
||||
virtual PkgInstallResult ExecuteInstallation(QString pkgPath, int entryId)
|
||||
{
|
||||
Q_UNUSED(entryId);
|
||||
auto item = mMenu->exec(QCursor::pos());
|
||||
if(item == mActOpenDir)
|
||||
QDesktopServices::openUrl(QUrl(SpkUtils::CutPath(pkgPath)));
|
||||
else if(item == mActOpen)
|
||||
QDesktopServices::openUrl(QUrl(pkgPath));
|
||||
return Ignored;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when Spark Store installs a software when it's running
|
||||
* in CLI mode
|
||||
* @param pkgPath Path to the package file
|
||||
*/
|
||||
virtual PkgInstallResult CliInstall(QString pkgPath)
|
||||
{
|
||||
qInfo() << tr("Spark Store cannot install your package because no supported "
|
||||
"packaging system has been found. You shall decide what you "
|
||||
"want to do with the downloaded package.\n\n"
|
||||
"File path:")
|
||||
<< pkgPath;
|
||||
return Ignored;
|
||||
}
|
||||
|
||||
public:
|
||||
static SpkPkgMgrBase *Instance() { return mInstance; }
|
||||
|
||||
protected:
|
||||
QAction *mActOpenDir, *mActOpen, *mActDesc;
|
||||
QMenu *mMenu;
|
||||
int mCurrentItemId; ///< ID of currently installing download item
|
||||
|
||||
private:
|
||||
static SpkPkgMgrBase *mInstance;
|
||||
|
||||
signals:
|
||||
void ReportInstallResult(int entryId,
|
||||
SpkPkgMgrBase::PkgInstallResult result,
|
||||
int exitCode);
|
||||
};
|
||||
20
inc/pkgs/spkpkgmgrpacman.h
Normal file
20
inc/pkgs/spkpkgmgrpacman.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spkpkgmgrbase.h"
|
||||
|
||||
class SpkPkgMgrPacman final : public SpkPkgMgrBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkPkgMgrPacman(QObject *parent = nullptr);
|
||||
|
||||
static bool DetectRequirements();
|
||||
|
||||
virtual PkgInstallResult ExecuteInstallation(QString pkgPath,
|
||||
int entryId) override;
|
||||
|
||||
|
||||
|
||||
};
|
||||
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]
|
||||
23
inc/spkabout.h
Normal file
23
inc/spkabout.h
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include "spkdialog.h"
|
||||
|
||||
class SpkAbout : public SpkDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkAbout(QWidget* parent = nullptr);
|
||||
~SpkAbout();
|
||||
static void Show();
|
||||
|
||||
private:
|
||||
QHBoxLayout *mIconLay;
|
||||
QLabel *mSpkIcon;
|
||||
QLabel *mSpkVersion;
|
||||
QLabel *mDescriptionText;
|
||||
};
|
||||
41
inc/spkappitem.h
Normal file
41
inc/spkappitem.h
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include "qt/elidedlabel.h"
|
||||
|
||||
class SpkAppItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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); }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
|
||||
public:
|
||||
static constexpr int IconSize = 72;
|
||||
static constexpr QSize IconSize_ = { IconSize, IconSize };
|
||||
|
||||
private:
|
||||
QLabel *mIcon;
|
||||
QLabel *mTitle;
|
||||
ElidedLabel *mDescription;
|
||||
int mAppId;
|
||||
|
||||
bool mPressCond;
|
||||
|
||||
QVBoxLayout *mLayText;
|
||||
QHBoxLayout *mMainLay;
|
||||
|
||||
signals:
|
||||
void clicked(int);
|
||||
};
|
||||
51
inc/spkconfig.h
Normal file
51
inc/spkconfig.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SPKCONFIG_H
|
||||
#define SPKCONFIG_H
|
||||
|
||||
#include <QSettings>
|
||||
#include <QHash>
|
||||
#include <QPair>
|
||||
#include <functional>
|
||||
|
||||
class SpkConfig : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkConfig(QObject *parent, QString configPath);
|
||||
~SpkConfig();
|
||||
|
||||
/**
|
||||
* @brief BindField If a variable is bound to the specified key, then future chanegs via SetField
|
||||
* will modify the provided variable. A callback can also be specified to make
|
||||
* sure the chanegs are acceptable.
|
||||
* @param key
|
||||
* @param value A pointer to the variable to be bound
|
||||
* @param defaultValue
|
||||
* @param callback When SetField is called to modify this specific key associated with a callback,
|
||||
* the callback is called. If the callback returned false then the original value
|
||||
* is restored to the value target, and changes won't be saved in mSettings, and
|
||||
* SetField will return false too. It is used to ensure if the target can accept
|
||||
* the changes.
|
||||
* @return false when the key is already bound.
|
||||
*/
|
||||
bool BindField(QString key, QString* value, QString defaultValue, std::function<bool(void)> callback = nullptr);
|
||||
bool BindField(QString key, int* value, int defaultValue, std::function<bool(void)> callback = nullptr);
|
||||
|
||||
bool SetField(QString key, QString value);
|
||||
bool SetField(QString key, int value);
|
||||
|
||||
// Wrapper of QSettings::value, used for "read once on startup" configurations
|
||||
QVariant ReadField(QString key, QVariant defaultValue);
|
||||
// Wrapper of QSettings::setValue, used for "set and restart to take effect" configurations
|
||||
void SetSettings(QString key, QVariant value);
|
||||
// Wrapper of QSettings::sync
|
||||
void Sync();
|
||||
|
||||
private:
|
||||
QSettings mSettings;
|
||||
|
||||
QHash<QString, QPair<QString*, std::function<bool(void)>>> mStringBindMap;
|
||||
QHash<QString, QPair<int*,std::function<bool(void)>>> mIntBindMap;
|
||||
|
||||
};
|
||||
|
||||
#endif // SPKCONFIG_H
|
||||
40
inc/spkdialog.h
Normal file
40
inc/spkdialog.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QButtonGroup>
|
||||
#include "spkui_general.h"
|
||||
#include "spktitlebar.h"
|
||||
#include "spkwindow.h"
|
||||
|
||||
class SpkDialog : public SpkWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SpkMsgBox;
|
||||
public:
|
||||
SpkDialog(QWidget *parent = nullptr);
|
||||
~SpkDialog();
|
||||
void AddButton(QString text, SpkUi::SpkButtonStyle style = SpkUi::SpkButtonStyle::Normal);
|
||||
void AddWidget(QWidget*);
|
||||
void AddLayout(QLayout*);
|
||||
void AddSpacing(int);
|
||||
void AddStretch(int a = 0);
|
||||
void SetMargin(int);
|
||||
void SetMargin(int, int, int, int);
|
||||
int Exec();
|
||||
|
||||
private slots:
|
||||
void ButtonPressed(int);
|
||||
void ForceClose();
|
||||
|
||||
signals:
|
||||
void ExitEventLoop(int);
|
||||
void CloseWindow();
|
||||
|
||||
protected:
|
||||
QWidget *mDialogWidget;
|
||||
private:
|
||||
QVBoxLayout *mMainVLay, *mWidgetsVLay;
|
||||
QHBoxLayout *mBtnLay;
|
||||
QButtonGroup *mBtnGroup;
|
||||
QList<QObject*> mWidgetsList, mParentsList;
|
||||
};
|
||||
125
inc/spkdownload.h
Normal file
125
inc/spkdownload.h
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spkstore.h"
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
* @note SpkDownloadMgr does NOT do download scheduling and other things; it's only a multithreaded
|
||||
* downloader; it manages the threads that are downloading stuff from the Internet.
|
||||
*
|
||||
* Because of this, SpkDownloadMgr does not support complex download queues, cannot handle
|
||||
* pauses, and can only work on a sequential list of tasks.
|
||||
*/
|
||||
|
||||
class SpkDownloadMgr : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkDownloadMgr(QObject *parent = nullptr);
|
||||
~SpkDownloadMgr();
|
||||
|
||||
static constexpr int MaximumThreadRetryCount = 3;
|
||||
|
||||
enum TaskResult
|
||||
{
|
||||
Success = 0,
|
||||
FailCannotCreateFile, ///< Failed because destination file cannot be created
|
||||
FailNoVaibleServer, ///< Failed because no server provides file size or download stalled on
|
||||
///< all of them
|
||||
FailCancel, ///< User has cancelled the task
|
||||
Fail
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DownloadWorker is not a real worker but more of a worker-like structure. It holds
|
||||
* information about one download thread, such as offset, total bytes and received bytes.
|
||||
* Workers' scheduling is still done by SpkDownloadMgr synchronously, avoiding
|
||||
* unnecessary race conditions and data safety problems.
|
||||
* DownloadWorker is also used in mFailureRetryQueue to indicate the blocks that needed
|
||||
* to be retried on other servers.
|
||||
*
|
||||
* Each worker has a watch dog value, incremented each time the download speed is
|
||||
* updated, and zeroed each time the worker has data ready. If the value exceeds a
|
||||
* preset maximum, then this worker is considered timed out and killed.
|
||||
*/
|
||||
struct DownloadWorker
|
||||
{
|
||||
QNetworkReply *Reply; ///< Reply from the network
|
||||
int Watchdog; ///< Watch dog value watching for a timed out worker
|
||||
qint64 BeginOffset; ///< Where should a worker start downloading
|
||||
qint64 BytesNeeded; ///< How many bytes a worker should fetch in total
|
||||
qint64 BytesRecvd; ///< How many bytes a worker has received till now
|
||||
};
|
||||
|
||||
constexpr static int WatchDogMaximum = 7;
|
||||
|
||||
struct RemoteFileInfo
|
||||
{
|
||||
qint64 Size = -1;
|
||||
bool SupportPartialDownload = false; ///< Whether this file can be downloaded multithreaded
|
||||
QByteArray Md5;
|
||||
};
|
||||
|
||||
void SetNewServers(QList<QString> servers);
|
||||
|
||||
/**
|
||||
* @note This function uses BLOCKING IO!
|
||||
*/
|
||||
static RemoteFileInfo GetRemoteFileInfo(QUrl url);
|
||||
|
||||
QString GetDestFilePath(QString downloadPath);
|
||||
|
||||
private:
|
||||
QList<QString> mServers; ///< Multithreaded download
|
||||
|
||||
QList<DownloadWorker> mScheduledWorkers;
|
||||
|
||||
// If one scheduled task fails a few times in a row, we must give it up on that server and put
|
||||
// its responsible block onto this queue so we can try downloading the block from other servers
|
||||
QQueue<DownloadWorker> mFailureRetryQueue;
|
||||
|
||||
QFile mDestFile;
|
||||
QString mDestFolder, mCurrentRemotePath;
|
||||
RemoteFileInfo mCurrentRemoteFileInfo;
|
||||
QTimer mProgressEmitterTimer;
|
||||
qint64 mDownloadedBytes;
|
||||
|
||||
int mCurrentDownloadId; ///< Indicates download status. -1 means no download going on.
|
||||
int mActiveWorkerCount;
|
||||
|
||||
QString mBulkServerPaths; ///< Config string, modification are taken care of by callback.
|
||||
|
||||
public slots:
|
||||
void SetDestinationFolder(QString path);
|
||||
|
||||
/**
|
||||
* @brief StartNewDownload try to start new download task.
|
||||
* @param path File path. Domain name excluded. No leading slashes.
|
||||
* @param downloadId Emitted with progress, finish and error so the UI know whose status it is.
|
||||
* @return true for success and false for failure.
|
||||
*/
|
||||
bool StartNewDownload(QString path, int downloadId);
|
||||
bool PauseCurrentDownload();
|
||||
bool CancelCurrentDownload();
|
||||
|
||||
private slots:
|
||||
void WorkerFinish();
|
||||
void WorkerDownloadProgress(); ///< Be connected to ***QNetworkReply::readyRead***
|
||||
void WorkerError(QNetworkReply::NetworkError);
|
||||
void ProgressTimer();
|
||||
|
||||
private:
|
||||
void ProcessWorkerError(DownloadWorker &, int id);
|
||||
void LinkReplyWithMe(QNetworkReply*);
|
||||
void TryScheduleFailureRetries();
|
||||
void TryScheduleFailureRetries(int i); ///< Try schedule on a specific task slot.
|
||||
|
||||
bool ServerAddressesChangedCallback(); ///< Called by SpkConfig upon address changing
|
||||
|
||||
signals:
|
||||
void DownloadProgressed(qint64 bytes, qint64 total, int id);
|
||||
void DownloadStopped(TaskResult status, int id);
|
||||
|
||||
|
||||
};
|
||||
78
inc/spkdownloadentry.h
Normal file
78
inc/spkdownloadentry.h
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include "qt/elidedlabel.h"
|
||||
#include "spkloading.h"
|
||||
#include <QProgressBar>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTime>
|
||||
|
||||
class SpkDownloadEntry : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpkDownloadEntry(QWidget* parent = nullptr);
|
||||
~SpkDownloadEntry();
|
||||
|
||||
static constexpr QSize IconSize { 64, 64 };
|
||||
|
||||
enum DownloadEntryStatus
|
||||
{
|
||||
Invalid = -1,
|
||||
Waiting,
|
||||
Starting,
|
||||
Downloading,
|
||||
DownloadFailed,
|
||||
ToBeInstalled,
|
||||
Installing,
|
||||
Installed,
|
||||
InstallFailed
|
||||
};
|
||||
|
||||
void SetTotalBytes(qint64 total);
|
||||
void SetBasicInfo(QString name, QPixmap icon, QString filePath);
|
||||
void SetStatus(DownloadEntryStatus status, QString msg = "");
|
||||
void Progress(qint64 bytes);
|
||||
QString GetTaskName() { return mAppName->text(); }
|
||||
QString GetFilePath() { return mFilePath; }
|
||||
|
||||
enum EntryAction
|
||||
{
|
||||
AbortDownload,
|
||||
RetryDownload,
|
||||
StartInstall,
|
||||
RemoveEntry
|
||||
};
|
||||
|
||||
private slots:
|
||||
void ActionButton();
|
||||
void DeleteButton();
|
||||
|
||||
private:
|
||||
QLabel *mIcon, *mMessage;
|
||||
ElidedLabel *mAppName;
|
||||
QProgressBar *mProgress;
|
||||
QPushButton *mBtnDelete,
|
||||
*mBtnActions; // Actions include Retry Pause Install etc, one status at a time
|
||||
SpkLoading *mLoading;
|
||||
|
||||
QHBoxLayout *mLayMsgs, *mLayMain;
|
||||
QVBoxLayout *mLayInfo;
|
||||
|
||||
// Download status data
|
||||
qint64 mTotalBytes, mDownloadedBytes;
|
||||
QTime mLastReportTime;
|
||||
QString mReadableTotalSize;
|
||||
|
||||
QString mFilePath;
|
||||
|
||||
DownloadEntryStatus mStatus;
|
||||
|
||||
signals:
|
||||
void Action(EntryAction);
|
||||
};
|
||||
4
inc/spkfillwidget.h
Normal file
4
inc/spkfillwidget.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#ifndef SPKFILLWIDGET_H
|
||||
#define SPKFILLWIDGET_H
|
||||
|
||||
#endif // SPKFILLWIDGET_H
|
||||
19
inc/spkfocuslineedit.h
Normal file
19
inc/spkfocuslineedit.h
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QLineEdit>
|
||||
|
||||
class SpkFocusLineEdit final : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpkFocusLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) {}
|
||||
|
||||
protected:
|
||||
void focusInEvent(QFocusEvent *e) override { emit focusGained(); }
|
||||
void focusOutEvent(QFocusEvent *e) override { emit focusLost(); }
|
||||
|
||||
signals:
|
||||
void focusGained();
|
||||
void focusLost();
|
||||
};
|
||||
30
inc/spkiconbutton.h
Normal file
30
inc/spkiconbutton.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef SPKICONBUTTON_H
|
||||
#define SPKICONBUTTON_H
|
||||
|
||||
#include <QPushButton>
|
||||
#include "spkui_general.h"
|
||||
|
||||
class SpkIconButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkIconButton(QWidget *parent = nullptr);
|
||||
~SpkIconButton() {};
|
||||
|
||||
void SetIcon(QIcon, QSize);
|
||||
void SetIcon(QPixmap);
|
||||
void SetIconSize(QSize);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
static enum { Dark, Light } sCurrentTheme;
|
||||
QPixmap mPmapPaintedIcon;
|
||||
QSize mPmapSize;
|
||||
|
||||
static constexpr int IconMargin = 8; // shall we make it changable?
|
||||
};
|
||||
|
||||
#endif // SPKICONBUTTON_H
|
||||
61
inc/spkimgviewer.h
Normal file
61
inc/spkimgviewer.h
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
#include <QScrollArea>
|
||||
#include "spkwindow.h"
|
||||
|
||||
class ImgView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ImgView(QWidget *parent = nullptr) : QWidget(parent) { mPixmap = nullptr; }
|
||||
void SetPixmap(QPixmap *p) { mPixmap = p; if(p) setFixedSize(p->size()); update(); }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPainter p(this);
|
||||
if(mPixmap)
|
||||
p.drawPixmap(0, 0, *mPixmap);
|
||||
p.end();
|
||||
e->accept();
|
||||
}
|
||||
|
||||
private:
|
||||
QPixmap *mPixmap;
|
||||
};
|
||||
|
||||
class SpkImgViewer : public SpkWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkImgViewer(QWidget *parent = nullptr);
|
||||
void ShowWithImage(int idx);
|
||||
void SetImageTotal(int a) { mTotalImg = a; }
|
||||
|
||||
public slots:
|
||||
void Clear();
|
||||
void SetPixmap(int idx, QPixmap *img);
|
||||
|
||||
private slots:
|
||||
void SwitchToImage(int idx);
|
||||
|
||||
protected:
|
||||
bool event(QEvent*) override;
|
||||
|
||||
private:
|
||||
void ResizeToFitImageSize(QSize);
|
||||
|
||||
private:
|
||||
QPushButton *mBtnPrev, *mBtnNext;
|
||||
QScrollArea *mImgArea;
|
||||
QLabel *mImgIndict;
|
||||
QMap<int, QPixmap*> mImgMap;
|
||||
int mCurrentImg, mTotalImg;
|
||||
QPixmap mIconLoading;
|
||||
|
||||
ImgView *mImgShow;
|
||||
};
|
||||
25
inc/spkjsonapiconsumer.h
Normal file
25
inc/spkjsonapiconsumer.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef SPKJSONAPICONSUMER_H
|
||||
#define SPKJSONAPICONSUMER_H
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
|
||||
class SpkJsonApiConsumer: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkJsonApiConsumer();
|
||||
|
||||
private:
|
||||
QNetworkAccessManager mNet;
|
||||
|
||||
public slots:
|
||||
void RequestUrl(QUrl);
|
||||
|
||||
signals:
|
||||
void RequestFinish(QJsonDocument, int);
|
||||
};
|
||||
|
||||
#endif // SPKJSONAPICONSUMER_H
|
||||
34
inc/spkloading.h
Normal file
34
inc/spkloading.h
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QTimeLine>
|
||||
|
||||
class SpkLoading : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpkLoading(QWidget *parent = nullptr);
|
||||
virtual void paintEvent(QPaintEvent *e) override;
|
||||
virtual void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
QTimeLine *mAnimTimer;
|
||||
QList<int> mSizeList;
|
||||
int mUserHeight = 0;
|
||||
int dw, dh;
|
||||
double dx, dy;
|
||||
|
||||
public slots:
|
||||
void start() { mAnimTimer->start(); }
|
||||
void stop() { mAnimTimer->stop(); }
|
||||
void Begin() { start(); setVisible(true); }
|
||||
void End() { stop(); setVisible(false); }
|
||||
void setHeight(int h) { mUserHeight = h; }
|
||||
void reset();
|
||||
|
||||
private slots:
|
||||
void timer(int s);
|
||||
void loop();
|
||||
};
|
||||
|
||||
44
inc/spklogging.h
Normal file
44
inc/spklogging.h
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
/**
|
||||
* @brief Simple logging for Spark Store
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma push_macro("signals")
|
||||
#undef signals
|
||||
#include <libnotify/notify.h>
|
||||
#define signals DUMMY
|
||||
#pragma pop_macro("signals")
|
||||
#include <QString>
|
||||
#include <QStringListModel>
|
||||
#include <QTextStream>
|
||||
#include <QFile>
|
||||
|
||||
class SpkLogger
|
||||
{
|
||||
QStringListModel mLogData;
|
||||
QString mLogPath;
|
||||
QFile mLogFile;
|
||||
QTextStream mLogWriter;
|
||||
static SpkLogger *Instance;
|
||||
void Initialize(QString suggestPath = "");
|
||||
bool NotifyViable = false;
|
||||
|
||||
public:
|
||||
SpkLogger(QString suggestPath = "");
|
||||
~SpkLogger();
|
||||
static SpkLogger *GetInstance() { return Instance; };
|
||||
void Log(QString message);
|
||||
void Warning(QString message);
|
||||
void Error(QString message, bool pop = false);
|
||||
void Critical(QString message);
|
||||
void Notify(QString);
|
||||
};
|
||||
|
||||
#define sLog(X) SpkLogger::GetInstance()->Log(X)
|
||||
#define sWarn(X) SpkLogger::GetInstance()->Warning(X)
|
||||
#define sErr(X) SpkLogger::GetInstance()->Error(X)
|
||||
#define sErrPop(X) SpkLogger::GetInstance()->Error(X,true)
|
||||
#define sCritical(X) SpkLogger::GetInstance()->Critical(X)
|
||||
#define sNotify(X) SpkLogger::GetInstance()->Notify(X)
|
||||
236
inc/spkmainwindow.h
Normal file
236
inc/spkmainwindow.h
Normal file
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/9.
|
||||
//
|
||||
|
||||
#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 <QTimeLine>
|
||||
#include <QQueue>
|
||||
#include "spkfocuslineedit.h"
|
||||
#include "spkiconbutton.h"
|
||||
#include "page/spkpageuitest.h"
|
||||
#include "page/spkpagehome.h"
|
||||
#include "page/spkpageapplist.h"
|
||||
#include "page/spkpageappdetails.h"
|
||||
#include "page/spkpagedownloads.h"
|
||||
#include "page/spkpagesettings.h"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
enum SpkStackedPages
|
||||
{
|
||||
PgInvalid = -1,
|
||||
PgHomepage,
|
||||
PgAppList,
|
||||
PgAppDetails,
|
||||
PgDownloads,
|
||||
PgSettings,
|
||||
PgQssTest // Must be at last
|
||||
};
|
||||
|
||||
const std::vector<SpkStackedPages> ResourceContexts
|
||||
{
|
||||
PgAppList
|
||||
};
|
||||
|
||||
class SpkSidebarSelector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QPushButton *mLastCheckedBtn;
|
||||
QTreeWidgetItem *mLastSelectedItem;
|
||||
QTreeWidget *mCategoryWidget;
|
||||
QVector<QTreeWidgetItem *> mUnusableItems; // Unselectable top level items; never changes
|
||||
|
||||
QTreeWidgetItem* mLastCategoryItem;
|
||||
int mLastCategoryPage;
|
||||
|
||||
public:
|
||||
SpkSidebarSelector(QObject *parent = nullptr) : QObject(parent)
|
||||
{
|
||||
mLastCheckedBtn = nullptr;
|
||||
mLastSelectedItem = nullptr;
|
||||
}
|
||||
// Tree item can either represent a page or a category, data of special roles
|
||||
// help identify them; Buttons instead can only represent a page
|
||||
constexpr static int RoleItemIsCategory = Qt::UserRole + 1;
|
||||
constexpr static int RoleItemCategoryPageId= Qt::UserRole + 2;
|
||||
void BindPageSwitcherButton(QAbstractButton* w)
|
||||
{
|
||||
connect(w, &QAbstractButton::toggled,
|
||||
this, &SpkSidebarSelector::ButtonToggled);
|
||||
connect(w, &QAbstractButton::pressed,
|
||||
this, &SpkSidebarSelector::ButtonPressed);
|
||||
}
|
||||
void BindCategoryWidget(QTreeWidget* w)
|
||||
{
|
||||
mCategoryWidget = w;
|
||||
connect(w, &QTreeWidget::itemPressed, this,
|
||||
&SpkSidebarSelector::TreeItemSelected);
|
||||
}
|
||||
void AddUnusableItem(QTreeWidgetItem *i) { mUnusableItems.append(i); }
|
||||
void GoBack()
|
||||
{
|
||||
emit SwitchToCategory(mLastCategoryPage, 0);
|
||||
mCategoryWidget->currentItem()->setSelected(false);
|
||||
mLastCategoryItem->setSelected(true);
|
||||
}
|
||||
|
||||
private slots:
|
||||
// We assume the objects in interest all have the correct properties
|
||||
void ButtonToggled(bool aBtnState)
|
||||
{
|
||||
auto b = qobject_cast<QPushButton*>(sender());
|
||||
if(mLastCheckedBtn)
|
||||
{
|
||||
if(mLastCheckedBtn != b)
|
||||
{
|
||||
mLastCheckedBtn->setChecked(false);
|
||||
mLastCheckedBtn = nullptr;
|
||||
}
|
||||
}
|
||||
else if(mLastSelectedItem)
|
||||
{
|
||||
mLastSelectedItem->setSelected(false);
|
||||
mLastSelectedItem = nullptr;
|
||||
}
|
||||
mLastCheckedBtn = b;
|
||||
auto id = b->property("spk_pageno").toInt();
|
||||
emit SwitchToPage(static_cast<SpkStackedPages>(id));
|
||||
}
|
||||
void ButtonPressed()
|
||||
{ // Prevent a selected button from being deselected by clicking on it
|
||||
auto b = qobject_cast<QPushButton*>(sender());
|
||||
if(mLastCheckedBtn == b)
|
||||
b->setChecked(false);
|
||||
}
|
||||
void TreeItemSelected(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
if(mUnusableItems.contains(item))
|
||||
{
|
||||
UnusableItemSelected(item); return;
|
||||
}
|
||||
if(mLastCheckedBtn)
|
||||
{
|
||||
mLastCheckedBtn->setChecked(false);
|
||||
mLastCheckedBtn = nullptr;
|
||||
}
|
||||
mLastSelectedItem = item;
|
||||
auto id = item->data(column, RoleItemCategoryPageId).toInt();
|
||||
if(item->data(column, RoleItemIsCategory).toBool())
|
||||
emit SwitchToCategory(id, 0), mLastCategoryPage = id, mLastCategoryItem = item;
|
||||
else
|
||||
emit SwitchToPage(static_cast<SpkStackedPages>(id));
|
||||
}
|
||||
void UnusableItemSelected(QTreeWidgetItem *i)
|
||||
{
|
||||
i->setSelected(false);
|
||||
if(mLastSelectedItem)
|
||||
{
|
||||
mLastSelectedItem->setSelected(true);
|
||||
}
|
||||
else if(mLastCheckedBtn)
|
||||
{
|
||||
mLastCheckedBtn->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
signals:
|
||||
void SwitchToCategory(int aCategoryId, int aPage);
|
||||
void SwitchToPage(SpkStackedPages aPageId);
|
||||
};
|
||||
|
||||
class SpkMainWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkMainWidget(QWidget *parent = nullptr);
|
||||
|
||||
QHBoxLayout *HorizontalDivide;
|
||||
|
||||
QStackedWidget *Pager;
|
||||
|
||||
// Category widget is for switching pages
|
||||
SpkIconButton *BtnSettings, *BtnFeedback, *BtnLogs, *BtnDayNight, *BtnBack;
|
||||
SpkSidebarTree *CategoryWidget;
|
||||
QMap<int, QTreeWidgetItem> *CategoryItemMap;
|
||||
SpkSidebarSelector *SidebarMgr;
|
||||
|
||||
QTreeWidgetItem *HomepageItem,
|
||||
*CategoryParentItem,
|
||||
*AppDetailsItem,
|
||||
*DownloadsItem,
|
||||
*UiTestItem;
|
||||
|
||||
// Title bar search bar
|
||||
SpkFocusLineEdit *SearchEdit;
|
||||
QAction *ActClearSearchBar, *ActSearchIcon;
|
||||
QTimeLine *SearchBarAnim;
|
||||
|
||||
//Pages
|
||||
SpkPageUiTest *PageQssTest;
|
||||
|
||||
SpkPageHome *PageHome;
|
||||
SpkPageAppList *PageAppList;
|
||||
SpkPageAppDetails *PageAppDetails;
|
||||
SpkPageDownloads *PageDownloads;
|
||||
SpkPageSettings *PageSettings;
|
||||
};
|
||||
}
|
||||
|
||||
class SpkMainWindow : public SpkWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
SpkUi::SpkMainWidget *ui;
|
||||
|
||||
public:
|
||||
SpkMainWindow(QWidget *parent = nullptr);
|
||||
|
||||
void PopulateCategories(QJsonArray);
|
||||
|
||||
private:
|
||||
void Initialize();
|
||||
|
||||
private:
|
||||
QPointer<QNetworkReply> mCategoryGetReply,
|
||||
mCategoryAppListGetReply,
|
||||
mAppDetailsGetReply;
|
||||
SpkUi::SpkStackedPages mCurrentPage = SpkUi::PgInvalid;
|
||||
QList<QPair<SpkIconButton*, QString>> mThemedUiIconReferences;
|
||||
|
||||
public slots:
|
||||
void ReloadThemedUiIcons();
|
||||
void RefreshCategoryData();
|
||||
|
||||
private slots:
|
||||
void SwitchDayNightTheme();
|
||||
|
||||
void SwitchToPage(SpkUi::SpkStackedPages page);
|
||||
|
||||
void CategoryDataReceived();
|
||||
// Enter a category (and switch pages)
|
||||
void EnterCategoryList(int aCategoryId, int aPage);
|
||||
void CategoryListDataReceived();
|
||||
// Search a keyword (and switch pages)
|
||||
void SearchKeyword(QString aKeyword, int aPage);
|
||||
void SearchDataReceived();
|
||||
// Enter the details page of an application (and switch pages)
|
||||
void EnterAppDetails(int aAppId);
|
||||
void AppDetailsDataReceived();
|
||||
|
||||
private:
|
||||
void PopulateAppList(QJsonObject appData, QString &&keyword);
|
||||
void PopulateAppDetails(QJsonObject appDetails);
|
||||
};
|
||||
20
inc/spkmsgbox.h
Normal file
20
inc/spkmsgbox.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStyle>
|
||||
#include <QMessageBox>
|
||||
#include "spkdialog.h"
|
||||
|
||||
class SpkMsgBox : public SpkDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkMsgBox(QWidget *parent = nullptr);
|
||||
static int StaticExec(QString msg, QString title, QMessageBox::Icon = QMessageBox::NoIcon,
|
||||
QMessageBox::StandardButtons = QMessageBox::Ok, QString extra = "",
|
||||
bool expanded = false);
|
||||
private:
|
||||
static void AddButtons(SpkMsgBox *me, QMessageBox::StandardButtons b);
|
||||
QList<QMessageBox::StandardButton> mButtonList;
|
||||
static constexpr QSize IconSize {48, 48};
|
||||
static constexpr int Margin = 10;
|
||||
};
|
||||
17
inc/spknotifydot.h
Normal file
17
inc/spknotifydot.h
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class SpkNotifyDot : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpkNotifyDot(QWidget *parent = nullptr);
|
||||
~SpkNotifyDot() {}
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
// virtual int widthForHeight(int h) override;
|
||||
};
|
||||
30
inc/spkpopup.h
Normal file
30
inc/spkpopup.h
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QSequentialAnimationGroup>
|
||||
|
||||
class SpkMainWindow;
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkPopup : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkPopup(QWidget *parent, int aMillis = 3000);
|
||||
|
||||
public slots:
|
||||
void Show(QString aText);
|
||||
|
||||
private:
|
||||
QPoint MoveOffset;
|
||||
QLabel *mText;
|
||||
QHBoxLayout *mBox;
|
||||
QPropertyAnimation *mAnimFadeIn, *mAnimFadeOut;
|
||||
QSequentialAnimationGroup *mAnim;
|
||||
};
|
||||
}
|
||||
51
inc/spkqsshelper.h
Normal file
51
inc/spkqsshelper.h
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <QColor>
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
QColor ColorTextOnBackground(QColor);
|
||||
|
||||
namespace Qss
|
||||
{
|
||||
enum ColorSetIndex
|
||||
{
|
||||
GlobalBgnd = 0,
|
||||
ControlsBgnd,
|
||||
ControlsBgndHighlighted,
|
||||
SelectionBgnd, AccentColor = SelectionBgnd,
|
||||
SelectionBgndHighlighted, AccentColorHighlighted = SelectionBgndHighlighted,
|
||||
LightCtrlsGradLight,
|
||||
LightCtrlsGradDark,
|
||||
LightCtrlsGradDarker,
|
||||
LightCtrlsDisabledBackground,
|
||||
DarkCtrlsGradLight,
|
||||
DarkCtrlsGradDark,
|
||||
DarkCtrlsGradDarker,
|
||||
DarkCtrlsDisabledBackground,
|
||||
TextOnSelection, TextOnAccentColor = TextOnSelection,
|
||||
TextOnGlobalBgnd,
|
||||
TextOnControlsBgnd,
|
||||
TextLighter,
|
||||
TextEvenLighter,
|
||||
TextDisabled,
|
||||
GlossyEdge,
|
||||
ShadesEdge,
|
||||
ScrollBarNorm,
|
||||
ScrollBarHover,
|
||||
DivideLine,
|
||||
};
|
||||
|
||||
extern const std::list<ColorSetIndex> AccentColorExceptions;
|
||||
|
||||
extern const std::map<ColorSetIndex, const char *> ColorSet2Token;
|
||||
|
||||
extern const std::map<ColorSetIndex, QColor>
|
||||
DarkColorSet, LightColorSet;
|
||||
|
||||
using ColorSet = std::map<Qss::ColorSetIndex, QColor>;
|
||||
}
|
||||
}
|
||||
101
inc/spkresource.h
Normal file
101
inc/spkresource.h
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
#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
|
||||
{
|
||||
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);
|
||||
|
||||
ResourceResult CacheLookup(QString pkgName, ResourceType type, QVariant info);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
17
inc/spksidebartree.h
Normal file
17
inc/spksidebartree.h
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTreeWidget>
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
class SpkSidebarTree : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkSidebarTree(QWidget* parent = nullptr);
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *) override;
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
};
|
||||
}
|
||||
51
inc/spkstore.h
Normal file
51
inc/spkstore.h
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
|
||||
#include "spklogging.h"
|
||||
#include "spkresource.h"
|
||||
#include "spkconfig.h"
|
||||
|
||||
class SpkMainWindow;
|
||||
|
||||
/**
|
||||
* @brief SpkStore class is the core of the store client side program, it is constructed first and
|
||||
* handling all processing after the launch. All client side data should be held by it,
|
||||
* including preferences class, category names, request URLs, downloads status, packaging
|
||||
* backend interfaces and so on.
|
||||
*/
|
||||
class SpkStore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static SpkStore *Instance;
|
||||
SpkConfig *mCfg;
|
||||
SpkStore(bool aCli, QString &aLogPath);
|
||||
~SpkStore();
|
||||
|
||||
SpkMainWindow* GetRootWindow() { return mMainWindow; }
|
||||
|
||||
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!
|
||||
QNetworkReply *SendDownloadRequest(QUrl file, qint64 fromByte = -1, qint64 toByte = -1);
|
||||
|
||||
QNetworkReply *SendCustomHeadRequest(QNetworkRequest);
|
||||
|
||||
private:
|
||||
SpkLogger *mLogger;
|
||||
SpkMainWindow *mMainWindow = nullptr;
|
||||
SpkResource *mResMgr = nullptr;
|
||||
QNetworkAccessManager *mNetMgr = nullptr;
|
||||
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;
|
||||
};
|
||||
71
inc/spktitlebar.h
Normal file
71
inc/spktitlebar.h
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/8.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QBrush>
|
||||
#include <QPushButton>
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
class SpkWindow;
|
||||
|
||||
class SpkTitleBarDefaultButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpkTitleBarDefaultButton(QWidget* parent = nullptr);
|
||||
enum OperationButton { Minimize = 1, MaximizeRestore = 2, Close = 4, Restore = 3 };
|
||||
void SetRole(OperationButton);
|
||||
constexpr static int ButtonWidth = 60;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
OperationButton Role;
|
||||
void PaintSymbol(QPainter &);
|
||||
};
|
||||
|
||||
class SpkTitleBar : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SpkWindow;
|
||||
public:
|
||||
SpkTitleBar(QWidget *parent = nullptr);
|
||||
~SpkTitleBar();
|
||||
static constexpr int Height = 48;
|
||||
using OperationButton = SpkTitleBarDefaultButton::OperationButton;
|
||||
void SetOperationButton(OperationButton);
|
||||
|
||||
void SetIcon(QPixmap p) { mIcon->setPixmap(p); }
|
||||
void SetTitle(QString t) { mTitle->setText(t); }
|
||||
QString GetTitle() { return mTitle->text(); }
|
||||
void SetUseIcon(bool t) { mIcon->setVisible(t); }
|
||||
void SetLinkedWindow(SpkWindow *w) { mLinkedWindow = w; }
|
||||
|
||||
QHBoxLayout *GetUserSpace() { return mUserSpace; }
|
||||
protected:
|
||||
bool event(QEvent*) override;
|
||||
|
||||
private:
|
||||
QLabel *mIcon;
|
||||
QLabel *mTitle;
|
||||
SpkWindow *mLinkedWindow;
|
||||
QHBoxLayout *mMainLayout, *mUserSpace, *mBtnGroup;
|
||||
SpkTitleBarDefaultButton *mBtnMin, *mBtnMaxRestore, *mBtnClose;
|
||||
|
||||
private slots:
|
||||
void CloseWindow();
|
||||
void MinimizeWindow();
|
||||
void MaximizeRestoreWindow();
|
||||
};
|
||||
81
inc/spkui_general.h
Normal file
81
inc/spkui_general.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/8.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QSize>
|
||||
#include <QColor>
|
||||
#include <map>
|
||||
|
||||
#include "spkqsshelper.h"
|
||||
#include "dtk/spkdtkplugin.h"
|
||||
|
||||
namespace SpkUi
|
||||
{
|
||||
|
||||
enum SpkUiStyle { Invalid, Light, Dark };
|
||||
enum SpkButtonStyle { Normal = 0, Recommend, Warn };
|
||||
|
||||
class SpkPopup;
|
||||
|
||||
constexpr int StackTraceArraySize = 64;
|
||||
constexpr const char * const StoreIconName = "spark-store";
|
||||
|
||||
class UiMetaObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
static UiMetaObject *sGlobalInstance;
|
||||
public:
|
||||
UiMetaObject() {}
|
||||
public slots:
|
||||
void SetAccentColor(QColor);
|
||||
void SetDarkLightTheme(bool isDark);
|
||||
signals:
|
||||
void SetThemeButtonVisible(bool);
|
||||
};
|
||||
|
||||
extern UiMetaObject SpkUiMetaObject;
|
||||
extern SpkUiStyle CurrentStyle;
|
||||
extern QString StylesheetBase, CurrentStylesheet;
|
||||
extern QColor ColorLine, ColorBack,
|
||||
ColorBtnMaskUnselected, ///< SpkIconButton icon mask colors, unselected & selected
|
||||
ColorBtnMaskSelected;
|
||||
extern QSize PrimaryScreenSize;
|
||||
extern SpkDtkPlugin *DtkPlugin;
|
||||
extern QStyle *OldSystemStyle;
|
||||
extern std::map<Qss::ColorSetIndex, QColor> CurrentColorSet;
|
||||
|
||||
extern SpkPopup *Popup;
|
||||
|
||||
namespace States
|
||||
{
|
||||
extern bool IsDDE, IsUsingDtkPlugin;
|
||||
|
||||
extern bool DoRespondAutoTheme;
|
||||
extern int LightDarkMode; ///< Tied to settings UI value
|
||||
|
||||
bool ThemeConfigCallback();
|
||||
}
|
||||
|
||||
namespace Priv
|
||||
{
|
||||
extern bool CrashHandlerActivated;
|
||||
using namespace SpkUi::Qss;
|
||||
}
|
||||
|
||||
void Initialize();
|
||||
void GuessAppropriateTheme();
|
||||
void PrepareForDeepinDesktop();
|
||||
bool CheckIsDeepinDesktop();
|
||||
QString StylesheetFromColors(Qss::ColorSet);
|
||||
|
||||
QIcon GetThemedIcon(QString);
|
||||
|
||||
void CrashSignalHandler(int);
|
||||
|
||||
void SetGlobalStyle(const SpkUiStyle, const bool aPreserveAccentColor);
|
||||
};
|
||||
32
inc/spkuimsg.h
Normal file
32
inc/spkuimsg.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/9.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma push_macro("signals")
|
||||
#undef signals
|
||||
#include <libnotify/notify.h>
|
||||
#define signals DUMMY
|
||||
#pragma pop_macro("signals")
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include "spkui_general.h"
|
||||
|
||||
class SpkUiMessage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void SendDesktopNotification(QString aMsg,
|
||||
const char * const aIcon = SpkUi::StoreIconName);
|
||||
static void SendStoreNotification(QString);
|
||||
static void SetDesktopNotifyTimeout(int ms);
|
||||
|
||||
private:
|
||||
static NotifyNotification *_notify;
|
||||
static int mTimeoutDesktop;
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
36
inc/spkutils.h
Normal file
36
inc/spkutils.h
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QFile>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "spkstore.h"
|
||||
#include "spklogging.h"
|
||||
|
||||
#define STORE (SpkStore::Instance)
|
||||
#define CFG (SpkStore::Instance->mCfg)
|
||||
#define RES (SpkResource::Instance)
|
||||
#define PKG (SpkPkgMgrBase::Instance())
|
||||
|
||||
namespace SpkUtils
|
||||
{
|
||||
QString GetDistroName();
|
||||
|
||||
void VerifySingleRequest(QPointer<QNetworkReply> aReply);
|
||||
|
||||
void DeleteReplyLater(QNetworkReply *aReply);
|
||||
|
||||
int VerifyReplyJson(QNetworkReply *aReply, QJsonValue& aRetDoc);
|
||||
|
||||
QString CutFileName(QString);
|
||||
QString CutPath(QString);
|
||||
QString BytesToSize(size_t s, int prec = 2);
|
||||
bool EnsureDirExists(QString path);
|
||||
|
||||
bool FindViableTerminal();
|
||||
|
||||
extern QPair<QString, QString> AvailableTerminal;
|
||||
}
|
||||
67
inc/spkwindow.h
Normal file
67
inc/spkwindow.h
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Created by rigoligo on 2021/5/8.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QCloseEvent>
|
||||
|
||||
class SpkTitleBar;
|
||||
|
||||
class SpkWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum SpkWindowStyle { Dark, Light };
|
||||
static constexpr int BorderWidth = 7;
|
||||
|
||||
private:
|
||||
QWidget *mUserCentralWidget;
|
||||
QVBoxLayout *mMainVLayout;
|
||||
SpkTitleBar *mTitleBarComponent;
|
||||
int mCornerRadius;
|
||||
bool mMoving, mResizing, mMaximized, mResizable;
|
||||
Qt::Edges mEdgesBeingResized;
|
||||
QPoint mMoveOffset;
|
||||
bool (*mCloseHook)(void);
|
||||
bool mUseCustomEvents;
|
||||
|
||||
public:
|
||||
SpkWindow(QWidget *parent = nullptr);
|
||||
~SpkWindow() override;
|
||||
void SetCentralWidget(QWidget *);
|
||||
bool GetUseTitleBar();
|
||||
bool GetResizable() { return mResizable; }
|
||||
void SetCloseHook(bool(*f)(void));
|
||||
|
||||
public slots:
|
||||
void SetCornerRadius(int);
|
||||
void SetUseTitleBar(bool);
|
||||
void SetResizable(bool a) { mResizable = a; }
|
||||
void SetCentralMargin(int, int, int, int);
|
||||
void ClearCloseHook();
|
||||
void RecalculateSize();
|
||||
SpkTitleBar *GetTitleBar();
|
||||
SpkTitleBar *SetTitleBar(SpkTitleBar*, bool replace = false);
|
||||
|
||||
signals:
|
||||
void Closed();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *) override;
|
||||
Qt::Edges DetectEdgeOnThis(QPoint);
|
||||
void SetMouseCursor(Qt::Edges);
|
||||
void ResizeWindowByCursor(QPoint);
|
||||
void closeEvent(QCloseEvent *) override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
void PopulateUi();
|
||||
|
||||
void paintWindowBorder();
|
||||
};
|
||||
20
inc/telemetry/collectid.h
Normal file
20
inc/telemetry/collectid.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
|
||||
namespace SpkTelemetry
|
||||
{
|
||||
void FunCollectId()
|
||||
{
|
||||
#if !defined(NDEBUG) || !defined(SPARK_FORCE_TELEMETRY) // Debug builds shouldn't transmit telemetry data unless asked for
|
||||
return;
|
||||
#else
|
||||
QFile idFile("/etc/machine-id");
|
||||
if(!idFile.open(QFile::ReadOnly))
|
||||
return;
|
||||
auto machineId = idFile.readAll();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
1077
lang/zh.ts
Normal file
1077
lang/zh.ts
Normal file
File diff suppressed because it is too large
Load Diff
28
main.cpp
28
main.cpp
@@ -1,28 +0,0 @@
|
||||
#include "mainwindow.h"
|
||||
#include <DApplication>
|
||||
#include <DWidgetUtil> //Dtk::Widget::moveToCenter(&w); 要调用它,就得引用DWidgetUtil
|
||||
|
||||
DWIDGET_USE_NAMESPACE
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
DApplication::loadDXcbPlugin(); //让bar处在标题栏中
|
||||
DApplication a(argc, argv);
|
||||
|
||||
a.setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
a.loadTranslator();
|
||||
a.setOrganizationName("deepin");
|
||||
a.setApplicationVersion(DApplication::buildVersion("1.0-beta3"));
|
||||
a.setApplicationAcknowledgementPage("https://gitee.com/deepin-community-store/deepin-community-store");
|
||||
a.setProductIcon(QIcon::fromTheme("deepin-community-store")); //设置Logo
|
||||
a.setProductName("深度社区应用商店");
|
||||
a.setApplicationName("社区应用商店"); //只有在这儿修改窗口标题才有效
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
//让打开时界面显示在正中
|
||||
Dtk::Widget::moveToCenter(&w);
|
||||
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <DMainWindow>
|
||||
|
||||
DWIDGET_USE_NAMESPACE
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: DMainWindow(parent)
|
||||
{
|
||||
w = new Widget;
|
||||
|
||||
resize(w->size()); //设置窗口大小
|
||||
setMinimumSize(950,700);
|
||||
setCentralWidget(w);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
23
mainwindow.h
23
mainwindow.h
@@ -1,23 +0,0 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <DMainWindow>
|
||||
#include "widget.h"
|
||||
#include <QThread>
|
||||
DWIDGET_USE_NAMESPACE
|
||||
|
||||
class MainWindow : public DMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
|
||||
Widget *w;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "outputwindow.h"
|
||||
#include "ui_outputwindow.h"
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
outputwindow::outputwindow(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::outputwindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle("安装详情");
|
||||
QDesktopWidget *desktop = QApplication::desktop();
|
||||
move((desktop->width() - width())/ 2, (desktop->height() - height()) /2);
|
||||
}
|
||||
|
||||
outputwindow::~outputwindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void outputwindow::setoutput(QString output)
|
||||
{
|
||||
qDebug()<<output;
|
||||
ui->textBrowser->setText(output);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef OUTPUTWINDOW_H
|
||||
#define OUTPUTWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class outputwindow;
|
||||
}
|
||||
|
||||
class outputwindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit outputwindow(QWidget *parent = nullptr);
|
||||
~outputwindow();
|
||||
void setoutput(QString);
|
||||
private:
|
||||
Ui::outputwindow *ui;
|
||||
};
|
||||
|
||||
#endif // OUTPUTWINDOW_H
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>outputwindow</class>
|
||||
<widget class="QWidget" name="outputwindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>391</width>
|
||||
<height>372</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTextBrowser" name="textBrowser"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
14
plugin/dtkplugin/CMakeLists.txt
Normal file
14
plugin/dtkplugin/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
set(PLUGIN_SOURCE
|
||||
spkdtkplugin.h
|
||||
spkdtkplugin_impl.h
|
||||
spkdtkplugin.cpp)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(dtkwidget REQUIRED IMPORTED_TARGET dtkwidget)
|
||||
pkg_check_modules(dtkgui REQUIRED IMPORTED_TARGET dtkgui)
|
||||
pkg_check_modules(dtkcore REQUIRED IMPORTED_TARGET dtkcore)
|
||||
add_library(spkdtkplugin SHARED ${PLUGIN_SOURCE})
|
||||
|
||||
target_link_libraries(spkdtkplugin PkgConfig::dtkwidget PkgConfig::dtkgui PkgConfig::dtkcore ${REQUIRED_LIBS_QUALIFIED})
|
||||
|
||||
install(TARGETS spkdtkplugin LIBRARY DESTINATION lib)
|
||||
36
plugin/dtkplugin/spkdtkplugin.cpp
Normal file
36
plugin/dtkplugin/spkdtkplugin.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <DGuiApplicationHelper>
|
||||
#include <DPlatformWindowHandle>
|
||||
#include <DPlatformTheme>
|
||||
#include "spkdtkplugin_impl.h"
|
||||
|
||||
using Dtk::Widget::DPlatformWindowHandle;
|
||||
|
||||
void SpkDtkPluginImpl::Initialize()
|
||||
{
|
||||
connect(DGuiApplicationHelper::instance()->systemTheme(),
|
||||
&DPlatformTheme::activeColorChanged,
|
||||
this,
|
||||
&SpkDtkPluginImpl::AccentColorChanged);
|
||||
connect(DGuiApplicationHelper::instance(),
|
||||
&DGuiApplicationHelper::themeTypeChanged,
|
||||
[&](Dtk::Gui::DGuiApplicationHelper::ColorType t)
|
||||
{
|
||||
this->DarkLightThemeChanged(t == Dtk::Gui::DGuiApplicationHelper::DarkType);
|
||||
});
|
||||
}
|
||||
|
||||
void SpkDtkPluginImpl::addWindow(QWidget *w, QObject *parent)
|
||||
{
|
||||
DPlatformWindowHandle *h = new DPlatformWindowHandle(w, parent);
|
||||
Q_UNUSED(h);
|
||||
}
|
||||
|
||||
QColor SpkDtkPluginImpl::GetAccentColor()
|
||||
{
|
||||
return DGuiApplicationHelper::instance()->systemTheme()->activeColor();
|
||||
}
|
||||
|
||||
bool SpkDtkPluginImpl::GetIsDarkTheme()
|
||||
{
|
||||
return DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType;
|
||||
}
|
||||
22
plugin/dtkplugin/spkdtkplugin.h
Normal file
22
plugin/dtkplugin/spkdtkplugin.h
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class SpkDtkPlugin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual ~SpkDtkPlugin() = default;
|
||||
virtual void Initialize() = 0;
|
||||
virtual void addWindow(QWidget* w, QObject* parent) = 0;
|
||||
virtual QColor GetAccentColor() = 0;
|
||||
virtual bool GetIsDarkTheme() = 0;
|
||||
|
||||
signals:
|
||||
void AccentColorChanged(QColor);
|
||||
void DarkLightThemeChanged(bool isDark);
|
||||
};
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_INTERFACE(SpkDtkPlugin, "org.spark-store.client.dtkplugin")
|
||||
QT_END_NAMESPACE
|
||||
19
plugin/dtkplugin/spkdtkplugin_impl.h
Normal file
19
plugin/dtkplugin/spkdtkplugin_impl.h
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QtPlugin>
|
||||
#include "spkdtkplugin.h"
|
||||
|
||||
class SpkDtkPluginImpl : public SpkDtkPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.spark-store.client.dtkplugin")
|
||||
Q_INTERFACES(SpkDtkPlugin)
|
||||
|
||||
public:
|
||||
void addWindow(QWidget* w, QObject* parent) override;
|
||||
void Initialize() override;
|
||||
QColor GetAccentColor() override;
|
||||
bool GetIsDarkTheme() override;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user