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 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 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 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='Orange Update Available', text='Current version: <b>{}</b><br>' 'Latest version: <b>{}</b>'.format(current, latest), accept_button_label="Download", reject_button_label="Skip this Version", 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 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 _userconfirmed(): session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) session_hist.setValue("{}/confirmed".format(message.persistent_id), True) session_hist.sync()
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 _userconfirmed(): session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) session_hist.setValue( "{}/confirmed".format(message.persistent_id), True) session_hist.sync()
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 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 handle_exception(cls, exc): etype, evalue, tb = exc exception = traceback.format_exception_only(etype, evalue)[-1].strip() stacktrace = ''.join(traceback.format_exception(etype, evalue, tb)) def _find_last_frame(tb): if not tb: return None while tb.tb_next: tb = tb.tb_next return tb err_locals, err_module, frame = None, None, _find_last_frame(tb) if frame: err_module = '{}:{}'.format( frame.tb_frame.f_globals.get( '__name__', frame.tb_frame.f_code.co_filename), frame.tb_lineno) err_locals = OrderedDict(sorted(frame.tb_frame.f_locals.items())) err_locals = try_(lambda: pformat(err_locals), try_(lambda: str(err_locals))) def _find_widget_frame(tb): while tb: if isinstance(tb.tb_frame.f_locals.get('self'), OWBaseWidget): return tb tb = tb.tb_next widget_module = widget_class = widget = workflow = None frame = _find_widget_frame(tb) if frame is not None: widget = frame.tb_frame.f_locals['self'] # type: OWBaseWidget widget_class = widget.__class__ widget_module = '{}:{}'.format(widget_class.__module__, frame.tb_lineno) if widget is not None: try: workflow = widget.signalManager.parent() if not isinstance(workflow, WidgetsScheme): raise TypeError except Exception: workflow = None packages = ', '.join(sorted(get_installed_distributions())) settings = QSettings() if settings.contains('error-reporting/machine-id'): machine_id = settings.value('error-reporting/machine-id') else: machine_id = str(uuid.uuid4()) settings.setValue('error-reporting/machine-id', machine_id) # If this exact error was already reported in this session, # just warn about it # or if computer not connected to the internet if (err_module, widget_module) in cls._cache or not internet_on(): QMessageBox( QMessageBox.Warning, 'Error Encountered', 'Error encountered{}:<br><br><tt>{}</tt>'.format( (' in widget <b>{}</b>'.format(widget_class.name) if widget_class else ''), stacktrace.replace('\n', '<br>').replace(' ', ' ')), QMessageBox.Ignore).exec() return F = cls.DataField data = OrderedDict() data[F.EXCEPTION] = exception data[F.MODULE] = err_module if widget_class is not None: data[F.WIDGET_NAME] = widget_class.name data[F.WIDGET_MODULE] = widget_module if workflow is not None \ and QSettings().value('reporting/add-scheme', True, type=bool): fd, filename = mkstemp(prefix='ows-', suffix='.ows.xml') os.close(fd) try: with open(filename, "wb") as f: workflow.save_to(f, pretty=True, pickle_fallback=True) with open(filename, encoding='utf-8') as f: data[F.WIDGET_SCHEME] = f.read() except Exception: pass data[F.VERSION] = QApplication.applicationVersion() data[F.ENVIRONMENT] = 'Python {} on {} {} {} {}'.format( platform.python_version(), platform.system(), platform.release(), platform.version(), platform.machine()) data[F.INSTALLED_PACKAGES] = packages data[F.MACHINE_ID] = machine_id data[F.STACK_TRACE] = stacktrace if err_locals: data[F.LOCALS] = err_locals cls(data=data).exec()