Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
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
Exemple #5
0
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)
Exemple #6
0
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 ''
    )
Exemple #7
0
 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)
Exemple #8
0
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
Exemple #10
0
    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()
Exemple #11
0
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
Exemple #12
0
    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)
Exemple #13
0
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
Exemple #14
0
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)
Exemple #16
0
    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)
Exemple #17
0
    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()
Exemple #18
0
    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)
Exemple #19
0
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)
Exemple #22
0
    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
Exemple #24
0
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
Exemple #25
0
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, "")
Exemple #26
0
    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
Exemple #27
0
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
Exemple #28
0
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
Exemple #29
0
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
Exemple #30
0
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
Exemple #31
0
    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(' ', '&nbsp;')
                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)
Exemple #32
0
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()
Exemple #33
0
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
Exemple #34
0
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)
Exemple #36
0
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
Exemple #37
0
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
Exemple #38
0
    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(' ', '&nbsp;')
                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)