Skip to content

Commit 67fafb0

Browse files
authored
Merge pull request #3855 from irgolic/popups-to-non-intrusive-notifications
[ENH] Replace popups with non-intrusive notifications
2 parents e60df9b + 2b216b4 commit 67fafb0

File tree

7 files changed

+804
-101
lines changed

7 files changed

+804
-101
lines changed

Orange/canvas/__main__.py

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
import pkg_resources
2121

22-
from AnyQt.QtGui import QFont, QColor, QPalette, QDesktopServices
23-
from AnyQt.QtWidgets import QMessageBox
22+
from AnyQt.QtGui import QFont, QColor, QPalette, QDesktopServices, QIcon
2423
from AnyQt.QtCore import (
2524
Qt, QDir, QUrl, QSettings, QThread, pyqtSignal, QT_VERSION
2625
)
@@ -39,6 +38,8 @@
3938
from Orange.canvas.registry import qt
4039
from Orange.canvas.registry import WidgetRegistry, set_global_registry
4140
from Orange.canvas.registry import cache
41+
from Orange.canvas.utils.overlay import NotificationWidget, NotificationOverlay
42+
from Orange.widgets import gui
4243

4344
log = logging.getLogger(__name__)
4445

@@ -180,32 +181,56 @@ def make_sql_logger(level=logging.INFO):
180181
sql_log.addHandler(handler)
181182

182183

183-
def show_survey():
184-
# If run for the first time, open a browser tab with a survey
185-
settings = QSettings()
186-
show_survey = settings.value("startup/show-survey", True, type=bool)
184+
def setup_notifications():
185+
settings = config.settings()
186+
187+
# If run for the fifth time, prompt short survey
188+
show_survey = settings["startup/show-short-survey"] and \
189+
settings["startup/launch-count"] >= 5
187190
if show_survey:
188-
question = QMessageBox(
189-
QMessageBox.Question,
190-
'Orange Survey',
191-
'We would like to know more about how our software is used.\n\n'
192-
'Would you care to fill our short 1-minute survey?',
193-
QMessageBox.Yes | QMessageBox.No)
194-
question.setDefaultButton(QMessageBox.Yes)
195-
later = question.addButton('Ask again later', QMessageBox.NoRole)
196-
question.setEscapeButton(later)
197-
198-
def handle_response(result):
199-
if result == QMessageBox.Yes:
191+
surveyDialogButtons = NotificationWidget.Ok | NotificationWidget.Close
192+
surveyDialog = NotificationWidget(icon=QIcon(gui.resource_filename("icons/information.png")),
193+
title="Survey",
194+
text="We want to understand our users better.\n"
195+
"Would you like to take a short survey?",
196+
standardButtons=surveyDialogButtons)
197+
198+
def handle_survey_response(button):
199+
if surveyDialog.buttonRole(button) == NotificationWidget.AcceptRole:
200200
success = QDesktopServices.openUrl(
201201
QUrl("https://orange.biolab.si/survey/short.html"))
202-
settings.setValue("startup/show-survey", not success)
203-
else:
204-
settings.setValue("startup/show-survey", result != QMessageBox.No)
202+
settings["startup/show-short-survey"] = not success
203+
elif surveyDialog.buttonRole(button) == NotificationWidget.RejectRole:
204+
settings["startup/show-short-survey"] = False
205+
206+
surveyDialog.clicked.connect(handle_survey_response)
207+
208+
NotificationOverlay.registerNotification(surveyDialog)
205209

206-
question.finished.connect(handle_response)
207-
question.show()
208-
return question
210+
# data collection permission
211+
if not settings["error-reporting/permission-requested"]:
212+
permDialogButtons = NotificationWidget.Ok | NotificationWidget.Close
213+
permDialog = NotificationWidget(icon=QIcon(gui.resource_filename(
214+
"../../distribute/icon-48.png")),
215+
title="Anonymous Usage Statistics",
216+
text="Do you wish to opt-in to sharing "
217+
"statistics about how you use Orange?\n"
218+
"All data is anonymized and used "
219+
"exclusively for understanding how users "
220+
"interact with Orange.",
221+
standardButtons=permDialogButtons)
222+
btnOK = permDialog.button(NotificationWidget.AcceptRole)
223+
btnOK.setText("Allow")
224+
225+
def handle_permission_response(button):
226+
if permDialog.buttonRole(button) != permDialog.DismissRole:
227+
settings["error-reporting/permission-requested"] = True
228+
if permDialog.buttonRole(button) == permDialog.AcceptRole:
229+
settings["error-reporting/send-statistics"] = True
230+
231+
permDialog.clicked.connect(handle_permission_response)
232+
233+
NotificationOverlay.registerNotification(permDialog)
209234

210235

211236
def check_for_updates():
@@ -249,31 +274,39 @@ def ua_string():
249274

250275
def compare_versions(latest):
251276
version = pkg_resources.parse_version
252-
if version(latest) <= version(current):
277+
skipped = settings.value('startup/latest-skipped-version', "", type=str)
278+
if version(latest) <= version(current) or \
279+
latest == skipped:
253280
return
254-
question = QMessageBox(
255-
QMessageBox.Information,
256-
'Orange Update Available',
257-
'A newer version of Orange is available.<br><br>'
258-
'<b>Current version:</b> {}<br>'
259-
'<b>Latest version:</b> {}'.format(current, latest),
260-
textFormat=Qt.RichText)
261-
ok = question.addButton('Download Now', question.AcceptRole)
262-
question.setDefaultButton(ok)
263-
question.addButton('Remind Later', question.RejectRole)
264-
question.finished.connect(
265-
lambda:
266-
question.clickedButton() == ok and
267-
QDesktopServices.openUrl(QUrl("https://orange.biolab.si/download/")))
268-
question.show()
281+
282+
questionButtons = NotificationWidget.Ok | NotificationWidget.Close
283+
question = NotificationWidget(icon=QIcon(gui.resource_filename('icons/Dlg_down3.png')),
284+
title='Orange Update Available',
285+
text='Current version: <b>{}</b><br>'
286+
'Latest version: <b>{}</b>'.format(current, latest),
287+
textFormat=Qt.RichText,
288+
standardButtons=questionButtons,
289+
acceptLabel="Download",
290+
rejectLabel="Skip this Version")
291+
292+
def handle_click(b):
293+
if question.buttonRole(b) != question.DismissRole:
294+
settings.setValue('startup/latest-skipped-version', latest)
295+
if question.buttonRole(b) == question.AcceptRole:
296+
QDesktopServices.openUrl(QUrl("https://orange.biolab.si/download/"))
297+
298+
question.clicked.connect(handle_click)
299+
300+
NotificationOverlay.registerNotification(question)
269301

270302
thread = GetLatestVersion()
271303
thread.resultReady.connect(compare_versions)
272304
thread.start()
273305
return thread
306+
return None
274307

275-
def send_usage_statistics():
276308

309+
def send_usage_statistics():
277310
class SendUsageStatistics(QThread):
278311
def run(self):
279312
try:
@@ -287,6 +320,7 @@ def run(self):
287320
thread.start()
288321
return thread
289322

323+
290324
def main(argv=None):
291325
if argv is None:
292326
argv = sys.argv
@@ -433,6 +467,8 @@ def onrequest(url):
433467

434468
settings = QSettings()
435469

470+
settings.setValue('startup/launch-count', settings.value('startup/launch-count', 0, int) + 1)
471+
436472
stylesheet = options.stylesheet or defaultstylesheet
437473
stylesheet_string = None
438474

@@ -557,8 +593,10 @@ def show_message(message):
557593
open_requests[-1])
558594
canvas_window.load_scheme(open_requests[-1].toLocalFile())
559595

596+
# initialize notifications
597+
setup_notifications()
598+
560599
# local references prevent destruction
561-
survey = show_survey()
562600
update_check = check_for_updates()
563601
send_stat = send_usage_statistics()
564602

@@ -591,7 +629,6 @@ def show_message(message):
591629
status = 42
592630

593631
del canvas_window
594-
del survey
595632
del update_check
596633
del send_stat
597634

Orange/canvas/application/canvasmain.py

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ def user_documents_path():
5353
return QDesktopServices.storageLocation(
5454
QDesktopServices.DocumentsLocation)
5555

56-
from ...widgets.utils.overlay import MessageOverlayWidget
57-
5856
from ..gui.dropshadow import DropShadowFrame
5957
from ..gui.dock import CollapsibleDockWidget
6058
from ..gui.quickhelp import QuickHelpTipEvent
6159
from ..gui.utils import message_critical, message_question, \
6260
message_warning, message_information
6361

62+
from ..utils.overlay import NotificationOverlay
63+
6464
from ..document.usagestatistics import UsageStatistics
6565

6666
from ..help import HelpManager
@@ -384,42 +384,7 @@ def touch():
384384

385385
self.setMinimumSize(600, 500)
386386

387-
# ask for anonymous data collection permission
388-
def requestDataCollectionPermission():
389-
permDialogButtons = MessageOverlayWidget.AcceptRole | MessageOverlayWidget.RejectRole
390-
permDialog = MessageOverlayWidget(parent=w,
391-
text="Do you wish to share anonymous usage "
392-
"statistics to help improve Orange?",
393-
wordWrap=True,
394-
standardButtons=permDialogButtons)
395-
btnOK = permDialog.button(MessageOverlayWidget.AcceptRole)
396-
btnOK.setText("Allow")
397-
398-
def respondToRequest():
399-
settings["error-reporting/permission-requested"] = True
400-
401-
def shareData():
402-
settings["error-reporting/send-statistics"] = True
403-
404-
permDialog.clicked.connect(respondToRequest)
405-
permDialog.accepted.connect(shareData)
406-
407-
permDialog.setStyleSheet("""
408-
MessageOverlayWidget {
409-
background: qlineargradient(
410-
x1: 0, y1: 0, x2: 0, y2: 1,
411-
stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666)
412-
}
413-
MessageOverlayWidget QLabel#text-label {
414-
color: white;
415-
}""")
416-
417-
permDialog.setWidget(w)
418-
permDialog.show()
419-
420-
settings = config.settings()
421-
if not settings["error-reporting/permission-requested"]:
422-
requestDataCollectionPermission()
387+
self.notification_overlay = NotificationOverlay(self.scheme_widget)
423388

424389
def setup_actions(self):
425390
"""Initialize main window actions.
@@ -1912,6 +1877,7 @@ def closeEvent(self, event):
19121877
settings.endGroup()
19131878
self.help_dock.close()
19141879
self.log_dock.close()
1880+
self.notification_overlay.close()
19151881
super().closeEvent(event)
19161882

19171883
__did_restore = False

Orange/canvas/config.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ def init():
5656
("startup/check-updates", bool, True,
5757
"Check for updates"),
5858

59+
("startup/launch-count", int, 0,
60+
""),
61+
62+
("startup/show-short-survey", bool, True,
63+
"Has the user not been asked to take a short survey yet"),
64+
5965
("stylesheet", str, "orange",
6066
"QSS stylesheet to use"),
6167

@@ -111,11 +117,14 @@ def init():
111117
("quickmenu/trigger-on-any-key", bool, False,
112118
"Show quick menu on double click."),
113119

114-
("logging/level", int, 1, "Logging level"),
120+
("logging/level", int, 1,
121+
"Logging level"),
115122

116-
("logging/show-on-error", bool, True, "Show log window on error"),
123+
("logging/show-on-error", bool, True,
124+
"Show log window on error"),
117125

118-
("logging/dockable", bool, True, "Allow log window to be docked"),
126+
("logging/dockable", bool, True,
127+
"Allow log window to be docked"),
119128

120129
("help/open-in-external-browser", bool, False,
121130
"Open help in an external browser"),
@@ -135,9 +144,11 @@ def init():
135144
("add-ons/pip-install-arguments", str, '',
136145
'Arguments to pass to "pip install" when installing add-ons.'),
137146

138-
("network/http-proxy", str, '', 'HTTP proxy.'),
147+
("network/http-proxy", str, '',
148+
'HTTP proxy.'),
139149

140-
("network/https-proxy", str, '', 'HTTPS proxy.'),
150+
("network/https-proxy", str, '',
151+
'HTTPS proxy.'),
141152
]
142153

143154
spec = [config_slot(*t) for t in spec]

Orange/canvas/gui/stackedwidget.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,25 @@ def maximumSize(self):
8686
else:
8787
return QStackedLayout.maximumSize(self)
8888

89+
def hasHeightForWidth(self) -> bool:
90+
current = self.currentWidget()
91+
if current is not None:
92+
return current.hasHeightForWidth()
93+
else:
94+
return False
95+
96+
def heightForWidth(self, width: int) -> int:
97+
current = self.currentWidget()
98+
if current is not None:
99+
return current.heightForWidth(width)
100+
else:
101+
return -1
102+
89103
def geometry(self):
90104
# Reimplemented due to QTBUG-47107.
91105
return QRect(self.__rect)
92106

93-
def setGeometry(self, rect):
94-
# type: (QRect) -> None
107+
def setGeometry(self, rect: QRect) -> None:
95108
if rect == self.__rect:
96109
return
97110
self.__rect = QRect(rect)

0 commit comments

Comments
 (0)