From 2fd08bfafc075a8755ade8b4861a216d588702c0 Mon Sep 17 00:00:00 2001 From: sleepwithoutbz Date: Tue, 16 Sep 2025 00:33:46 +0800 Subject: [PATCH] Init commit. --- .clang-format | 37 ++++ .gitignore | 6 + CMakeLists.txt | 51 ++++++ CMakeLists.txt.user | 418 ++++++++++++++++++++++++++++++++++++++++++++ images/bad.png | Bin 0 -> 2101 bytes images/heart.png | Bin 0 -> 1834 bytes images/trash.png | Bin 0 -> 1285 bytes src/main.cpp | 46 +++++ src/window.cpp | 227 ++++++++++++++++++++++++ src/window.h | 73 ++++++++ systray.pro | 11 ++ systray.qrc | 7 + 12 files changed, 876 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakeLists.txt.user create mode 100644 images/bad.png create mode 100644 images/heart.png create mode 100644 images/trash.png create mode 100644 src/main.cpp create mode 100644 src/window.cpp create mode 100644 src/window.h create mode 100644 systray.pro create mode 100644 systray.qrc diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..19fa6ca --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +BasedOnStyle: LLVM + +# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto +Language: Cpp + +# tab宽度 +TabWidth: 4 + +# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always +UseTab: Never + +# 每行字符的限制,0表示没有限制 +ColumnLimit: 100 + +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 + +# 缩进case标签 +IndentCaseLabels: false + +# 要使用的预处理器指令缩进样式 +IndentPPDirectives: AfterHash + +# 缩进宽度 +IndentWidth: 4 + +# 函数返回类型换行时,缩进函数声明或函数定义的函数名 +IndentWrappedFunctionNames: false + +# 连续赋值时,对齐所有等号 +AlignConsecutiveAssignments: true + +# 连续声明时,对齐所有声明的变量名 +AlignConsecutiveDeclarations: true + +# 用于在使用反斜杠换行中对齐反斜杠的选项 +AlignEscapedNewlines: Left diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b7653d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.cache/ +.vs/ +.vscode/ + +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f98d6bb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(systray LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_standard_project_setup() + +# 导出src下的cpp文件 +file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS src/*.cpp) +qt_add_executable(systray ${SRC_FILES}) + +set_target_properties(systray PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(systray PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +# Resources: +set(systray_resource_files + "images/bad.png" + "images/heart.png" + "images/trash.png" +) + +qt_add_resources(systray "systray" + PREFIX + "/" + FILES + ${systray_resource_files} +) + +install(TARGETS systray + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET systray + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..11404e8 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,418 @@ + + + + + + EnvironmentId + {fd82fbfc-f75d-4f93-a8b6-cde9869ae40a} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop Qt 6.9.2 MinGW 64-bit + Desktop Qt 6.9.2 MinGW 64-bit + qt.qt6.692.win64_mingw_kit + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DQT_MAINTENANCE_TOOL:FILEPATH=D:/Programs/Qt/MaintenanceTool.exe +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + 0 + D:\Programs\Qt\Examples\Qt-6.9.2\widgets\desktop\systray\build\Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + + + + all + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + 0 + 0 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + systray + CMakeProjectManager.CMakeRunConfiguration. + systray + false + true + true + true + D:/Programs/Qt/Examples/Qt-6.9.2/widgets/desktop/systray/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + + Release + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DQT_MAINTENANCE_TOOL:FILEPATH=D:/Programs/Qt/MaintenanceTool.exe +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + D:\Programs\Qt\Examples\Qt-6.9.2\widgets\desktop\systray\build\Desktop_Qt_6_9_2_MinGW_64_bit-Release + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + 0 + -1 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + install + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + 0 + + 2 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + systray + CMakeProjectManager.CMakeRunConfiguration. + systray + false + true + true + true + D:/Programs/Qt/Examples/Qt-6.9.2/widgets/desktop/systray/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/images/bad.png b/images/bad.png new file mode 100644 index 0000000000000000000000000000000000000000..b09de85869ba3af5affa55553704affb566e5845 GIT binary patch literal 2101 zcmV-52+H?~P)5Fi=n}QrN)&00)stL_t(| z+U=d`y5k@Sh7tGd?Rfv!I?pW6bhg$6sG+Uz@1B!<0TC4yI-O3Z)9G|Nold9I>2x}s z&OPj9jGM^=qATBVh&h`rewhQj7xqgugDVek!{XmW_8b>1JHaK+@XB0bcI6;0#|yk9 z^M-{8nwK;5y!|xoM`#Y-6#;Ow@CwYih>=H7&ds<&;HcTm6}dFfXq8-%%Rr^%Kjgl@ zXiRm67dGs=Sktqbn!iBAi1w z>dtOhrZ}e6{Z481Zz)7Te~1BN7xhj8{_tVXQeday`~NBzOWA&r@v9Nd>$> ze=PrCT;XY0)c2vM08b=LF9&FV9h!#fO~J6` zK||g^n5bf4>YD{F>on*E@U5CL62ELi#Y3CNjL?{|MKiWK{P_p5L2rzT^geJm6nlay zz2x`dJM;u6VV)bQ7j9UTC*Mo?K!8!v`c5^^y#gQPyN^7pyJLj_T)d8i<8R`#;+92a z0jn~QaN#)t5l`Qr5RhPEdY5#1F%7`_6cZa&8ON^Z;-6q(1QiVOCN^pv`WuO0Vp51U zpoB@<#G|?t>ra9cHZkfL^B+w=oUnmmAK^037a(Bgc@8X-B%qNQ1|IWmhX$pzK97vJ zl_mQmJX$jDi3=)d=|%~HF>pLQmf48ipj*i%_HrQ$36t1nw_d^sG>e6;nQUMrcHD;2 z7L!=mGE@VP#8kzwJ}-atic|wd5Q^oDUHZ-W7Y+v6ch9kI;JnoRSX9prR8V!BCA^e8qLW?5JgVK^h1c zo?fO;URVw?KqJLQY^``?_2Ub)QEY(3!s?eM!Ll90(%a~jjbvSxX`vS)>z`#>uf$hu zMAz;!OK^hcD>gu8X2n^g#CX19BV4tGEWs9@t=LExSRtzymn?9T*9(@FTUHV+3WlSX z|IY0NgQ@9#H^E>Y>+OPHUGS&5wCCj?kFfi4`Wrfpu(kZh3)ehq|{G%gvM(XX3C2T)X zTgY|Xsm<8B=N%J#E&7`A-OHv%^>+-&-bT2HxsR?>h`0L-6)$mBh`#svw99EAX}MnN zq26LajOi6FnDU}wQOgxmu~Z$pUlP}^0aSO%BB6R@0%^i<_HFGUmm3Z8T-fn9rNG8hVr?mXYRhMj8kMLM3u;Z2dsSPvP zxSK0=On`RyvImR=GuuCmt6~^_Q(R}*=0mpix%`2i>BEF&C%BB4U8ar3m7TZ*&9PYa z=t_I|lMQ^g=A+)7 zYtfpIy1V$#F*0hXiEC$Mr=mU#z%Hc;SKPkI$-c(P2x}sPDhZh*p_H9H_6Ep00000NkvXXu0mjfsubqD literal 0 HcmV?d00001 diff --git a/images/heart.png b/images/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..5d466b1b89417c407d0ee35153d6195971c2bd58 GIT binary patch literal 1834 zcmZvdc{Ce{7RK$`D56q|Dy0*98~akJJw_xMB`S(oD|XE(MTc05s3ppDw6UvY?5$D6 zAht>^vDDZat-Vsz5~GMxlXv>hJLkP~zWaT@bMHO(-}~4OWi1Sl25@n43ETW^Y0t@> z|I`^?j-O4FD&~ZV9Rh8|Nnv4OAP~s${{{{V2?>dah;ZPCpF4Ms13xx7qaTp5sqzcw z1SrfN<-j#BAGZxG^5}$&+C6z`de1cT9%Mg4(2$EuAlb&!3?0e%@?m3qLjIh)Kpi(#Y3%N&IS?|vE2m7Tq9m5?2+xer4IJYTILZ1<9ePpKav|~ zJ0o-Cupfu`+P&YaARe8Y3!{Ys+O`DAtkEl{C-?66JN4A5uRs?nBC}^4x{yJiRkq^d z^nb~SHhB1Eq#xZF+HhD~Z-eSbE4{Z$5iT3%S6)Cu@w?I`gAlFBs*5{Fq(9Wz@9|dK z8`fBlc$Jif)^HU>dvC@{1M5>QCX-yy>T0T>D##FW-yC?%xnq47GS=pm`(PU?<0|W$7@4 zl`eh&tJn-$S#ISwx_Yn`${Qk-))otPzx*)17p4Txbm;*HSYRw7*!rc^&phILy--m@ z<0oV9pcChpYR>w%KeWF=d%3c9!5A~J{`pTyLXzx9P2~bx7@_jG2-76zf!cp?(z{oy z_o_p?4-4F`1Eazcf-P_M!I~bp;0M$6P;8K#5-Y&EeLV;8=(ZU29TFQ(XteQ9wcdPC zna83bS~|k^m*TROP5jp7))Qc@cTv^oPgBf2)3hEHpS?6o8u898UU;@K@_8a`GEzH- z`I_;lAU4ofKU=zQW3T>3H+j((*V zkTtYmB~>z%;qYry8jiCpZ3+IM$^aM$0=VQjTw)4>IMxe(;N9ZJHFr2NN*3Jba=^#w zr!76+6*cC8Z#`dFq3;~+i-__PB$BK%s|QHLDhdxeSVEU8XEoJdDG(v^`hh$)(apdm zT2Xa1$&E6`u%-80`;gikpuy`2C;0^`p~kJ=mej#C(zLVR*&9L!wg+nc{0sJX)9hpb zLFG-vcw@POV88d$#XD}#L;X^nmjmXLeJdjES|2lm)Et5^Oo0)`AV?1W%u&Di@zHoC z6lW^_DaYwoy0G5@rZy@M4?Ee&@qGGfl^9`X)xI9?Sbww18q@$QRc@TDok;$DTTyVL z!``~!?10Z1Yb^=O1gJtItGl1z}_tpnz2`GVI!P6m}# z8bG_l#3WR!R8T{f6orNfu7kgjcR{mMa6uXN~OqWgs3kF8I4nE2_YUVgU30fk@N~DcY*htT{1^n)`*c;0ahooa)>O4+GjRgoD@dP*Zn!g13ATFt63pAT0 z^zHbhA`Vhb642VzU!$Z6`b^{Zk?fee+E{`+J3r{zyp#QFED4)2Kct^HjtK9>VUQf_ zUmX&I%q2t>;-?Eh88MsI)u-Xea;D;V`_x|}+b=c@NX!36Cv3rot-3gCSjQL6$KtZF LLRr?EdnEh|XvAl= literal 0 HcmV?d00001 diff --git a/images/trash.png b/images/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..454074f9f29b3a62f07effcee11d079ecc6f4d99 GIT binary patch literal 1285 zcmeAS@N?(olHy`uVBq!ia0vp^A3&Ic8Ax(=OD+IX_5nU2u0WcXmsdbQ08RoKXn>E8 z42w{z()pV53%a_@iS?bs`;v{V{ZdXA^mUY~YB-$P;IMoyrK z5D@l5^UKO&oz*vvF5BBvBJ}T4`6kcldh@)@-KN&6w6|1>{M)tjIgdVZ4V>wA0EroFl2lBK22R^S@t z^QFrrNa#q1gZZ4#EUV`}3Opm>>8zi&Wr<>!_F!?oc*)Z@1kX; zV7cMe#a}ah{#~-J)K0H_ckRXo8O7KW3jdE5NrZ2=01D7aayTQ7F*;|W!a=hHJ1;kdxrUz zOb84!U2-pb()$=*_vtruZ_gdYTW_Yq$W_D_;I&bo{ z^q*$;+jpEbD!%#rx4gpBpOsRnniJb*Jm0zP{5Nxl>CX?Fr2L+h*1P=F(o0WYZY+(Nc+gSo)!N5hJb5V#Z(6N8@xV{)&b2~Uw*5wY2RYn6#B;bA zGzsP?u^i->rzimw+0ZG#qFm9Iz@jX1P|Zo8=YeFCU{3+W!0-m23Aa>%zmdxFgSv`eP8CssZ(>(e%wv93Gx*tTBn(ub4&PdmT+(wm1n zil#67SzehTm+`G2=43bjU!hrkM@+Xazj?SYRbRif?Qz{bp+diDT2^E2+q$f+KuHC9(Fa_-Ujx$cEz^o_a`6X%8H zd5G?v`Cd0i_<3aZ^7pfH-z3~xm6P~>|BVxBg-_<*5xjl+$xGFJyS@bOll;L_|HQ3d zM0IuBw*zTw&#y?Gen|gCe)!2}UY0-nj9zBg=gf-p``C9cSmgaHTgQ)gX8T|5T=j0| zy{7km;r>5a#Z+S^bA~EwCH*`7v;V=90=)^UZx5cl6ZPZEy<3O=F&EWKPm*bzJ#oz^ z=b}$x>*Rgr-Kqbv(RbtH>E5wETYpBtN{NQYe_8%z?tk>aby*=u%+uA+Wt~$(696Lo BOZ@-< literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..367b027 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,46 @@ +#include + +#ifndef QT_NO_SYSTEMTRAYICON + +# include "window.h" +# include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + auto choice = + QMessageBox::critical(nullptr, QObject::tr("Systray"), + QObject::tr("I couldn't detect any system tray on this system."), + QMessageBox::Close | QMessageBox::Ignore); + if (choice == QMessageBox::Close) + return 1; + // Otherwise "lurk": if a system tray is started later, the icon will + // appear. + } + QApplication::setQuitOnLastWindowClosed(false); + + Window window; + window.show(); + return app.exec(); +} + +#else + +# include +# include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + QString text("QSystemTrayIcon is not supported on this platform"); + + QLabel *label = new QLabel(text); + label->setWordWrap(true); + + label->show(); + qDebug() << text; + + app.exec(); +} + +#endif diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..ac62606 --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,227 @@ +#include "window.h" + +#ifndef QT_NO_SYSTEMTRAYICON + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +//! [0] +Window::Window() { + createIconGroupBox(); + createMessageGroupBox(); + + iconLabel->setMinimumWidth(durationLabel->sizeHint().width()); + + createActions(); + createTrayIcon(); + + connect(showMessageButton, &QAbstractButton::clicked, this, &Window::showMessage); + connect(showIconCheckBox, &QAbstractButton::toggled, trayIcon, &QSystemTrayIcon::setVisible); + connect(iconComboBox, &QComboBox::currentIndexChanged, this, &Window::setIcon); + connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &Window::messageClicked); + connect(trayIcon, &QSystemTrayIcon::activated, this, &Window::iconActivated); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(iconGroupBox); + mainLayout->addWidget(messageGroupBox); + setLayout(mainLayout); + + iconComboBox->setCurrentIndex(1); + trayIcon->show(); + + setWindowTitle(tr("Systray")); + resize(400, 300); +} +//! [0] + +//! [1] +void Window::setVisible(bool visible) { + minimizeAction->setEnabled(visible); + maximizeAction->setEnabled(!isMaximized()); + restoreAction->setEnabled(isMaximized() || !visible); + QDialog::setVisible(visible); +} +//! [1] + +//! [2] +void Window::closeEvent(QCloseEvent *event) { + if (!event->spontaneous() || !isVisible()) + return; + if (trayIcon->isVisible()) { + QMessageBox::information(this, tr("Systray"), + tr("The program will keep running in the " + "system tray. To terminate the program, " + "choose Quit in the context menu " + "of the system tray entry.")); + hide(); + event->ignore(); + } +} +//! [2] + +//! [3] +void Window::setIcon(int index) { + QIcon icon = iconComboBox->itemIcon(index); + trayIcon->setIcon(icon); + setWindowIcon(icon); + + trayIcon->setToolTip(iconComboBox->itemText(index)); +} +//! [3] + +//! [4] +void Window::iconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + iconComboBox->setCurrentIndex((iconComboBox->currentIndex() + 1) % iconComboBox->count()); + break; + case QSystemTrayIcon::MiddleClick: + showMessage(); + break; + default:; + } +} +//! [4] + +//! [5] +void Window::showMessage() { + showIconCheckBox->setChecked(true); + int selectedIcon = typeComboBox->itemData(typeComboBox->currentIndex()).toInt(); + QSystemTrayIcon::MessageIcon msgIcon = QSystemTrayIcon::MessageIcon(selectedIcon); + + if (selectedIcon == -1) { // custom icon + QIcon icon(iconComboBox->itemIcon(iconComboBox->currentIndex())); + trayIcon->showMessage(titleEdit->text(), bodyEdit->toPlainText(), icon, + durationSpinBox->value() * 1000); + } else { + trayIcon->showMessage(titleEdit->text(), bodyEdit->toPlainText(), msgIcon, + durationSpinBox->value() * 1000); + } +} +//! [5] + +//! [6] +void Window::messageClicked() { + QMessageBox::information(nullptr, tr("Systray"), + tr("Sorry, I already gave what help I could.\n" + "Maybe you should try asking a human?")); +} +//! [6] + +void Window::createIconGroupBox() { + iconGroupBox = new QGroupBox(tr("Tray Icon")); + + iconLabel = new QLabel("Icon:"); + + iconComboBox = new QComboBox; + iconComboBox->addItem(QIcon(":/images/bad.png"), tr("Bad")); + iconComboBox->addItem(QIcon(":/images/heart.png"), tr("Heart")); + iconComboBox->addItem(QIcon(":/images/trash.png"), tr("Trash")); + + showIconCheckBox = new QCheckBox(tr("Show icon")); + showIconCheckBox->setChecked(true); + + QHBoxLayout *iconLayout = new QHBoxLayout; + iconLayout->addWidget(iconLabel); + iconLayout->addWidget(iconComboBox); + iconLayout->addStretch(); + iconLayout->addWidget(showIconCheckBox); + iconGroupBox->setLayout(iconLayout); +} + +void Window::createMessageGroupBox() { + messageGroupBox = new QGroupBox(tr("Balloon Message")); + + typeLabel = new QLabel(tr("Type:")); + + typeComboBox = new QComboBox; + typeComboBox->addItem(tr("None"), QSystemTrayIcon::NoIcon); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxInformation), + tr("Information"), QSystemTrayIcon::Information); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxWarning), tr("Warning"), + QSystemTrayIcon::Warning); + typeComboBox->addItem(style()->standardIcon(QStyle::SP_MessageBoxCritical), tr("Critical"), + QSystemTrayIcon::Critical); + typeComboBox->addItem(QIcon(), tr("Custom icon"), -1); + typeComboBox->setCurrentIndex(1); + + durationLabel = new QLabel(tr("Duration:")); + + durationSpinBox = new QSpinBox; + durationSpinBox->setRange(5, 60); + durationSpinBox->setSuffix(" s"); + durationSpinBox->setValue(15); + + durationWarningLabel = new QLabel(tr("(some systems might ignore this " + "hint)")); + durationWarningLabel->setIndent(10); + + titleLabel = new QLabel(tr("Title:")); + + titleEdit = new QLineEdit(tr("Cannot connect to network")); + + bodyLabel = new QLabel(tr("Body:")); + + bodyEdit = new QTextEdit; + bodyEdit->setPlainText(tr("Don't believe me. Honestly, I don't have a " + "clue.\nClick this balloon for details.")); + + showMessageButton = new QPushButton(tr("Show Message")); + showMessageButton->setDefault(true); + + QGridLayout *messageLayout = new QGridLayout; + messageLayout->addWidget(typeLabel, 0, 0); + messageLayout->addWidget(typeComboBox, 0, 1, 1, 2); + messageLayout->addWidget(durationLabel, 1, 0); + messageLayout->addWidget(durationSpinBox, 1, 1); + messageLayout->addWidget(durationWarningLabel, 1, 2, 1, 3); + messageLayout->addWidget(titleLabel, 2, 0); + messageLayout->addWidget(titleEdit, 2, 1, 1, 4); + messageLayout->addWidget(bodyLabel, 3, 0); + messageLayout->addWidget(bodyEdit, 3, 1, 2, 4); + messageLayout->addWidget(showMessageButton, 5, 4); + messageLayout->setColumnStretch(3, 1); + messageLayout->setRowStretch(4, 1); + messageGroupBox->setLayout(messageLayout); +} + +void Window::createActions() { + minimizeAction = new QAction(tr("Mi&nimize"), this); + connect(minimizeAction, &QAction::triggered, this, &QWidget::hide); + + maximizeAction = new QAction(tr("Ma&ximize"), this); + connect(maximizeAction, &QAction::triggered, this, &QWidget::showMaximized); + + restoreAction = new QAction(tr("&Restore"), this); + connect(restoreAction, &QAction::triggered, this, &QWidget::showNormal); + + quitAction = new QAction(tr("&Quit"), this); + connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); +} + +void Window::createTrayIcon() { + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(minimizeAction); + trayIconMenu->addAction(maximizeAction); + trayIconMenu->addAction(restoreAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); +} + +#endif diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..c1cca5f --- /dev/null +++ b/src/window.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#ifndef QT_NO_SYSTEMTRAYICON + +# include + +QT_BEGIN_NAMESPACE +class QAction; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QMenu; +class QPushButton; +class QSpinBox; +class QTextEdit; +QT_END_NAMESPACE + +//! [0] +class Window : public QDialog { + Q_OBJECT + +public: + Window(); + + void setVisible(bool visible) override; + +protected: + void closeEvent(QCloseEvent *event) override; + +private slots: + void setIcon(int index); + void iconActivated(QSystemTrayIcon::ActivationReason reason); + void showMessage(); + void messageClicked(); + +private: + void createIconGroupBox(); + void createMessageGroupBox(); + void createActions(); + void createTrayIcon(); + + QGroupBox *iconGroupBox; + QLabel *iconLabel; + QComboBox *iconComboBox; + QCheckBox *showIconCheckBox; + + QGroupBox *messageGroupBox; + QLabel *typeLabel; + QLabel *durationLabel; + QLabel *durationWarningLabel; + QLabel *titleLabel; + QLabel *bodyLabel; + QComboBox *typeComboBox; + QSpinBox *durationSpinBox; + QLineEdit *titleEdit; + QTextEdit *bodyEdit; + QPushButton *showMessageButton; + + QAction *minimizeAction; + QAction *maximizeAction; + QAction *restoreAction; + QAction *quitAction; + + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; +}; +//! [0] + +#endif // QT_NO_SYSTEMTRAYICON diff --git a/systray.pro b/systray.pro new file mode 100644 index 0000000..e0708ea --- /dev/null +++ b/systray.pro @@ -0,0 +1,11 @@ +HEADERS = window.h +SOURCES = main.cpp \ + window.cpp +RESOURCES = systray.qrc + +QT += widgets +requires(qtConfig(combobox)) + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/widgets/desktop/systray +INSTALLS += target diff --git a/systray.qrc b/systray.qrc new file mode 100644 index 0000000..a8b6535 --- /dev/null +++ b/systray.qrc @@ -0,0 +1,7 @@ + + + images/bad.png + images/heart.png + images/trash.png + +