def save_plot(data, file_formats, filename=""): _LAST_DIR_KEY = "directories/last_graph_directory" _LAST_FILTER_KEY = "directories/last_graph_filter" settings = QSettings() start_dir = settings.value(_LAST_DIR_KEY, filename) if not start_dir or \ (not os.path.exists(start_dir) and not os.path.exists(os.path.split(start_dir)[0])): start_dir = os.path.expanduser("~") last_filter = settings.value(_LAST_FILTER_KEY, "") filename, writer, filter = \ filedialogs.open_filename_dialog_save(start_dir, last_filter, file_formats) if not filename: return try: writer.write(filename, data) except OSError as e: mb = QMessageBox( None, windowTitle="Error", text='Error occurred while saving file "{}": {}'.format(filename, e), detailedText=traceback.format_exc(), icon=QMessageBox.Critical) mb.exec_() else: settings.setValue(_LAST_DIR_KEY, os.path.split(filename)[0]) settings.setValue(_LAST_FILTER_KEY, filter)
def setup_notifications(): settings = QSettings() # data collection permission if not settings.value("error-reporting/permission-requested", False, type=bool) and \ not settings.value("error-reporting/send-statistics", False, bool): notif = Notification(icon=QIcon( resource_filename("canvas/icons/statistics-request.png")), title="Anonymous Usage Statistics", text="Do you wish to opt-in to sharing " "statistics about how you use Orange? " "All data is anonymized and used " "exclusively for understanding how users " "interact with Orange.", accept_button_label="Allow", reject_button_label="No") def handle_permission_response(role): if role != notif.DismissRole: settings.setValue("error-reporting/permission-requested", True) if role == notif.AcceptRole: UsageStatistics.set_enabled(True) settings.setValue("error-reporting/send-statistics", True) notif.clicked.connect(handle_permission_response) canvas.notification_server_instance.registerNotification(notif)
def save_plot(data, file_formats, filename=""): _LAST_DIR_KEY = "directories/last_graph_directory" _LAST_FILTER_KEY = "directories/last_graph_filter" settings = QSettings() start_dir = settings.value(_LAST_DIR_KEY, filename) if not start_dir or \ (not os.path.exists(start_dir) and not os.path.exists(os.path.split(start_dir)[0])): start_dir = os.path.expanduser("~") last_filter = settings.value(_LAST_FILTER_KEY, "") filename, writer, filter = \ filedialogs.get_file_name(start_dir, last_filter, file_formats) if not filename: return try: writer.write(filename, data) except Exception as e: mb = QMessageBox( None, windowTitle="Error", text='Error occurred while saving file "{}": {}'.format(filename, e), detailedText=traceback.format_exc(), icon=QMessageBox.Critical) mb.exec_() else: settings.setValue(_LAST_DIR_KEY, os.path.split(filename)[0]) settings.setValue(_LAST_FILTER_KEY, filter)
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) class GetLatestVersion(QThread): resultReady = pyqtSignal(str) def run(self): try: request = Request('https://orange.biolab.si/version/', headers={ 'Accept': 'text/plain', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'User-Agent': ua_string() }) contents = urlopen(request, timeout=10).read().decode() # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to check for updates') else: self.resultReady.emit(contents) def compare_versions(latest): version = pkg_resources.parse_version skipped = settings.value('startup/latest-skipped-version', "", type=str) if version(latest) <= version(current) or \ latest == skipped: return notif = Notification( title='橙现智能有更新版本', text='菜单栏点击: 选项 -> 升级与插件, 选中 orange3-zh 升级', icon=QIcon(resource_filename("canvas/icons/update.png"))) def handle_click(role): if role == notif.RejectRole: settings.setValue('startup/latest-skipped-version', latest) if role == notif.AcceptRole: QDesktopServices.openUrl( QUrl("https://orange.biolab.si/download/")) notif.clicked.connect(handle_click) canvas.notification_server_instance.registerNotification(notif) thread = GetLatestVersion() thread.resultReady.connect(compare_versions) thread.start() return thread return None
def setup_notifications(): settings = QSettings() # If run for the fifth time, prompt short survey show_survey = settings.value("startup/show-short-survey", True, type=bool) and \ settings.value("startup/launch-count", 0, type=int) >= 5 if show_survey: 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-short-survey", not success) elif surveyDialog.buttonRole( button) == NotificationWidget.RejectRole: settings.setValue("startup/show-short-survey", False) surveyDialog.clicked.connect(handle_survey_response) NotificationOverlay.registerNotification(surveyDialog) # data collection permission if not settings.value( "error-reporting/permission-requested", False, type=bool): 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.setValue("error-reporting/permission-requested", True) if permDialog.buttonRole(button) == permDialog.AcceptRole: UsageStatistics.set_enabled(True) settings.setValue("error-reporting/send-statistics", True) permDialog.clicked.connect(handle_permission_response) NotificationOverlay.registerNotification(permDialog)
def ua_string(): settings = QSettings() is_anaconda = 'Continuum' in sys.version or 'conda' in sys.version machine_id = settings.value("reporting/machine-id", "", str) return 'Orange{orange_version}:Python{py_version}:{platform}:{conda}:{uuid}'.format( orange_version=current, py_version='.'.join(str(a) for a in sys.version_info[:3]), platform=sys.platform, conda='Anaconda' if is_anaconda else '', uuid=machine_id if settings.value("reporting/send-statistics", False, bool) else '' )
def test_qsettings(self): s = QSettings() s.setValue("one", 1) s.setValue("one-half", 0.5) s.setValue("empty", "") s.setValue("true", True) s.sync() del s s = QSettings() self.assertEqual(s.value("one", type=int), 1) self.assertEqual(s.value("one-half", type=float), 0.5) self.assertEqual(s.value("empty", type=str), "") self.assertEqual(s.value("true", type=bool), True)
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) from urllib.request import urlopen from Orange.version import version as current class GetLatestVersion(QThread): resultReady = pyqtSignal(str) def run(self): try: self.resultReady.emit( urlopen('https://orange.biolab.si/version/', timeout=10).read().decode()) # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to check for updates') def compare_versions(latest): version = pkg_resources.parse_version if version(latest) <= version(current): 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() thread = GetLatestVersion() thread.resultReady.connect(compare_versions) thread.start() return thread
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) thread = GetLatestVersion() thread.resultReady.connect(compare_versions) thread.start() return thread
def open_report(self): """ Present an 'Open report' dialog to the user, load a '.report' file (as saved by OWReport) and create a new canvas window associated with the OWReport instance. """ settings = QSettings() KEY = "report/file-dialog-dir" start_dir = settings.value(KEY, "", type=str) dlg = QFileDialog( self, windowTitle=self.tr("Open Report"), acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile, ) if os.path.isdir(start_dir): dlg.setDirectory(start_dir) dlg.setWindowModality(Qt.ApplicationModal) dlg.setNameFilters(["Report (*.report)"]) def accepted(): directory = dlg.directory().absolutePath() filename = dlg.selectedFiles()[0] settings.setValue(KEY, directory) self._open_report(filename) dlg.accepted.connect(accepted) dlg.exec()
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) 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: 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) question.finished.connect(handle_response) question.show() return question
def send_statistics(url): """Send the statistics to the remote at `url`""" import json import requests settings = QSettings() if not settings.value( "error-reporting/send-statistics", False, type=bool): log.info("Not sending usage statistics (preferences setting).") return if not UsageStatistics.is_enabled(): log.info("Not sending usage statistics (disabled).") return usage = UsageStatistics() data = usage.load() for d in data: d["Orange Version"] = d.pop("Application Version", "") try: r = requests.post(url, files={'file': json.dumps(data)}) if r.status_code != 200: log.warning( "Error communicating with server while attempting to send " "usage statistics.") return # success - wipe statistics file log.info("Usage statistics sent.") with open(usage.filename(), 'w', encoding="utf-8") as f: json.dump([], f) except (ConnectionError, requests.exceptions.RequestException): log.warning( "Connection error while attempting to send usage statistics.") except Exception: # pylint: disable=broad-except log.warning("Failed to send usage statistics.", exc_info=True)
def fix_set_proxy_env(): """ Set http_proxy/https_proxy environment variables (for requests, pip, ...) from user-specified settings or, if none, from system settings on OS X and from registry on Windos. """ # save default proxies so that setting can be reset global default_proxies if default_proxies is None: default_proxies = getproxies( ) # can also read windows and macos settings settings = QSettings() proxies = getproxies() for scheme in set(["http", "https"]) | set(proxies): from_settings = settings.value("network/" + scheme + "-proxy", "", type=str) from_default = default_proxies.get(scheme, "") env_scheme = scheme + '_proxy' if from_settings: os.environ[env_scheme] = from_settings elif from_default: os.environ[ env_scheme] = from_default # crucial for windows/macos support else: os.environ.pop(env_scheme, "")
def test_qsettings_type(self): """ Test if QSettings as exported by qtcompat has the 'type' parameter. """ with tempfile.NamedTemporaryFile("w+b", suffix=".ini", delete=False) as f: settings = QSettings(f.name, QSettings.IniFormat) settings.setValue("bar", "foo") self.assertEqual(settings.value("bar", type=str), "foo") settings.setValue("frob", 4) del settings settings = QSettings(f.name, QSettings.IniFormat) self.assertEqual(settings.value("bar", type=str), "foo") self.assertEqual(settings.value("frob", type=int), 4)
def setup_application(self): super().setup_application() clear_settings_flag = os.path.join(widget_settings_dir(), "DELETE_ON_START") # NOTE: No OWWidgetBase subclass should be imported before this options = self.options if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): self.clear_widget_settings() if options.clear_all: self.clear_widget_settings() self.clear_caches() self.clear_application_settings() notif_server = NotificationServer() canvas.notification_server_instance = notif_server self._update_check = check_for_updates() self._send_stat = send_usage_statistics() self._pull_notifs = pull_notifications() settings = QSettings() settings.setValue('startup/launch-count', settings.value('startup/launch-count', 0, int) + 1) if settings.value("reporting/send-statistics", False, type=bool) \ and is_release: UsageStatistics.set_enabled(True) app = self.application # set pyqtgraph colors def onPaletteChange(): p = app.palette() bg = p.base().color().name() fg = p.windowText().color().name() log.info('Setting pyqtgraph background to %s', bg) pyqtgraph.setConfigOption('background', bg) log.info('Setting pyqtgraph foreground to %s', fg) pyqtgraph.setConfigOption('foreground', fg) app.setProperty('darkMode', p.color(QPalette.Base).value() < 127) app.paletteChanged.connect(onPaletteChange) onPaletteChange()
def send_statistics(url): """Send the statistics to the remote at `url`""" import json import requests settings = QSettings() if not settings.value("reporting/send-statistics", False, type=bool): log.info("Not sending usage statistics (preferences setting).") return if not UsageStatistics.is_enabled(): log.info("Not sending usage statistics (disabled).") return if settings.contains('reporting/machine-id'): machine_id = settings.value('reporting/machine-id') else: machine_id = str(uuid.uuid4()) settings.setValue('reporting/machine-id', machine_id) is_anaconda = 'Continuum' in sys.version or 'conda' in sys.version data = UsageStatistics.load() for d in data: d["Orange Version"] = d.pop("Application Version", "") d["Anaconda"] = is_anaconda d["UUID"] = machine_id try: r = requests.post(url, files={'file': json.dumps(data)}) if r.status_code != 200: log.warning( "Error communicating with server while attempting to send " "usage statistics. Status code " + str(r.status_code)) return # success - wipe statistics file log.info("Usage statistics sent.") with open(UsageStatistics.filename(), 'w', encoding="utf-8") as f: json.dump([], f) except (ConnectionError, requests.exceptions.RequestException): log.warning( "Connection error while attempting to send usage statistics.") except Exception: # pylint: disable=broad-except log.warning("Failed to send usage statistics.", exc_info=True)
def save_plot(data, file_formats, filename=""): _LAST_DIR_KEY = "directories/last_graph_directory" _LAST_FILTER_KEY = "directories/last_graph_filter" settings = QSettings() start_dir = settings.value(_LAST_DIR_KEY, filename) if not start_dir or \ (not os.path.exists(start_dir) and not os.path.exists(os.path.split(start_dir)[0])): start_dir = os.path.expanduser("~") last_filter = settings.value(_LAST_FILTER_KEY, "") filename, writer, filter = \ filedialogs.get_file_name(start_dir, last_filter, file_formats) if not filename: return try: writer.write(filename, data) except Exception as e: QMessageBox.critical( None, "Error", 'Error occurred while saving file "{}": {}'.format(filename, e)) else: settings.setValue(_LAST_DIR_KEY, os.path.split(filename)[0]) settings.setValue(_LAST_FILTER_KEY, filter)
def configureStyle(): from orangecanvas import styles args = CanvasApplication.__args settings = QSettings() settings.beginGroup("application-style") name = settings.value("style-name", "", type=str) if args is not None and args.style: # command line params take precedence name = args.style if name != "": inst = QApplication.instance() if inst is not None: if inst.style().objectName().lower() != name.lower(): QApplication.setStyle(name) theme = settings.value("palette", "", type=str) if args is not None and args.colortheme: theme = args.colortheme if theme and theme in styles.colorthemes: palette = styles.colorthemes[theme]() QApplication.setPalette(palette)
def max_active(self) -> int: value = self.__max_running # type: Optional[int] if value is None: value = mapping_get(os.environ, "MAX_ACTIVE_NODES", int, None) if value is None: s = QSettings() s.beginGroup(__name__) value = s.value("max-active-nodes", defaultValue=1, type=int) if value < 0: ccount = os.cpu_count() if ccount is None: return 1 else: return max(1, ccount + value) else: return max(1, value)
def show_welcome_screen(self, parent: CanvasMainWindow): """Show the initial welcome screen.""" settings = QSettings() options = self.options want_welcome = settings.value("startup/show-welcome-screen", True, type=bool) and not options.no_welcome def trigger(): if not parent.is_transient(): return swp_loaded = parent.ask_load_swp_if_exists() if not swp_loaded and want_welcome: parent.welcome_action.trigger() # On a timer to allow FileOpen events to be delivered. If so # then do not show the welcome screen. QTimer.singleShot(0, trigger)
def _networkAccessManagerInstance(cls): netmanager = cls._NETMANAGER_REF and cls._NETMANAGER_REF() settings = QSettings() settings.beginGroup(__name__) cache_dir = os.path.join(config.cache_dir(), "help", __name__) cache_size = settings.value("cache_size_mb", defaultValue=50, type=int) if netmanager is None: try: os.makedirs(cache_dir, exist_ok=True) except OSError: pass netmanager = QNetworkAccessManager() cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 2**20) netmanager.setCache(cache) cls._NETMANAGER_REF = ref(netmanager) return netmanager
def list_selected_encodings(): # type: () -> List[str] """ Return a list of all current selected encodings from user preferences. """ settings = QSettings() settings.beginGroup(SettingsGroup) res = [] for encoding, _ in ENCODING_DISPLAY_NAME: try: co = codecs.lookup(encoding) except LookupError: continue selected = settings.value(co.name, defaultValue=co.name in DEFAULT_ENCODINGS, type=bool) if selected: res.append(co.name) return res
def fix_set_proxy_env(): """ Set http_proxy/https_proxy environment variables (for requests, pip, ...) from user-specified settings or, if none, from system settings on OS X and from registry on Windos. """ # save default proxies so that setting can be reset global default_proxies if default_proxies is None: default_proxies = getproxies() # can also read windows and macos settings settings = QSettings() proxies = getproxies() for scheme in set(["http", "https"]) | set(proxies): from_settings = settings.value("network/" + scheme + "-proxy", "", type=str) from_default = default_proxies.get(scheme, "") env_scheme = scheme + '_proxy' if from_settings: os.environ[env_scheme] = from_settings elif from_default: os.environ[env_scheme] = from_default # crucial for windows/macos support else: os.environ.pop(env_scheme, "")
def splash_screen(self) -> SplashScreen: """Return the application splash screen""" if self.__splash_screen is not None: return self.__splash_screen[0] settings = QSettings() options = self.options want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = self.config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setAttribute(Qt.WA_DeleteOnClose) splash_screen.setFont(QFont("Helvetica", 12)) palette = splash_screen.palette() color = QColor("#FFD39F") palette.setColor(QPalette.Text, color) splash_screen.setPalette(palette) else: splash_screen = None self.__splash_screen = (splash_screen, ) return splash_screen
def pull_notifications(): settings = QSettings() check_notifs = settings.value("notifications/check-notifications", True, bool) if not check_notifs: return None Version = pkg_resources.parse_version # create settings_dict for notif requirements purposes (read-only) spec = canvasconfig.spec + config.spec settings_dict = canvasconfig.Settings(defaults=spec, store=settings) # map of installed addon name -> version installed_list = [ ep.dist for ep in config.addon_entry_points() if ep.dist is not None ] installed = defaultdict(lambda: "-1") for addon in installed_list: installed[addon.project_name] = addon.version # get set of already displayed notification IDs, stored in settings["notifications/displayed"] displayedIDs = literal_eval( settings.value("notifications/displayed", "set()", str)) # get notification feed from Github class GetNotifFeed(QThread): resultReady = pyqtSignal(str) def run(self): try: request = Request('https://orange.biolab.si/notification-feed', headers={ 'Accept': 'text/plain', 'Connection': 'close', 'User-Agent': ua_string(), 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }) contents = urlopen(request, timeout=10).read().decode() # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to pull notification feed') else: self.resultReady.emit(contents) thread = GetNotifFeed() def parse_yaml_notification(YAMLnotif: YAMLNotification): # check if notification has been displayed and responded to previously if YAMLnotif.id and YAMLnotif.id in displayedIDs: return # check if type is filtered by user allowAnnouncements = settings.value('notifications/announcements', True, bool) allowBlog = settings.value('notifications/blog', True, bool) allowNewFeatures = settings.value('notifications/new-features', True, bool) if YAMLnotif.type and \ (YAMLnotif.type == 'announcement' and not allowAnnouncements or YAMLnotif.type == 'blog' and not allowBlog or YAMLnotif.type == 'new-features' and not allowNewFeatures): return # check time validity today = date.today() if (YAMLnotif.start and YAMLnotif.start > today) or \ (YAMLnotif.end and YAMLnotif.end < today): return # check requirements reqs = YAMLnotif.requirements # Orange/addons version if reqs and 'installed' in reqs and \ not requirementsSatisfied(reqs['installed'], installed, req_type=Version): return # local config values if reqs and 'local_config' in reqs and \ not requirementsSatisfied(reqs['local_config'], settings_dict): return # if no custom icon is set, default to notif type icon if YAMLnotif.icon is None and YAMLnotif.type is not None: YAMLnotif.icon = "canvas/icons/" + YAMLnotif.type + ".png" # instantiate and return Notification notif = YAMLnotif.toNotification() # connect link to notification notif.accepted.connect(lambda: open_link(QUrl(YAMLnotif.link))) # remember notification id def remember_notification(role): # if notification was accepted or rejected, write its ID to preferences if role == notif.DismissRole or YAMLnotif.id is None: return displayedIDs.add(YAMLnotif.id) settings.setValue("notifications/displayed", repr(displayedIDs)) notif.clicked.connect(remember_notification) # display notification canvas.notification_server_instance.registerNotification(notif) def setup_notification_feed(feed_str): feed = yaml.safe_load(feed_str) for YAMLnotif in feed: parse_yaml_notification(YAMLnotif) thread.resultReady.connect(setup_notification_feed) thread.start() return thread
def main(argv=None): if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--clear-widget-settings", action="store_true", help="Remove stored widget setting") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default=None) parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) (options, args) = parser.parse_args(argv[1:]) levels = [logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Try to fix fonts on OSX Mavericks fix_osx_10_9_private_font() # File handler should always be at least INFO level so we need # the application root level to be at least at INFO. root_level = min(levels[options.log_level], logging.INFO) rootlogger = logging.getLogger(canvas.__name__) rootlogger.setLevel(root_level) # Initialize SQL query and execution time logger (in SqlTable) sql_level = min(levels[options.log_level], logging.INFO) make_sql_logger(sql_level) # Standard output stream handler at the requested level stream_hander = logging.StreamHandler() stream_hander.setLevel(level=levels[options.log_level]) rootlogger.addHandler(stream_hander) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] style = options.style defaultstylesheet = "orange.qss" fusiontheme = None if style is not None: if style.startswith("fusion:"): qt_argv += ["-style", "fusion"] _, _, fusiontheme = style.partition(":") else: qt_argv += ["-style", style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args if QT_VERSION >= 0x50600: CanvasApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) if app.style().metaObject().className() == "QFusionStyle": if fusiontheme == "breeze-dark": app.setPalette(breeze_dark()) defaultstylesheet = "darkorange.qss" palette = app.palette() if style is None and palette.color(QPalette.Window).value() < 127: log.info("Switching default stylesheet to darkorange") defaultstylesheet = "darkorange.qss" # NOTE: config.init() must be called after the QApplication constructor config.init() clear_settings_flag = os.path.join( config.widget_settings_dir(), "DELETE_ON_START") if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): log.info("Clearing widget settings") shutil.rmtree( config.widget_settings_dir(), ignore_errors=True) # Set http_proxy environment variables, after (potentially) clearing settings fix_set_proxy_env() file_handler = logging.FileHandler( filename=os.path.join(config.log_dir(), "canvas.log"), mode="w" ) file_handler.setLevel(root_level) rootlogger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() stylesheet = options.stylesheet or defaultstylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = canvas.__name__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode() base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE ) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(canvas.__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = CanvasMainWindow() canvas_window.setWindowIcon(config.application_icon()) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_discovery = qt.QtWidgetDiscovery(cached_descriptions=reg_cache) widget_registry = qt.QtWidgetRegistry() widget_discovery.found_category.connect( widget_registry.register_category ) widget_discovery.found_widget.connect( widget_registry.register_widget ) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_discovery.discovery_start.connect(splash_screen.show) widget_discovery.discovery_process.connect(show_message) widget_discovery.discovery_finished.connect(splash_screen.hide) log.info("Running widget discovery process.") cache_filename = os.path.join(cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: widget_discovery.run(config.widgets_entry_points()) # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # local references prevent destruction _ = show_survey() __ = check_for_updates() # Tee stdout and stderr into Output dock log_view = canvas_window.log_view() stdout = TextStream() stdout.stream.connect(log_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = log_view.formated(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) log.info("Entering main event loop.") try: with patch('sys.excepthook', ExceptHook(stream=stderr, canvas=canvas_window, handledException=handle_exception)),\ patch('sys.stderr', stderr),\ patch('sys.stdout', stdout): status = app.exec_() except BaseException: log.error("Error in main event loop.", exc_info=True) canvas_window.deleteLater() app.processEvents() app.flush() del canvas_window # Collect any cycles before deleting the QApplication instance gc.collect() del app return status
def main(argv=None): if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default=None) parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) parser.add_option("--config", help="Configuration namespace", type="str", default="orangecanvas.example") # -m canvas orange.widgets # -m canvas --config orange.widgets (options, args) = parser.parse_args(argv[1:]) levels = [ logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG ] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Set http_proxy environment variable(s) for some clients fix_set_proxy_env() # Try to fix macOS automatic window tabbing (Sierra and later) fix_macos_nswindow_tabbing() # File handler should always be at least INFO level so we need # the application root level to be at least at INFO. root_level = min(levels[options.log_level], logging.INFO) rootlogger = logging.getLogger(__package__) rootlogger.setLevel(root_level) # Standard output stream handler at the requested level stream_hander = logging.StreamHandler() stream_hander.setLevel(level=levels[options.log_level]) rootlogger.addHandler(stream_hander) if options.config is not None: try: cfg = utils.name_lookup(options.config) except (ImportError, AttributeError): pass else: config.set_default(cfg()) log.info("activating %s", options.config) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] style = options.style defaultstylesheet = "orange.qss" fusiontheme = None if style is not None: if style.startswith("fusion:"): qt_argv += ["-style", "fusion"] _, _, fusiontheme = style.partition(":") else: qt_argv += ["-style", style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args if QT_VERSION >= 0x50600: CanvasApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) trans = QTranslator() trans.load("./orangecanvas/orangecanvas_cn") app.installTranslator(trans) if app.style().metaObject().className() == "QFusionStyle": if fusiontheme == "breeze-dark": app.setPalette(breeze_dark()) defaultstylesheet = "darkorange.qss" palette = app.palette() if style is None and palette.color(QPalette.Window).value() < 127: log.info("Switching default stylesheet to darkorange") defaultstylesheet = "darkorange.qss" # NOTE: config.init() must be called after the QApplication constructor config.init() file_handler = logging.FileHandler(filename=os.path.join( config.log_dir(), "canvas.log"), mode="w") file_handler.setLevel(root_level) rootlogger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() stylesheet = options.stylesheet or defaultstylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with io.open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = __package__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode("utf-8") base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = CanvasMainWindow() canvas_window.setAttribute(Qt.WA_DeleteOnClose) canvas_window.setWindowIcon(config.application_icon()) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_registry = qt.QtWidgetRegistry() widget_discovery = config.widget_discovery(widget_registry, cached_descriptions=reg_cache) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setAttribute(Qt.WA_DeleteOnClose) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_registry.category_added.connect(show_message) show_splash = splash_screen.show close_splash = splash_screen.close else: show_splash = close_splash = lambda: None log.info("Running widget discovery process.") cache_filename = os.path.join(config.cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: show_splash() widget_discovery.run(config.widgets_entry_points()) close_splash() # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # Tee stdout and stderr into Output dock output_view = canvas_window.output_view() stdout = TextStream() stdout.stream.connect(output_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = output_view.formated(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) with ExitStack() as stack: stack.enter_context(closing(stderr)) stack.enter_context(closing(stdout)) stack.enter_context(redirect_stdout(stdout)) stack.enter_context(redirect_stderr(stderr)) log.info("Entering main event loop.") sys.excepthook = ExceptHook(stream=stderr) try: status = app.exec_() finally: sys.excepthook = sys.__excepthook__ del canvas_window app.processEvents() app.flush() # Collect any cycles before deleting the QApplication instance gc.collect() del app if status == 96: log.info('Restarting via exit code 96.') run_after_exit([sys.executable, sys.argv[0]]) return status
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) from Orange.version import version as current class GetLatestVersion(QThread): resultReady = pyqtSignal(str) def run(self): try: request = Request('https://orange.biolab.si/version/', headers={ 'Accept': 'text/plain', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'User-Agent': self.ua_string()}) contents = urlopen(request, timeout=10).read().decode() # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to check for updates') else: self.resultReady.emit(contents) @staticmethod def ua_string(): is_anaconda = 'Continuum' in sys.version or 'conda' in sys.version return 'Orange{orange_version}:Python{py_version}:{platform}:{conda}'.format( orange_version=current, py_version='.'.join(sys.version[:3]), platform=sys.platform, conda='Anaconda' if is_anaconda else '', ) def compare_versions(latest): version = pkg_resources.parse_version if version(latest) <= version(current): 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() thread = GetLatestVersion() thread.resultReady.connect(compare_versions) thread.start() return thread
def __init__(self, data): icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxWarning) F = self.DataField def _finished(*, key=(data.get(F.MODULE), data.get(F.WIDGET_MODULE)), filename=data.get(F.WIDGET_SCHEME)): self._cache.add(key) try: os.remove(filename) except Exception: pass super().__init__(None, Qt.Window, modal=True, sizeGripEnabled=True, windowIcon=icon, windowTitle='Unexpected Error', finished=_finished) self._data = data layout = QVBoxLayout(self) self.setLayout(layout) labels = QWidget(self) labels_layout = QHBoxLayout(self) labels.setLayout(labels_layout) labels_layout.addWidget(QLabel(pixmap=icon.pixmap(50, 50))) labels_layout.addWidget( QLabel('The program encountered an unexpected error. Please<br>' 'report it anonymously to the developers.<br><br>' 'The following data will be reported:')) labels_layout.addStretch(1) layout.addWidget(labels) font = QFont('Monospace', 10) font.setStyleHint(QFont.Monospace) font.setFixedPitch(True) textbrowser = QTextBrowser(self, font=font, openLinks=False, lineWrapMode=QTextBrowser.NoWrap, anchorClicked=QDesktopServices.openUrl) layout.addWidget(textbrowser) def _reload_text(): add_scheme = cb.isChecked() settings.setValue('error-reporting/add-scheme', add_scheme) lines = ['<table>'] for k, v in data.items(): if k.startswith('_'): continue _v, v = v, escape(str(v)) if k == F.WIDGET_SCHEME: if not add_scheme: continue v = '<a href="{}">{}</a>'.format( urljoin('file:', pathname2url(_v)), v) if k in (F.STACK_TRACE, F.LOCALS): v = v.replace('\n', '<br>').replace(' ', ' ') lines.append( '<tr><th align="left">{}:</th><td>{}</td></tr>'.format( k, v)) lines.append('</table>') textbrowser.setHtml(''.join(lines)) settings = QSettings() cb = QCheckBox('Include workflow (data will NOT be transmitted)', self, checked=settings.value('error-reporting/add-scheme', True, type=bool)) cb.stateChanged.connect(_reload_text) _reload_text() layout.addWidget(cb) buttons = QWidget(self) buttons_layout = QHBoxLayout(self) buttons.setLayout(buttons_layout) buttons_layout.addWidget( QPushButton('Send Report (Thanks!)', default=True, clicked=self.accept)) buttons_layout.addWidget( QPushButton("Don't Send", default=False, clicked=self.reject)) layout.addWidget(buttons)
def welcome_dialog_paged(self): # type: (CanvasMainWindow) -> None """ Show a modal multipaged welcome screen. """ dlg = PagedDialog( self, windowTitle=self.tr("Orange Data Mining"), ) dlg.setWindowModality(Qt.ApplicationModal) dlg.setAttribute(Qt.WA_DeleteOnClose) dlg.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) dlg.setStyleSheet(""" TabView::item:selected { background: rgb(243, 171, 86); } """) main = FancyWelcomeScreen() spec = welcome_screen_specs() if spec.image: background = QImage(spec.image) else: background = QImage("canvas_icons:orange-start-background.png") main.setImage(background) if spec.css: main.setStyleSheet(spec.css) else: main.setStyleSheet( "StartItem { background-color: rgb(123, 164, 214) }") def decorate_icon(icon): return decorate_welcome_icon(icon, "#6dacb2") for i, item in zip(range(3), spec.items): main.setItemText(i, item.text) main.setItemToolTip(i, item.tooltip) main.setItemIcon(i, decorate_icon(QIcon(item.icon))) main.setItemActiveIcon(i, decorate_icon(QIcon(item.active_icon))) main.item(i).setProperty("path", item.path) main.setCurrentIndex(0) main.activated.connect(lambda: openselectedbutton.click()) PageWelcome = dlg.addPage(sc_icon("Welcome.svg"), "Welcome", main) examples_ = examples.workflows(config.default) items = [previewmodel.PreviewItem(path=t.abspath()) for t in examples_] model = previewmodel.PreviewModel(items=items) model.delayedScanUpdate() browser = previewbrowser.PreviewBrowser() browser.setModel(model) PageTemplates = dlg.addPage(sc_icon("Templates.svg"), "Templates", browser) browser.activated.connect(lambda: openselectedbutton.click()) recent = [ previewmodel.PreviewItem(name=item.title, path=item.path) for item in self.recent_schemes ] model = previewmodel.PreviewModel(items=recent) browser = previewbrowser.PreviewBrowser() browser.setModel(model) model.delayedScanUpdate() PageRecent = dlg.addPage(self.recent_action.icon(), "Recent", browser) browser.activated.connect(lambda: openselectedbutton.click()) dlg.setPageEnabled(PageRecent, model.rowCount() > 0) page = SingleLinkPage( image=QImage( resource_path("icons/getting-started-video-tutorials.png")), heading="Getting Started", link=QUrl("https://www.youtube.com/watch?v=3nMcI4Hxm7c"), ) page.setContentsMargins(25, 25, 25, 25) PageGetStarted = dlg.addPage( canvas_icons("YouTube.svg"), "Get Started", page, ) buttons = dlg.buttonBox() buttons.setVisible(True) buttons.setStandardButtons(QDialogButtonBox.Open | QDialogButtonBox.Cancel) # choose the selected workflow button openselectedbutton = buttons.button(QDialogButtonBox.Open) openselectedbutton.setText(self.tr("Open")) openselectedbutton.setToolTip("Open the selected workflow") openselectedbutton.setDefault(True) newbutton = QPushButton("New", toolTip="Create a new workflow") s = QShortcut(QKeySequence.New, newbutton) s.activated.connect(newbutton.click) buttons.addButton(newbutton, QDialogButtonBox.AcceptRole) openexisting = QPushButton("Open Existing\N{HORIZONTAL ELLIPSIS}", toolTip="Open an existing workflow file") s = QShortcut(QKeySequence.Open, dlg) s.activated.connect(openexisting.click) buttons.addButton(openexisting, QDialogButtonBox.AcceptRole) settings = QSettings() show_start_key = "startup/show-welcome-screen" show_start = QCheckBox("Show at startup", checked=settings.value(show_start_key, True, type=bool)) # Abusing ResetRole to push the check box to the left in all button # layouts. buttons.addButton(show_start, QDialogButtonBox.ResetRole) def update_show_at_startup(value): settings.setValue(show_start_key, value) show_start.toggled.connect(update_show_at_startup) def on_page_changed(index): if index == PageWelcome: openselectedbutton.setEnabled(True) elif index == PageTemplates: openselectedbutton.setEnabled(bool(examples)) elif index == PageRecent: openselectedbutton.setEnabled(bool(recent)) elif index == PageGetStarted: openselectedbutton.setEnabled(False) else: openselectedbutton.setEnabled(False) dlg.currentIndexChanged.connect(on_page_changed) def open_example_workflow(path): # open a workflow without filename/directory tracking. wf = self.new_scheme_from(path) if self.is_transient(): window = self else: window = self.create_new_window() window.show() window.raise_() window.activateWindow() window.set_new_scheme(wf) def open_url(url): return QDesktopServices.openUrl(QUrl(url)) def on_clicked(button): current = dlg.currentIndex() path = None open_workflow_file = None if current == PageWelcome: open_workflow_file = open_url elif current == PageTemplates: open_workflow_file = open_example_workflow elif current == PageRecent: open_workflow_file = self.open_scheme_file if button is openselectedbutton and \ current in {PageTemplates, PageRecent}: w = dlg.widget(current) assert isinstance(w, previewbrowser.PreviewBrowser) assert w.currentIndex() != -1 model = w.model() item = model.item(w.currentIndex()) path = item.path() elif button is openselectedbutton and current == PageWelcome: w = dlg.widget(current) assert isinstance(w, FancyWelcomeScreen) assert w.currentIndex() != -1 path = w.item(w.currentIndex()).property("path") if path is not None: open_workflow_file(path) dlg.accept() buttons.clicked.connect(on_clicked) def on_open_existing(): filedlg = self._open_workflow_dialog() filedlg.fileSelected.connect(self.open_scheme_file) filedlg.accepted.connect(dlg.accept) filedlg.exec() openexisting.clicked.connect(on_open_existing) def new_window(): if not self.is_transient(): self.new_workflow_window() dlg.accept() newbutton.clicked.connect(new_window) dlg.show()
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) from urllib.request import urlopen, Request from Orange.version import version as current class GetLatestVersion(QThread): resultReady = pyqtSignal(str) def run(self): try: request = Request('https://orange.biolab.si/version/', headers={ 'Accept': 'text/plain', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'User-Agent': self.ua_string() }) contents = urlopen(request, timeout=10).read().decode() # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to check for updates') else: self.resultReady.emit(contents) @staticmethod def ua_string(): is_anaconda = 'Continuum' in sys.version or 'conda' in sys.version return 'Orange{orange_version}:Python{py_version}:{platform}:{conda}'.format( orange_version=current, py_version='.'.join(sys.version[:3]), platform=sys.platform, conda='Anaconda' if is_anaconda else '', ) def compare_versions(latest): version = pkg_resources.parse_version if version(latest) <= version(current): 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() thread = GetLatestVersion() thread.resultReady.connect(compare_versions) thread.start() return thread
def main(argv=None): if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--clear-widget-settings", action="store_true", help="Remove stored widget setting") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default="orange.qss") parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) (options, args) = parser.parse_args(argv[1:]) levels = [ logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG ] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Try to fix fonts on OSX Mavericks fix_osx_10_9_private_font() # File handler should always be at least INFO level so we need # the application root level to be at least at INFO. root_level = min(levels[options.log_level], logging.INFO) rootlogger = logging.getLogger(canvas.__name__) rootlogger.setLevel(root_level) # Initialize SQL query and execution time logger (in SqlTable) sql_level = min(levels[options.log_level], logging.INFO) make_sql_logger(sql_level) # Standard output stream handler at the requested level stream_hander = logging.StreamHandler() stream_hander.setLevel(level=levels[options.log_level]) rootlogger.addHandler(stream_hander) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] if options.style is not None: qt_argv += ["-style", options.style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) # NOTE: config.init() must be called after the QApplication constructor config.init() clear_settings_flag = os.path.join(config.widget_settings_dir(), "DELETE_ON_START") if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): log.info("Clearing widget settings") shutil.rmtree(config.widget_settings_dir(), ignore_errors=True) file_handler = logging.FileHandler(filename=os.path.join( config.log_dir(), "canvas.log"), mode="w") file_handler.setLevel(root_level) rootlogger.addHandler(file_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() stylesheet = options.stylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): stylesheet_string = open(stylesheet, "rb").read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = canvas.__name__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode() base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(canvas.__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = CanvasMainWindow() canvas_window.setWindowIcon(config.application_icon()) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_discovery = qt.QtWidgetDiscovery(cached_descriptions=reg_cache) widget_registry = qt.QtWidgetRegistry() widget_discovery.found_category.connect(widget_registry.register_category) widget_discovery.found_widget.connect(widget_registry.register_widget) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_discovery.discovery_start.connect(splash_screen.show) widget_discovery.discovery_process.connect(show_message) widget_discovery.discovery_finished.connect(splash_screen.hide) log.info("Running widget discovery process.") cache_filename = os.path.join(cache_dir(), "widget-registry.pck") if options.no_discovery: widget_registry = pickle.load(open(cache_filename, "rb")) widget_registry = qt.QtWidgetRegistry(widget_registry) else: widget_discovery.run(config.widgets_entry_points()) # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) pickle.dump(WidgetRegistry(widget_registry), open(cache_filename, "wb")) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if want_welcome and not args and not open_requests: canvas_window.welcome_dialog() elif args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) # If run for the first time, open a browser tab with a survey show_survey = settings.value("startup/show-survey", True, type=bool) 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: success = QDesktopServices.openUrl( QUrl("http://orange.biolab.si/survey/short.html")) settings.setValue("startup/show-survey", not success) else: settings.setValue("startup/show-survey", result != QMessageBox.No) question.finished.connect(handle_response) question.show() # Tee stdout and stderr into Output dock log_view = canvas_window.log_view() stdout = TextStream() stdout.stream.connect(log_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = log_view.formated(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) log.info("Entering main event loop.") try: with patch('sys.excepthook', ExceptHook(stream=stderr, canvas=canvas_window, handledException=handle_exception)),\ patch('sys.stderr', stderr),\ patch('sys.stdout', stdout): status = app.exec_() except BaseException: log.error("Error in main event loop.", exc_info=True) canvas_window.deleteLater() app.processEvents() app.flush() del canvas_window # Collect any cycles before deleting the QApplication instance gc.collect() del app return status
def fix_qt_plugins_path(): """ Attempt to fix qt plugins path if it is invalid. https://www.riverbankcomputing.com/pipermail/pyqt/2018-November/041089.html """ # PyQt5 loads a runtime generated qt.conf file into qt's resource system # but does not correctly (INI) encode non-latin1 characters in paths # (https://www.riverbankcomputing.com/pipermail/pyqt/2018-November/041089.html) # Need to be careful not to mess the plugins path when not installed as # a (delocated) wheel. s = QSettings(":qt/etc/qt.conf", QSettings.IniFormat) path = s.value("Paths/Prefix", type=str) # does the ':qt/etc/qt.conf' exist and has prefix path that does not exist if path and os.path.exists(path): return # Use QLibraryInfo.location to resolve the plugins dir pluginspath = QLibraryInfo.location(QLibraryInfo.PluginsPath) # Check effective library paths. Someone might already set the search # paths (including via QT_PLUGIN_PATH). QApplication.libraryPaths() returns # existing paths only. paths = QApplication.libraryPaths() if paths: return if AnyQt.USED_API == "pyqt5": import PyQt5.QtCore as qc elif AnyQt.USED_API == "pyside2": import PySide2.QtCore as qc else: return def normpath(path): return os.path.normcase(os.path.normpath(path)) # guess the appropriate path relative to the installation dir based on the # PyQt5 installation dir and the 'recorded' plugins path. I.e. match the # 'PyQt5' directory name in the recorded path and replace the 'invalid' # prefix with the real PyQt5 install dir. def maybe_match_prefix(prefix: str, path: str) -> Optional[str]: """ >>> maybe_match_prefix("aa/bb/cc", "/a/b/cc/a/b") "aa/bb/cc/a/b" >>> maybe_match_prefix("aa/bb/dd", "/a/b/cc/a/b") None """ prefix = normpath(prefix) path = normpath(path) basename = os.path.basename(prefix) path_components = path.split(os.sep) # find the (rightmost) basename in the prefix_components idx = None try: start = 0 while True: idx = path_components.index(basename, start) start = idx + 1 except ValueError: pass if idx is None: return return os.path.join(prefix, *path_components[idx + 1:]) newpath = maybe_match_prefix( os.path.dirname(qc.__file__), pluginspath ) if newpath is not None and os.path.exists(newpath): QApplication.addLibraryPath(newpath)
def check_for_updates(): settings = QSettings() check_updates = settings.value('startup/check-updates', True, type=bool) last_check_time = settings.value('startup/last-update-check-time', 0, type=int) ONE_DAY = 86400 if check_updates and time.time() - last_check_time > ONE_DAY: settings.setValue('startup/last-update-check-time', int(time.time())) from Orange.version import version as current class GetLatestVersion(QThread): resultReady = pyqtSignal(str) def run(self): try: request = Request('https://orange.biolab.si/version/', headers={ 'Accept': 'text/plain', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'User-Agent': self.ua_string() }) contents = urlopen(request, timeout=10).read().decode() # Nothing that this fails with should make Orange crash except Exception: # pylint: disable=broad-except log.exception('Failed to check for updates') else: self.resultReady.emit(contents) @staticmethod def ua_string(): is_anaconda = 'Continuum' in sys.version or 'conda' in sys.version return 'Orange{orange_version}:Python{py_version}:{platform}:{conda}'.format( orange_version=current, py_version='.'.join(sys.version[:3]), platform=sys.platform, conda='Anaconda' if is_anaconda else '', ) def compare_versions(latest): version = pkg_resources.parse_version skipped = settings.value('startup/latest-skipped-version', "", type=str) if version(latest) <= version(current) or \ latest == skipped: return 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.RejectRole: 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 main(argv=None): # Allow termination with CTRL + C signal.signal(signal.SIGINT, signal.SIG_DFL) # Disable pyqtgraph's atexit and QApplication.aboutToQuit cleanup handlers. pyqtgraph.setConfigOption("exitCleanup", False) if argv is None: argv = sys.argv usage = "usage: %prog [options] [workflow_file]" parser = optparse.OptionParser(usage=usage) parser.add_option("--no-discovery", action="store_true", help="Don't run widget discovery " "(use full cache instead)") parser.add_option("--force-discovery", action="store_true", help="Force full widget discovery " "(invalidate cache)") parser.add_option("--clear-widget-settings", action="store_true", help="Remove stored widget setting") parser.add_option("--no-welcome", action="store_true", help="Don't show welcome dialog.") parser.add_option("--no-splash", action="store_true", help="Don't show splash screen.") parser.add_option("-l", "--log-level", help="Logging level (0, 1, 2, 3, 4)", type="int", default=1) parser.add_option("--style", help="QStyle to use", type="str", default=None) parser.add_option("--stylesheet", help="Application level CSS style sheet to use", type="str", default=None) parser.add_option("--qt", help="Additional arguments for QApplication", type="str", default=None) (options, args) = parser.parse_args(argv[1:]) levels = [ logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG ] # Fix streams before configuring logging (otherwise it will store # and write to the old file descriptors) fix_win_pythonw_std_stream() # Try to fix macOS automatic window tabbing (Sierra and later) fix_macos_nswindow_tabbing() logging.basicConfig( level=levels[options.log_level], handlers=[make_stdout_handler(levels[options.log_level])]) # set default application configuration config_ = config.Config() canvasconfig.set_default(config_) log.info("Starting 'Orange Canvas' application.") qt_argv = argv[:1] style = options.style defaultstylesheet = "orange.qss" fusiontheme = None if style is not None: if style.startswith("fusion:"): qt_argv += ["-style", "fusion"] _, _, fusiontheme = style.partition(":") else: qt_argv += ["-style", style] if options.qt is not None: qt_argv += shlex.split(options.qt) qt_argv += args if QT_VERSION >= 0x50600: CanvasApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) log.debug("Starting CanvasApplicaiton with argv = %r.", qt_argv) app = CanvasApplication(qt_argv) config_.init() if app.style().metaObject().className() == "QFusionStyle": if fusiontheme == "breeze-dark": app.setPalette(breeze_dark()) defaultstylesheet = "darkorange.qss" palette = app.palette() if style is None and palette.color(QPalette.Window).value() < 127: log.info("Switching default stylesheet to darkorange") defaultstylesheet = "darkorange.qss" # Initialize SQL query and execution time logger (in SqlTable) sql_level = min(levels[options.log_level], logging.INFO) make_sql_logger(sql_level) clear_settings_flag = os.path.join(widget_settings_dir(), "DELETE_ON_START") if options.clear_widget_settings or \ os.path.isfile(clear_settings_flag): log.info("Clearing widget settings") shutil.rmtree(widget_settings_dir(), ignore_errors=True) # Set http_proxy environment variables, after (potentially) clearing settings fix_set_proxy_env() # Setup file log handler for the select logger list - this is always # at least INFO level = min(levels[options.log_level], logging.INFO) file_handler = logging.FileHandler(filename=os.path.join( config.log_dir(), "canvas.log"), mode="w") formatter = logging.Formatter( "%(asctime)s:%(levelname)s:%(name)s: %(message)s") file_handler.setFormatter(formatter) file_handler.setLevel(level) stream = TextStream() stream_handler = logging.StreamHandler(stream) stream_handler.setFormatter(formatter) stream_handler.setLevel(level) for namespace in ["orangecanvas", "orangewidget", "Orange"]: logger = logging.getLogger(namespace) logger.setLevel(level) logger.addHandler(file_handler) logger.addHandler(stream_handler) # intercept any QFileOpenEvent requests until the main window is # fully initialized. # NOTE: The QApplication must have the executable ($0) and filename # arguments passed in argv otherwise the FileOpen events are # triggered for them (this is done by Cocoa, but QApplicaiton filters # them out if passed in argv) open_requests = [] def onrequest(url): log.info("Received an file open request %s", url) path = url.path() exists = QFile(path).exists() if exists and \ ('pydevd.py' not in url.path() and # PyCharm debugger 'run_profiler.py' not in url.path()): # PyCharm profiler open_requests.append(url) app.fileOpenRequest.connect(onrequest) settings = QSettings() settings.setValue('startup/launch-count', settings.value('startup/launch-count', 0, int) + 1) if settings.value("reporting/send-statistics", False, type=bool) \ and is_release: UsageStatistics.set_enabled(True) stylesheet = options.stylesheet or defaultstylesheet stylesheet_string = None if stylesheet != "none": if os.path.isfile(stylesheet): with open(stylesheet, "r") as f: stylesheet_string = f.read() else: if not os.path.splitext(stylesheet)[1]: # no extension stylesheet = os.path.extsep.join([stylesheet, "qss"]) pkg_name = orangecanvas.__name__ resource = "styles/" + stylesheet if pkg_resources.resource_exists(pkg_name, resource): stylesheet_string = \ pkg_resources.resource_string(pkg_name, resource).decode() base = pkg_resources.resource_filename(pkg_name, "styles") pattern = re.compile( r"^\s@([a-zA-Z0-9_]+?)\s*:\s*([a-zA-Z0-9_/]+?);\s*$", flags=re.MULTILINE) matches = pattern.findall(stylesheet_string) for prefix, search_path in matches: QDir.addSearchPath(prefix, os.path.join(base, search_path)) log.info("Adding search path %r for prefix, %r", search_path, prefix) stylesheet_string = pattern.sub("", stylesheet_string) else: log.info("%r style sheet not found.", stylesheet) # Add the default canvas_icons search path dirpath = os.path.abspath(os.path.dirname(orangecanvas.__file__)) QDir.addSearchPath("canvas_icons", os.path.join(dirpath, "icons")) canvas_window = MainWindow() canvas_window.setAttribute(Qt.WA_DeleteOnClose) canvas_window.setWindowIcon(config.application_icon()) canvas_window.connect_output_stream(stream) # initialize notification server, set to initial canvas notif_server = NotificationServer() canvas.notification_server_instance = notif_server canvas_window.set_notification_server(notif_server) if stylesheet_string is not None: canvas_window.setStyleSheet(stylesheet_string) if not options.force_discovery: reg_cache = cache.registry_cache() else: reg_cache = None widget_registry = qt.QtWidgetRegistry() widget_discovery = config_.widget_discovery(widget_registry, cached_descriptions=reg_cache) want_splash = \ settings.value("startup/show-splash-screen", True, type=bool) and \ not options.no_splash if want_splash: pm, rect = config.splash_screen() splash_screen = SplashScreen(pixmap=pm, textRect=rect) splash_screen.setFont(QFont("Helvetica", 12)) color = QColor("#FFD39F") def show_message(message): splash_screen.showMessage(message, color=color) widget_registry.category_added.connect(show_message) log.info("Running widget discovery process.") cache_filename = os.path.join(config.cache_dir(), "widget-registry.pck") if options.no_discovery: with open(cache_filename, "rb") as f: widget_registry = pickle.load(f) widget_registry = qt.QtWidgetRegistry(widget_registry) else: if want_splash: splash_screen.show() widget_discovery.run(config.widgets_entry_points()) if want_splash: splash_screen.hide() splash_screen.deleteLater() # Store cached descriptions cache.save_registry_cache(widget_discovery.cached_descriptions) with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) set_global_registry(widget_registry) canvas_window.set_widget_registry(widget_registry) canvas_window.show() canvas_window.raise_() want_welcome = \ settings.value("startup/show-welcome-screen", True, type=bool) \ and not options.no_welcome # Process events to make sure the canvas_window layout has # a chance to activate (the welcome dialog is modal and will # block the event queue, plus we need a chance to receive open file # signals when running without a splash screen) app.processEvents() app.fileOpenRequest.connect(canvas_window.open_scheme_file) if args: log.info("Loading a scheme from the command line argument %r", args[0]) canvas_window.load_scheme(args[0]) elif open_requests: log.info("Loading a scheme from an `QFileOpenEvent` for %r", open_requests[-1]) canvas_window.load_scheme(open_requests[-1].toLocalFile()) else: swp_loaded = canvas_window.ask_load_swp_if_exists() if not swp_loaded and want_welcome: canvas_window.welcome_dialog() # local references prevent destruction update_check = check_for_updates() send_stat = send_usage_statistics() pull_notifs = pull_notifications() # Tee stdout and stderr into Output dock log_view = canvas_window.output_view() stdout = TextStream() stdout.stream.connect(log_view.write) if sys.stdout: stdout.stream.connect(sys.stdout.write) stdout.flushed.connect(sys.stdout.flush) stderr = TextStream() error_writer = log_view.formatted(color=Qt.red) stderr.stream.connect(error_writer.write) if sys.stderr: stderr.stream.connect(sys.stderr.write) stderr.flushed.connect(sys.stderr.flush) log.info("Entering main event loop.") excepthook = ExceptHook(stream=stderr) excepthook.handledException.connect(handle_exception) try: with closing(stdout),\ closing(stderr),\ closing(stream), \ patch('sys.excepthook', excepthook),\ patch('sys.stderr', stderr),\ patch('sys.stdout', stdout): status = app.exec_() except BaseException: log.error("Error in main event loop.", exc_info=True) status = 42 del canvas_window del update_check del send_stat del pull_notifs app.processEvents() app.flush() # Collect any cycles before deleting the QApplication instance gc.collect() del app if status == 96: log.info('Restarting via exit code 96.') run_after_exit([sys.executable, sys.argv[0]]) return status
def __init__(self, data): icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxWarning) F = self.DataField def _finished(*, key=(data.get(F.MODULE), data.get(F.WIDGET_MODULE)), filename=data.get(F.WIDGET_SCHEME)): self._cache.add(key) try: os.remove(filename) except Exception: pass super().__init__(None, Qt.Window, modal=True, sizeGripEnabled=True, windowIcon=icon, windowTitle='Unexpected Error', finished=_finished) self._data = data layout = QVBoxLayout(self) self.setLayout(layout) labels = QWidget(self) labels_layout = QHBoxLayout(self) labels.setLayout(labels_layout) labels_layout.addWidget(QLabel(pixmap=icon.pixmap(50, 50))) labels_layout.addWidget(QLabel( 'The program encountered an unexpected error. Please<br>' 'report it anonymously to the developers.<br><br>' 'The following data will be reported:')) labels_layout.addStretch(1) layout.addWidget(labels) font = QFont('Monospace', 10) font.setStyleHint(QFont.Monospace) font.setFixedPitch(True) textbrowser = QTextBrowser(self, font=font, openLinks=False, lineWrapMode=QTextBrowser.NoWrap, anchorClicked=QDesktopServices.openUrl) layout.addWidget(textbrowser) def _reload_text(): add_scheme = cb.isChecked() settings.setValue('error-reporting/add-scheme', add_scheme) lines = ['<table>'] for k, v in data.items(): if k.startswith('_'): continue _v, v = v, escape(str(v)) if k == F.WIDGET_SCHEME: if not add_scheme: continue v = '<a href="{}">{}</a>'.format(urljoin('file:', pathname2url(_v)), v) if k in (F.STACK_TRACE, F.LOCALS): v = v.replace('\n', '<br>').replace(' ', ' ') lines.append('<tr><th align="left">{}:</th><td>{}</td></tr>'.format(k, v)) lines.append('</table>') textbrowser.setHtml(''.join(lines)) settings = QSettings() cb = QCheckBox( 'Include workflow (data will NOT be transmitted)', self, checked=settings.value('error-reporting/add-scheme', True, type=bool)) cb.stateChanged.connect(_reload_text) _reload_text() layout.addWidget(cb) buttons = QWidget(self) buttons_layout = QHBoxLayout(self) buttons.setLayout(buttons_layout) buttons_layout.addWidget( QPushButton('Send Report (Thanks!)', default=True, clicked=self.accept)) buttons_layout.addWidget(QPushButton("Don't Send", default=False, clicked=self.reject)) layout.addWidget(buttons)