Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] Replace popups with non-intrusive notifications #3855

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 80 additions & 43 deletions Orange/canvas/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@

import pkg_resources

from AnyQt.QtGui import QFont, QColor, QPalette, QDesktopServices
from AnyQt.QtWidgets import QMessageBox
from AnyQt.QtGui import QFont, QColor, QPalette, QDesktopServices, QIcon
from AnyQt.QtCore import (
Qt, QDir, QUrl, QSettings, QThread, pyqtSignal, QT_VERSION
)
Expand All @@ -39,6 +38,8 @@
from Orange.canvas.registry import qt
from Orange.canvas.registry import WidgetRegistry, set_global_registry
from Orange.canvas.registry import cache
from Orange.canvas.utils.overlay import NotificationWidget, NotificationOverlay
from Orange.widgets import gui

log = logging.getLogger(__name__)

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


def show_survey():
# If run for the first time, open a browser tab with a survey
settings = QSettings()
show_survey = settings.value("startup/show-survey", True, type=bool)
def setup_notifications():
settings = config.settings()

# If run for the fifth time, prompt short survey
show_survey = settings["startup/show-short-survey"] and \
settings["startup/launch-count"] >= 5
if show_survey:
question = QMessageBox(
QMessageBox.Question,
'Orange Survey',
'We would like to know more about how our software is used.\n\n'
'Would you care to fill our short 1-minute survey?',
QMessageBox.Yes | QMessageBox.No)
question.setDefaultButton(QMessageBox.Yes)
later = question.addButton('Ask again later', QMessageBox.NoRole)
question.setEscapeButton(later)

def handle_response(result):
if result == QMessageBox.Yes:
surveyDialogButtons = NotificationWidget.Ok | NotificationWidget.Close
surveyDialog = NotificationWidget(icon=QIcon(gui.resource_filename("icons/information.png")),
title="Survey",
text="We want to understand our users better.\n"
"Would you like to take a short survey?",
standardButtons=surveyDialogButtons)

def handle_survey_response(button):
if surveyDialog.buttonRole(button) == NotificationWidget.AcceptRole:
success = QDesktopServices.openUrl(
QUrl("https://orange.biolab.si/survey/short.html"))
settings.setValue("startup/show-survey", not success)
else:
settings.setValue("startup/show-survey", result != QMessageBox.No)
settings["startup/show-short-survey"] = not success
elif surveyDialog.buttonRole(button) == NotificationWidget.RejectRole:
settings["startup/show-short-survey"] = False

surveyDialog.clicked.connect(handle_survey_response)

NotificationOverlay.registerNotification(surveyDialog)

question.finished.connect(handle_response)
question.show()
return question
# data collection permission
if not settings["error-reporting/permission-requested"]:
permDialogButtons = NotificationWidget.Ok | NotificationWidget.Close
permDialog = NotificationWidget(icon=QIcon(gui.resource_filename(
"../../distribute/icon-48.png")),
title="Anonymous Usage Statistics",
text="Do you wish to opt-in to sharing "
"statistics about how you use Orange?\n"
"All data is anonymized and used "
"exclusively for understanding how users "
"interact with Orange.",
standardButtons=permDialogButtons)
btnOK = permDialog.button(NotificationWidget.AcceptRole)
btnOK.setText("Allow")

def handle_permission_response(button):
if permDialog.buttonRole(button) != permDialog.DismissRole:
settings["error-reporting/permission-requested"] = True
if permDialog.buttonRole(button) == permDialog.AcceptRole:
settings["error-reporting/send-statistics"] = True

permDialog.clicked.connect(handle_permission_response)

NotificationOverlay.registerNotification(permDialog)


def check_for_updates():
Expand Down Expand Up @@ -249,31 +274,39 @@ def ua_string():

def compare_versions(latest):
version = pkg_resources.parse_version
if version(latest) <= version(current):
skipped = settings.value('startup/latest-skipped-version', "", type=str)
if version(latest) <= version(current) or \
latest == skipped:
return
question = QMessageBox(
QMessageBox.Information,
'Orange Update Available',
'A newer version of Orange is available.<br><br>'
'<b>Current version:</b> {}<br>'
'<b>Latest version:</b> {}'.format(current, latest),
textFormat=Qt.RichText)
ok = question.addButton('Download Now', question.AcceptRole)
question.setDefaultButton(ok)
question.addButton('Remind Later', question.RejectRole)
question.finished.connect(
lambda:
question.clickedButton() == ok and
QDesktopServices.openUrl(QUrl("https://orange.biolab.si/download/")))
question.show()

questionButtons = NotificationWidget.Ok | NotificationWidget.Close
question = NotificationWidget(icon=QIcon(gui.resource_filename('icons/Dlg_down3.png')),
title='Orange Update Available',
text='Current version: <b>{}</b><br>'
'Latest version: <b>{}</b>'.format(current, latest),
textFormat=Qt.RichText,
standardButtons=questionButtons,
acceptLabel="Download",
rejectLabel="Skip this Version")

def handle_click(b):
if question.buttonRole(b) != question.DismissRole:
settings.setValue('startup/latest-skipped-version', latest)
if question.buttonRole(b) == question.AcceptRole:
QDesktopServices.openUrl(QUrl("https://orange.biolab.si/download/"))

question.clicked.connect(handle_click)

NotificationOverlay.registerNotification(question)

thread = GetLatestVersion()
thread.resultReady.connect(compare_versions)
thread.start()
return thread
return None

def send_usage_statistics():

def send_usage_statistics():
class SendUsageStatistics(QThread):
def run(self):
try:
Expand All @@ -287,6 +320,7 @@ def run(self):
thread.start()
return thread


def main(argv=None):
if argv is None:
argv = sys.argv
Expand Down Expand Up @@ -433,6 +467,8 @@ def onrequest(url):

settings = QSettings()

settings.setValue('startup/launch-count', settings.value('startup/launch-count', 0, int) + 1)

stylesheet = options.stylesheet or defaultstylesheet
stylesheet_string = None

Expand Down Expand Up @@ -557,8 +593,10 @@ def show_message(message):
open_requests[-1])
canvas_window.load_scheme(open_requests[-1].toLocalFile())

# initialize notifications
setup_notifications()

# local references prevent destruction
survey = show_survey()
update_check = check_for_updates()
send_stat = send_usage_statistics()

Expand Down Expand Up @@ -591,7 +629,6 @@ def show_message(message):
status = 42

del canvas_window
del survey
del update_check
del send_stat

Expand Down
42 changes: 4 additions & 38 deletions Orange/canvas/application/canvasmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ def user_documents_path():
return QDesktopServices.storageLocation(
QDesktopServices.DocumentsLocation)

from ...widgets.utils.overlay import MessageOverlayWidget

from ..gui.dropshadow import DropShadowFrame
from ..gui.dock import CollapsibleDockWidget
from ..gui.quickhelp import QuickHelpTipEvent
from ..gui.utils import message_critical, message_question, \
message_warning, message_information

from ..utils.overlay import NotificationOverlay

from ..document.usagestatistics import UsageStatistics

from ..help import HelpManager
Expand Down Expand Up @@ -384,42 +384,7 @@ def touch():

self.setMinimumSize(600, 500)

# ask for anonymous data collection permission
def requestDataCollectionPermission():
permDialogButtons = MessageOverlayWidget.AcceptRole | MessageOverlayWidget.RejectRole
permDialog = MessageOverlayWidget(parent=w,
text="Do you wish to share anonymous usage "
"statistics to help improve Orange?",
wordWrap=True,
standardButtons=permDialogButtons)
btnOK = permDialog.button(MessageOverlayWidget.AcceptRole)
btnOK.setText("Allow")

def respondToRequest():
settings["error-reporting/permission-requested"] = True

def shareData():
settings["error-reporting/send-statistics"] = True

permDialog.clicked.connect(respondToRequest)
permDialog.accepted.connect(shareData)

permDialog.setStyleSheet("""
MessageOverlayWidget {
background: qlineargradient(
x1: 0, y1: 0, x2: 0, y2: 1,
stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666)
}
MessageOverlayWidget QLabel#text-label {
color: white;
}""")

permDialog.setWidget(w)
permDialog.show()

settings = config.settings()
if not settings["error-reporting/permission-requested"]:
requestDataCollectionPermission()
self.notification_overlay = NotificationOverlay(self.scheme_widget)

def setup_actions(self):
"""Initialize main window actions.
Expand Down Expand Up @@ -1912,6 +1877,7 @@ def closeEvent(self, event):
settings.endGroup()
self.help_dock.close()
self.log_dock.close()
self.notification_overlay.close()
super().closeEvent(event)

__did_restore = False
Expand Down
21 changes: 16 additions & 5 deletions Orange/canvas/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ def init():
("startup/check-updates", bool, True,
"Check for updates"),

("startup/launch-count", int, 0,
""),

("startup/show-short-survey", bool, True,
"Has the user not been asked to take a short survey yet"),

("stylesheet", str, "orange",
"QSS stylesheet to use"),

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

("logging/level", int, 1, "Logging level"),
("logging/level", int, 1,
"Logging level"),

("logging/show-on-error", bool, True, "Show log window on error"),
("logging/show-on-error", bool, True,
"Show log window on error"),

("logging/dockable", bool, True, "Allow log window to be docked"),
("logging/dockable", bool, True,
"Allow log window to be docked"),

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

("network/http-proxy", str, '', 'HTTP proxy.'),
("network/http-proxy", str, '',
'HTTP proxy.'),

("network/https-proxy", str, '', 'HTTPS proxy.'),
("network/https-proxy", str, '',
'HTTPS proxy.'),
]

spec = [config_slot(*t) for t in spec]
Expand Down
17 changes: 15 additions & 2 deletions Orange/canvas/gui/stackedwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,25 @@ def maximumSize(self):
else:
return QStackedLayout.maximumSize(self)

def hasHeightForWidth(self) -> bool:
current = self.currentWidget()
if current is not None:
return current.hasHeightForWidth()
else:
return False

def heightForWidth(self, width: int) -> int:
current = self.currentWidget()
if current is not None:
return current.heightForWidth(width)
else:
return -1

def geometry(self):
# Reimplemented due to QTBUG-47107.
return QRect(self.__rect)

def setGeometry(self, rect):
# type: (QRect) -> None
def setGeometry(self, rect: QRect) -> None:
if rect == self.__rect:
return
self.__rect = QRect(rect)
Expand Down
Loading