1#############################################################################
2##
3## Copyright (C) 2020 The Qt Company Ltd.
4## Contact: http://www.qt.io/licensing/
5##
6## This file is part of the Qt for Python examples of the Qt Toolkit.
7##
8## $QT_BEGIN_LICENSE:BSD$
9## You may use this file under the terms of the BSD license as follows:
10##
11## "Redistribution and use in source and binary forms, with or without
12## modification, are permitted provided that the following conditions are
13## met:
14##   * Redistributions of source code must retain the above copyright
15##     notice, this list of conditions and the following disclaimer.
16##   * Redistributions in binary form must reproduce the above copyright
17##     notice, this list of conditions and the following disclaimer in
18##     the documentation and/or other materials provided with the
19##     distribution.
20##   * Neither the name of The Qt Company Ltd nor the names of its
21##     contributors may be used to endorse or promote products derived
22##     from this software without specific prior written permission.
23##
24##
25## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36##
37## $QT_END_LICENSE$
38##
39#############################################################################
40
41from PySide2.QtCore import Slot
42from PySide2.QtGui import QIcon
43from PySide2.QtWidgets import (QAction, QCheckBox, QComboBox, QDialog,
44                               QGridLayout, QGroupBox, QHBoxLayout, QLabel,
45                               QLineEdit, QMenu, QMessageBox, QPushButton,
46                               QSpinBox, QStyle, QSystemTrayIcon, QTextEdit,
47                               QVBoxLayout)
48
49import rc_systray
50
51
52class Window(QDialog):
53    def __init__(self, parent=None):
54        super(Window, self).__init__(parent)
55
56        self.iconGroupBox = QGroupBox()
57        self.iconLabel = QLabel()
58        self.iconComboBox = QComboBox()
59        self.showIconCheckBox = QCheckBox()
60
61        self.messageGroupBox = QGroupBox()
62        self.typeLabel = QLabel()
63        self.durationLabel = QLabel()
64        self.durationWarningLabel = QLabel()
65        self.titleLabel = QLabel()
66        self.bodyLabel = QLabel()
67
68        self.typeComboBox = QComboBox()
69        self.durationSpinBox = QSpinBox()
70        self.titleEdit = QLineEdit()
71        self.bodyEdit = QTextEdit()
72        self.showMessageButton = QPushButton()
73
74        self.minimizeAction = QAction()
75        self.maximizeAction = QAction()
76        self.restoreAction = QAction()
77        self.quitAction = QAction()
78
79        self.trayIcon = QSystemTrayIcon()
80        self.trayIconMenu = QMenu()
81
82        self.createIconGroupBox()
83        self.createMessageGroupBox()
84
85        self.iconLabel.setMinimumWidth(self.durationLabel.sizeHint().width())
86
87        self.createActions()
88        self.createTrayIcon()
89
90        self.showMessageButton.clicked.connect(self.showMessage)
91        self.showIconCheckBox.toggled.connect(self.trayIcon.setVisible)
92        self.iconComboBox.currentIndexChanged.connect(self.setIcon)
93        self.trayIcon.messageClicked.connect(self.messageClicked)
94        self.trayIcon.activated.connect(self.iconActivated)
95
96        self.mainLayout = QVBoxLayout()
97        self.mainLayout.addWidget(self.iconGroupBox)
98        self.mainLayout.addWidget(self.messageGroupBox)
99        self.setLayout(self.mainLayout)
100
101        self.iconComboBox.setCurrentIndex(1)
102        self.trayIcon.show()
103
104        self.setWindowTitle("Systray")
105        self.resize(400, 300)
106
107    def setVisible(self, visible):
108        self.minimizeAction.setEnabled(visible)
109        self.maximizeAction.setEnabled(not self.isMaximized())
110        self.restoreAction.setEnabled(self.isMaximized() or not visible)
111        super().setVisible(visible)
112
113    def closeEvent(self, event):
114        if not event.spontaneous() or not self.isVisible():
115            return
116        if self.trayIcon.isVisible():
117            QMessageBox.information(self, "Systray",
118                                    "The program will keep running in the system tray. "
119                                    "To terminate the program, choose <b>Quit</b> in the context "
120                                    "menu of the system tray entry.")
121            self.hide()
122            event.ignore()
123
124    @Slot(int)
125    def setIcon(self, index):
126        icon = self.iconComboBox.itemIcon(index)
127        self.trayIcon.setIcon(icon)
128        self.setWindowIcon(icon)
129        self.trayIcon.setToolTip(self.iconComboBox.itemText(index))
130
131    @Slot(str)
132    def iconActivated(self, reason):
133        if reason == QSystemTrayIcon.Trigger:
134            pass
135        if reason == QSystemTrayIcon.DoubleClick:
136            self.iconComboBox.setCurrentIndex(
137                (self.iconComboBox.currentIndex() + 1) % self.iconComboBox.count()
138            )
139        if reason == QSystemTrayIcon.MiddleClick:
140            self.showMessage()
141
142    @Slot()
143    def showMessage(self):
144        self.showIconCheckBox.setChecked(True)
145        selectedIcon = self.typeComboBox.itemData(self.typeComboBox.currentIndex())
146        msgIcon = QSystemTrayIcon.MessageIcon(selectedIcon)
147
148        if selectedIcon == -1:  # custom icon
149            icon = QIcon(self.iconComboBox.itemIcon(self.iconComboBox.currentIndex()))
150            self.trayIcon.showMessage(
151                self.titleEdit.text(),
152                self.bodyEdit.toPlainText(),
153                icon,
154                self.durationSpinBox.value() * 1000,
155            )
156        else:
157            self.trayIcon.showMessage(
158                self.titleEdit.text(),
159                self.bodyEdit.toPlainText(),
160                msgIcon,
161                self.durationSpinBox.value() * 1000,
162            )
163
164    @Slot()
165    def messageClicked(self):
166        QMessageBox.information(None, "Systray",
167                                "Sorry, I already gave what help I could.\n"
168                                "Maybe you should try asking a human?")
169
170    def createIconGroupBox(self):
171        self.iconGroupBox = QGroupBox("Tray Icon")
172
173        self.iconLabel = QLabel("Icon:")
174
175        self.iconComboBox = QComboBox()
176        self.iconComboBox.addItem(QIcon(":/images/bad.png"), "Bad")
177        self.iconComboBox.addItem(QIcon(":/images/heart.png"), "Heart")
178        self.iconComboBox.addItem(QIcon(":/images/trash.png"), "Trash")
179
180        self.showIconCheckBox = QCheckBox("Show icon")
181        self.showIconCheckBox.setChecked(True)
182
183        iconLayout = QHBoxLayout()
184        iconLayout.addWidget(self.iconLabel)
185        iconLayout.addWidget(self.iconComboBox)
186        iconLayout.addStretch()
187        iconLayout.addWidget(self.showIconCheckBox)
188        self.iconGroupBox.setLayout(iconLayout)
189
190    def createMessageGroupBox(self):
191        self.messageGroupBox = QGroupBox("Balloon Message")
192
193        self.typeLabel = QLabel("Type:")
194
195        self.typeComboBox = QComboBox()
196        self.typeComboBox.addItem("None", QSystemTrayIcon.NoIcon)
197        self.typeComboBox.addItem(
198            self.style().standardIcon(QStyle.SP_MessageBoxInformation),
199            "Information",
200            QSystemTrayIcon.Information,
201        )
202        self.typeComboBox.addItem(
203            self.style().standardIcon(QStyle.SP_MessageBoxWarning),
204            "Warning",
205            QSystemTrayIcon.Warning,
206        )
207        self.typeComboBox.addItem(
208            self.style().standardIcon(QStyle.SP_MessageBoxCritical),
209            "Critical",
210            QSystemTrayIcon.Critical,
211        )
212        self.typeComboBox.addItem(QIcon(), "Custom icon", -1)
213        self.typeComboBox.setCurrentIndex(1)
214
215        self.durationLabel = QLabel("Duration:")
216
217        self.durationSpinBox = QSpinBox()
218        self.durationSpinBox.setRange(5, 60)
219        self.durationSpinBox.setSuffix(" s")
220        self.durationSpinBox.setValue(15)
221
222        self.durationWarningLabel = QLabel("(some systems might ignore this hint)")
223        self.durationWarningLabel.setIndent(10)
224
225        self.titleLabel = QLabel("Title:")
226        self.titleEdit = QLineEdit("Cannot connect to network")
227        self.bodyLabel = QLabel("Body:")
228
229        self.bodyEdit = QTextEdit()
230        self.bodyEdit.setPlainText("Don't believe me. Honestly, I don't have a clue."
231                                   "\nClick this balloon for details.")
232
233        self.showMessageButton = QPushButton("Show Message")
234        self.showMessageButton.setDefault(True)
235
236        messageLayout = QGridLayout()
237        messageLayout.addWidget(self.typeLabel, 0, 0)
238        messageLayout.addWidget(self.typeComboBox, 0, 1, 1, 2)
239        messageLayout.addWidget(self.durationLabel, 1, 0)
240        messageLayout.addWidget(self.durationSpinBox, 1, 1)
241        messageLayout.addWidget(self.durationWarningLabel, 1, 2, 1, 3)
242        messageLayout.addWidget(self.titleLabel, 2, 0)
243        messageLayout.addWidget(self.titleEdit, 2, 1, 1, 4)
244        messageLayout.addWidget(self.bodyLabel, 3, 0)
245        messageLayout.addWidget(self.bodyEdit, 3, 1, 2, 4)
246        messageLayout.addWidget(self.showMessageButton, 5, 4)
247        messageLayout.setColumnStretch(3, 1)
248        messageLayout.setRowStretch(4, 1)
249        self.messageGroupBox.setLayout(messageLayout)
250
251    def createActions(self):
252        self.minimizeAction = QAction("Minimize", self)
253        self.minimizeAction.triggered.connect(self.hide)
254
255        self.maximizeAction = QAction("Maximize", self)
256        self.maximizeAction.triggered.connect(self.showMaximized)
257
258        self.restoreAction = QAction("Restore", self)
259        self.restoreAction.triggered.connect(self.showNormal)
260
261        self.quitAction = QAction("Quit", self)
262        self.quitAction.triggered.connect(qApp.quit)
263
264    def createTrayIcon(self):
265        self.trayIconMenu = QMenu(self)
266        self.trayIconMenu.addAction(self.minimizeAction)
267        self.trayIconMenu.addAction(self.maximizeAction)
268        self.trayIconMenu.addAction(self.restoreAction)
269        self.trayIconMenu.addSeparator()
270        self.trayIconMenu.addAction(self.quitAction)
271
272        self.trayIcon = QSystemTrayIcon(self)
273        self.trayIcon.setContextMenu(self.trayIconMenu)
274