Ejemplo n.º 1
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    valueChanged = Signal()

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def _restart_dialog(self, event=None, extra_str=""):
        """Displays the dialog informing user a restart is required.

        Paramters
        ---------
        event : Event
        extra_str : str
            Extra information to add to the message about needing a restart.
        """

        text_str = trans._(
            "napari requires a restart for image rendering changes to apply.")

        widget = ResetNapariInfoDialog(
            parent=self,
            text=text_str,
        )
        widget.exec_()

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        settings = get_settings()
        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.
        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in settings.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """
        schema = json.loads(setting['json_schema'])

        # Resolve allOf references
        definitions = schema.get("definitions", {})
        if definitions:
            for key, data in schema["properties"].items():
                if "allOf" in data:
                    allof = data["allOf"]
                    allof = [d["$ref"].rsplit("/")[-1] for d in allof]
                    for definition in allof:
                        local_def = definitions[definition]
                        schema["properties"][key]["enum"] = local_def["enum"]
                        schema["properties"][key]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""
        self._reset_dialog = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        self._reset_dialog.valueChanged.connect(self._reset_widgets)
        self._reset_dialog.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self.valueChanged.emit()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to settings."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        settings = get_settings()
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = settings.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        settings = get_settings()
        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)

        # Disable widgets that loaded settings from environment variables
        section = schema["section"]
        form_layout = form.widget.layout()
        for row in range(form.widget.layout().rowCount()):
            widget = form_layout.itemAt(row, form_layout.FieldRole).widget()
            name = widget._name
            disable = bool(
                settings._env_settings.get(section, {}).get(name, None))
            widget.setDisabled(disable)
            try:
                widget.opacity.setOpacity(0.3 if disable else 1)
            except AttributeError:
                # some widgets may not have opacity (such as the QtPluginSorter)
                pass

        # set state values for widget
        form.widget.state = values

        if section == 'experimental':
            # need to disable async if octree is enabled.
            if values['octree'] is True:
                form = self._disable_async(form, values)

        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _disable_async(self, form, values, disable=True, state=True):
        """Disable async if octree is True."""
        settings = get_settings()
        # need to make sure that if async_ is an environment setting, that we don't
        # enable it here.
        if (settings._env_settings['experimental'].get('async_', None)
                is not None):
            disable = True

        idx = list(values.keys()).index('async_')
        form_layout = form.widget.layout()
        widget = form_layout.itemAt(idx, form_layout.FieldRole).widget()
        widget.opacity.setOpacity(0.3 if disable else 1)
        widget.setDisabled(disable)

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def set_current_index(self, index: int):
        """
        Set the current page on the preferences by index.

        Parameters
        ----------
        index : int
            Index of page to set as current one.
        """
        self._list.setCurrentRow(index)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        settings = get_settings()
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in settings
            for setting_name, value in different_values.items():
                try:
                    setattr(settings._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict

                    if page == 'experimental':

                        if setting_name == 'octree':

                            # disable/enable async checkbox
                            widget = self._stack.currentWidget()
                            cstate = True if value is True else False
                            self._disable_async(widget,
                                                new_dict,
                                                disable=cstate)

                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                        elif setting_name == 'async_':
                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                except:  # noqa: E722
                    continue
class FindOptions(QWidget):
    """Find widget with options"""
    REGEX_INVALID = "background-color:rgb(255, 175, 90);"
    find = Signal()
    stop = Signal()

    def __init__(self, parent, search_text, search_text_regexp, search_path,
                 exclude, exclude_idx, exclude_regexp, supported_encodings,
                 in_python_path, more_options, case_sensitive,
                 external_path_history):
        QWidget.__init__(self, parent)

        if search_path is None:
            search_path = getcwd_or_home()

        self.path = ''
        self.project_path = None
        self.file_path = None
        self.external_path = None
        self.external_path_history = external_path_history

        if not isinstance(search_text, (list, tuple)):
            search_text = [search_text]
        if not isinstance(search_path, (list, tuple)):
            search_path = [search_path]
        if not isinstance(exclude, (list, tuple)):
            exclude = [exclude]
        if not isinstance(external_path_history, (list, tuple)):
            external_path_history = [external_path_history]

        self.supported_encodings = supported_encodings

        # Layout 1
        hlayout1 = QHBoxLayout()
        self.search_text = PatternComboBox(self, search_text,
                                           _("Search pattern"))
        self.edit_regexp = create_toolbutton(self,
                                             icon=ima.icon('advanced'),
                                             tip=_('Regular expression'))
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.setChecked(case_sensitive)
        self.edit_regexp.setCheckable(True)
        self.edit_regexp.setChecked(search_text_regexp)
        self.more_widgets = ()
        self.more_options = create_toolbutton(self,
                                              toggled=self.toggle_more_options)
        self.more_options.setCheckable(True)
        self.more_options.setChecked(more_options)

        self.ok_button = create_toolbutton(self,
                                           text=_("Search"),
                                           icon=ima.icon('find'),
                                           triggered=lambda: self.find.emit(),
                                           tip=_("Start search"),
                                           text_beside_icon=True)
        self.ok_button.clicked.connect(self.update_combos)
        self.stop_button = create_toolbutton(
            self,
            text=_("Stop"),
            icon=ima.icon('editclear'),
            triggered=lambda: self.stop.emit(),
            tip=_("Stop search"),
            text_beside_icon=True)
        self.stop_button.setEnabled(False)
        for widget in [
                self.search_text, self.edit_regexp, self.case_button,
                self.ok_button, self.stop_button, self.more_options
        ]:
            hlayout1.addWidget(widget)

        # Layout 2
        hlayout2 = QHBoxLayout()
        self.exclude_pattern = PatternComboBox(self, exclude,
                                               _("Excluded filenames pattern"))
        if exclude_idx is not None and exclude_idx >= 0 \
           and exclude_idx < self.exclude_pattern.count():
            self.exclude_pattern.setCurrentIndex(exclude_idx)
        self.exclude_regexp = create_toolbutton(self,
                                                icon=ima.icon('advanced'),
                                                tip=_('Regular expression'))
        self.exclude_regexp.setCheckable(True)
        self.exclude_regexp.setChecked(exclude_regexp)
        exclude_label = QLabel(_("Exclude:"))
        exclude_label.setBuddy(self.exclude_pattern)
        for widget in [
                exclude_label, self.exclude_pattern, self.exclude_regexp
        ]:
            hlayout2.addWidget(widget)

        # Layout 3
        hlayout3 = QHBoxLayout()

        search_on_label = QLabel(_("Search in:"))
        self.path_selection_combo = PatternComboBox(self, exclude,
                                                    _('Search directory'))
        self.path_selection_combo.setEditable(False)
        self.path_selection_contents = QListWidget(self.path_selection_combo)
        self.path_selection_contents.hide()
        self.path_selection_combo.setModel(
            self.path_selection_contents.model())

        self.path_selection_contents.addItem(_("Current working directory"))
        item = self.path_selection_contents.item(0)
        item.setToolTip(
            _("Search in all files and "
              "directories present on the"
              "current Spyder path"))

        self.path_selection_contents.addItem(_("Project"))
        item = self.path_selection_contents.item(1)
        item.setToolTip(
            _("Search in all files and "
              "directories present on the"
              "current project path "
              "(If opened)"))
        item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

        self.path_selection_contents.addItem(_("File").replace('&', ''))
        item = self.path_selection_contents.item(2)
        item.setToolTip(_("Search in current opened file"))

        self.path_selection_contents.addItem(_("Select other directory"))
        item = self.path_selection_contents.item(3)
        item.setToolTip(_("Search in other folder present on the file system"))

        self.path_selection_combo.insertSeparator(3)
        self.path_selection_combo.insertSeparator(5)
        for path in external_path_history:
            item = ExternalPathItem(None, path)
            self.path_selection_contents.addItem(item)

        self.path_selection_combo.currentIndexChanged.connect(
            self.path_selection_changed)

        hlayout3.addWidget(search_on_label)
        hlayout3.addWidget(self.path_selection_combo)

        self.search_text.valid.connect(lambda valid: self.find.emit())
        self.exclude_pattern.valid.connect(lambda valid: self.find.emit())

        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addLayout(hlayout1)
        vlayout.addLayout(hlayout2)
        vlayout.addLayout(hlayout3)
        self.more_widgets = (hlayout2, )
        self.toggle_more_options(more_options)
        self.setLayout(vlayout)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)

    @Slot(bool)
    def toggle_more_options(self, state):
        for layout in self.more_widgets:
            for index in range(layout.count()):
                if state and self.isVisible() or not state:
                    layout.itemAt(index).widget().setVisible(state)
        if state:
            icon = ima.icon('options_less')
            tip = _('Hide advanced options')
        else:
            icon = ima.icon('options_more')
            tip = _('Show advanced options')
        self.more_options.setIcon(icon)
        self.more_options.setToolTip(tip)

    @Slot()
    def path_selection_changed(self):
        idx = self.path_selection_combo.currentIndex()
        if idx == EXTERNAL_PATH:
            external_path = self.select_directory()
            if len(external_path) > 0:
                item = ExternalPathItem(None, external_path)
                self.path_selection_contents.addItem(item)

                total_items = (self.path_selection_combo.count() -
                               MAX_PATH_HISTORY)
                for i in range(6, total_items):
                    self.path_selection_contents.takeItem(i)

                self.path_selection_combo.setCurrentIndex(
                    self.path_selection_combo.count() - 1)
            else:
                self.path_selection_combo.setCurrentIndex(CWD)
        elif idx > EXTERNAL_PATH:
            item = self.path_selection_contents.item(idx)
            self.external_path = item.path

    def update_combos(self):
        self.search_text.lineEdit().returnPressed.emit()
        self.exclude_pattern.lineEdit().returnPressed.emit()

    def set_search_text(self, text):
        if text:
            self.search_text.add_text(text)
            self.search_text.lineEdit().selectAll()
        self.search_text.setFocus()

    def get_options(self, all=False):
        # Getting options
        self.search_text.lineEdit().setStyleSheet("")
        self.exclude_pattern.lineEdit().setStyleSheet("")

        utext = to_text_string(self.search_text.currentText())
        if not utext:
            return
        try:
            texts = [(utext.encode('utf-8'), 'utf-8')]
        except UnicodeEncodeError:
            texts = []
            for enc in self.supported_encodings:
                try:
                    texts.append((utext.encode(enc), enc))
                except UnicodeDecodeError:
                    pass
        text_re = self.edit_regexp.isChecked()
        exclude = to_text_string(self.exclude_pattern.currentText())
        exclude_re = self.exclude_regexp.isChecked()
        case_sensitive = self.case_button.isChecked()
        python_path = False

        if not case_sensitive:
            texts = [(text[0].lower(), text[1]) for text in texts]

        file_search = False
        selection_idx = self.path_selection_combo.currentIndex()
        if selection_idx == CWD:
            path = self.path
        elif selection_idx == PROJECT:
            path = self.project_path
        elif selection_idx == FILE_PATH:
            path = self.file_path
            file_search = True
        else:
            path = self.external_path

        # Finding text occurrences
        if not exclude_re:
            exclude = fnmatch.translate(exclude)
        else:
            try:
                exclude = re.compile(exclude)
            except Exception:
                exclude_edit = self.exclude_pattern.lineEdit()
                exclude_edit.setStyleSheet(self.REGEX_INVALID)
                return None

        if text_re:
            try:
                texts = [(re.compile(x[0]), x[1]) for x in texts]
            except Exception:
                self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID)
                return None

        if all:
            search_text = [
                to_text_string(self.search_text.itemText(index))
                for index in range(self.search_text.count())
            ]
            exclude = [
                to_text_string(self.exclude_pattern.itemText(index))
                for index in range(self.exclude_pattern.count())
            ]
            path_history = [
                to_text_string(self.path_selection_contents.item(index))
                for index in range(6, self.path_selection_combo.count())
            ]
            exclude_idx = self.exclude_pattern.currentIndex()
            more_options = self.more_options.isChecked()
            return (search_text, text_re, [], exclude, exclude_idx, exclude_re,
                    python_path, more_options, case_sensitive, path_history)
        else:
            return (path, file_search, exclude, texts, text_re, case_sensitive)

    @Slot()
    def select_directory(self):
        """Select directory"""
        self.parent().redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.path)
        if directory:
            directory = to_text_string(osp.abspath(to_text_string(directory)))
        self.parent().redirect_stdio.emit(True)
        return directory

    def set_directory(self, directory):
        self.path = to_text_string(osp.abspath(to_text_string(directory)))

    def set_project_path(self, path):
        self.project_path = to_text_string(osp.abspath(to_text_string(path)))
        item = self.path_selection_contents.item(PROJECT)
        item.setFlags(item.flags() | Qt.ItemIsEnabled)

    def disable_project_search(self):
        item = self.path_selection_contents.item(PROJECT)
        item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
        self.project_path = None

    def set_file_path(self, path):
        self.file_path = path

    def keyPressEvent(self, event):
        """Reimplemented to handle key events"""
        ctrl = event.modifiers() & Qt.ControlModifier
        shift = event.modifiers() & Qt.ShiftModifier
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.find.emit()
        elif event.key() == Qt.Key_F and ctrl and shift:
            # Toggle find widgets
            self.parent().toggle_visibility.emit(not self.isVisible())
        else:
            QWidget.keyPressEvent(self, event)
Ejemplo n.º 3
0
class FileAssociationsWidget(QWidget):
    """Widget to add applications association to file extensions."""

    # This allows validating a single extension entry or a list of comma
    # separated values (eg `*.json` or `*.json,*.txt,MANIFEST.in`)
    _EXTENSIONS_LIST_REGEX = (r'(?:(?:\*{1,1}|\w+)\.\w+)'
                              r'(?:,(?:\*{1,1}|\w+)\.\w+){0,20}')
    sig_data_changed = Signal(dict)

    def __init__(self, parent=None):
        """Widget to add applications association to file extensions."""
        super(FileAssociationsWidget, self).__init__(parent=parent)

        # Variables
        self._data = {}
        self._dlg_applications = None
        self._dlg_input = None
        self._regex = re.compile(self._EXTENSIONS_LIST_REGEX)

        # Widgets
        self.label = QLabel(
            _('Here you can associate which external applications you want'
              'to use to open specific file extensions <br> (e.g. .txt '
              'files with Notepad++ or .csv files with Excel).'))
        self.label_extensions = QLabel(_('File types:'))
        self.list_extensions = QListWidget()
        self.button_add = QPushButton(_('Add'))
        self.button_remove = QPushButton(_('Remove'))
        self.button_edit = QPushButton(_('Edit'))

        self.label_applications = QLabel(_('Associated applications:'))
        self.list_applications = QListWidget()
        self.button_add_application = QPushButton(_('Add'))
        self.button_remove_application = QPushButton(_('Remove'))
        self.button_default = QPushButton(_('Set default'))

        # Layout
        layout_extensions = QHBoxLayout()
        layout_extensions.addWidget(self.list_extensions, 4)

        layout_buttons_extensions = QVBoxLayout()
        layout_buttons_extensions.addWidget(self.button_add)
        layout_buttons_extensions.addWidget(self.button_remove)
        layout_buttons_extensions.addWidget(self.button_edit)
        layout_buttons_extensions.addStretch()

        layout_applications = QHBoxLayout()
        layout_applications.addWidget(self.list_applications, 4)

        layout_buttons_applications = QVBoxLayout()
        layout_buttons_applications.addWidget(self.button_add_application)
        layout_buttons_applications.addWidget(self.button_remove_application)
        layout_buttons_applications.addWidget(self.button_default)
        layout_buttons_applications.addStretch()

        layout_extensions.addLayout(layout_buttons_extensions, 2)
        layout_applications.addLayout(layout_buttons_applications, 2)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.label_extensions)
        layout.addLayout(layout_extensions)
        layout.addWidget(self.label_applications)
        layout.addLayout(layout_applications)

        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_association())
        self.button_remove.clicked.connect(self.remove_association)
        self.button_edit.clicked.connect(self.edit_association)
        self.button_add_application.clicked.connect(self.add_application)
        self.button_remove_application.clicked.connect(self.remove_application)
        self.button_default.clicked.connect(self.set_default_application)
        self.list_extensions.currentRowChanged.connect(self.update_extensions)
        self.list_extensions.itemDoubleClicked.connect(self.edit_association)
        self.list_applications.currentRowChanged.connect(
            self.update_applications)
        self._refresh()
        self._create_association_dialog()

    def _refresh(self):
        """Refresh the status of buttons on widget."""
        self.setUpdatesEnabled(False)
        for widget in [
                self.button_remove, self.button_add_application,
                self.button_edit, self.button_remove_application,
                self.button_default
        ]:
            widget.setDisabled(True)

        item = self.list_extensions.currentItem()
        if item:
            for widget in [
                    self.button_remove, self.button_add_application,
                    self.button_remove_application, self.button_edit
            ]:
                widget.setDisabled(False)
        self.update_applications()
        self.setUpdatesEnabled(True)

    def _add_association(self, value):
        """Add association helper."""
        # Check value is not pressent
        for row in range(self.list_extensions.count()):
            item = self.list_extensions.item(row)
            if item.text().strip() == value.strip():
                break
        else:
            item = QListWidgetItem(value)
            self.list_extensions.addItem(item)
            self.list_extensions.setCurrentItem(item)

        self._refresh()

    def _add_application(self, app_name, fpath):
        """Add application helper."""
        app_not_found_text = _(' (Application not found!)')
        for row in range(self.list_applications.count()):
            item = self.list_applications.item(row)
            # Ensure the actual name is checked without the `app not found`
            # additional text, in case app was not found
            item_text = item.text().replace(app_not_found_text, '').strip()
            if item and item_text == app_name:
                break
        else:
            icon = get_application_icon(fpath)

            if not (os.path.isfile(fpath) or os.path.isdir(fpath)):
                app_name += app_not_found_text

            item = QListWidgetItem(icon, app_name)
            self.list_applications.addItem(item)
            self.list_applications.setCurrentItem(item)

        if not (os.path.isfile(fpath) or os.path.isdir(fpath)):
            item.setToolTip(_('Application not found!'))

    def _update_extensions(self):
        """Update extensions list."""
        self.list_extensions.clear()
        for extension, _ in sorted(self._data.items()):
            self._add_association(extension)

        # Select first item
        self.list_extensions.setCurrentRow(0)
        self.update_extensions()
        self.update_applications()

    def _create_association_dialog(self):
        """Create input extension dialog and save it to for reuse."""
        self._dlg_input = InputTextDialog(
            self,
            title=_('File association'),
            label=(_('Enter new file extension. You can add several values '
                     'separated by commas.<br>Examples include:') +
                   '<ul><li><code>*.txt</code></li>' +
                   '<li><code>*.json,*,csv</code></li>' +
                   '<li><code>*.json,README.md</code></li></ul>'),
        )
        self._dlg_input.set_regex_validation(self._EXTENSIONS_LIST_REGEX)

    def load_values(self, data=None):
        """
        Load file associations data.

        Format {'*.ext': [['Application Name', '/path/to/app/executable']]}

        `/path/to/app/executable` is an executable app on mac and windows and
        a .desktop xdg file on linux.
        """
        self._data = data
        self._update_extensions()

    def add_association(self, value=None):
        """Add extension file association."""
        if value is None:
            text, ok_pressed = '', False
            self._dlg_input.set_text('')

            if self._dlg_input.exec_():
                text = self._dlg_input.text()
                ok_pressed = True
        else:
            match = self._regex.match(value)
            text, ok_pressed = value, bool(match)

        if ok_pressed:
            if text not in self._data:
                self._data[text] = []
                self._add_association(text)
                self.check_data_changed()

    def remove_association(self):
        """Remove extension file association."""
        if self._data:
            if self.current_extension:
                self._data.pop(self.current_extension)
                self._update_extensions()
                self._refresh()
                self.check_data_changed()

    def edit_association(self):
        """Edit text of current selected association."""
        old_text = self.current_extension
        self._dlg_input.set_text(old_text)

        if self._dlg_input.exec_():
            new_text = self._dlg_input.text()
            if old_text != new_text:
                values = self._data.pop(self.current_extension)
                self._data[new_text] = values
                self._update_extensions()
                self._refresh()
                for row in range(self.list_extensions.count()):
                    item = self.list_extensions.item(row)
                    if item.text() == new_text:
                        self.list_extensions.setCurrentItem(item)
                        break
                self.check_data_changed()

    def add_application(self):
        """Remove application to selected extension."""
        if self.current_extension:
            if self._dlg_applications is None:
                self._dlg_applications = ApplicationsDialog(self)

            self._dlg_applications.set_extension(self.current_extension)

            if self._dlg_applications.exec_():
                app_name = self._dlg_applications.application_name
                fpath = self._dlg_applications.application_path
                self._data[self.current_extension].append((app_name, fpath))
                self._add_application(app_name, fpath)
                self.check_data_changed()

    def remove_application(self):
        """Remove application from selected extension."""
        current_row = self.list_applications.currentRow()
        values = self._data.get(self.current_extension)
        if values and current_row != -1:
            values.pop(current_row)
            self.update_extensions()
            self.update_applications()
            self.check_data_changed()

    def set_default_application(self):
        """
        Set the selected item on the application list as default application.
        """
        current_row = self.list_applications.currentRow()
        if current_row != -1:
            values = self._data[self.current_extension]
            value = values.pop(current_row)
            values.insert(0, value)
            self._data[self.current_extension] = values
            self.update_extensions()
            self.check_data_changed()

    def update_extensions(self, row=None):
        """Update extensiosn list after additions or deletions."""
        self.list_applications.clear()
        for extension, values in self._data.items():
            if extension.strip() == self.current_extension:
                for (app_name, fpath) in values:
                    self._add_application(app_name, fpath)
                break
        self.list_applications.setCurrentRow(0)
        self._refresh()

    def update_applications(self, row=None):
        """Update application list after additions or deletions."""
        current_row = self.list_applications.currentRow()
        self.button_default.setEnabled(current_row != 0)

    def check_data_changed(self):
        """Check if data has changed and emit signal as needed."""
        self.sig_data_changed.emit(self._data)

    @property
    def current_extension(self):
        """Return the current selected extension text."""
        item = self.list_extensions.currentItem()
        if item:
            return item.text()

    @property
    def data(self):
        """Return the current file associations data."""
        return self._data.copy()
Ejemplo n.º 4
0
class MeasurementSettings(QWidget):
    """
    :type settings: Settings
    """
    def __init__(self, settings: PartSettings, parent=None):
        super().__init__(parent)
        self.chosen_element: Optional[MeasurementListWidgetItem] = None
        self.chosen_element_area: Optional[Tuple[AreaType, float]] = None
        self.settings = settings
        self.profile_list = QListWidget(self)
        self.profile_description = QTextEdit(self)
        self.profile_description.setReadOnly(True)
        self.profile_options = QListWidget()
        self.profile_options_chosen = QListWidget()
        self.measurement_area_choose = QEnumComboBox(enum_class=AreaType)
        self.per_component = QEnumComboBox(enum_class=PerComponent)
        self.power_num = QDoubleSpinBox()
        self.power_num.setDecimals(3)
        self.power_num.setRange(-100, 100)
        self.power_num.setValue(1)
        self.choose_butt = QPushButton("→", self)
        self.discard_butt = QPushButton("←", self)
        self.proportion_butt = QPushButton("Ratio", self)
        self.proportion_butt.setToolTip("Create proportion from two parameter")
        self.move_up = QPushButton("↑", self)
        self.move_down = QPushButton("↓", self)
        self.remove_button = QPushButton("Remove")
        self.save_butt = QPushButton("Save")
        self.save_butt.setToolTip(
            "Set name for set and choose at least one parameter")
        self.save_butt_with_name = QPushButton(
            "Save with custom parameters designation")
        self.save_butt_with_name.setToolTip(
            "Set name for set and choose at least one parameter")
        self.reset_butt = QPushButton("Clear")
        self.soft_reset_butt = QPushButton("Remove user parameters")
        self.profile_name = QLineEdit(self)

        self.delete_profile_butt = QPushButton("Delete ")
        self.export_profiles_butt = QPushButton("Export")
        self.import_profiles_butt = QPushButton("Import")
        self.edit_profile_butt = QPushButton("Edit")

        self.choose_butt.setDisabled(True)
        self.choose_butt.clicked.connect(self.choose_option)
        self.discard_butt.setDisabled(True)
        self.discard_butt.clicked.connect(self.discard_option)
        self.proportion_butt.setDisabled(True)
        self.proportion_butt.clicked.connect(self.proportion_action)
        self.save_butt.setDisabled(True)
        self.save_butt.clicked.connect(self.save_action)
        self.save_butt_with_name.setDisabled(True)
        self.save_butt_with_name.clicked.connect(self.named_save_action)
        self.profile_name.textChanged.connect(self.name_changed)
        self.move_down.setDisabled(True)
        self.move_down.clicked.connect(self.move_down_fun)
        self.move_up.setDisabled(True)
        self.move_up.clicked.connect(self.move_up_fun)
        self.remove_button.setDisabled(True)
        self.remove_button.clicked.connect(self.remove_element)
        self.reset_butt.clicked.connect(self.reset_action)
        self.soft_reset_butt.clicked.connect(self.soft_reset)
        self.delete_profile_butt.setDisabled(True)
        self.delete_profile_butt.clicked.connect(self.delete_profile)
        self.export_profiles_butt.clicked.connect(
            self.export_measurement_profiles)
        self.import_profiles_butt.clicked.connect(
            self.import_measurement_profiles)
        self.edit_profile_butt.clicked.connect(self.edit_profile)

        self.profile_list.itemSelectionChanged.connect(self.profile_chosen)
        self.profile_options.itemSelectionChanged.connect(
            self.create_selection_changed)
        self.profile_options_chosen.itemSelectionChanged.connect(
            self.create_selection_chosen_changed)
        self.settings.measurement_profiles_changed.connect(
            self._refresh_profiles)

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Measurement set:"))
        profile_layout = QHBoxLayout()
        profile_layout.addWidget(self.profile_list)
        profile_layout.addWidget(self.profile_description)
        profile_buttons_layout = QHBoxLayout()
        profile_buttons_layout.addWidget(self.delete_profile_butt)
        profile_buttons_layout.addWidget(self.export_profiles_butt)
        profile_buttons_layout.addWidget(self.import_profiles_butt)
        profile_buttons_layout.addWidget(self.edit_profile_butt)
        profile_buttons_layout.addStretch()
        layout.addLayout(profile_layout)
        layout.addLayout(profile_buttons_layout)
        heading_layout = QHBoxLayout()
        # heading_layout.addWidget(QLabel("Create profile"), 1)
        heading_layout.addWidget(h_line(), 6)
        layout.addLayout(heading_layout)
        name_layout = QHBoxLayout()
        name_layout.addWidget(QLabel("Set name:"))
        name_layout.addWidget(self.profile_name)
        name_layout.addStretch()
        name_layout.addWidget(QLabel("Per component:"))
        name_layout.addWidget(self.per_component)
        name_layout.addWidget(QLabel("Area:"))
        name_layout.addWidget(self.measurement_area_choose)
        name_layout.addWidget(QLabel("to power:"))
        name_layout.addWidget(self.power_num)
        layout.addLayout(name_layout)
        create_layout = QHBoxLayout()
        create_layout.addWidget(self.profile_options)
        butt_op_layout = QVBoxLayout()
        butt_op_layout.addStretch()
        butt_op_layout.addWidget(self.choose_butt)
        butt_op_layout.addWidget(self.discard_butt)
        butt_op_layout.addWidget(self.proportion_butt)
        butt_op_layout.addWidget(self.reset_butt)
        butt_op_layout.addStretch()
        create_layout.addLayout(butt_op_layout)
        create_layout.addWidget(self.profile_options_chosen)
        butt_move_layout = QVBoxLayout()
        butt_move_layout.addStretch()
        butt_move_layout.addWidget(self.move_up)
        butt_move_layout.addWidget(self.move_down)
        butt_move_layout.addWidget(self.remove_button)
        butt_move_layout.addStretch()
        create_layout.addLayout(butt_move_layout)
        layout.addLayout(create_layout)
        save_butt_layout = QHBoxLayout()
        save_butt_layout.addWidget(self.soft_reset_butt)
        save_butt_layout.addStretch()
        save_butt_layout.addWidget(self.save_butt)
        save_butt_layout.addWidget(self.save_butt_with_name)
        layout.addLayout(save_butt_layout)
        self.setLayout(layout)

        for profile in MEASUREMENT_DICT.values():
            help_text = profile.get_description()
            lw = MeasurementListWidgetItem(profile.get_starting_leaf())
            lw.setToolTip(help_text)
            self.profile_options.addItem(lw)
        self._refresh_profiles()

    def _refresh_profiles(self):
        item = self.profile_list.currentItem()
        items = list(self.settings.measurement_profiles.keys())
        try:
            index = items.index(item.text())
        except (ValueError, AttributeError):
            index = -1

        self.profile_list.clear()
        self.profile_list.addItems(items)
        self.profile_list.setCurrentRow(index)

        if self.profile_list.count() == 0:
            self.export_profiles_butt.setDisabled(True)
        else:
            self.export_profiles_butt.setEnabled(True)

    def remove_element(self):
        elem = self.profile_options_chosen.currentItem()
        if elem is None:
            return
        index = self.profile_options_chosen.currentRow()
        self.profile_options_chosen.takeItem(index)
        if self.profile_options_chosen.count() == 0:
            self.move_down.setDisabled(True)
            self.move_up.setDisabled(True)
            self.remove_button.setDisabled(True)
            self.discard_butt.setDisabled(True)
            self.save_butt.setDisabled(True)
            self.save_butt_with_name.setDisabled(True)

    def delete_profile(self):
        item = self.profile_list.currentItem()
        del self.settings.measurement_profiles[str(item.text())]

    def profile_chosen(self):
        self.delete_profile_butt.setEnabled(True)
        if self.profile_list.count() == 0:
            self.profile_description.setText("")
            return
        item = self.profile_list.currentItem()
        if item is None:
            self.profile_description.setText("")
            return
        profile = self.settings.measurement_profiles[item.text()]
        self.profile_description.setText(str(profile))

    def create_selection_changed(self):
        self.choose_butt.setEnabled(True)
        self.proportion_butt.setEnabled(True)

    def proportion_action(self):
        # TODO use get_parameters
        if self.chosen_element is None:
            item = self.profile_options.currentItem()
            self.chosen_element_area = self.get_parameters(
                deepcopy(item.stat),
                self.measurement_area_choose.currentEnum(),
                self.per_component.currentEnum(),
                self.power_num.value(),
            )
            if self.chosen_element_area is None:
                return
            self.chosen_element = item
            item.setIcon(QIcon(os.path.join(icons_dir, "task-accepted.png")))
        elif (self.profile_options.currentItem() == self.chosen_element
              and self.measurement_area_choose.currentEnum()
              == self.chosen_element_area.area
              and self.per_component.currentEnum()
              == self.chosen_element_area.per_component):
            self.chosen_element.setIcon(QIcon())
            self.chosen_element = None
        else:
            item: MeasurementListWidgetItem = self.profile_options.currentItem(
            )
            leaf = self.get_parameters(
                deepcopy(item.stat),
                self.measurement_area_choose.currentEnum(),
                self.per_component.currentEnum(),
                self.power_num.value(),
            )
            if leaf is None:
                return
            lw = MeasurementListWidgetItem(
                Node(op="/", left=self.chosen_element_area, right=leaf))
            lw.setToolTip("User defined")
            self._add_option(lw)
            self.chosen_element.setIcon(QIcon())
            self.chosen_element = None
            self.chosen_element_area = None

    def _add_option(self, item: MeasurementListWidgetItem):
        for i in range(self.profile_options_chosen.count()):
            if item.text() == self.profile_options_chosen.item(i).text():
                return
        self.profile_options_chosen.addItem(item)
        if self.good_name():
            self.save_butt.setEnabled(True)
            self.save_butt_with_name.setEnabled(True)
        if self.profile_options.count() == 0:
            self.choose_butt.setDisabled(True)

    def create_selection_chosen_changed(self):
        # print(self.profile_options_chosen.count())
        self.remove_button.setEnabled(True)
        if self.profile_options_chosen.count() == 0:
            self.move_down.setDisabled(True)
            self.move_up.setDisabled(True)
            self.remove_button.setDisabled(True)
            return
        self.discard_butt.setEnabled(True)
        if self.profile_options_chosen.currentRow() != 0:
            self.move_up.setEnabled(True)
        else:
            self.move_up.setDisabled(True)
        if self.profile_options_chosen.currentRow(
        ) != self.profile_options_chosen.count() - 1:
            self.move_down.setEnabled(True)
        else:
            self.move_down.setDisabled(True)

    def good_name(self):
        return str(self.profile_name.text()).strip() != ""

    def move_down_fun(self):
        row = self.profile_options_chosen.currentRow()
        item = self.profile_options_chosen.takeItem(row)
        self.profile_options_chosen.insertItem(row + 1, item)
        self.profile_options_chosen.setCurrentRow(row + 1)
        self.create_selection_chosen_changed()

    def move_up_fun(self):
        row = self.profile_options_chosen.currentRow()
        item = self.profile_options_chosen.takeItem(row)
        self.profile_options_chosen.insertItem(row - 1, item)
        self.profile_options_chosen.setCurrentRow(row - 1)
        self.create_selection_chosen_changed()

    def name_changed(self):
        if self.good_name() and self.profile_options_chosen.count() > 0:
            self.save_butt.setEnabled(True)
            self.save_butt_with_name.setEnabled(True)
        else:
            self.save_butt.setDisabled(True)
            self.save_butt_with_name.setDisabled(True)

    def form_dialog(self, arguments):
        return FormDialog(arguments, settings=self.settings, parent=self)

    def get_parameters(self, node: Union[Node, Leaf], area: AreaType,
                       component: PerComponent, power: float):
        if isinstance(node, Node):
            return node
        node = node.replace_(power=power)
        if node.area is None:
            node = node.replace_(area=area)
        if node.per_component is None:
            node = node.replace_(per_component=component)
        with suppress(KeyError):
            arguments = MEASUREMENT_DICT[str(node.name)].get_fields()
            if len(arguments) > 0 and len(node.dict) == 0:
                dial = self.form_dialog(arguments)
                if dial.exec_():
                    node = node._replace(dict=dial.get_values())
                else:
                    return
        return node

    def choose_option(self):
        selected_item = self.profile_options.currentItem()
        # selected_row = self.profile_options.currentRow()
        if not isinstance(selected_item, MeasurementListWidgetItem):
            raise ValueError(
                f"Current item (type: {type(selected_item)} is not instance of MeasurementListWidgetItem"
            )
        node = deepcopy(selected_item.stat)
        # noinspection PyTypeChecker
        node = self.get_parameters(node,
                                   self.measurement_area_choose.currentEnum(),
                                   self.per_component.currentEnum(),
                                   self.power_num.value())
        if node is None:
            return
        lw = MeasurementListWidgetItem(node)
        lw.setToolTip(selected_item.toolTip())
        self._add_option(lw)

    def discard_option(self):
        selected_item: MeasurementListWidgetItem = self.profile_options_chosen.currentItem(
        )
        #  selected_row = self.profile_options_chosen.currentRow()
        lw = MeasurementListWidgetItem(deepcopy(selected_item.stat))
        lw.setToolTip(selected_item.toolTip())
        self.create_selection_chosen_changed()
        for i in range(self.profile_options.count()):
            if lw.text() == self.profile_options.item(i).text():
                return
        self.profile_options.addItem(lw)

    def edit_profile(self):
        item = self.profile_list.currentItem()
        if item is None:
            return
        profile = self.settings.measurement_profiles[str(item.text())]
        self.profile_options_chosen.clear()
        self.profile_name.setText(item.text())
        for ch in profile.chosen_fields:
            self.profile_options_chosen.addItem(
                MeasurementListWidgetItem(ch.calculation_tree))
        # self.gauss_img.setChecked(profile.use_gauss_image)
        self.save_butt.setEnabled(True)
        self.save_butt_with_name.setEnabled(True)

    def save_action(self):
        if self.profile_name.text() in self.settings.measurement_profiles:
            ret = QMessageBox.warning(
                self,
                "Profile exist",
                "Profile exist\nWould you like to overwrite it?",
                QMessageBox.No | QMessageBox.Yes,
            )
            if ret == QMessageBox.No:
                return
        selected_values = []
        for i in range(self.profile_options_chosen.count()):
            element: MeasurementListWidgetItem = self.profile_options_chosen.item(
                i)
            selected_values.append(
                MeasurementEntry(element.text(), element.stat))
        stat_prof = MeasurementProfile(self.profile_name.text(),
                                       selected_values)
        self.settings.measurement_profiles[stat_prof.name] = stat_prof
        self.settings.dump()
        self.export_profiles_butt.setEnabled(True)

    def named_save_action(self):
        if self.profile_name.text() in self.settings.measurement_profiles:
            ret = QMessageBox.warning(
                self,
                "Profile exist",
                "Profile exist\nWould you like to overwrite it?",
                QMessageBox.No | QMessageBox.Yes,
            )
            if ret == QMessageBox.No:
                return
        selected_values = []
        for i in range(self.profile_options_chosen.count()):
            txt = str(self.profile_options_chosen.item(i).text())
            selected_values.append((txt, str, txt))
        val_dialog = MultipleInput("Set fields name",
                                   list(selected_values),
                                   parent=self)
        if val_dialog.exec_():
            selected_values = []
            for i in range(self.profile_options_chosen.count()):
                element: MeasurementListWidgetItem = self.profile_options_chosen.item(
                    i)
                selected_values.append(
                    MeasurementEntry(val_dialog.result[element.text()],
                                     element.stat))
            stat_prof = MeasurementProfile(self.profile_name.text(),
                                           selected_values)
            self.settings.measurement_profiles[stat_prof.name] = stat_prof
            self.export_profiles_butt.setEnabled(True)

    def reset_action(self):
        self.profile_options.clear()
        self.profile_options_chosen.clear()
        self.profile_name.setText("")
        self.save_butt.setDisabled(True)
        self.save_butt_with_name.setDisabled(True)
        self.move_down.setDisabled(True)
        self.move_up.setDisabled(True)
        self.proportion_butt.setDisabled(True)
        self.choose_butt.setDisabled(True)
        self.discard_butt.setDisabled(True)
        for profile in MEASUREMENT_DICT.values():
            help_text = profile.get_description()
            lw = MeasurementListWidgetItem(profile.get_starting_leaf())
            lw.setToolTip(help_text)
            self.profile_options.addItem(lw)

    def soft_reset(self):
        # TODO rim should not be removed
        shift = 0
        for i in range(self.profile_options.count()):
            item = self.profile_options.item(i - shift)
            if str(item.text()) not in MEASUREMENT_DICT:
                self.profile_options.takeItem(i - shift)
                if item == self.chosen_element:
                    self.chosen_element = None
                del item
                shift += 1
        self.create_selection_changed()

    def export_measurement_profiles(self):
        exp = ExportDialog(self.settings.measurement_profiles,
                           StringViewer,
                           parent=self)
        if not exp.exec_():
            return
        dial = PSaveDialog(
            "Measurement profile (*.json)",
            settings=self.settings,
            path="io.export_directory",
            caption="Export settings profiles",
        )
        dial.selectFile("measurements_profile.json")

        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            data = {
                x: self.settings.measurement_profiles[x]
                for x in exp.get_export_list()
            }
            with open(file_path, "w", encoding="utf-8") as ff:
                json.dump(data,
                          ff,
                          cls=self.settings.json_encoder_class,
                          indent=2)

    def import_measurement_profiles(self):
        dial = PLoadDialog(
            "Measurement profile (*.json)",
            settings=self.settings,
            path="io.export_directory",
            caption="Import settings profiles",
            parent=self,
        )
        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            stat, err = self.settings.load_part(file_path)
            if err:
                QMessageBox.warning(
                    self, "Import error",
                    "error during importing, part of data were filtered.")
            measurement_dict = self.settings.measurement_profiles
            imp = ImportDialog(stat, measurement_dict, StringViewer)
            if not imp.exec_():
                return
            for original_name, final_name in imp.get_import_list():
                measurement_dict[final_name] = stat[original_name]
            self.settings.dump()
Ejemplo n.º 5
0
class CustomizePlotDialog(QDialog):
    applySettings = Signal()
    undoSettings = Signal()
    redoSettings = Signal()
    resetSettings = Signal()
    copySettings = Signal(str)
    copySettingsToOthers = Signal(list)

    def __init__(self, title, parent=None, key=''):
        QDialog.__init__(self, parent)
        self.setWindowTitle(title)

        self._ert = ERT.ert
        """:type: res.enkf.enkf_main.EnKFMain"""

        self.key_manager = self._ert.getKeyManager()
        """:type: res.enkf.key_manager.KeyManager """

        self.current_key = key

        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)

        self._tab_map = {}
        self._tab_order = []

        layout = QVBoxLayout()

        self._tabs = QTabWidget()
        layout.addWidget(self._tabs)
        layout.setSizeConstraint(QLayout.SetFixedSize)  # not resizable!!!

        self._button_layout = QHBoxLayout()

        self._reset_button = QToolButton()
        self._reset_button.setIcon(resourceIcon("update.png"))
        self._reset_button.setToolTip("Reset all settings back to default")
        self._reset_button.clicked.connect(self.resetSettings)

        self._undo_button = QToolButton()
        self._undo_button.setIcon(resourceIcon("undo.png"))
        self._undo_button.setToolTip("Undo")
        self._undo_button.clicked.connect(self.undoSettings)

        self._redo_button = QToolButton()
        self._redo_button.setIcon(resourceIcon("redo.png"))
        self._redo_button.setToolTip("Redo")
        self._redo_button.clicked.connect(self.redoSettings)
        self._redo_button.setEnabled(False)

        self._copy_from_button = QToolButton()
        self._copy_from_button.setIcon(resourceIcon("copy_from.png"))
        self._copy_from_button.setToolTip("Copy settings from another key")
        self._copy_from_button.setPopupMode(QToolButton.InstantPopup)
        self._copy_from_button.setEnabled(False)

        self._copy_to_button = QToolButton()
        self._copy_to_button.setIcon(resourceIcon("copy_to.png"))
        self._copy_to_button.setToolTip(
            "Copy current plot settings to other keys")
        self._copy_to_button.setPopupMode(QToolButton.InstantPopup)
        self._copy_to_button.clicked.connect(self.initiateCopyStyleToDialog)
        self._copy_to_button.setEnabled(True)

        tool_menu = QMenu(self._copy_from_button)
        self._popup_list = QListWidget(tool_menu)
        self._popup_list.setSortingEnabled(True)
        self._popup_list.itemClicked.connect(self.keySelected)
        action = QWidgetAction(tool_menu)
        action.setDefaultWidget(self._popup_list)
        tool_menu.addAction(action)
        self._copy_from_button.setMenu(tool_menu)

        self._apply_button = QPushButton("Apply")
        self._apply_button.setToolTip("Apply the new settings")
        self._apply_button.clicked.connect(self.applySettings)
        self._apply_button.setDefault(True)

        self._close_button = QPushButton("Close")
        self._close_button.setToolTip("Hide this dialog")
        self._close_button.clicked.connect(self.hide)

        self._button_layout.addWidget(self._reset_button)
        self._button_layout.addStretch()
        self._button_layout.addWidget(self._undo_button)
        self._button_layout.addWidget(self._redo_button)
        self._button_layout.addWidget(self._copy_from_button)
        self._button_layout.addWidget(self._copy_to_button)
        self._button_layout.addStretch()
        self._button_layout.addWidget(self._apply_button)
        self._button_layout.addWidget(self._close_button)

        layout.addStretch()
        layout.addLayout(self._button_layout)

        self.setLayout(layout)

    def initiateCopyStyleToDialog(self):
        all_other_keys = [
            k for k in self.key_manager.allDataTypeKeys()
            if k != self.current_key
        ]
        dialog = CopyStyleToDialog(self, self.current_key, all_other_keys)
        if dialog.exec_():
            self.copySettingsToOthers.emit(dialog.getSelectedKeys())

    def addCopyableKey(self, key):
        self._popup_list.addItem(key)

    def keySelected(self, list_widget_item):
        self.copySettings.emit(str(list_widget_item.text()))

    def currentPlotKeyChanged(self, new_key):
        self.current_key = new_key

    def keyPressEvent(self, q_key_event):
        if q_key_event.key() == Qt.Key_Escape:
            self.hide()
        else:
            QDialog.keyPressEvent(self, q_key_event)

    def addTab(self, attribute_name, title, widget):
        self._tabs.addTab(widget, title)
        self._tab_map[attribute_name] = widget
        self._tab_order.append(attribute_name)

    def __getitem__(self, item):
        """ @rtype: ert_gui.tools.plot.customize.customization_view.CustomizationView """
        return self._tab_map[item]

    def __iter__(self):
        for attribute_name in self._tab_order:
            yield self._tab_map[attribute_name]

    def setUndoRedoCopyState(self, undo, redo, copy=False):
        self._undo_button.setEnabled(undo)
        self._redo_button.setEnabled(redo)
        self._copy_from_button.setEnabled(copy)
Ejemplo n.º 6
0
class PathManager(QDialog):
    redirect_stdio = Signal(bool)

    def __init__(self,
                 parent=None,
                 pathlist=None,
                 ro_pathlist=None,
                 not_active_pathlist=None,
                 sync=True):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        assert isinstance(pathlist, list)
        self.pathlist = pathlist
        if not_active_pathlist is None:
            not_active_pathlist = []
        self.not_active_pathlist = not_active_pathlist
        if ro_pathlist is None:
            ro_pathlist = []
        self.ro_pathlist = ro_pathlist

        self.last_path = getcwd_or_home()

        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)

        self.selection_widgets = []

        layout = QVBoxLayout()
        self.setLayout(layout)

        top_layout = QHBoxLayout()
        layout.addLayout(top_layout)
        self.toolbar_widgets1 = self.setup_top_toolbar(top_layout)

        self.listwidget = QListWidget(self)
        self.listwidget.currentRowChanged.connect(self.refresh)
        self.listwidget.itemChanged.connect(self.update_not_active_pathlist)
        layout.addWidget(self.listwidget)

        bottom_layout = QHBoxLayout()
        layout.addLayout(bottom_layout)
        self.sync_button = None
        self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync)

        # Buttons configuration
        bbox = QDialogButtonBox(QDialogButtonBox.Close)
        bbox.rejected.connect(self.reject)
        bottom_layout.addWidget(bbox)

        self.update_list()
        self.refresh()

    @property
    def active_pathlist(self):
        return [
            path for path in self.pathlist
            if path not in self.not_active_pathlist
        ]

    def _add_widgets_to_layout(self, layout, widgets):
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            layout.addWidget(widget)

    def setup_top_toolbar(self, layout):
        toolbar = []
        movetop_button = create_toolbutton(
            self,
            text=_("Move to top"),
            icon=ima.icon('2uparrow'),
            triggered=lambda: self.move_to(absolute=0),
            text_beside_icon=True)
        toolbar.append(movetop_button)
        moveup_button = create_toolbutton(
            self,
            text=_("Move up"),
            icon=ima.icon('1uparrow'),
            triggered=lambda: self.move_to(relative=-1),
            text_beside_icon=True)
        toolbar.append(moveup_button)
        movedown_button = create_toolbutton(
            self,
            text=_("Move down"),
            icon=ima.icon('1downarrow'),
            triggered=lambda: self.move_to(relative=1),
            text_beside_icon=True)
        toolbar.append(movedown_button)
        movebottom_button = create_toolbutton(
            self,
            text=_("Move to bottom"),
            icon=ima.icon('2downarrow'),
            triggered=lambda: self.move_to(absolute=1),
            text_beside_icon=True)
        toolbar.append(movebottom_button)
        self.selection_widgets.extend(toolbar)
        self._add_widgets_to_layout(layout, toolbar)
        return toolbar

    def setup_bottom_toolbar(self, layout, sync=True):
        toolbar = []
        add_button = create_toolbutton(self,
                                       text=_('Add path'),
                                       icon=ima.icon('edit_add'),
                                       triggered=self.add_path,
                                       text_beside_icon=True)
        toolbar.append(add_button)
        remove_button = create_toolbutton(self,
                                          text=_('Remove path'),
                                          icon=ima.icon('edit_remove'),
                                          triggered=self.remove_path,
                                          text_beside_icon=True)
        toolbar.append(remove_button)
        self.selection_widgets.append(remove_button)
        self._add_widgets_to_layout(layout, toolbar)
        layout.addStretch(1)
        if os.name == 'nt' and sync:
            self.sync_button = create_toolbutton(
                self,
                text=_("Synchronize..."),
                icon=ima.icon('fileimport'),
                triggered=self.synchronize,
                tip=_("Synchronize Spyder's path list with PYTHONPATH "
                      "environment variable"),
                text_beside_icon=True)
            layout.addWidget(self.sync_button)
        return toolbar

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms
        """
        answer = QMessageBox.question(
            self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
              "<b>PYTHONPATH</b> environment variable for current user, "
              "allowing you to run your Python modules outside Spyder "
              "without having to configure sys.path. "
              "<br>Do you want to clear contents of PYTHONPATH before "
              "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False
        from spyder.utils.environ import (get_user_env, set_user_env,
                                          listdict2envdict)
        env = get_user_env()
        if remove:
            ppath = self.active_pathlist + self.ro_pathlist
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]
            ppath = [
                path for path in ppath
                if path not in (self.active_pathlist + self.ro_pathlist)
            ]
            ppath.extend(self.active_pathlist + self.ro_pathlist)
        env['PYTHONPATH'] = ppath
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_list(self):
        """Return path list (does not include the read-only path list)"""
        return self.pathlist

    def update_not_active_pathlist(self, item):
        path = item.text()
        if bool(item.checkState()) is True:
            self.remove_from_not_active_pathlist(path)
        else:
            self.add_to_not_active_pathlist(path)

    def add_to_not_active_pathlist(self, path):
        if path not in self.not_active_pathlist:
            self.not_active_pathlist.append(path)

    def remove_from_not_active_pathlist(self, path):
        if path in self.not_active_pathlist:
            self.not_active_pathlist.remove(path)

    def update_list(self):
        """Update path list"""
        self.listwidget.clear()
        for name in self.pathlist + self.ro_pathlist:
            item = QListWidgetItem(name)
            item.setIcon(ima.icon('DirClosedIcon'))
            if name in self.ro_pathlist:
                item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            elif name in self.not_active_pathlist:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Unchecked)
            else:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            self.listwidget.addItem(item)
        self.refresh()

    def refresh(self, row=None):
        """Refresh widget"""
        for widget in self.selection_widgets:
            widget.setEnabled(self.listwidget.currentItem() is not None)
        not_empty = self.listwidget.count() > 0
        if self.sync_button is not None:
            self.sync_button.setEnabled(not_empty)

    def move_to(self, absolute=None, relative=None):
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = len(self.pathlist) - 1
            else:
                new_index = 0
        else:
            new_index = index + relative
        new_index = max(0, min(len(self.pathlist) - 1, new_index))
        path = self.pathlist.pop(index)
        self.pathlist.insert(new_index, path)
        self.update_list()
        self.listwidget.setCurrentRow(new_index)

    @Slot()
    def remove_path(self):
        answer = QMessageBox.warning(
            self, _("Remove path"),
            _("Do you really want to remove selected path?"),
            QMessageBox.Yes | QMessageBox.No)
        if answer == QMessageBox.Yes:
            self.pathlist.pop(self.listwidget.currentRow())
            self.remove_from_not_active_pathlist(
                self.listwidget.currentItem().text())
            self.update_list()

    @Slot()
    def add_path(self):
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.last_path)
        self.redirect_stdio.emit(True)
        if directory:
            directory = osp.abspath(directory)
            self.last_path = directory
            if directory in self.pathlist:
                item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
                item.setCheckState(Qt.Checked)
                answer = QMessageBox.question(
                    self, _("Add path"),
                    _("This directory is already included in Spyder path "
                      "list.<br>Do you want to move it to the top of "
                      "the list?"), QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.pathlist.remove(directory)
                else:
                    return
            self.pathlist.insert(0, directory)
            self.update_list()
Ejemplo n.º 7
0
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int)
    sig_close_file = Signal(int)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]

    def __init__(self, parent, tabs, data):
        QDialog.__init__(self, parent)

        # Variables
        self.tabs = tabs                  # Editor stack tabs
        self.data = data                  # Editor data
        self.mode = self.FILE_MODE        # By default start in this mode
        self.initial_cursors = None       # {fullpath: QCursor}
        self.initial_path = None          # Fullpath of initial active editor
        self.initial_editor = None        # Initial active editor
        self.line_number = None           # Selected line number in filer
        self.is_visible = False           # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = FilesFilterLine(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

        # Setup
        self.save_initial_state()
        self.set_dialog_position()
        self.setup()

    # --- Properties
    @property
    def editors(self):
        return [self.tabs.widget(index) for index in range(self.tabs.count())]

    @property
    def line_count(self):
        return [editor.get_line_count() for editor in self.editors]

    @property
    def save_status(self):
        return [getattr(td, 'newly_created', False) for td in self.data]

    @property
    def paths(self):
        return [getattr(td, 'filename', None) for td in self.data]

    @property
    def filenames(self):
        return [os.path.basename(getattr(td, 'filename',
                                         None)) for td in self.data]

    @property
    def current_path(self):
        return self.paths_by_editor[self.get_editor()]

    @property
    def paths_by_editor(self):
        return dict(zip(self.editors, self.paths))

    @property
    def editors_by_path(self):
        return dict(zip(self.paths, self.editors))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, _str):
        self.edit.setText(_str)

    def save_initial_state(self):
        """Saves initial cursors and initial active editor."""
        paths = self.paths
        self.initial_editor = self.get_editor()
        self.initial_cursors = {}

        for i, editor in enumerate(self.editors):
            if editor is self.initial_editor:
                self.initial_path = paths[i]
            # This try is needed to make the fileswitcher work with 
            # plugins that does not have a textCursor.
            try:
                self.initial_cursors[paths[i]] = editor.textCursor()
            except AttributeError:
                pass

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        editors = self.editors_by_path

        if not self.edit.clicked_outside:
            for path in self.initial_cursors:
                cursor = self.initial_cursors[path]
                if path in editors:
                    self.set_editor_cursor(editors[path], cursor)

            if self.initial_editor in self.paths_by_editor:
                index = self.paths.index(self.initial_path)
                self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog in the center of the editor."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup

        left = parent.geometry().width()/2 - width/2
        top = 0
        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        # Note: the +1 pixel on the top makes it look better
        self.move(left, top + self.tabs.tabBar().geometry().height() + 1)

    def fix_size(self, content, extra=50):
        """
        Adjusts the width and height of the file switcher,
        based on its content.
        """
        # Update size of dialog based on longest shortened path
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()

            # Max width
            max_width = max([fm.width(s) * 1.3 for s in strings])
            self.list.setMinimumWidth(max_width + extra)

            # Max height
            if len(strings) < 8:
                max_entries = len(strings)
            else:
                max_entries = 8
            max_height = fm.height() * max_entries * 2.5
            self.list.setMinimumHeight(max_height)

            # Set position according to size
            self.set_dialog_position()

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        self.select_row(+1)

    # --- Helper methods: Editor
    def get_editor(self, index=None, path=None):
        """Get editor by index or path.
        
        If no path or index specified the current active editor is returned
        """
        if index:
            return self.tabs.widget(index)
        elif path:
            return self.tabs.widget(index)
        else:
            return self.tabs.currentWidget()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            editor = self.get_editor()
            editor.go_to_line(min(line_number, editor.get_line_count()))

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the list of symbols present in the file."""
        try:
            oedata = self.get_editor().get_outlineexplorer_data()
        except AttributeError:
            oedata = {}
        return oedata

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.sig_goto_file.emit(stack_index)
                    self.goto_line(self.line_number)
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
        else:
            line_number = None

        # Get all available filenames and get the scores for "fuzzy" matching
        scores = get_search_scores(filter_text, self.filenames,
                                   template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text.replace('&', '') + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                text_item += u"<br><i>{0:}</i>".format(short_paths[index])
                results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        for result in sorted(results):
            index = result[1]
            text = result[-1]
            path = paths[index]
            item = QListWidgetItem(ima.icon('FileIcon'), text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = True

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)
        self.fix_size(short_paths, extra=200)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text,
                                fold_level, icons[index], token))

        template = '{0}{1}'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;'*(fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template.format(fold_space, rich_text)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = False

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

        # Update list size
        self.fix_size(lines, extra=125)

    def setup(self):
        """Setup list widget content."""
        if not self.tabs.count():
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)
Ejemplo n.º 8
0
class SelectPackages_Dialog(QDialog):
    """
    Now only used to state new paths to required packages that are not valid anymore.
    The 'auto import' feature is therefore probably useless by now.
    """
    def __init__(self, parent, required_packages: [str]):
        super(SelectPackages_Dialog, self).__init__(parent)

        self.file_paths = []
        self.required_packages: [str] = required_packages
        self.packages = []

        self.setLayout(QVBoxLayout())

        self.layout().addWidget(
            QLabel(
                'You need to select the locations of the following required node packages'
            ))

        # package lists
        required_packages_list_widget = QListWidget()
        for p_name in required_packages:
            required_packages_list_widget.addItem(QListWidgetItem(p_name))

        selected_items_widget = QWidget()
        selected_items_widget.setLayout(QVBoxLayout())
        self.selected_packages_list_widget = QListWidget()
        selected_items_widget.layout().addWidget(
            self.selected_packages_list_widget)

        auto_import_button = QPushButton('auto import')
        auto_import_button.setFocus()
        auto_import_button.clicked.connect(self.auto_import_button_clicked)
        selected_items_widget.layout().addWidget(auto_import_button)

        add_package_button = QPushButton('add')
        add_package_button.clicked.connect(self.add_package_button_clicked)
        selected_items_widget.layout().addWidget(add_package_button)

        clear_package_list_button = QPushButton('clear')
        clear_package_list_button.clicked.connect(
            self.clear_selected_packages_list)
        selected_items_widget.layout().addWidget(clear_package_list_button)

        finished_button = QPushButton('OK')
        finished_button.clicked.connect(self.finished_button_clicked)
        selected_items_widget.layout().addWidget(finished_button)

        packages_lists_widget = QWidget()
        packages_lists_widget.setLayout(QHBoxLayout())
        packages_lists_widget.layout().addWidget(required_packages_list_widget)
        packages_lists_widget.layout().addWidget(selected_items_widget)

        self.layout().addWidget(packages_lists_widget)

        self.setWindowTitle('select required packages')

    def auto_import_button_clicked(self):
        packages_dir = '../packages'
        folders_list = [
            basename(x[0]) for x in os.walk(packages_dir)
            if basename(x[0]) in self.required_packages
        ]

        required_packages = self.required_packages.copy()

        for pkg_name in required_packages:

            p_dir = join(packages_dir, pkg_name)
            p_file = normpath(join(p_dir, 'nodes.py'))

            if pkg_name in folders_list and \
                    'nodes.py' in os.listdir(p_dir) and \
                    p_file not in self.file_paths:

                self.file_paths.append(p_file)
                self.packages.append(NodesPackage(dirname(p_file)))

        self.rebuild_selected_packages_list_widget()

        self.clean_packages_list()

        if self.all_required_packages_selected():
            self.finished()

    def add_package_button_clicked(self):

        file_names = QFileDialog.getOpenFileNames(
            self, 'select package file (nodes.py)', '../packages', '(*.py)')[0]

        for file_name in file_names:
            try:
                # simply try to open the file to make sure it's valid
                f = open(file_name)
                f.close()

                self.file_paths.append(file_name)
                self.packages.append(NodesPackage(dirname(file_name)))

            except FileNotFoundError:
                pass

        self.rebuild_selected_packages_list_widget()

    def rebuild_selected_packages_list_widget(self):
        # remove all items
        self.selected_packages_list_widget.clear()

        for f in self.file_paths:
            file_item = QListWidgetItem()
            file_item.setText(f)
            self.selected_packages_list_widget.addItem(file_item)

    def clear_selected_packages_list(self):
        self.file_paths.clear()
        self.packages.clear()

        self.rebuild_selected_packages_list_widget()

    def finished_button_clicked(self):
        self.clean_packages_list()
        if self.all_required_packages_selected():
            self.finished()

    def clean_packages_list(self):
        """remove duplicates from self.file_paths"""

        files_dict = {}

        for p in self.file_paths:
            package_name = basename(dirname(p))
            files_dict[package_name] = p

        self.file_paths = list(files_dict.values())

        self.rebuild_selected_packages_list_widget()

    def all_required_packages_selected(self):

        selected_packages = [
            basename(normpath(np.directory)) for np in self.packages
        ]

        # search for missing packages
        for p in self.required_packages:
            if p not in selected_packages:
                return False

        return True

    def finished(self):
        self.accept()

    def closeEvent(self, arg__1) -> None:
        sys.exit()
Ejemplo n.º 9
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""

        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.

        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in SETTINGS.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """

        schema = json.loads(setting['json_schema'])
        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = SETTINGS.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in SETTINGS
            for setting_name, value in different_values.items():
                try:
                    setattr(SETTINGS._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict
                except:  # noqa: E722
                    continue
class AddFiles(QWidget):
    """Docstring for AddFiles. """

    file_list_changed = Signal(set)

    def __init__(self,
                 settings: BaseSettings,
                 parent=None,
                 btn_layout=QHBoxLayout):
        """TODO: to be defined1. """
        QWidget.__init__(self, parent)
        self.settings = settings
        self.files_to_proceed = set()
        self.paths = QLineEdit(self)
        self.selected_files = QListWidget(self)
        self.selected_files.itemSelectionChanged.connect(self.file_chosen)
        self.found_button = QPushButton("Find all", self)
        self.found_button.clicked.connect(self.find_all)
        self.select_files_button = QPushButton("Select files")
        self.select_dir_button = QPushButton("Select directory")
        self.select_files_button.clicked.connect(self.select_files)
        self.select_dir_button.clicked.connect(self.select_directory)
        self.delete_button = QPushButton("Remove file", self)
        self.delete_button.setDisabled(True)
        self.delete_button.clicked.connect(self.delete_element)
        self.clean_button = QPushButton("Remove all", self)
        self.clean_button.clicked.connect(self.clean)
        layout = QVBoxLayout()
        layout.addWidget(self.paths)
        select_layout = btn_layout()
        select_layout.addWidget(self.select_files_button)
        select_layout.addWidget(self.select_dir_button)
        select_layout.addWidget(self.found_button)
        select_layout.addStretch()
        select_layout.addWidget(self.clean_button)
        select_layout.addWidget(self.delete_button)
        layout.addLayout(select_layout)
        layout.addWidget(self.selected_files)
        self.setLayout(layout)

    def find_all(self):
        paths = glob(str(self.paths.text()))
        paths = sorted([
            x for x in (set(paths) - self.files_to_proceed)
            if not os.path.isdir(x)
        ])
        if len(paths) > 0:
            dialog = AcceptFiles(paths)
            if dialog.exec_():
                new_paths = dialog.get_files()
                for path in new_paths:
                    size = os.stat(path).st_size
                    size = float(size) / (1024**2)
                    lwi = QListWidgetItem("{:s} ({:.2f} MB)".format(
                        path, size))
                    lwi.setTextAlignment(Qt.AlignRight)
                    self.selected_files.addItem(lwi)
                self.files_to_proceed.update(new_paths)
                self.file_list_changed.emit(self.files_to_proceed)
        else:
            QMessageBox.warning(self, "No new files", "No new files found",
                                QMessageBox.Ok)

    def select_files(self):
        dial = QFileDialog(self, "Select files")
        dial.setDirectory(
            self.settings.get(
                "io.batch_directory",
                self.settings.get("io.load_image_directory",
                                  str(Path.home()))))
        dial.setFileMode(QFileDialog.ExistingFiles)
        if dial.exec_():
            self.settings.set("io.batch_directory",
                              os.path.dirname(str(dial.selectedFiles()[0])))
            new_paths = sorted(
                set(map(str, dial.selectedFiles())) - self.files_to_proceed)
            for path in new_paths:
                size = os.stat(path).st_size
                size = float(size) / (1024**2)
                lwi = QListWidgetItem("{:s} ({:.2f} MB)".format(path, size))
                lwi.setTextAlignment(Qt.AlignRight)
                self.selected_files.addItem(lwi)
            self.files_to_proceed.update(new_paths)
            self.file_list_changed.emit(self.files_to_proceed)

    def select_directory(self):
        dial = QFileDialog(self, "Select directory")
        dial.setDirectory(
            self.settings.get(
                "io.batch_directory",
                self.settings.get("io.load_image_directory",
                                  str(Path.home()))))
        dial.setFileMode(QFileDialog.Directory)
        if dial.exec_():
            self.paths.setText(dial.selectedFiles()[0])
            self.settings.set("io.batch_directory",
                              str(dial.selectedFiles()[0]))

    def file_chosen(self):
        self.delete_button.setEnabled(True)

    def delete_element(self):
        item = self.selected_files.takeItem(self.selected_files.currentRow())
        path = str(item.text())
        path = path[:path.rfind("(") - 1]
        self.files_to_proceed.remove(path)
        self.file_list_changed.emit(self.files_to_proceed)
        if self.selected_files.count() == 0:
            self.delete_button.setDisabled(True)

    def clean(self):
        self.selected_files.clear()
        self.files_to_proceed.clear()
        self.file_list_changed.emit(self.files_to_proceed)

    def get_paths(self):
        return list(sorted(self.files_to_proceed))
Ejemplo n.º 11
0
class ExportDialog(QDialog):
    def __init__(self, export_dict, viewer):
        super(ExportDialog, self).__init__()
        self.setWindowTitle("Export")
        self.export_dict = export_dict
        self.viewer = viewer()
        self.list_view = QListWidget()
        self.check_state = np.zeros(len(export_dict), dtype=np.bool)
        self.check_state[...] = True
        for el in sorted(export_dict.keys()):
            item = QListWidgetItem(el)
            # noinspection PyTypeChecker
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
            self.list_view.addItem(item)

        self.checked_num = len(export_dict)

        self.export_btn = QPushButton("Export")
        self.cancel_btn = QPushButton("Cancel")
        self.check_btn = QPushButton("Check all")
        self.uncheck_btn = QPushButton("Uncheck all")

        self.cancel_btn.clicked.connect(self.close)
        self.export_btn.clicked.connect(self.accept)
        self.check_btn.clicked.connect(self.check_all)
        self.uncheck_btn.clicked.connect(self.uncheck_all)

        self.list_view.itemSelectionChanged.connect(self.preview)
        self.list_view.itemChanged.connect(self.checked_change)

        layout = QVBoxLayout()
        info_layout = QHBoxLayout()
        info_layout.addWidget(self.list_view)
        info_layout.addWidget(self.viewer)
        layout.addLayout(info_layout)
        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.check_btn)
        btn_layout.addWidget(self.uncheck_btn)
        btn_layout.addStretch()
        btn_layout.addWidget(self.export_btn)
        btn_layout.addWidget(self.cancel_btn)
        layout.addLayout(btn_layout)
        self.setLayout(layout)

    def checked_change(self, item):
        if item.checkState() == Qt.Unchecked:
            self.checked_num -= 1
        else:
            self.checked_num += 1
        if self.checked_num == 0:
            self.export_btn.setDisabled(True)
        else:
            self.export_btn.setEnabled(True)

    def get_checked(self):
        res = []
        for i in range(self.list_view.count()):
            it = self.list_view.item(i)
            if it.checkState() == Qt.Checked:
                res.append(str(it.text()))
        return res

    def preview(self):
        name = str(self.list_view.currentItem().text())
        self.viewer.preview_object(self.export_dict[name])

    def check_change(self):
        item = self.list_view.currentItem()  # type: QListWidgetItem
        index = self.list_view.currentRow()
        checked = item.checkState() == Qt.Checked
        self.check_state[index] = checked
        self.export_btn.setEnabled(np.any(self.check_state))

    def uncheck_all(self):
        for index in range(self.list_view.count()):
            item = self.list_view.item(index)
            item.setCheckState(Qt.Unchecked)
        self.check_state[...] = False
        self.export_btn.setDisabled(True)
        self.checked_num = 0

    def check_all(self):
        for index in range(self.list_view.count()):
            item = self.list_view.item(index)
            item.setCheckState(Qt.Checked)
        self.checked_num = len(self.export_dict)
        self.check_state[...] = True
        self.export_btn.setDisabled(False)

    def get_export_list(self):
        res = []
        for num in range(self.list_view.count()):
            item = self.list_view.item(num)
            if item.checkState() == Qt.Checked:
                res.append(str(item.text()))
        return res
Ejemplo n.º 12
0
class SelectFromListDialog(QDialog):
    """Dialog box for selecting multiple items.

    The original order of items are preserved in the candidate list.
    """

    def __init__(self, parent=None, allItems=(), selectedItems=()):
        QDialog.__init__(
            self, parent, flags=Qt.WindowSystemMenuHint | Qt.WindowTitleHint)

        self.allItems = allItems

        self.fromKeys = list(range(len(allItems)))
        self.selectedKeys = []
        for item in selectedItems:
            key = allItems.index(item)
            self.fromKeys.remove(key)
            self.selectedKeys.append(key)

        self.setAttribute(Qt.WA_DeleteOnClose)

        fromLabel = QLabel(_("Select from"))
        self.fromList = QListWidget(self)
        self.fromList.addItems(allItems[key] for key in self.fromKeys)

        selectedLabel = QLabel(_("Selected"))
        self.selectedList = QListWidget(self)
        self.selectedList.addItems(allItems[key] for key in self.selectedKeys)

        self.selectButton = QPushButton(_("Select"))
        self.deselectButton = QPushButton(_("Deselect"))
        self.selectBox = QDialogButtonBox(Qt.Vertical)
        self.selectBox.addButton(self.selectButton, QDialogButtonBox.ActionRole)
        self.selectBox.addButton(self.deselectButton, QDialogButtonBox.ActionRole)
        self.selectButton.clicked.connect(self.on_select)
        self.deselectButton.clicked.connect(self.on_deselect)

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(
            QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        mainLayout = QGridLayout(self)
        mainLayout.addWidget(fromLabel, 0, 0)
        mainLayout.addWidget(selectedLabel, 0, 2)
        mainLayout.addWidget(self.fromList, 1, 0)
        mainLayout.addWidget(self.selectBox, 1, 1)
        mainLayout.addWidget(self.selectedList, 1, 2)
        mainLayout.addWidget(self.buttonBox, 2, 0, 1, 3)
        mainLayout.setAlignment(self.selectBox, Qt.AlignCenter)
        self.setLayout(mainLayout)

    def on_select(self):
        if len(self.fromList.selectedItems()):
            idx = self.fromList.currentRow()
            key = self.fromKeys.pop(idx)
            self.selectedKeys.append(key)
            item = self.fromList.takeItem(idx)
            self.selectedList.addItem(item)

    def on_deselect(self):
        if len(self.selectedList.selectedItems()):
            idx = self.selectedList.currentRow()
            item = self.selectedList.takeItem(idx)
            key = self.selectedKeys.pop(idx)
            idx = next((i for i, v in enumerate(self.fromKeys) if v > key),
                       len(self.fromKeys))
            self.fromKeys.insert(idx, key)
            self.fromList.insertItem(idx, item)
Ejemplo n.º 13
0
class PayloadWindow(FramelessWindow):
    get_build_options = Signal()
    start_build = Signal(str, str, list)
    stop_build = Signal()

    def __init__(self, parent):
        super(PayloadWindow, self).__init__(parent)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.setContentsMargins(11, 11, 11, 11)
        self.progress_windows = []

        self.content_widget = QWidget(self)
        self.addContentWidget(self.content_widget)

        self.widget_layout = QFormLayout(self.content_widget)
        self.content_widget.setLayout(self.widget_layout)

        self.spinner = WaitingSpinner(self,
                                      modality=Qt.WindowModal,
                                      roundness=70.0,
                                      fade=70.0,
                                      radius=15.0,
                                      lines=6,
                                      line_length=25.0,
                                      line_width=4.0,
                                      speed=1.0)

        self.build_name_label = QLabel("Name", self.content_widget)
        self.build_name_edit = QLineEdit(self.content_widget)
        self.widget_layout.addRow(self.build_name_label, self.build_name_edit)

        self.icon_label = QLabel("Icon", self.content_widget)
        self.icon_combobox = QComboBox(self.content_widget)
        self.widget_layout.addRow(self.icon_label, self.icon_combobox)

        self.generators_label = QLabel("Generators", self.content_widget)
        self.generators_list = QListWidget(self.content_widget)
        self.widget_layout.addRow(self.generators_label, self.generators_list)

        self.build_button = QPushButton("Build", self.content_widget)
        self.build_button.clicked.connect(self.on_build_button_clicked)
        self.widget_layout.addWidget(self.build_button)

        self.menu = QMenu(self)
        self.menu.setTitle("Payload")
        self.reload_action = QAction(self.menu)
        self.reload_action.setText("Reload options")
        self.reload_action.triggered.connect(self.setupUi)
        self.menu.addAction(self.reload_action)
        self.addMenu(self.menu)

    @Slot()
    def setupUi(self):
        self.spinner.start()
        self.get_build_options.emit()

    @Slot(dict)
    def process_build_message(self, message):
        if self.isVisible():
            event = message.get("event")
            if event == "options":
                options = message.get("options")
                self.set_options(options.get("generators"),
                                 options.get("icons"))
            elif event == "started":
                self.set_started(message.get("generator_name"))
            elif event == "stopped":
                self.set_stopped()
            elif event == "progress":
                self.set_progress(message.get("generator_name"),
                                  message.get("progress"))
            elif event == "error":
                self.set_error(message.get("error"))
            elif event == "generator_finished":
                self.set_generator_finished(message.get("generator_name"),
                                            message.get("exit_code"))
            elif event == "build_finished":
                self.set_build_finished()

    @Slot(str, str)
    def set_progress(self, generator_name, progress):
        for win in self.progress_windows:
            if win.generator() == generator_name:
                win.appendProgress(progress)

    @Slot(str)
    def set_started(self, generator_name):
        win = ProgressWindow(self, generator_name)
        win.show()
        self.progress_windows.append(win)
        self.logger.info("Generator {} started!".format(generator_name))
        self.reload_action.setEnabled(False)
        self.spinner.start()

    @Slot(str, int)
    def set_generator_finished(self, generator_name, exit_code):
        self.logger.info("Generator {} finished with exit code {}.".format(
            generator_name, exit_code))

    @Slot(list, list)
    def set_options(self, generators, icons):
        self.build_name_edit.clear()
        self.generators_list.clear()
        self.icon_combobox.clear()
        for generator in generators:
            item = QListWidgetItem(generator, self.generators_list)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
            self.generators_list.addItem(item)
        for icon in icons:
            name = icon.get("name")
            pix = QPixmap()
            pix.loadFromData(base64.b64decode(icon.get("ico")))
            ico = QIcon()
            ico.addPixmap(pix)
            self.icon_combobox.addItem(ico, name)
        self.spinner.stop()

    @Slot()
    def set_stopped(self):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.stopped_messagebox = FramelessInformationMessageBox(self)
        self.stopped_messagebox.setText(
            "Build process has been stopped successfully.")
        self.stopped_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.stopped_messagebox.button(QDialogButtonBox.Ok).clicked.connect(
            self.stopped_messagebox.close)
        self.stopped_messagebox.show()

    @Slot(str)
    def set_error(self, error):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.error_messagebox = FramelessCriticalMessageBox(self)
        self.error_messagebox.setText(error)
        self.error_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.error_messagebox.button(QDialogButtonBox.Ok).clicked.connect(
            self.error_messagebox.close)
        self.error_messagebox.show()

    @Slot()
    def set_build_finished(self):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.build_finished_messagebox = FramelessInformationMessageBox(self)
        self.build_finished_messagebox.setText(
            "Build process has been finished.")
        self.build_finished_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.build_finished_messagebox.button(
            QDialogButtonBox.Ok).clicked.connect(
                self.build_finished_messagebox.close)
        self.build_finished_messagebox.show()

    @Slot()
    def on_build_button_clicked(self):
        if self.build_button.text() == "Build":
            generators = []
            for i in range(self.generators_list.count()):
                item = self.generators_list.item(i)
                if item.checkState() == Qt.Checked:
                    generators.append(item.text())
            self.start_build.emit(self.build_name_edit.text(),
                                  self.icon_combobox.currentText(), generators)
            self.build_button.setText("Stop")
        else:
            self.stop_build_messagebox = FramelessQuestionMessageBox(self)
            self.stop_build_messagebox.setText(
                "Do you want to stop build process?")
            self.stop_build_messagebox.setStandardButtons(
                QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Cancel).clicked.connect(
                    self.stop_build_messagebox.close)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Ok).clicked.connect(self.stop_build.emit)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Ok).clicked.connect(
                    self.stop_build_messagebox.close)
            self.stop_build_messagebox.show()

    @Slot()
    def on_build_finished(self):
        self.reload_action.setEnabled(True)
        self.spinner.stop()

    @Slot()
    def close(self) -> bool:
        for win in self.progress_windows:
            win.close()
        return super().close()
Ejemplo n.º 14
0
class RemoteKernelSetupDialog(QDialog):
    """Dialog to connect to existing kernels (either local or remote)."""

    def __init__(self, parent=None):
        super(RemoteKernelSetupDialog, self).__init__(parent)
        self.setWindowTitle(_('Setup remote kernel'))

        self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files'
        self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir'

        # Name of the connection
        cfg_name_label = QLabel(_('Configuration name:'))
        self.cfg_name_line_edit = QLineEdit()

        # SSH connection
        hostname_label = QLabel(_('Hostname:'))
        self.hostname_lineedit = QLineEdit()
        port_label = QLabel(_('Port:'))
        self.port_lineeidt = QLineEdit()
        self.port_lineeidt.setMaximumWidth(75)

        username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:'))

        self.pw = QLineEdit()
        self.pw.setEchoMode(QLineEdit.Password)
        self.pw_radio.toggled.connect(self.pw.setEnabled)
        self.keyfile_radio.toggled.connect(self.pw.setDisabled)

        self.keyfile_path_lineedit = QLineEdit()
        keyfile_browse_btn = QPushButton(_('Browse'))
        keyfile_browse_btn.clicked.connect(self.select_ssh_key)
        keyfile_layout = QHBoxLayout()
        keyfile_layout.addWidget(self.keyfile_path_lineedit)
        keyfile_layout.addWidget(keyfile_browse_btn)

        passphrase_label = QLabel(_('Passphrase:'))
        self.passphrase_lineedit = QLineEdit()
        self.passphrase_lineedit.setPlaceholderText(_('Optional'))
        self.passphrase_lineedit.setEchoMode(QLineEdit.Password)

        self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled)
        self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled)
        self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled)
        self.keyfile_radio.toggled.connect(passphrase_label.setEnabled)
        self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled)
        self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled)
        self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled)
        self.pw_radio.toggled.connect(passphrase_label.setDisabled)

        # Button to fetch JSON files listing
        # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN))
        # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files)
        # self.cb_remote_conn_files = QComboBox()
        # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file)

        # Remote kernel groupbox
        self.start_remote_kernel_group = QGroupBox(_("Start remote kernel"))

        # Advanced settings to get remote connection files
        jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:'))
        self.jupyter_runtime_location_cmd_lineedit = QLineEdit()
        self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME))

        # SSH layout
        ssh_layout = QGridLayout()
        ssh_layout.addWidget(cfg_name_label, 0, 0)
        ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2)

        ssh_layout.addWidget(hostname_label, 1, 0, 1, 2)
        ssh_layout.addWidget(self.hostname_lineedit, 1, 2)
        ssh_layout.addWidget(port_label, 1, 3)
        ssh_layout.addWidget(self.port_lineeidt, 1, 4)
        ssh_layout.addWidget(username_label, 2, 0, 1, 2)
        ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3)

        # SSH authentication layout
        auth_layout = QGridLayout()
        auth_layout.addWidget(self.pw_radio, 1, 0)
        auth_layout.addWidget(pw_label, 1, 1)
        auth_layout.addWidget(self.pw, 1, 2)
        auth_layout.addWidget(self.keyfile_radio, 2, 0)
        auth_layout.addWidget(keyfile_label, 2, 1)
        auth_layout.addLayout(keyfile_layout, 2, 2)
        auth_layout.addWidget(passphrase_label, 3, 1)
        auth_layout.addWidget(self.passphrase_lineedit, 3, 2)

        auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1)
        auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2)
        # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1)
        # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2)

        auth_group.setLayout(auth_layout)

        # Remote kernel layout
        self.rm_group = QGroupBox(_("Setup up of a remote connection"))
        self.rm_group.setEnabled(False)
        rm_layout = QVBoxLayout()
        rm_layout.addLayout(ssh_layout)
        rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        rm_layout.addWidget(auth_group)
        self.rm_group.setLayout(rm_layout)
        self.rm_group.setCheckable(False)

        # Ok and Cancel buttons
        self.accept_btns = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)

        self.accept_btns.accepted.connect(self.accept)
        self.accept_btns.rejected.connect(self.reject)

        btns_layout = QHBoxLayout()
        btns_layout.addWidget(self.accept_btns)

        # Dialog layout
        layout = QVBoxLayout()
        layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
        layout.addWidget(self.rm_group)
        layout.addLayout(btns_layout)

        # Main layout
        hbox_layout = QHBoxLayout(self)

        # Left side with the list of all remote connection configurations
        items_label = QLabel(text="Configured remote locations")
        self.items_list = QListWidget()
        self.items_list.clicked.connect(self._on_items_list_click)

        items_layout = QVBoxLayout()
        items_layout.addWidget(items_label)
        items_layout.addWidget(self.items_list)
        edit_delete_new_buttons_layout = QHBoxLayout()
        edit_btn = QPushButton(text="Edit")
        add_btn = QPushButton(text="Add")
        delete_btn = QPushButton(text="Delete")

        add_btn.clicked.connect(self._on_add_btn_click)
        edit_btn.clicked.connect(self._on_edit_btn_click)
        delete_btn.clicked.connect(self._on_delete_btn_click)

        edit_delete_new_buttons_layout.addWidget(add_btn)
        edit_delete_new_buttons_layout.addWidget(edit_btn)
        edit_delete_new_buttons_layout.addWidget(delete_btn)

        items_layout.addLayout(edit_delete_new_buttons_layout)

        hbox_layout.addSpacerItem(QSpacerItem(10, 0))
        hbox_layout.addLayout(items_layout)
        hbox_layout.addLayout(layout)

        self.lst_with_connecion_configs = []

    def _on_items_list_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        cfg = self.lst_with_connecion_configs[idx_of_config]
        if isinstance(cfg, RemoteConnectionSettings):
            self._update_remote_connection_input_fields(cfg)
        else:
            show_info_dialog("Information", "This functionality is still not available")

    def _clear_remote_connection_input_fields(self):
        self.keyfile_path_lineedit.setText("")
        self.passphrase_lineedit.setText("")
        self.hostname_lineedit.setText("")
        self.username_lineedit.setText("")
        self.port_lineeidt.setText("")
        self.cfg_name_line_edit.setText("")
        self.jupyter_runtime_location_cmd_lineedit.setText("")

        self.keyfile_radio.setChecked(False)
        self.pw_radio.setChecked(False)

    def _update_remote_connection_input_fields(self, remote_conn_settings):
        self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path)
        self.passphrase_lineedit.setText(remote_conn_settings.password)
        self.hostname_lineedit.setText(remote_conn_settings.hostname)
        self.username_lineedit.setText(remote_conn_settings.username)
        self.port_lineeidt.setText(str(remote_conn_settings.port))
        self.cfg_name_line_edit.setText(remote_conn_settings.connection_name)
        self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location)

        self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None)
        self.pw_radio.setChecked(remote_conn_settings.password is not None)

    def _on_add_btn_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings

        username = self.username_lineedit.text()
        passphrase = self.passphrase_lineedit.text()
        hostname = self.hostname_lineedit.text()
        keyfile_path = self.keyfile_path_lineedit.text()
        port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22
        jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text()
        cfg_name = self.cfg_name_line_edit.text()

        cfg = RemoteConnectionSettings(
            username=username,
            hostname=hostname,
            keyfile_path=keyfile_path,
            port=port,
            connection_name=cfg_name,
            cmd_for_jupyter_runtime_location=jup_runtime_cmd,
            password=passphrase
        )

        self.lst_with_connecion_configs.append(cfg)
        self._update_list_with_configs()
        self.rm_group.setEnabled(False)

    def _on_edit_btn_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        self.rm_group.setEnabled(True)
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        cfg = self.lst_with_connecion_configs[idx_of_config]
        if isinstance(cfg, RemoteConnectionSettings):
            self._update_remote_connection_input_fields(cfg)
        else:
            show_info_dialog("Information", "This functionality is still not available")

    def _on_delete_btn_click(self):
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        self.lst_with_connecion_configs.pop(idx_of_config)
        self._update_list_with_configs()

    def select_ssh_key(self):
        kf = getopenfilename(self, _('Select SSH keyfile'),
                             get_home_dir(), '*.pem;;*')[0]
        self.keyfile_path_lineedit.setText(kf)

    def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files):
        remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files]
        self.cf.setText(remote_path_filename)

    def set_connection_configs(self, lst_with_connecion_configs):
        self.lst_with_connecion_configs = lst_with_connecion_configs
        self._update_list_with_configs()

    def _update_list_with_configs(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        # now, fill the list
        self.items_list.clear()
        for cfg in self.lst_with_connecion_configs:
            if isinstance(cfg, LocalConnectionSettings):
                self.items_list.addItem(f"Local: {cfg.connection_name}")
            elif isinstance(cfg, RemoteConnectionSettings):
                self.items_list.addItem(f"Remote: {cfg.connection_name}")

    def get_connection_settings(self):
        return self.lst_with_connecion_configs
Ejemplo n.º 15
0
class FindOptions(QWidget):
    """Find widget with options"""
    REGEX_INVALID = "background-color:rgb(255, 175, 90);"
    find = Signal()
    stop = Signal()
    redirect_stdio = Signal(bool)

    def __init__(self, parent, search_text, search_text_regexp, search_path,
                 exclude, exclude_idx, exclude_regexp,
                 supported_encodings, in_python_path, more_options,
                 case_sensitive, external_path_history):
        QWidget.__init__(self, parent)

        if search_path is None:
            search_path = getcwd()

        self.path = ''
        self.project_path = None
        self.file_path = None
        self.external_path = None
        self.external_path_history = external_path_history

        if not isinstance(search_text, (list, tuple)):
            search_text = [search_text]
        if not isinstance(search_path, (list, tuple)):
            search_path = [search_path]
        if not isinstance(exclude, (list, tuple)):
            exclude = [exclude]
        if not isinstance(external_path_history, (list, tuple)):
            external_path_history = [external_path_history]

        self.supported_encodings = supported_encodings

        # Layout 1
        hlayout1 = QHBoxLayout()
        self.search_text = PatternComboBox(self, search_text,
                                           _("Search pattern"))
        self.edit_regexp = create_toolbutton(self,
                                             icon=ima.icon('advanced'),
                                             tip=_('Regular expression'))
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.setChecked(case_sensitive)
        self.edit_regexp.setCheckable(True)
        self.edit_regexp.setChecked(search_text_regexp)
        self.more_widgets = ()
        self.more_options = create_toolbutton(self,
                                              toggled=self.toggle_more_options)
        self.more_options.setCheckable(True)
        self.more_options.setChecked(more_options)

        self.ok_button = create_toolbutton(self, text=_("Search"),
                                           icon=ima.icon('find'),
                                           triggered=lambda: self.find.emit(),
                                           tip=_("Start search"),
                                           text_beside_icon=True)
        self.ok_button.clicked.connect(self.update_combos)
        self.stop_button = create_toolbutton(self, text=_("Stop"),
                                             icon=ima.icon('editclear'),
                                             triggered=lambda:
                                             self.stop.emit(),
                                             tip=_("Stop search"),
                                             text_beside_icon=True)
        self.stop_button.setEnabled(False)
        for widget in [self.search_text, self.edit_regexp, self.case_button,
                       self.ok_button, self.stop_button, self.more_options]:
            hlayout1.addWidget(widget)

        # Layout 2
        hlayout2 = QHBoxLayout()
        self.exclude_pattern = PatternComboBox(self, exclude,
                                               _("Excluded filenames pattern"))
        if exclude_idx is not None and exclude_idx >= 0 \
           and exclude_idx < self.exclude_pattern.count():
            self.exclude_pattern.setCurrentIndex(exclude_idx)
        self.exclude_regexp = create_toolbutton(self,
                                                icon=ima.icon('advanced'),
                                                tip=_('Regular expression'))
        self.exclude_regexp.setCheckable(True)
        self.exclude_regexp.setChecked(exclude_regexp)
        exclude_label = QLabel(_("Exclude:"))
        exclude_label.setBuddy(self.exclude_pattern)
        for widget in [exclude_label, self.exclude_pattern,
                       self.exclude_regexp]:
            hlayout2.addWidget(widget)

        # Layout 3
        hlayout3 = QHBoxLayout()

        search_on_label = QLabel(_("Search in:"))
        self.path_selection_combo = PatternComboBox(self, exclude,
                                                    _('Search directory'))
        self.path_selection_combo.setEditable(False)
        self.path_selection_contents = QListWidget(self.path_selection_combo)
        self.path_selection_contents.hide()
        self.path_selection_combo.setModel(
            self.path_selection_contents.model())

        self.path_selection_contents.addItem(_("Current working directory"))
        item = self.path_selection_contents.item(0)
        item.setToolTip(_("Search in all files and "
                          "directories present on the"
                          "current Spyder path"))

        self.path_selection_contents.addItem(_("Project"))
        item = self.path_selection_contents.item(1)
        item.setToolTip(_("Search in all files and "
                          "directories present on the"
                          "current project path "
                          "(If opened)"))
        item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

        self.path_selection_contents.addItem(_("File").replace('&', ''))
        item = self.path_selection_contents.item(2)
        item.setToolTip(_("Search in current opened file"))

        self.path_selection_contents.addItem(_("Select other directory"))
        item = self.path_selection_contents.item(3)
        item.setToolTip(_("Search in other folder present on the file system"))

        self.path_selection_combo.insertSeparator(3)
        self.path_selection_combo.insertSeparator(5)
        for path in external_path_history:
            item = ExternalPathItem(None, path)
            self.path_selection_contents.addItem(item)

        self.path_selection_combo.currentIndexChanged.connect(
            self.path_selection_changed)

        hlayout3.addWidget(search_on_label)
        hlayout3.addWidget(self.path_selection_combo)

        self.search_text.valid.connect(lambda valid: self.find.emit())
        self.exclude_pattern.valid.connect(lambda valid: self.find.emit())

        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addLayout(hlayout1)
        vlayout.addLayout(hlayout2)
        vlayout.addLayout(hlayout3)
        self.more_widgets = (hlayout2,)
        self.toggle_more_options(more_options)
        self.setLayout(vlayout)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)

    @Slot(bool)
    def toggle_more_options(self, state):
        for layout in self.more_widgets:
            for index in range(layout.count()):
                if state and self.isVisible() or not state:
                    layout.itemAt(index).widget().setVisible(state)
        if state:
            icon = ima.icon('options_less')
            tip = _('Hide advanced options')
        else:
            icon = ima.icon('options_more')
            tip = _('Show advanced options')
        self.more_options.setIcon(icon)
        self.more_options.setToolTip(tip)

    @Slot()
    def path_selection_changed(self):
        idx = self.path_selection_combo.currentIndex()
        if idx == EXTERNAL_PATH:
            external_path = self.select_directory()
            if len(external_path) > 0:
                item = ExternalPathItem(None, external_path)
                self.path_selection_contents.addItem(item)

                total_items = (self.path_selection_combo.count() -
                               MAX_PATH_HISTORY)
                for i in range(6, total_items):
                    self.path_selection_contents.takeItem(i)

                self.path_selection_combo.setCurrentIndex(
                    self.path_selection_combo.count() - 1)
            else:
                self.path_selection_combo.setCurrentIndex(CWD)
        elif idx > EXTERNAL_PATH:
            item = self.path_selection_contents.item(idx)
            self.external_path = item.path

    def update_combos(self):
        self.search_text.lineEdit().returnPressed.emit()
        self.exclude_pattern.lineEdit().returnPressed.emit()

    def set_search_text(self, text):
        if text:
            self.search_text.add_text(text)
            self.search_text.lineEdit().selectAll()
        self.search_text.setFocus()

    def get_options(self, all=False):
        # Getting options
        self.search_text.lineEdit().setStyleSheet("")
        self.exclude_pattern.lineEdit().setStyleSheet("")

        utext = to_text_string(self.search_text.currentText())
        if not utext:
            return
        try:
            texts = [(utext.encode('utf-8'), 'utf-8')]
        except UnicodeEncodeError:
            texts = []
            for enc in self.supported_encodings:
                try:
                    texts.append((utext.encode(enc), enc))
                except UnicodeDecodeError:
                    pass
        text_re = self.edit_regexp.isChecked()
        exclude = to_text_string(self.exclude_pattern.currentText())
        exclude_re = self.exclude_regexp.isChecked()
        case_sensitive = self.case_button.isChecked()
        python_path = False

        if not case_sensitive:
            texts = [(text[0].lower(), text[1]) for text in texts]

        file_search = False
        selection_idx = self.path_selection_combo.currentIndex()
        if selection_idx == CWD:
            path = self.path
        elif selection_idx == PROJECT:
            path = self.project_path
        elif selection_idx == FILE_PATH:
            path = self.file_path
            file_search = True
        else:
            path = self.external_path

        # Finding text occurrences
        if not exclude_re:
            exclude = fnmatch.translate(exclude)
        else:
            try:
                exclude = re.compile(exclude)
            except Exception:
                exclude_edit = self.exclude_pattern.lineEdit()
                exclude_edit.setStyleSheet(self.REGEX_INVALID)
                return None

        if text_re:
            try:
                texts = [(re.compile(x[0]), x[1]) for x in texts]
            except Exception:
                self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID)
                return None

        if all:
            search_text = [to_text_string(self.search_text.itemText(index))
                           for index in range(self.search_text.count())]
            exclude = [to_text_string(self.exclude_pattern.itemText(index))
                       for index in range(self.exclude_pattern.count())]
            path_history = [to_text_string(
                            self.path_selection_contents.item(index))
                            for index in range(
                                6, self.path_selection_combo.count())]
            exclude_idx = self.exclude_pattern.currentIndex()
            more_options = self.more_options.isChecked()
            return (search_text, text_re, [],
                    exclude, exclude_idx, exclude_re,
                    python_path, more_options, case_sensitive, path_history)
        else:
            return (path, file_search, exclude, texts, text_re, case_sensitive)

    @Slot()
    def select_directory(self):
        """Select directory"""
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.path)
        if directory:
            directory = to_text_string(osp.abspath(to_text_string(directory)))
        self.redirect_stdio.emit(True)
        return directory

    def set_directory(self, directory):
        self.path = to_text_string(osp.abspath(to_text_string(directory)))

    def set_project_path(self, path):
        self.project_path = to_text_string(osp.abspath(to_text_string(path)))
        item = self.path_selection_contents.item(PROJECT)
        item.setFlags(item.flags() | Qt.ItemIsEnabled)

    def disable_project_search(self):
        item = self.path_selection_contents.item(PROJECT)
        item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
        self.project_path = None

    def set_file_path(self, path):
        self.file_path = path

    def keyPressEvent(self, event):
        """Reimplemented to handle key events"""
        ctrl = event.modifiers() & Qt.ControlModifier
        shift = event.modifiers() & Qt.ShiftModifier
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.find.emit()
        elif event.key() == Qt.Key_F and ctrl and shift:
            # Toggle find widgets
            self.parent().toggle_visibility.emit(not self.isVisible())
        else:
            QWidget.keyPressEvent(self, event)
Ejemplo n.º 16
0
class CycleWindow(SiriusMainWindow):
    """Power supplies cycle window."""
    def __init__(self, parent=None, checked_accs=(), adv_mode=False):
        """Constructor."""
        super().__init__(parent)
        self.setObjectName('ASApp')
        cor = get_appropriate_color(section='AS')
        self.setWindowIcon(qta.icon('mdi.recycle', color=cor))
        self._is_adv_mode = adv_mode
        # Data structs
        self._psnames = get_psnames(isadv=self._is_adv_mode)
        self._timing = Timing()
        self._ps2cycle = list()
        self._ps_ready = list()
        self._ps_failed = list()
        self._checked_accs = checked_accs
        # Flags
        self._is_preparing = ''
        self._prepared_init_vals = {
            'timing': False,
            'ps_sofbmode': False,
            'ps_om_slowref': False,
            'ps_current': False,
            'ps_params': False,
            'ps_om_cycle': False,
            'trims': True
        }
        self._prepared = self._prepared_init_vals.copy()
        self._icon_check = qta.icon('fa5s.check')
        self._pixmap_check = self._icon_check.pixmap(
            self._icon_check.actualSize(QSize(16, 16)))
        self._icon_not = qta.icon('fa5s.times')
        self._pixmap_not = self._icon_not.pixmap(
            self._icon_not.actualSize(QSize(16, 16)))
        # Tasks
        self._step_2_task = {
            'save_timing': SaveTiming,
            'timing': PrepareTiming,
            'ps_sofbmode': PreparePSSOFBMode,
            'ps_om_slowref': PreparePSOpModeSlowRef,
            'ps_current': PreparePSCurrentZero,
            'ps_params': PreparePSParams,
            'ps_om_cycle': PreparePSOpModeCycle,
            'trims': CycleTrims,
            'cycle': Cycle,
            'restore_timing': RestoreTiming,
        }
        # Setup UI
        self._needs_update_setup = False
        self._setup_ui()
        self._update_setup_timer = QTimer(self)
        self._update_setup_timer.timeout.connect(self._update_setup)
        self._update_setup_timer.setInterval(250)
        self._update_setup_timer.start()
        self.setWindowTitle('PS Cycle')

    def _setup_ui(self):
        # central widget
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)

        # tree
        gb_tree = QGroupBox('Select power supplies:')
        self.pwrsupplies_tree = PVNameTree(self._psnames, ('sec', 'mag_group'),
                                           tuple(), self)
        self.pwrsupplies_tree.tree.setHeaderHidden(True)
        self.pwrsupplies_tree.tree.setColumnCount(1)
        glay_tree = QVBoxLayout(gb_tree)
        glay_tree.addWidget(self.pwrsupplies_tree)

        # commands
        lb_prep_ti = QLabel('<h4>Prepare Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames()
        ]
        self.ticonn_led = PyDMLedMultiConn(self, channels=ti_ch)

        self.save_timing_bt = QPushButton('1. Save Timing Initial State', self)
        self.save_timing_bt.setToolTip(
            'Save timing current state as initial state.')
        self.save_timing_bt.clicked.connect(
            _part(self._run_task, 'save_timing'))
        self.save_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_bt = QPushButton('2. Prepare Timing', self)
        self.prepare_timing_bt.setToolTip('Prepare EVG, triggers and events')
        self.prepare_timing_bt.clicked.connect(_part(self._run_task, 'timing'))
        self.prepare_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_lb = QLabel(self)
        self.prepare_timing_lb.setPixmap(self._pixmap_not)

        lb_prep_ps = QLabel('<h4>Prepare PS</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.psconn_led = PyDMLedMultiConn(self)

        self.set_ps_sofbmode_off_bt = QPushButton('3. Turn off PS SOFBMode',
                                                  self)
        self.set_ps_sofbmode_off_bt.setToolTip(
            'Turn off power supplies SOFBMode.')
        self.set_ps_sofbmode_off_bt.clicked.connect(
            _part(self._run_task, 'ps_sofbmode'))
        self.set_ps_sofbmode_off_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_sofbmode_off_lb = QLabel(self)
        self.set_ps_sofbmode_off_lb.setPixmap(self._pixmap_not)

        self.set_ps_opmode_slowref_bt = QPushButton(
            '4. Set PS OpMode to SlowRef', self)
        self.set_ps_opmode_slowref_bt.setToolTip(
            'Set power supplies OpMode to SlowRef.')
        self.set_ps_opmode_slowref_bt.clicked.connect(
            _part(self._run_task, 'ps_om_slowref'))
        self.set_ps_opmode_slowref_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_opmode_slowref_lb = QLabel(self)
        self.set_ps_opmode_slowref_lb.setPixmap(self._pixmap_not)

        self.set_ps_current_zero_bt = QPushButton('5. Set PS current to zero',
                                                  self)
        self.set_ps_current_zero_bt.setToolTip(
            'Set power supplies current to zero.')
        self.set_ps_current_zero_bt.clicked.connect(
            _part(self._run_task, 'ps_current'))
        self.set_ps_current_zero_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_current_zero_lb = QLabel(self)
        self.set_ps_current_zero_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_params_bt = QPushButton('6. Prepare PS Parameters',
                                                self)
        self.prepare_ps_params_bt.setToolTip(
            'Check power supplies OpMode in SlowRef, check\n'
            'current is zero and configure cycle parameters.')
        self.prepare_ps_params_bt.clicked.connect(
            _part(self._run_task, 'ps_params'))
        self.prepare_ps_params_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_params_lb = QLabel(self)
        self.prepare_ps_params_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_opmode_bt = QPushButton('7. Prepare PS OpMode', self)
        self.prepare_ps_opmode_bt.setToolTip(
            'Set power supplies OpMode to Cycle.')
        self.prepare_ps_opmode_bt.clicked.connect(
            _part(self._run_task, 'ps_om_cycle'))
        self.prepare_ps_opmode_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_opmode_lb = QLabel(self)
        self.prepare_ps_opmode_lb.setPixmap(self._pixmap_not)

        lb_cycle = QLabel('<h4>Cycle</h4>', self, alignment=Qt.AlignCenter)

        self.cycle_trims_bt = QPushButton('8. Cycle Trims', self)
        self.cycle_trims_bt.setToolTip(
            'Cycle trims:\nStep 1) CH, QS and QTrims\nStep 2) CV')
        self.cycle_trims_bt.clicked.connect(_part(self._run_task, 'trims'))
        self.cycle_trims_bt.clicked.connect(self._set_lastcomm)
        self.cycle_trims_bt.setVisible(False)

        self.cycle_trims_lb = QLabel(self)
        self.cycle_trims_lb.setPixmap(self._pixmap_check)
        self.cycle_trims_lb.setVisible(False)

        self.cycle_bt = QPushButton('8. Cycle', self)
        self.cycle_bt.setToolTip(
            'Check all configurations,\nenable triggers and run cycle.')
        self.cycle_bt.clicked.connect(_part(self._run_task, 'cycle'))
        self.cycle_bt.clicked.connect(self._set_lastcomm)
        self.cycle_bt.setEnabled(False)

        lb_rest_ti = QLabel('<h4>Restore Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.restore_timing_bt = QPushButton('9. Restore Timing Initial State',
                                             self)
        self.restore_timing_bt.setToolTip('Restore timing initial state.')
        self.restore_timing_bt.clicked.connect(
            _part(self._run_task, 'restore_timing'))
        self.restore_timing_bt.clicked.connect(self._set_lastcomm)

        self._prepared_labels = {
            'timing': self.prepare_timing_lb,
            'ps_sofbmode': self.set_ps_sofbmode_off_lb,
            'ps_om_slowref': self.set_ps_opmode_slowref_lb,
            'ps_current': self.set_ps_current_zero_lb,
            'ps_params': self.prepare_ps_params_lb,
            'ps_om_cycle': self.prepare_ps_opmode_lb,
            'trims': self.cycle_trims_lb
        }

        gb_commsts = QGroupBox()
        gb_commsts.setStyleSheet("""
            QPushButton{min-height:1.5em;}
            QLabel{qproperty-alignment: AlignCenter;}""")
        lay_commsts = QGridLayout(gb_commsts)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 0, 0, 1, 2)
        lay_commsts.addWidget(lb_prep_ti, 1, 0)
        lay_commsts.addWidget(self.ticonn_led, 1, 1)
        lay_commsts.addWidget(self.save_timing_bt, 2, 0)
        lay_commsts.addWidget(self.prepare_timing_bt, 3, 0)
        lay_commsts.addWidget(self.prepare_timing_lb, 3, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 4, 0)
        lay_commsts.addWidget(lb_prep_ps, 5, 0)
        lay_commsts.addWidget(self.psconn_led, 5, 1)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_bt, 6, 0)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_lb, 6, 1)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_bt, 7, 0)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_lb, 7, 1)
        lay_commsts.addWidget(self.set_ps_current_zero_bt, 8, 0)
        lay_commsts.addWidget(self.set_ps_current_zero_lb, 8, 1)
        lay_commsts.addWidget(self.prepare_ps_params_bt, 9, 0)
        lay_commsts.addWidget(self.prepare_ps_params_lb, 9, 1)
        lay_commsts.addWidget(self.prepare_ps_opmode_bt, 10, 0)
        lay_commsts.addWidget(self.prepare_ps_opmode_lb, 10, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 11, 0)
        lay_commsts.addWidget(lb_cycle, 12, 0)
        lay_commsts.addWidget(self.cycle_trims_bt, 13, 0)
        lay_commsts.addWidget(self.cycle_trims_lb, 13, 1)
        lay_commsts.addWidget(self.cycle_bt, 14, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 15, 0)
        lay_commsts.addWidget(lb_rest_ti, 16, 0)
        lay_commsts.addWidget(self.restore_timing_bt, 17, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 18, 0)
        lay_commsts.setColumnStretch(0, 10)
        lay_commsts.setColumnStretch(1, 1)
        lay_commsts.setVerticalSpacing(12)
        lay_commsts.setHorizontalSpacing(6)

        self.label_lastcomm = QLabel('Last Command: ', self)
        self.clearhist_bt = QPushButton('Clear', self)
        self.clearhist_bt.clicked.connect(self._clear_lastcomm)
        lay_lc = QHBoxLayout()
        lay_lc.setContentsMargins(0, 0, 0, 0)
        lay_lc.addWidget(self.label_lastcomm, alignment=Qt.AlignLeft)
        lay_lc.addWidget(self.clearhist_bt, alignment=Qt.AlignRight)
        lay_lc.setStretch(0, 10)
        lay_lc.setStretch(1, 1)

        self.progress_list = QListWidget(self)
        self.progress_list.setObjectName('progresslist')
        self.progress_list.setStyleSheet('#progresslist{min-width:20em;}')
        self.progress_list.itemDoubleClicked.connect(self._open_ps_detail)
        self.progress_list.setSelectionMode(QAbstractItemView.MultiSelection)
        self.progress_list.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')

        self.progress_bar = MyProgressBar(self)

        lay_log = QVBoxLayout()
        lay_log.addLayout(lay_lc)
        lay_log.addWidget(self.progress_list)
        lay_log.addWidget(self.progress_bar)

        # connect tree signals
        self.pwrsupplies_tree.tree.doubleClicked.connect(self._open_ps_detail)
        self.pwrsupplies_tree.tree.itemChanged.connect(
            self._handle_checked_items_changed)
        self.pwrsupplies_tree.check_requested_levels(self._checked_accs)

        # layout
        layout = QGridLayout()
        layout.setVerticalSpacing(10)
        layout.setHorizontalSpacing(10)
        layout.addWidget(
            QLabel('<h3>PS Cycle</h3>', self, alignment=Qt.AlignCenter), 0, 0,
            1, 3)
        layout.addWidget(gb_tree, 1, 0)
        layout.addWidget(gb_commsts, 1, 1)
        layout.addLayout(lay_log, 1, 2)
        layout.setRowStretch(0, 1)
        layout.setRowStretch(1, 15)
        layout.setColumnStretch(0, 5)
        layout.setColumnStretch(1, 4)
        layout.setColumnStretch(2, 8)
        self.central_widget.setLayout(layout)

    # --- handle tasks ---

    def _run_task(self, control=''):
        if not self._check_connected(control):
            return
        pwrsupplies = self._get_ps_list()
        if not pwrsupplies:
            return

        if 'ps' in control and not self._verify_ps(pwrsupplies):
            return

        if control in self._step_2_task:
            task_class = self._step_2_task[control]
        else:
            raise NotImplementedError(
                "Task not defined for control '{}'".format(control))
        self._is_preparing = control

        self._handle_buttons_enabled(False)
        self.progress_list.clear()

        task = task_class(parent=self,
                          psnames=pwrsupplies,
                          timing=self._timing,
                          isadv=self._is_adv_mode)
        task.updated.connect(self._update_progress)
        duration = task.duration()

        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(duration)
        self.progress_bar.setValue(0)
        pal = self.progress_bar.palette()
        pal.setColor(QPalette.Highlight, self.progress_bar.default_color)
        self.progress_bar.setPalette(pal)

        self.update_bar = UpdateProgressBar(duration, self)
        self.update_bar.increment.connect(self.progress_bar.increment)

        task.start()
        self.update_bar.start()

    def _update_progress(self, text, done, warning=False, error=False):
        """Update automated cycle progress list and bar."""
        if done:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            curr_text = last_item.text()
            last_item.setText(curr_text + ' done.')
        elif 'Remaining time' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Remaining time' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Sent ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Sent ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Successfully checked ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Successfully checked ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Created connections ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Created connections ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        else:
            item = QListWidgetItem(text)
            if error:
                item.setForeground(errorcolor)
                self.update_bar.exit_task()
                pal = self.progress_bar.palette()
                pal.setColor(QPalette.Highlight,
                             self.progress_bar.warning_color)
                self.progress_bar.setPalette(pal)
                if self._is_preparing in self._prepared.keys():
                    self._prepared[self._is_preparing] = False
                cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)
            elif warning:
                item.setForeground(warncolor)
            elif 'finished' in text:
                self.update_bar.exit_task()
                self.progress_bar.setValue(self.progress_bar.maximum())
                if self._is_preparing == 'cycle':
                    self._prepared = {k: False for k in self._prepared.keys()}
                    if not self.cycle_trims_bt.isVisible():
                        self._prepared['trims'] = True
                    cycle = False
                else:
                    if self._is_preparing in self._prepared.keys():
                        self._prepared[self._is_preparing] = True
                    cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)

            self._handle_stslabels_content()

            self.progress_list.addItem(item)
            self.progress_list.scrollToBottom()

    def _handle_buttons_enabled(self, enable, cycle=False):
        self.save_timing_bt.setEnabled(enable)
        self.prepare_timing_bt.setEnabled(enable)
        self.set_ps_sofbmode_off_bt.setEnabled(enable)
        self.set_ps_opmode_slowref_bt.setEnabled(enable)
        self.set_ps_current_zero_bt.setEnabled(enable)
        self.prepare_ps_params_bt.setEnabled(enable)
        self.prepare_ps_opmode_bt.setEnabled(enable)
        self.cycle_trims_bt.setEnabled(enable)
        self.cycle_bt.setEnabled(cycle)
        self.restore_timing_bt.setEnabled(enable)
        self.clearhist_bt.setEnabled(enable)
        self.pwrsupplies_tree.setEnabled(enable)

    def _handle_stslabels_content(self):
        for prep, value in self._prepared.items():
            pixmap = self._pixmap_check if value else self._pixmap_not
            self._prepared_labels[prep].setPixmap(pixmap)

    def _set_lastcomm(self):
        sender_text = self.sender().text()
        self.label_lastcomm.setText('Last Command: ' + sender_text)

    def _clear_lastcomm(self):
        self.progress_bar.setValue(0)
        self.progress_list.clear()
        self.label_lastcomm.setText('Last Command: ')

    # --- handle ps selection ---

    def _get_ps_list(self):
        """Return list of power supplies to cycle."""
        # Get power supplies list
        pwrsupplies = self.pwrsupplies_tree.checked_items()
        if not pwrsupplies:
            QMessageBox.critical(self, 'Message', 'No power supply selected!')
            return False

        sections = get_sections(pwrsupplies)
        if 'BO' in sections and len(sections) > 1:
            QMessageBox.critical(self, 'Error',
                                 'Can not cycle Booster with other sectors!')
            return False

        create_task = CreateCyclers(parent=self, psnames=pwrsupplies)
        dlg = ProgressDialog('Creating cycles...', create_task, self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            return False

        return pwrsupplies

    def _handle_checked_items_changed(self, item):
        psname = PVName(item.data(0, Qt.DisplayRole))
        if not _re.match('.*-.*:.*-.*', psname):
            return

        if not self._is_adv_mode and psname.sec == 'SI' and \
                not psname.dev.startswith('FC'):
            psname2check = Filter.process_filters(self._psnames,
                                                  filters={
                                                      'sec': 'SI',
                                                      'dev': '(?!FC)'
                                                  })
            psname2check.remove(psname)
            state2set = item.checkState(0)
            self.pwrsupplies_tree.tree.blockSignals(True)
            for psn in psname2check:
                item2check = self.pwrsupplies_tree._item_map[psn]
                if item2check.checkState(0) != state2set:
                    item2check.setData(0, Qt.CheckStateRole, state2set)
            self.pwrsupplies_tree.tree.blockSignals(False)
        else:
            if (psname.sec in ['BO', 'SI'] and psname.dev in ['B', 'B1B2']):
                psname2check = PSSearch.get_psnames({
                    'sec': psname.sec,
                    'dev': 'B.*'
                })
                psname2check.remove(psname)
                item2check = self.pwrsupplies_tree._item_map[psname2check[0]]

                state2set = item.checkState(0)
                state2change = item2check.checkState(0)
                if state2change != state2set:
                    self.pwrsupplies_tree.tree.blockSignals(True)
                    item2check.setData(0, Qt.CheckStateRole, state2set)
                    self.pwrsupplies_tree.tree.blockSignals(False)

        self._prepared.update(self._prepared_init_vals)
        self._needs_update_setup = True

    def _update_setup(self):
        if not self._needs_update_setup:
            return
        self._needs_update_setup = False

        # update leds
        psnames = self.pwrsupplies_tree.checked_items()
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames(psnames)
        ]
        self.ticonn_led.set_channels(ti_ch)

        ps_ch = list()
        for name in psnames:
            ps_ch.append(
                PVName(name).substitute(prefix=VACA_PREFIX,
                                        propty='PwrState-Sts'))
        self.psconn_led.set_channels(ps_ch)

        # update buttons and self._prepared dict if not in advanced mode
        if not self._is_adv_mode:
            has_si = False
            for psn in PSSearch.get_psnames({'sec': 'SI', 'dis': 'PS'}):
                if psn not in self.pwrsupplies_tree._item_map:
                    continue
                item = self.pwrsupplies_tree._item_map[psn]
                has_si |= item.checkState(0) != 0

            if not has_si:
                self.cycle_bt.setText('8. Cycle')
                self.restore_timing_bt.setText(
                    '9. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(False)
                self.cycle_trims_lb.setVisible(False)
                self._prepared['trims'] = True
            else:
                self.cycle_bt.setText('9. Cycle')
                self.restore_timing_bt.setText(
                    '10. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(True)
                self.cycle_trims_lb.setVisible(True)
                self._prepared['trims'] = False

        self._handle_stslabels_content()
        self._handle_buttons_enabled(True)

    # --- auxiliary checks ---

    def _check_connected(self, control):
        if control in ['trims', 'cycle']:
            leds = [self.ticonn_led, self.psconn_led]
        elif 'timing' in control:
            leds = [
                self.ticonn_led,
            ]
        else:
            leds = [
                self.psconn_led,
            ]

        for led in leds:
            pvs_disconnected = set()
            for ch, v in led.channels2conn.items():
                if not v:
                    pvs_disconnected.add(ch)
            if pvs_disconnected:
                sttr = ''
                for item in pvs_disconnected:
                    sttr += item + '\n'
                QMessageBox.information(
                    self, 'Message',
                    'The following PVs are not connected:\n' + sttr)
                return False
        return True

    def _verify_ps(self, pwrsupplies):
        self._ps_failed = set()

        task = VerifyPS(parent=self, psnames=pwrsupplies)
        task.itemDone.connect(self._get_ps_not_ready_2_cycle)

        dlg = ProgressDialog('Verifying power supplies initial state...', task,
                             self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            self._handle_buttons_enabled(True)
            return False

        if self._ps_failed:
            text = 'Verify power state and interlocks' \
                ' of the following power supplies'
            dlg = PSStatusDialog(self._ps_failed, text, self)
            dlg.exec_()
            self._handle_buttons_enabled(True)
            return False

        return True

    def _get_ps_not_ready_2_cycle(self, psname, status):
        if not status:
            self._ps_failed.add(psname)

    def _open_ps_detail(self, item):
        if self.sender() == self.progress_list:
            text_split = item.data(Qt.DisplayRole).split(' ')
            psname = ''
            for text in text_split:
                if _re.match('.*-.*:.*-.*', text):
                    psname = text
            if not psname:
                return
        else:
            psname = item.data()
        if not _re.match('.*-.*:.*-.*', psname):
            if item.model().rowCount(item) == 1:
                psname = item.child(0, 0).data()
            else:
                return
        run_newprocess(['sirius-hla-as-ps-detail.py', psname])

    # --- events ---

    def keyPressEvent(self, evt):
        """Implement keyPressEvent."""
        if evt.matches(QKeySequence.Copy) and self.progress_list.underMouse():
            items = self.progress_list.selectedItems()
            items = '\n'.join([i.text() for i in items])
            QApplication.clipboard().setText(items)
        if evt.key() == Qt.Key_Escape and self.progress_list.underMouse():
            items = self.progress_list.clearSelection()
        super().keyPressEvent(evt)

    def closeEvent(self, ev):
        self._update_setup_timer.stop()
        super().closeEvent(ev)
Ejemplo n.º 17
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
        "shortcuts": {
            "ui:widget": "shortcuts"
        },
    }

    resized = Signal(QSize)

    def __init__(self, parent=None):
        from ...settings import get_settings

        super().__init__(parent)
        self.setWindowTitle(trans._("Preferences"))

        self._settings = get_settings()
        self._stack = QStackedWidget(self)
        self._list = QListWidget(self)
        self._list.setObjectName("Preferences")
        self._list.currentRowChanged.connect(self._stack.setCurrentIndex)

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_cancel.clicked.connect(self.reject)
        self._button_ok = QPushButton(trans._("OK"))
        self._button_ok.clicked.connect(self.accept)
        self._button_ok.setDefault(True)
        self._button_restore = QPushButton(trans._("Restore defaults"))
        self._button_restore.clicked.connect(self._restore_default_dialog)

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._button_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        self.setLayout(QHBoxLayout())
        self.layout().addLayout(left_layout, 1)
        self.layout().addWidget(self._stack, 3)

        # Build dialog from settings
        self._rebuild_dialog()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Escape:
            # escape key should just close the window
            # which implies "accept"
            e.accept()
            self.accept()
            return
        super().keyPressEvent(e)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def _rebuild_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # FIXME: this dialog should not need to know about the plugin manager
        from ...plugins import plugin_manager

        self._starting_pm_order = plugin_manager.call_order()
        self._starting_values = self._settings.dict(exclude={'schema_version'})

        self._list.clear()
        while self._stack.count():
            self._stack.removeWidget(self._stack.currentWidget())

        for field in self._settings.__fields__.values():
            if isinstance(field.type_, type) and issubclass(
                    field.type_, BaseModel):
                self._add_page(field)

        self._list.setCurrentRow(0)

    def _add_page(self, field: 'ModelField'):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        field : ModelField
            subfield for which to create a page.
        """
        from ..._vendor.qt_json_builder.qt_jsonschema_form import WidgetBuilder

        schema, values = self._get_page_dict(field)
        name = field.field_info.title or field.name

        form = WidgetBuilder().create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        # make settings follow state of the form widget
        form.widget.on_changed.connect(
            lambda d: getattr(self._settings, name.lower()).update(d))

        # need to disable async if octree is enabled.
        # TODO: this shouldn't live here... if there is a coupling/dependency
        # between these settings, it should be declared in the settings schema
        if (name.lower() == 'experimental'
                and values['octree'] and self._settings.env_settings().get(
                    'experimental', {}).get('async_') not in (None, '0')):
            form_layout = form.widget.layout()
            for i in range(form_layout.count()):
                wdg = form_layout.itemAt(i, form_layout.FieldRole).widget()
                if getattr(wdg, '_name') == 'async_':
                    wdg.opacity.setOpacity(0.3)
                    wdg.setDisabled(True)
                    break

        self._list.addItem(field.field_info.title or field.name)
        self._stack.addWidget(form)

    def _get_page_dict(self, field: 'ModelField') -> Tuple[dict, dict, dict]:
        """Provides the schema, set of values for each setting, and the
        properties for each setting."""
        ftype = cast('BaseModel', field.type_)
        schema = json.loads(ftype.schema_json())

        # find enums:
        for name, subfield in ftype.__fields__.items():
            if isinstance(subfield.type_, EnumMeta):
                enums = [s.value for s in subfield.type_]  # type: ignore
                schema["properties"][name]["enum"] = enums
                schema["properties"][name]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        setting = getattr(self._settings, field.name)
        with setting.enums_as_values():
            values = setting.dict()
        napari_config = getattr(setting, "NapariConfig", None)
        if hasattr(napari_config, 'preferences_exclude'):
            for val in napari_config.preferences_exclude:
                schema['properties'].pop(val, None)
                values.pop(val, None)

        return schema, values

    def _restore_default_dialog(self):
        """Launches dialog to confirm restore settings choice."""
        response = QMessageBox.question(
            self,
            trans._("Restore Settings"),
            trans._("Are you sure you want to restore default settings?"),
            QMessageBox.RestoreDefaults | QMessageBox.Cancel,
            QMessageBox.RestoreDefaults,
        )
        if response == QMessageBox.RestoreDefaults:
            self._settings.reset()
            self._rebuild_dialog()  # TODO: do we need this?

    def _restart_required_dialog(self):
        """Displays the dialog informing user a restart is required."""
        QMessageBox.information(
            self,
            trans._("Restart required"),
            trans.
            _("A restart is required for some new settings to have an effect."
              ),
        )

    def closeEvent(self, event: 'QCloseEvent') -> None:
        event.accept()
        self.accept()

    def reject(self):
        """Restores the settings in place when dialog was launched."""
        self._settings.update(self._starting_values)

        # FIXME: this dialog should not need to know about the plugin manager
        if self._starting_pm_order:
            from ...plugins import plugin_manager

            plugin_manager.set_call_order(self._starting_pm_order)
        super().reject()
Ejemplo n.º 18
0
class EditableList(QWidget):
    """
    Editable list with label, add, delete and edit buttons.
    """
    sig_item_edited = Signal(object)
    sig_item_removed = Signal(object)
    sig_item_selected = Signal()

    def __init__(self,
                 title=None,
                 items_text=[],
                 duplicates=False,
                 normalize=True,
                 min_items=1,
                 confirm_remove=True,
                 regex=None,
                 tooltip=None):
        super(EditableList, self).__init__()

        self.duplicates = duplicates
        self.items_text = items_text
        self.normalize = normalize
        self.min_items = min_items
        self.confirm_remove = confirm_remove

        # Widgets
        self.label_title = QLabel(title)
        self.button_add = QPushButton(qta.icon('fa.plus'), '')
        self.button_remove = QPushButton(qta.icon('fa.minus'), '')
        self.button_edit = QPushButton(qta.icon('fa.edit'), '')
        self.list = QListWidget()
        self.delegate = EditableListDelegate(regex=regex, tooltip=tooltip)

        # Widget setup
        self.list.setItemDelegate(self.delegate)
        self.setMaximumHeight(self._height() * 4)

        # Layout
        label_buttons_layout = QHBoxLayout()
        label_buttons_layout.addWidget(self.label_title, 0)
        label_buttons_layout.addStretch()
        label_buttons_layout.addWidget(self.button_add)
        label_buttons_layout.addWidget(self.button_remove)
        label_buttons_layout.addWidget(self.button_edit)

        layout = QVBoxLayout()
        layout.addLayout(label_buttons_layout)
        layout.addWidget(self.list)

        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(self.add)
        self.button_edit.clicked.connect(self.edit)
        self.button_remove.clicked.connect(self.remove)

        self.delegate.closeEditor.connect(self.check_value)
        self.list.currentItemChanged.connect(self.refresh)
        self.list.itemSelectionChanged.connect(self.sig_item_selected)

        # Setup
        self.setup()

        # Expose list methods
        self.clear = self.list.clear
        self.currentItem = self.list.currentItem
        self.setCurrentRow = self.list.setCurrentRow
        self.currentRow = self.list.currentRow
        self.item = self.list.item
        self.count = self.list.count

    def _height(self):
        """
        Get the height for the row in the widget based on OS font metrics.
        """
        return self.fontMetrics().height() * 2

    def setup(self):
        """
        Initial setup for populating items if any.
        """
        # TODO: Check against regex and raise error accordingly!
        new_items = []
        for text in self.items_text:
            if self.normalize:
                text = text.lower()
            new_items.append(text)

        self.items_text = new_items

        if not self.duplicates:
            if len(set(self.items_text)) != len(self.items_text):
                raise Exception('The list cannot contains duplicates.')

        for item in self.items_text:
            item = QListWidgetItem(item)
            item.extra_data = None
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            self.add(item)
            item.setSizeHint(QSize(item.sizeHint().width(), self._height()))

        self.refresh()

    def get_texts(self):
        """
        Returns the list of texts in the list, excluding the ones under
        edition.
        """
        row = self.list.currentRow()
        texts = []
        for i in range(self.list.count()):
            item = self.list.item(i)
            if item:
                text = item.text().lower() if self.normalize else item.text()
                texts.append(text)

        # Check for duplicates. But the entered text already is part of the
        # items, so that needs to be removed to make the check
        if texts and row != -1:
            texts.pop(row)

        return texts

    def add(self, item=None):
        """
        Return the text of all items in the list, except the current one being
        edited.
        """
        if item:
            if item.text() in self.get_texts() and not self.duplicates:
                raise Exception
            else:
                self.list.addItem(item)
        else:
            item = QListWidgetItem()
            item.extra_data = None
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable
                          | Qt.ItemIsEditable)
            self.list.addItem(item)
            self.list.setCurrentItem(item)
            item.setSizeHint(QSize(item.sizeHint().width(), self._height()))
            self.edit()
        self.refresh()

    def remove(self):
        """
        Remove item fron the list.
        """
        row = self.list.currentRow()
        item = self.list.currentItem()

        if item is None:
            self.refresh()
        else:
            text = item.text()

        if self.confirm_remove:
            message = ("Are you sure you want to remove<br>"
                       "<strong>{0}</strong>?".format(text))
            reply = QMessageBox.question(self, 'Remove item', message,
                                         QMessageBox.Yes, QMessageBox.No)
            remove_item = reply == QMessageBox.Yes
        else:
            remove_item = True

        if row != -1 and remove_item:
            item = self.list.takeItem(row)
            self.sig_item_removed.emit(item)

        self.refresh()

    def edit(self):
        """
        Start editing item from the list.
        """
        self.button_remove.setDisabled(True)
        self.button_add.setDisabled(True)
        self.button_edit.setDisabled(True)
        item = self.current_item()
        if item:
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable
                          | Qt.ItemIsEditable)
            item._text = item.text()
            self.list.editItem(item)

    def current_item(self):
        """
        Return the current selected item.
        """
        item = None
        row = self.list.currentRow()
        if row != -1:
            item = self.list.item(row)
        return item

    def check_value(self):
        """
        After editing an item check the value is valid and return to edit mode
        if it does not pass, or accept the value and add it.
        """
        texts = self.get_texts()
        item = self.current_item()
        self._temp_item = item
        if item:
            text = item.text().lower() if self.normalize else item.text()
            row = self.list.currentRow()
            if text.strip() == '':
                self.list.takeItem(row)
            if text.strip() in texts and not self.duplicates:
                self.edit()
            else:
                self.sig_item_edited.emit(item)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

    def refresh(self):
        """
        Refresh the enabled status of the actions buttons.
        """
        current_row = self.list.currentRow()

        if self.list.count() == 0:
            self.button_edit.setDisabled(True)
            self.button_remove.setDisabled(True)

        elif self.list.count() != 0 and current_row == -1:
            self.button_edit.setDisabled(True)
            self.button_remove.setDisabled(True)

        elif self.list.count() == self.min_items:
            self.button_add.setDisabled(False)
            self.button_edit.setDisabled(False)
            self.button_remove.setDisabled(True)

        elif self.list.count() != 0 and current_row != -1:
            self.button_add.setDisabled(False)
            self.button_edit.setDisabled(False)
            self.button_remove.setDisabled(False)

        for i in range(self.list.count()):
            item = self.list.item(i)
            item.setSizeHint(QSize(item.sizeHint().width(), self._height()))
Ejemplo n.º 19
0
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int, object)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]
    MAX_WIDTH = 600

    def __init__(self, parent, plugin, tabs, data, icon):
        QDialog.__init__(self, parent)

        # Variables
        self.plugins_tabs = []
        self.plugins_data = []
        self.plugins_instances = []
        self.add_plugin(plugin, tabs, data, icon)
        self.plugin = None                # Last plugin with focus
        self.mode = self.FILE_MODE        # By default start in this mode
        self.initial_cursors = None       # {fullpath: QCursor}
        self.initial_path = None          # Fullpath of initial active editor
        self.initial_widget = None        # Initial active editor
        self.line_number = None           # Selected line number in filer
        self.is_visible = False           # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = FilesFilterLine(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Properties
    @property
    def widgets(self):
        widgets = []
        for tabs, plugin in self.plugins_tabs:
            widgets += [(tabs.widget(index), plugin) for
                        index in range(tabs.count())]
        return widgets

    @property
    def line_count(self):
        line_count = []
        for widget in self.widgets:
            try:
                current_line_count = widget[0].get_line_count()
            except AttributeError:
                current_line_count = 0
            line_count.append(current_line_count)
        return line_count

    @property
    def save_status(self):
        save_status = []
        for da, icon in self.plugins_data:
            save_status += [getattr(td, 'newly_created', False) for td in da]
        return save_status

    @property
    def paths(self):
        paths = []
        for da, icon in self.plugins_data:
            paths += [getattr(td, 'filename', None) for td in da]
        return paths

    @property
    def filenames(self):
        filenames = []
        for da, icon in self.plugins_data:
            filenames += [os.path.basename(getattr(td,
                                                   'filename',
                                                   None)) for td in da]
        return filenames

    @property
    def icons(self):
        icons = []
        for da, icon in self.plugins_data:
            icons += [icon for td in da]
        return icons

    @property
    def current_path(self):
        return self.paths_by_widget[self.get_widget()]

    @property
    def paths_by_widget(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(widgets, self.paths))

    @property
    def widgets_by_path(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(self.paths, widgets))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, _str):
        self.edit.setText(_str)

    def save_initial_state(self):
        """Save initial cursors and initial active widget."""
        paths = self.paths
        self.initial_widget = self.get_widget()
        self.initial_cursors = {}

        for i, editor in enumerate(self.widgets):
            if editor is self.initial_widget:
                self.initial_path = paths[i]
            # This try is needed to make the fileswitcher work with 
            # plugins that does not have a textCursor.
            try:
                self.initial_cursors[paths[i]] = editor.textCursor()
            except AttributeError:
                pass

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        widgets = self.widgets_by_path

        if not self.edit.clicked_outside:
            for path in self.initial_cursors:
                cursor = self.initial_cursors[path]
                if path in widgets:
                    self.set_editor_cursor(widgets[path], cursor)

            if self.initial_widget in self.paths_by_widget:
                index = self.paths.index(self.initial_path)
                self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup
        left = parent.geometry().width()/2 - width/2
        # Note: the +1 pixel on the top makes it look better
        if isinstance(parent, QMainWindow):
            top = (parent.toolbars_menu.geometry().height() +
                   parent.menuBar().geometry().height() + 1)
        else:
            top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1

        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        self.move(left, top)

    def get_item_size(self, content):
        """
        Get the max size (width and height) for the elements of a list of
        strings as a QLabel.
        """
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()

            return (max([fm.width(s) * 1.3 for s in strings]), fm.height())

    def fix_size(self, content):
        """
        Adjusts the width and height of the file switcher
        based on the relative size of the parent and content.
        """
        # Update size of dialog based on relative size of the parent
        if content:
            width, height = self.get_item_size(content)

            # Width
            parent = self.parent()
            relative_width = parent.geometry().width() * 0.65
            if relative_width > self.MAX_WIDTH:
                relative_width = self.MAX_WIDTH
            self.list.setMinimumWidth(relative_width)

            # Height
            if len(content) < 8:
                max_entries = len(content)
            else:
                max_entries = 8
            max_height = height * max_entries * 2.5
            self.list.setMinimumHeight(max_height)

            # Resize
            self.list.resize(relative_width, self.list.height())

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(-1)
            return
        prev_row = self.current_row() - 1
        if prev_row >= 0:
            title = self.list.item(prev_row).text()
        else:
            title = ''
        if prev_row == 0 and '</b></big><br>' in title:
            self.list.scrollToTop()
        elif '</b></big><br>' in title:
            # Select the next previous row, the one following is a title
            self.select_row(-2)
        else:
            self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(+1)
            return
        next_row = self.current_row() + 1
        if next_row < self.count():
            if '</b></big><br>' in self.list.item(next_row).text():
                # Select the next next row, the one following is a title
                self.select_row(+2)
            else:
                self.select_row(+1)

    def get_stack_index(self, stack_index, plugin_index):
        """Get the real index of the selected item."""
        other_plugins_count = sum([other_tabs[0].count() \
                                   for other_tabs in \
                                   self.plugins_tabs[:plugin_index]])
        real_index = stack_index - other_plugins_count

        return real_index

    # --- Helper methods: Widget
    def get_widget(self, index=None, path=None, tabs=None):
        """Get widget by index.
        
        If no tabs and index specified the current active widget is returned.
        """
        if index and tabs:
            return tabs.widget(index)
        elif path and tabs:
            return tabs.widget(index)
        elif self.plugin:
            index = self.plugins_instances.index(self.plugin)
            return self.plugins_tabs[index][0].currentWidget()
        else:
            return self.plugins_tabs[0][0].currentWidget()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            editor = self.get_widget()
            try:
                editor.go_to_line(min(line_number, editor.get_line_count()))
            except AttributeError:
                pass

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the list of symbols present in the file."""
        try:
            oedata = self.get_widget().get_outlineexplorer_data()
        except AttributeError:
            oedata = {}
        return oedata

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if '</b></big><br>' in self.list.currentItem().text() and row == 0:
                self.next_row()
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.plugin = self.widgets[stack_index][1]
                    plugin_index = self.plugins_instances.index(self.plugin)
                    # Count the real index in the tabWidget of the
                    # current plugin
                    real_index = self.get_stack_index(stack_index,
                                                      plugin_index)
                    self.sig_goto_file.emit(real_index,
                                            self.plugin.get_current_tab_manager())
                    self.goto_line(self.line_number)
                    try:
                        self.plugin.switch_to_plugin()
                        self.raise_()
                    except AttributeError:
                        # The widget using the fileswitcher is not a plugin
                        pass
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        icons = self.icons
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
            # Get all the available filenames
            scores = get_search_scores('', self.filenames,
                                       template="<b>{0}</b>")
        else:
            line_number = None
            # Get all available filenames and get the scores for
            # "fuzzy" matching
            scores = get_search_scores(filter_text, self.filenames,
                                       template="<b>{0}</b>")


        # Get max width to determine if shortpaths should be used
        max_width = self.get_item_size(paths)[0]
        self.fix_size(paths)

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text.replace('&', '') + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                if max_width > self.list.width():
                    text_item += u"<br><i>{0:}</i>".format(short_paths[index])
                else:
                    text_item += u"<br><i>{0:}</i>".format(paths[index])
                if (trying_for_line_number and self.line_count[index] != 0 or
                        not trying_for_line_number):
                    results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        plugin = None
        for result in sorted(results):
            index = result[1]
            path = paths[index]
            icon = icons[index]
            text = ''
            try:
                title = self.widgets[index][1].get_plugin_title().split(' - ')
                if plugin != title[0]:
                    plugin = title[0]
                    text += '<br><big><b>' + plugin + '</b></big><br>'
                    item = QListWidgetItem(text)
                    item.setToolTip(path)
                    item.setSizeHint(QSize(0, 25))
                    item.setFlags(Qt.ItemIsEditable)
                    self.list.addItem(item)
                    self.filtered_path.append(path)
            except:
                # The widget using the fileswitcher is not a plugin
                pass
            text = ''
            text += result[-1]
            item = QListWidgetItem(icon, text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = True

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        # The list of paths here is needed in order to have the same
        # point of measurement for the list widget size as in the file list
        # See issue 4648
        paths = self.paths
        # Update list size
        self.fix_size(paths)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text,
                                fold_level, icons[index], token))

        template = '{0}{1}'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;'*(fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template.format(fold_space, rich_text)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = False

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

    def setup(self):
        """Setup list widget content."""
        if len(self.plugins_tabs) == 0:
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)

        # Set position according to size
        self.set_dialog_position()

    def add_plugin(self, plugin, tabs, data, icon):
        """Add a plugin to display its files."""
        self.plugins_tabs.append((tabs, plugin))
        self.plugins_data.append((data, icon))
        self.plugins_instances.append(plugin)
class MainWindow(QMainWindow):
    def __init__(self):
        self.logger = logging.getLogger(APPLICATION_NAME + '.MainWindow')
        self.logger.info("MainWindow.__init__")

        super(MainWindow, self).__init__()

        self.init_ui()

        self.read_settings()

    def init_ui(self):
        # Define standard icon.
        standard_icon = self.style().standardIcon

        # Central widget.
        self.main_widget = SpectrumWidget()
        self.main_widget.setFocus()
        self.setCentralWidget(self.main_widget)

        # Project action
        new_project_action = QAction(QIcon(':/oi/svg/document.svg'),
                                     'New project', self)
        new_project_action.setShortcut('Ctrl+N')
        new_project_action.setStatusTip('New project')
        new_project_action.triggered.connect(self.new_project)

        open_project_action = QAction(QIcon(':/oi/svg/envelope-open.svg'),
                                      'Open project', self)
        open_project_action.setShortcut('Ctrl+O')
        open_project_action.setStatusTip('Open project')
        open_project_action.triggered.connect(self.open_project)

        close_project_action = QAction(QIcon(':/oi/svg/envelope-closed.svg'),
                                       'Close project', self)
        close_project_action.setShortcut('Ctrl+C')
        close_project_action.setStatusTip('Close project')
        close_project_action.triggered.connect(self.close_project)

        save_project_action = QAction(QIcon(':/oi/svg/hard-drive.svg'),
                                      'Save project', self)
        save_project_action.setShortcut('Ctrl+S')
        save_project_action.setStatusTip('Save project')
        save_project_action.triggered.connect(self.save_project)

        saveas_project_action = QAction(QIcon(':/oi/svg/hard-drive.svg'),
                                        'Save project as ...', self)
        # saveas_project_action.setShortcut('Ctrl+S')
        saveas_project_action.setStatusTip('Save project as ...')
        saveas_project_action.triggered.connect(self.saveas_project)

        # Spectrum action
        import_spectrum_action = QAction(QIcon(':/oi/svg/account-login.svg'),
                                         'Import spectrum', self)
        import_spectrum_action.setShortcut('Ctrl+I')
        import_spectrum_action.setStatusTip('Import spectrum')
        import_spectrum_action.triggered.connect(self.import_spectrum)

        export_spectrum_action = QAction(QIcon(':/oi/svg/account-logout.svg'),
                                         'Export spectrum', self)
        # export_spectrum_action.setShortcut('Ctrl+I')
        export_spectrum_action.setStatusTip('Export spectrum')
        export_spectrum_action.triggered.connect(self.export_spectrum)

        # Exit action
        exit_action = QAction(QIcon(':/oi/svg/x.svg'), 'Exit', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.setStatusTip('Exit application')
        exit_action.triggered.connect(self.close)

        # Status bar.
        self.statusBar()

        # Menu bar.
        menubar = self.menuBar()
        file_menu = menubar.addMenu('&File')
        file_menu.addAction(new_project_action)
        file_menu.addAction(open_project_action)
        file_menu.addAction(save_project_action)
        file_menu.addAction(saveas_project_action)
        file_menu.addAction(close_project_action)
        file_menu.addSeparator()
        file_menu.addAction(exit_action)

        view_menu = menubar.addMenu('&View')

        spectrum_menu = menubar.addMenu('&Spectrum')
        spectrum_menu.addAction(import_spectrum_action)

        analysis_menu = menubar.addMenu('&Analysis')

        # Toolbar
        file_toolbar = self.addToolBar('File')
        file_toolbar.addAction(new_project_action)
        file_toolbar.addAction(open_project_action)
        file_toolbar.addAction(save_project_action)
        file_toolbar.addAction(saveas_project_action)
        file_toolbar.addAction(close_project_action)
        file_toolbar.addAction(exit_action)
        view_menu.addAction(file_toolbar.toggleViewAction())

        spectrum_toolbar = self.addToolBar('Spectrum')
        spectrum_toolbar.addAction(import_spectrum_action)
        view_menu.addAction(spectrum_toolbar.toggleViewAction())

        analysis_toolbar = self.addToolBar('Analysis')
        view_menu.addAction(analysis_toolbar.toggleViewAction())

        view_menu.addSeparator()

        # Dock widget.
        self.graphic_settings_dock = QDockWidget("Graphic settings", self)
        self.graphic_settings_dock.setObjectName("graphic_settings_dock")
        self.graphic_settings_dock.setAllowedAreas(Qt.AllDockWidgetAreas)
        label_test = QLabel("Test label")
        self.graphic_settings_dock.setWidget(label_test)
        view_menu.addAction(self.graphic_settings_dock.toggleViewAction())
        self.addDockWidget(Qt.AllDockWidgetAreas, self.graphic_settings_dock)
        print(self.graphic_settings_dock.objectName())

        # Final options.
        self.setWindowTitle('X-ray spectrum analyzer')
        self.show()

    def closeEvent(self, event):
        self.save_settings()
        super(MainWindow, self).closeEvent(event)

    def save_settings(self):
        settings = QSettings("openMicroanalysis", "xrayspectrumanalyzergui")
        # print(settings.fileName())

        settings.beginGroup("MainWindow")
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("window_state", self.saveState())
        settings.endGroup()

        settings.beginGroup("graphic_settings_dock")
        settings.setValue("visible", self.graphic_settings_dock.isVisible())
        settings.endGroup()

        # settings.beginGroup("zero_loss_peak_dock")
        # settings.setValue("visible", self.zero_loss_peak_dock.isVisible())
        # settings.endGroup()

    def read_settings(self):
        settings = QSettings("openMicroanalysis", "xrayspectrumanalyzergui")
        # print(settings.fileName())
        # settings.clear()

        settings.beginGroup("MainWindow")
        geometry_value = settings.value("geometry")
        if geometry_value is None:
            self.setGeometry(300, 300, 600, 400)
        else:
            self.restoreGeometry(geometry_value)
        window_state_value = settings.value("window_state")
        if window_state_value is not None:
            self.restoreState(window_state_value)
        settings.endGroup()

        settings.beginGroup("graphic_settings_dock")
        visible_value = settings.value("visible")
        if visible_value is not None:
            if visible_value == "true":
                self.graphic_settings_dock.setVisible(True)
            elif visible_value == "false":
                self.graphic_settings_dock.setVisible(False)
        settings.endGroup()

        # settings.beginGroup("zero_loss_peak_dock")
        # visible_value = settings.value("visible")
        # if visible_value is not None:
        #     if visible_value == "true":
        #         self.zero_loss_peak_dock.setVisible(True)
        #     elif visible_value == "false":
        #         self.zero_loss_peak_dock.setVisible(False)
        # settings.endGroup()

    def import_spectrum(self):
        self.statusBar().showMessage("Import spectrum", 2000)

        path = os.path.dirname(__file__)
        formats = ["*.msa", "*.txt"]
        file_filters = "Spectrum file ({:s})".format(" ".join(formats))
        file_names = QFileDialog.getOpenFileName(self,
                                                 "Import an x-ray spectrum",
                                                 path, file_filters)

    def export_spectrum(self):
        self.statusBar().showMessage("Export spectrum", 2000)

    def new_project(self):
        self.statusBar().showMessage("New project", 2000)

    def open_project(self):
        self.statusBar().showMessage("Open project", 2000)

    def close_project(self):
        self.statusBar().showMessage("Close project", 2000)

    def save_project(self):
        self.statusBar().showMessage("Save project", 2000)

    def saveas_project(self):
        self.statusBar().showMessage("Save project as ...", 2000)

    def create_gui(self):
        self.logger.info("MainWindow.create_gui")

        self._create_main_window()
        self._create_actions()
        self._create_menus()
        self._create_toolbars()
        self._create_tooltip()
        self._create_spectra_display()
        self._create_data_display()
        self._create_operations_display()
        self._create_layout()
        self._create_statusbar()

        self._read_settings()

        self.show()

    def _create_main_window(self):
        self.setGeometry(300, 300, 500, 500)
        self.setWindowTitle('Spectrum Analyzer')
        # self.setWindowIcon(QIcon('../../../images/cog.svg'))
        self.setWindowIcon(self.style().standardIcon(QStyle.SP_DesktopIcon))
        self._center_main_window()

    def _center_main_window(self):
        self.logger.info("MainWindow._center_main_window")

        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def _create_menus(self):
        self.logger.info("MainWindow._create_menus")

        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def _create_layout(self):
        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.dataGroupBox)
        mainLayout.addWidget(self.plotGroupBox)
        mainLayout.addWidget(self.operationsGroupBox)

        self.mainGroupBox = QGroupBox("Main layout")
        self.mainGroupBox.setLayout(mainLayout)
        self.setCentralWidget(self.mainGroupBox)

    def _create_spectra_display(self):
        self.plotGroupBox = QGroupBox("Plot layout")

        self.figure1 = Figure(facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setParent(self.plotGroupBox)
        self.canvas1.setFocusPolicy(Qt.StrongFocus)
        self.canvas1.setFocus()
        self.canvas1.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.canvas1.updateGeometry()

        self.mpl_toolbar1 = NavigationToolbar(self.canvas1, self.plotGroupBox)
        self.canvas1.mpl_connect('key_press_event', self.on_key_press)

        self.figure2 = Figure(facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setParent(self.plotGroupBox)
        self.mpl_toolbar2 = NavigationToolbar(self.canvas1, self.plotGroupBox)

        layout = QVBoxLayout()
        layout.addWidget(self.canvas1)
        layout.addWidget(self.mpl_toolbar1)
        layout.addWidget(self.canvas2)
        layout.addWidget(self.mpl_toolbar2)
        self.plotGroupBox.setLayout(layout)

    def _create_data_display(self):
        self.dataGroupBox = QGroupBox("Data layout")
        data_layout = QVBoxLayout()

        group_box = QGroupBox("Spectra")
        self.spectra_list_view = QListWidget(self)
        self.spectra_list_view.setMinimumWidth(200)

        layout = QVBoxLayout()
        layout.addWidget(self.spectra_list_view)
        group_box.setLayout(layout)
        data_layout.addWidget(group_box)

        group_box = QGroupBox("ROI")
        roi_list_view = QListWidget(self)
        layout = QVBoxLayout()
        layout.addWidget(roi_list_view)
        group_box.setLayout(layout)
        data_layout.addWidget(group_box)

        group_box = QGroupBox("Elements")
        element_list_view = QListWidget(self)
        layout = QVBoxLayout()
        layout.addWidget(element_list_view)
        group_box.setLayout(layout)
        data_layout.addWidget(group_box)

        self.dataGroupBox.setLayout(data_layout)

    def _create_operations_display(self):
        self.operationsGroupBox = QGroupBox("Operations layout")
        results_layout = QVBoxLayout()

        group_box = QGroupBox("Operation")
        results_layout.addWidget(group_box)
        group_box = QGroupBox("Results")
        results_layout.addWidget(group_box)

        self.operationsGroupBox.setLayout(results_layout)

    def _create_tooltip(self):
        QToolTip.setFont(QFont('SansSerif', 10))
        self.setToolTip('This is a <b>QWidget</b> widget')

    def _create_actions(self):
        self.logger.info("MainWindow._create_actions")

        self.newAct = QAction(self.style().standardIcon(QStyle.SP_FileIcon),
                              "&New",
                              self,
                              shortcut=QKeySequence.New,
                              statusTip="Create a new file",
                              triggered=self.newFile)

        self.openAct = QAction(self.style().standardIcon(
            QStyle.SP_DirOpenIcon),
                               "&Open...",
                               self,
                               shortcut=QKeySequence.Open,
                               statusTip="Open an existing file",
                               triggered=self.open)

        self.saveAct = QAction(self.style().standardIcon(
            QStyle.SP_DialogSaveButton),
                               "&Save",
                               self,
                               shortcut=QKeySequence.Save,
                               statusTip="Save the document to disk",
                               triggered=self.save)

        self.saveAsAct = QAction(
            self.style().standardIcon(QStyle.SP_DialogSaveButton),
            "Save &As...",
            self,
            shortcut=QKeySequence.SaveAs,
            statusTip="Save the document under a new name",
            triggered=self.saveAs)

        self.exitAct = QAction(self.style().standardIcon(
            QStyle.SP_DialogCloseButton),
                               "E&xit",
                               self,
                               shortcut="Ctrl+Q",
                               statusTip="Exit the application",
                               triggered=self.close)

        self.textEdit = QTextEdit()

        self.aboutAct = QAction(self.style().standardIcon(
            QStyle.SP_MessageBoxInformation),
                                "&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

        self.aboutQtAct = QAction(self.style().standardIcon(
            QStyle.SP_TitleBarMenuButton),
                                  "About &Qt",
                                  self,
                                  statusTip="Show the Qt library's About box",
                                  triggered=QApplication().aboutQt)

    def _create_toolbars(self):
        self.logger.info("MainWindow._create_toolbars")

        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newAct)
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.saveAct)

    def _create_statusbar(self):
        self.logger.info("MainWindow._create_statusbar")

        self.statusBar().showMessage("Ready")

    def _read_settings(self):
        self.logger.info("MainWindow._read_settings")

        settings = QSettings(ORGANIZATION_NAME, APPLICATION_NAME)
        pos = settings.value("pos", QPoint(200, 200))
        size = settings.value("size", QSize(400, 400))
        self.resize(size)
        self.move(pos)

    def _write_settings(self):
        self.logger.info("MainWindow._write_settings")

        settings = QSettings(ORGANIZATION_NAME, APPLICATION_NAME)
        settings.setValue("pos", self.pos())
        settings.setValue("size", self.size())

    def maybeSave(self):
        self.logger.info("MainWindow.maybeSave")

        if self.textEdit.document().isModified():
            ret = QMessageBox.warning(
                self, "Application",
                "The document has been modified.\nDo you want to save "
                "your changes?",
                QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
            if ret == QMessageBox.Save:
                return self.save()
            elif ret == QMessageBox.Cancel:
                return False
        return True

    def closeEvent(self, event):
        self.logger.info("MainWindow.closeEvent")

        if self.maybeSave():
            self._write_settings()
            event.accept()
        else:
            event.ignore()

    def newFile(self):
        self.logger.info("MainWindow.newFile")

        if self.maybeSave():
            self.textEdit.clear()
            self.setCurrentFile('')

    def open(self):
        self.logger.info("MainWindow.open")

        if self.maybeSave():
            filepath, _filtr = QFileDialog.getOpenFileName(self)
            if filepath:
                self.spectrumAnalyzer.readSpectrum(filepath)
                filename = os.path.basename(filepath)
                self.spectra_list_view.addItem(filename)
                self.spectrumAnalyzer.plotSpectrum(self.figure1)
                self.canvas1.draw()

    def save(self):
        self.logger.info("MainWindow.save")

        if self.curFile:
            return self.saveFile(self.curFile)

        return self.saveAs()

    def saveAs(self):
        self.logger.info("MainWindow.saveAs")

        fileName, _filtr = QFileDialog.getSaveFileName(self)
        if fileName:
            return self.saveFile(fileName)

        return False

    def about(self):
        self.logger.info("MainWindow.about")

        QMessageBox.about(
            self, "About xrayspectrumanalyzer",
            "The <b>xrayspectrumanalyzer</b> extract peak intensity from EDS spectrum."
        )

    def documentWasModified(self):
        self.logger.info("MainWindow.documentWasModified")

        self.setWindowModified(self.textEdit.document().isModified())

    def on_key_press(self, event):
        print('you pressed', event.key)
        # implement the default mpl key press events described at
        # http://matplotlib.org/users/navigation_toolbar.html#navigation-keyboard-shortcuts
        key_press_handler(event, self.canvas1, self.mpl_toolbar1)
Ejemplo n.º 21
0
class PMGListCtrl(BaseExtendedWidget):
    def __init__(self,
                 layout_dir: str,
                 title: str,
                 initial_value: List[List[str]],
                 new_id_func: Callable = None):
        super().__init__(layout_dir)
        self.choices = []
        self.text_list = []
        lab_title = QLabel(text=title)
        layout = QHBoxLayout()
        self.central_layout.addWidget(lab_title)
        self.on_check_callback = None
        self.list_widget = QListWidget()
        self.list_widget.mouseDoubleClickEvent = self.on_listwidget_double_cicked

        self.set_value(initial_value)
        layout_tools = QVBoxLayout()
        self.button_add_item = QPushButton('+')
        self.button_delete_item = QPushButton('-')
        self.button_delete_item.clicked.connect(self.delete_row)
        self.button_add_item.clicked.connect(self.add_row)
        self.button_add_item.setMaximumWidth(20)
        self.button_delete_item.setMaximumWidth(20)
        layout_tools.addWidget(self.button_add_item)
        layout_tools.addWidget(self.button_delete_item)
        layout.addLayout(layout_tools)
        layout.addWidget(self.list_widget)
        self.central_layout.addLayout(layout)
        self.data = initial_value
        self.new_id_func = new_id_func

        self.text_edit = QLineEdit(parent=self.list_widget)
        self.text_edit.setWindowFlags(self.text_edit.windowFlags() | Qt.Dialog
                                      | Qt.FramelessWindowHint)
        self.text_edit.hide()
        self.completer = QCompleter()
        self.text_edit.setCompleter(self.completer)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)

    def set_completions(self, completions: List[str]):
        """
        设置补全内容
        Args:
            completions:

        Returns:

        """
        self.completer.setModel(QStringListModel(completions))

    def new_id(self):
        if callable(self.new_id_func):
            return self.new_id_func()
        else:
            return None

    def add_row(self):
        self.data = self.get_value()
        self.data[0].append(self.new_id())
        self.data[1].append('Unnamed')
        self.list_widget.addItem(QListWidgetItem('Unnamed'))

    def delete_row(self):
        index = self.list_widget.currentIndex().row()
        self.data[0].pop(index)
        self.data[1].pop(index)
        self.list_widget.takeItem(index)

    def on_listwidget_double_cicked(self, evt: QMouseEvent):
        print('edit', evt)
        pos = evt.globalPos()
        current_item: QListWidgetItem = self.list_widget.currentItem()

        def set_value():
            current_item.setText(self.text_edit.text())
            self.text_edit.hide()
            self.text_edit.returnPressed.disconnect(set_value)

        item: QListWidgetItem = self.list_widget.currentItem()

        self.text_edit.setGeometry(pos.x(), pos.y(), 200, 20)
        self.text_edit.returnPressed.connect(set_value)
        self.text_edit.show()
        # self.list_widget.editItem(item)

    def get_value(self):
        text = []
        for i in range(self.list_widget.count()):
            text.append(self.list_widget.item(i).text())
        self.data[1] = text
        assert len(self.data[1]) == len(self.data[0]), repr(self.data)
        return self.data

    def set_value(self, data: List[List[str]]):
        self.list_widget.clear()
        self.list_widget.addItems(data[1])
        self.data = data
        for index in range(self.list_widget.count()):
            item: QListWidgetItem = self.list_widget.item(index)
Ejemplo n.º 22
0

# parse input file
base_path = 'data/RxRx19a/images/VERO-1/Plate1/'
files = sorted(glob(base_path + "*.png"))

names = [os.path.basename(f)[:7] for f in files]
unique_names, counts = np.unique(names, return_counts=True)
names = list(unique_names[counts == 5])

with napari.gui_qt():
    viewer = napari.Viewer()

    # instantiate the widget
    gui = segmentation.Gui()

    list_widget = QListWidget()
    for n in names:
        list_widget.addItem(n)

    list_widget.currentItemChanged.connect(open_name)

    viewer.window.add_dock_widget([list_widget, gui], area='right')

    # keep the dropdown menus in the gui in sync with the layer model
    viewer.layers.events.changed.connect(lambda x: gui.refresh_choices())

    gui.refresh_choices()

    list_widget.setCurrentRow(0)
Ejemplo n.º 23
0
class PathManager(QDialog):
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent=None, pathlist=None, ro_pathlist=None,
                 not_active_pathlist=None, sync=True):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        assert isinstance(pathlist, list)
        self.pathlist = pathlist
        if not_active_pathlist is None:
            not_active_pathlist = []
        self.not_active_pathlist = not_active_pathlist
        if ro_pathlist is None:
            ro_pathlist = []
        self.ro_pathlist = ro_pathlist
        
        self.last_path = getcwd()
        
        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)
        
        self.selection_widgets = []
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        top_layout = QHBoxLayout()
        layout.addLayout(top_layout)
        self.toolbar_widgets1 = self.setup_top_toolbar(top_layout)

        self.listwidget = QListWidget(self)
        self.listwidget.currentRowChanged.connect(self.refresh)
        self.listwidget.itemChanged.connect(self.update_not_active_pathlist)
        layout.addWidget(self.listwidget)

        bottom_layout = QHBoxLayout()
        layout.addLayout(bottom_layout)
        self.sync_button = None
        self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync)        
        
        # Buttons configuration
        bbox = QDialogButtonBox(QDialogButtonBox.Close)
        bbox.rejected.connect(self.reject)
        bottom_layout.addWidget(bbox)
        
        self.update_list()
        self.refresh()

    @property
    def active_pathlist(self):
        return [path for path in self.pathlist
                if path not in self.not_active_pathlist]

    def _add_widgets_to_layout(self, layout, widgets):
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            layout.addWidget(widget)
        
    def setup_top_toolbar(self, layout):
        toolbar = []
        movetop_button = create_toolbutton(self,
                                    text=_("Move to top"),
                                    icon=ima.icon('2uparrow'),
                                    triggered=lambda: self.move_to(absolute=0),
                                    text_beside_icon=True)
        toolbar.append(movetop_button)
        moveup_button = create_toolbutton(self,
                                    text=_("Move up"),
                                    icon=ima.icon('1uparrow'),
                                    triggered=lambda: self.move_to(relative=-1),
                                    text_beside_icon=True)
        toolbar.append(moveup_button)
        movedown_button = create_toolbutton(self,
                                    text=_("Move down"),
                                    icon=ima.icon('1downarrow'),
                                    triggered=lambda: self.move_to(relative=1),
                                    text_beside_icon=True)
        toolbar.append(movedown_button)
        movebottom_button = create_toolbutton(self,
                                    text=_("Move to bottom"),
                                    icon=ima.icon('2downarrow'),
                                    triggered=lambda: self.move_to(absolute=1),
                                    text_beside_icon=True)
        toolbar.append(movebottom_button)
        self.selection_widgets.extend(toolbar)
        self._add_widgets_to_layout(layout, toolbar)
        return toolbar
    
    def setup_bottom_toolbar(self, layout, sync=True):
        toolbar = []
        add_button = create_toolbutton(self, text=_('Add path'),
                                       icon=ima.icon('edit_add'),
                                       triggered=self.add_path,
                                       text_beside_icon=True)
        toolbar.append(add_button)
        remove_button = create_toolbutton(self, text=_('Remove path'),
                                          icon=ima.icon('edit_remove'),
                                          triggered=self.remove_path,
                                          text_beside_icon=True)
        toolbar.append(remove_button)
        self.selection_widgets.append(remove_button)
        self._add_widgets_to_layout(layout, toolbar)
        layout.addStretch(1)
        if os.name == 'nt' and sync:
            self.sync_button = create_toolbutton(self,
                  text=_("Synchronize..."),
                  icon=ima.icon('fileimport'), triggered=self.synchronize,
                  tip=_("Synchronize Spyder's path list with PYTHONPATH "
                              "environment variable"),
                  text_beside_icon=True)
            layout.addWidget(self.sync_button)
        return toolbar

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms
        """
        answer = QMessageBox.question(self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
                    "<b>PYTHONPATH</b> environment variable for current user, "
                    "allowing you to run your Python modules outside Spyder "
                    "without having to configure sys.path. "
                    "<br>Do you want to clear contents of PYTHONPATH before "
                    "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False
        from spyder.utils.environ import (get_user_env, set_user_env,
                                          listdict2envdict)
        env = get_user_env()
        if remove:
            ppath = self.active_pathlist+self.ro_pathlist
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]
            ppath = [path for path in ppath
                     if path not in (self.active_pathlist+self.ro_pathlist)]
            ppath.extend(self.active_pathlist+self.ro_pathlist)
        env['PYTHONPATH'] = ppath
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_list(self):
        """Return path list (does not include the read-only path list)"""
        return self.pathlist

    def update_not_active_pathlist(self, item):
        path = item.text()
        if bool(item.checkState()) is True:
            self.remove_from_not_active_pathlist(path)
        else:
            self.add_to_not_active_pathlist(path)

    def add_to_not_active_pathlist(self, path):
        if path not in self.not_active_pathlist:
            self.not_active_pathlist.append(path)

    def remove_from_not_active_pathlist(self, path):
        if path in self.not_active_pathlist:
            self.not_active_pathlist.remove(path)
        
    def update_list(self):
        """Update path list"""
        self.listwidget.clear()
        for name in self.pathlist+self.ro_pathlist:
            item = QListWidgetItem(name)
            item.setIcon(ima.icon('DirClosedIcon'))
            if name in self.ro_pathlist:
                item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            elif name in self.not_active_pathlist:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Unchecked)
            else:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            self.listwidget.addItem(item)
        self.refresh()
        
    def refresh(self, row=None):
        """Refresh widget"""
        for widget in self.selection_widgets:
            widget.setEnabled(self.listwidget.currentItem() is not None)
        not_empty = self.listwidget.count() > 0
        if self.sync_button is not None:
            self.sync_button.setEnabled(not_empty)
    
    def move_to(self, absolute=None, relative=None):
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = len(self.pathlist)-1
            else:
                new_index = 0
        else:
            new_index = index + relative        
        new_index = max(0, min(len(self.pathlist)-1, new_index))
        path = self.pathlist.pop(index)
        self.pathlist.insert(new_index, path)
        self.update_list()
        self.listwidget.setCurrentRow(new_index)

    @Slot()
    def remove_path(self):
        answer = QMessageBox.warning(self, _("Remove path"),
            _("Do you really want to remove selected path?"),
            QMessageBox.Yes | QMessageBox.No)
        if answer == QMessageBox.Yes:
            self.pathlist.pop(self.listwidget.currentRow())
            self.remove_from_not_active_pathlist(
                    self.listwidget.currentItem().text())
            self.update_list()

    @Slot()
    def add_path(self):
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.last_path)
        self.redirect_stdio.emit(True)
        if directory:
            directory = osp.abspath(directory)
            self.last_path = directory
            if directory in self.pathlist:
                item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
                item.setCheckState(Qt.Checked)
                answer = QMessageBox.question(self, _("Add path"),
                    _("This directory is already included in Spyder path "
                            "list.<br>Do you want to move it to the top of "
                            "the list?"),
                    QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.pathlist.remove(directory)
                else:
                    return
            self.pathlist.insert(0, directory)
            self.update_list()
Ejemplo n.º 24
0
class DownloadBibleMp3Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()

        self.bibles = {
            "BBE (British accent)":
            ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"),
            "KJV (American accent)":
            ("KJV", "otseng/UniqueBible_MP3_KJV", "default"),
            "KJV (American soft music)":
            ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"),
            "NHEB (Indian accent)":
            ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"),
            "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB",
                                      "default"),
            "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"),
            "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"),
            "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"),
            "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"),
        }
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"])
        self.setMinimumSize(150, 450)
        self.selectedRendition = None
        self.selectedText = None
        self.selectedRepo = None
        self.selectedDirectory = None
        self.settingBibles = False
        self.thread = None
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)

    def selectItem(self, item):
        self.selectRendition(item.text())

    def selectRendition(self, rendition):
        from util.GithubUtil import GithubUtil

        self.selectedRendition = rendition
        self.downloadTable.setEnabled(True)
        self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[
            self.selectedRendition]
        self.github = GithubUtil(self.selectedRepo)
        self.repoData = self.github.getRepoData()
        self.settingBibles = True
        self.dataViewModel.clear()
        rowCount = 0
        for file in self.repoData.keys():
            if len(str(file)) > 3:
                engFullBookName = file[3:]
            else:
                engFullBookName = BibleBooks().eng[str(int(file))][1]
            item = QStandardItem(file[:3].strip())
            folder = os.path.join("audio", "bibles", self.selectedText,
                                  self.selectedDirectory, file)
            folderWithName = os.path.join("audio", "bibles", self.selectedText,
                                          self.selectedDirectory,
                                          file + " " + engFullBookName)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item.setCheckable(False)
                item.setCheckState(Qt.Unchecked)
                item.setEnabled(False)
            else:
                item.setCheckable(True)
                item.setCheckState(Qt.Checked)
                item.setEnabled(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(engFullBookName)
            self.dataViewModel.setItem(rowCount, 1, item)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item = QStandardItem("Installed")
                self.dataViewModel.setItem(rowCount, 2, item)
            else:
                item = QStandardItem("")
                self.dataViewModel.setItem(rowCount, 2, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["menu_book"],
            config.thisTranslation["name"], ""
        ])
        self.downloadTable.setColumnWidth(0, 90)
        self.downloadTable.setColumnWidth(1, 125)
        self.downloadTable.setColumnWidth(2, 125)
        # self.downloadTable.resizeColumnsToContents()
        self.settingBibles = False

    def selectAll(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            if item.isEnabled():
                item.setCheckState(Qt.Checked)

    def selectNone(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            item.setCheckState(Qt.Unchecked)

    def selectOT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum <= 39:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def selectNT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum >= 40:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.setStatus(config.thisTranslation["message_installing"])
        self.closeButton.setEnabled(False)
        folder = os.path.join("audio", "bibles")
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText)
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText,
                              self.selectedDirectory)
        if not os.path.exists(folder):
            os.mkdir(folder)
        self.thread = QThread()
        self.worker = DownloadFromGitHub(self.github, self.repoData,
                                         self.dataViewModel, self.selectedText,
                                         self.selectedDirectory)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.finishedDownloading)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def finishedDownloading(self, count):
        self.selectRendition(self.selectedRendition)
        self.setStatus("")
        self.downloadButton.setEnabled(True)
        self.closeButton.setEnabled(True)
        if count > 0:
            self.parent.displayMessage(
                config.thisTranslation["message_installed"])

    def setStatus(self, message):
        self.status.setText(message)
        QApplication.processEvents()

    def closeDialog(self):
        if self.thread:
            if self.thread.isRunning():
                self.thread.quit()
        self.close()
Ejemplo n.º 25
0
class RateLawWidget(QWidget):
    """
    Rate Law widget
    """
    redirect_stdio = Signal(bool)

    def __init__(self, parent, max_entries=100):
        "Initialize Various list objects before assignment"

        displaylist = []
        displaynamelist = []
        infixmod = []
        infixlist = []
        desclist = []
        parameternamelist = []
        parameterdesclist = []
        buttonlist = []
        xmldoc = minidom.parse(
            os.path.dirname(os.path.abspath(__file__)) + '\\ratelaw2_0_3.xml')

        lawlistxml = xmldoc.getElementsByTagName('law')

        o = 0
        for s in lawlistxml:
            o = o + 1

        parameternamelistlist = [0 for x in range(o)]
        parameterdesclistlist = [0 for x in range(o)]
        """i is the number of laws currently in the xml file"""
        i = 0
        """
        Parsing xml: Acquiring rate law name, description, and list of parameter information
        """
        for s in lawlistxml:
            """Gets Latec Expression"""
            displaylist.append(s.getAttribute('display'))
            """Gets Rate-Law Name"""
            displaynamelist.append(s.getAttribute('displayName'))
            """"Gets Raw Rate-Law expression"""
            infixlist.append(s.getAttribute('infixExpression'))
            """Gets description statement"""
            desclist.append(s.getAttribute('description'))
            """Gets listOfParameters Object"""
            parameterlist = s.getElementsByTagName('listOfParameters')[0]
            """Gets a list of parameters within ListOfParameters object"""
            parameters = parameterlist.getElementsByTagName('parameter')

            for param in parameters:
                parameternamelist.append(param.attributes['name'].value)
                parameterdesclist.append(param.attributes['description'].value)

            parameternamelistlist[i] = parameternamelist
            parameterdesclistlist[i] = parameterdesclist

            parameternamelist = []
            parameterdesclist = []
            i = i + 1

        SLElistlist = [0 for x in range(i)]
        PLElistlist = [0 for x in range(i)]
        ILElistlist = [0 for x in range(i)]
        paramLElistlist = [0 for x in range(i)]
        numlistlist = [0 for x in range(i)]

        QWidget.__init__(self, parent)

        self.setWindowTitle("Rate Law Library")

        self.output = None
        self.error_output = None
        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.lawlist = QListWidget()
        self.lawpage = QStackedWidget()
        for j in range(i):
            item = QListWidgetItem(displaynamelist[j])
            self.lawlist.addItem(item)
            self.lawdetailpage = QWidget()
            setup_group = QGroupBox(displaynamelist[j])
            infixmod.append(infixlist[j].replace("___", " "))
            setup_label = QLabel(infixmod[j])
            setup_label.setWordWrap(True)

            desc_group = QGroupBox("Description")
            desc_label = QLabel(desclist[j])
            desc_label.setWordWrap(True)

            param_label = QGridLayout()
            nm = QLabel("Name:")
            des = QLabel("Description:")
            repl = QLabel("Replace with:")
            param_label.addWidget(nm, 0, 0)
            param_label.addWidget(des, 0, 1)
            param_label.addWidget(repl, 0, 2)
            """g is the total number of alterable values"""
            """t is the total number of alterable non-parameters"""
            t = 1

            snum = 0
            pnum = 0
            inum = 0
            """range of N is the max number of possible substrates OR products"""
            N = 5
            for n in range(N):
                nl = n + 1
                if (infixmod[j].find('S%s' % nl) > -1):
                    z = QLabel('S%s is present' % nl)
                    param_label.addWidget(z, t, 0)
                    snum = snum + 1
                    t = t + 1

            for n in range(N):
                nl = n + 1
                if (infixmod[j].find('P%s' % nl) > -1):
                    z = QLabel('P%s is present' % nl)
                    param_label.addWidget(z, t, 0)
                    pnum = pnum + 1
                    t = t + 1

            for n in range(N):
                nl = n + 1
                if (infixmod[j].find('I%s' % nl) > -1):
                    z = QLabel('I%s is present' % nl)
                    param_label.addWidget(z, t, 0)
                    inum = inum + 1
                    t = t + 1
            """Initialize lists of list of parameter lineedit"""
            length = len(parameternamelistlist[j])
            for b in range(length):
                p = QLabel("%s :" % parameternamelistlist[j][b])
                param_label.addWidget(p, b + t, 0)
                d = QLabel("'%s'" % parameterdesclistlist[j][b])
                param_label.addWidget(d, b + t, 1)

            Slineeditlist = [0 for x in range(snum)]
            Plineeditlist = [0 for x in range(pnum)]
            Ilineeditlist = [0 for x in range(inum)]
            paramlineeditlist = [0 for x in range(length)]

            editcount = 1
            """Place lineedit widgets for parameters"""
            for s in range(snum):
                Slineeditlist[s] = QLineEdit()
                param_label.addWidget(Slineeditlist[s], editcount, 2)
                editcount = editcount + 1

            SLElistlist[j] = Slineeditlist

            for s in range(pnum):
                Plineeditlist[s] = QLineEdit()
                param_label.addWidget(Plineeditlist[s], editcount, 2)
                editcount = editcount + 1

            PLElistlist[j] = Plineeditlist

            for s in range(inum):
                Ilineeditlist[s] = QLineEdit()
                param_label.addWidget(Ilineeditlist[s], editcount, 2)
                editcount = editcount + 1

            ILElistlist[j] = Ilineeditlist

            for s in range(length):
                paramlineeditlist[s] = QLineEdit()
                param_label.addWidget(paramlineeditlist[s], editcount, 2)
                editcount = editcount + 1

            paramLElistlist[j] = paramlineeditlist
            """Necessary lists for editable parameters. Housekeeping essentially."""
            numlistlist[j] = [snum, pnum, inum, length]
            charlist = ["S", "P", "I"]

            buttonlist.append(QPushButton(self))
            buttonlist[j].setText("Insert Rate Law: %s" % displaynamelist[j])

            # Warning: do not try to regroup the following QLabel contents with
            # widgets above -- this string was isolated here in a single QLabel
            # on purpose: to fix Issue 863
            """Page formatting"""
            setup_layout = QVBoxLayout()
            setup_layout.addWidget(setup_label)
            setup_group.setLayout(setup_layout)

            desc_group.setLayout(param_label)

            vlayout2 = QVBoxLayout()
            vlayout2.addWidget(setup_group)
            vlayout2.addWidget(desc_group)
            vlayout2.addWidget(buttonlist[j])
            vlayout2.addStretch(1)
            self.lawdetailpage.setLayout(vlayout2)
            self.lawpage.addWidget(self.lawdetailpage)
        """Set up button functionality"""
        for k in range(47):
            buttonlist[k].clicked.connect(
                self.pressbutton(infixmod[k], SLElistlist[k], PLElistlist[k],
                                 ILElistlist[k], paramLElistlist[k],
                                 parameternamelistlist[k], numlistlist[k],
                                 charlist, k))

        self.lawlist.currentRowChanged.connect(self.lawpage.setCurrentIndex)
        self.lawlist.setCurrentRow(0)
        self.lawlist.setMinimumWidth(self.lawlist.sizeHintForColumn(0))
        """Set up high-level widget formatting."""
        hsplitter = QSplitter()
        hsplitter.addWidget(self.lawlist)
        hsplitter.addWidget(self.lawpage)

        layout = QVBoxLayout()
        layout.addWidget(hsplitter)
        self.setLayout(layout)

    """Testing LineEdit functionality"""

    def entertext(self, le, string, stringlist):
        def entertextreal():
            stuff = self.le.text()
            string = "%s" % stuff
            stringlist[0].replace("vo", "%s" % string)

        return entertextreal

    """Analyze lineedits on page, replace appropriate text, then paste text in console"""

    def pressbutton(ratelaw, string, SLElist, PLElist, ILElist, paramLElist,
                    paramlist, numlist, charlist, j):
        def pressbuttoninsert():
            stringmod = string
            for k in range(4):

                for i in range(numlist[k]):
                    l = i + 1
                    if (k == 0):
                        stuff = SLElist[i].text()
                        if (stuff.find(" ") != -1 or stuff.find('"') != -1):
                            pass
                        elif (stuff == ""):
                            pass
                        else:
                            stringmod = stringmod.replace(
                                charlist[k] + str(l), "%s" % stuff)
                    if (k == 1):
                        stuff = PLElist[i].text()
                        if (stuff.find(" ") != -1 or stuff.find('"') != -1):
                            pass
                        elif (stuff == ""):
                            pass
                        else:
                            stringmod = stringmod.replace(
                                charlist[k] + str(l), "%s" % stuff)
                    if (k == 2):
                        stuff = ILElist[i].text()
                        if (stuff.find(" ") != -1 or stuff.find('"') != -1):
                            pass
                        elif (stuff == ""):
                            pass
                        else:
                            stringmod = stringmod.replace(
                                charlist[k] + str(l), "%s" % stuff)
                    if (k == 3):
                        stuff = paramLElist[i].text()
                        if (stuff.find(" ") != -1 or stuff.find('"') != -1):
                            pass
                        elif (stuff == ""):
                            pass
                        else:
                            stringmod = stringmod.replace(
                                paramlist[i], "%s" % stuff)
            ratelaw.insertText(stringmod)

        return pressbuttoninsert
Ejemplo n.º 26
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    resized = Signal(QSize)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # Because there are multiple pages, need to keep a list of values sets.
        self._values_orig_set_list = []
        self._values_set_list = []
        for _key, setting in SETTINGS.schemas().items():
            schema = json.loads(setting['json_schema'])
            # Need to remove certain properties that will not be displayed on the GUI
            properties = schema.pop('properties')
            model = setting['model']
            values = model.dict()
            napari_config = getattr(model, "NapariConfig", None)
            if napari_config is not None:
                for val in napari_config.preferences_exclude:
                    properties.pop(val)
                    values.pop(val)

            schema['properties'] = properties
            self._values_orig_set_list.append(set(values.items()))
            self._values_set_list.append(set(values.items()))

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper set list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            self.check_differences(
                self._values_orig_set_list[n],
                self._values_set_list[n],
            )
        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, {})
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            set(d.items()),
            self._values_set_list[self._list.currentIndex().row()],
        ))

        return form

    def check_differences(self, new_set, values_set):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_set : set
            The set of new values, with tuples of key value pairs for each
            setting.
        values_set : set
            The old set of values.
        """

        page = self._list.currentItem().text().split(" ")[0].lower()
        different_values = list(new_set - values_set)

        if len(different_values) > 0:
            # change the values in SETTINGS
            for val in different_values:
                try:
                    setattr(SETTINGS._settings[page], val[0], val[1])
                    self._values_set_list[
                        self._list.currentIndex().row()] = new_set
                except:  # noqa: E722
                    continue
Ejemplo n.º 27
0
class CheckList(QWidget):
    def __init__(self, model, label="", help_link="", custom_filter_button=None):
        """
        :param custom_filter_button:  if needed, add a button that opens a
        custom filter menu. Useful when search alone isn't enough to filter the
        list.
        :type custom_filter_button: QToolButton
        """
        QWidget.__init__(self)

        self._model = model

        if help_link != "":
            addHelpToWidget(self, help_link)

        layout = QVBoxLayout()

        self._createCheckButtons()

        self._list = QListWidget()
        self._list.setContextMenuPolicy(Qt.CustomContextMenu)
        self._list.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self._search_box = SearchBox()

        check_button_layout = QHBoxLayout()

        check_button_layout.setContentsMargins(0, 0, 0, 0)
        check_button_layout.setSpacing(0)
        check_button_layout.addWidget(QLabel(label))
        check_button_layout.addStretch(1)
        check_button_layout.addWidget(self._checkAllButton)
        check_button_layout.addWidget(self._uncheckAllButton)

        layout.addLayout(check_button_layout)
        layout.addWidget(self._list)

        """
        Inserts the custom filter button, if provided. The caller is responsible
        for all related actions.
        """
        if custom_filter_button is not None:
            search_bar_layout = QHBoxLayout()
            search_bar_layout.addWidget(self._search_box)
            search_bar_layout.addWidget(custom_filter_button)
            layout.addLayout(search_bar_layout)
        else:
            layout.addWidget(self._search_box)

        self.setLayout(layout)

        self._checkAllButton.clicked.connect(self.checkAll)
        self._uncheckAllButton.clicked.connect(self.uncheckAll)
        self._list.itemChanged.connect(self.itemChanged)
        self._search_box.filterChanged.connect(self.filterList)
        self._list.customContextMenuRequested.connect(self.showContextMenu)

        self._model.selectionChanged.connect(self.modelChanged)
        self._model.modelChanged.connect(self.modelChanged)

        self.modelChanged()

    def _createCheckButtons(self):
        self._checkAllButton = QToolButton()
        self._checkAllButton.setIcon(resourceIcon("check.svg"))
        self._checkAllButton.setIconSize(QSize(16, 16))
        self._checkAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self._checkAllButton.setAutoRaise(True)
        self._checkAllButton.setToolTip("Select all")
        self._uncheckAllButton = QToolButton()
        self._uncheckAllButton.setIcon(resourceIcon("checkbox_outline.svg"))
        self._uncheckAllButton.setIconSize(QSize(16, 16))
        self._uncheckAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self._uncheckAllButton.setAutoRaise(True)
        self._uncheckAllButton.setToolTip("Unselect all")

    def itemChanged(self, item):
        """@type item: QListWidgetItem"""
        if item.checkState() == Qt.Checked:
            self._model.selectValue(str(item.text()))
        elif item.checkState() == Qt.Unchecked:
            self._model.unselectValue(str(item.text()))
        else:
            raise AssertionError("Unhandled checkstate!")

    def modelChanged(self):
        self._list.clear()

        items = self._model.getList()

        for item in items:
            list_item = QListWidgetItem(item)
            list_item.setFlags(list_item.flags() | Qt.ItemIsUserCheckable)

            if self._model.isValueSelected(item):
                list_item.setCheckState(Qt.Checked)
            else:
                list_item.setCheckState(Qt.Unchecked)

            self._list.addItem(list_item)

        self.filterList(self._search_box.filter())

    def setSelectionEnabled(self, enabled):
        self.setEnabled(enabled)
        self._checkAllButton.setEnabled(enabled)
        self._uncheckAllButton.setEnabled(enabled)

    def filterList(self, filter):
        filter = filter.lower()

        for index in range(0, self._list.count()):
            item = self._list.item(index)
            text = str(item.text()).lower()

            if filter == "":
                item.setHidden(False)
            elif filter in text:
                item.setHidden(False)
            else:
                item.setHidden(True)

    def checkAll(self):
        """
        Checks all visible items in the list.
        """
        for index in range(0, self._list.count()):
            item = self._list.item(index)
            if not item.isHidden():
                self._model.selectValue(str(item.text()))

    def uncheckAll(self):
        """
        Unchecks all items in the list, visible or not
        """
        self._model.unselectAll()

    def checkSelected(self):
        items = []
        for item in self._list.selectedItems():
            items.append(str(item.text()))

        for item in items:
            self._model.selectValue(item)

    def uncheckSelected(self):
        items = []
        for item in self._list.selectedItems():
            items.append(str(item.text()))

        for item in items:
            self._model.unselectValue(item)

    def showContextMenu(self, point):
        p = self._list.mapToGlobal(point)
        menu = QMenu()
        check_selected = menu.addAction("Check selected")
        uncheck_selected = menu.addAction("Uncheck selected")
        menu.addSeparator()
        clear_selection = menu.addAction("Clear selection")

        selected_item = menu.exec_(p)

        if selected_item == check_selected:
            self.checkSelected()
        elif selected_item == uncheck_selected:
            self.uncheckSelected()
        elif selected_item == clear_selection:
            self._list.clearSelection()
Ejemplo n.º 28
0
def _get_scenes(path: "PathLike", img: AICSImage, in_memory: bool) -> None:
    # Get napari viewer from current process
    viewer = napari.current_viewer()

    # Add a checkbox widget if not present
    if AICSIMAGEIO_CHOICES not in viewer.window._dock_widgets:
        # Create a checkbox widget to set "Clear On Scene Select" or not
        scene_clear_checkbox = QCheckBox(CLEAR_LAYERS_ON_SELECT)
        scene_clear_checkbox.setChecked(False)

        # Create a checkbox widget to set "Unpack Channels" or not
        channel_unpack_checkbox = QCheckBox(UNPACK_CHANNELS_TO_LAYERS)
        channel_unpack_checkbox.setChecked(False)

        # Add all scene management state to a single box
        scene_manager_group = QGroupBox()
        scene_manager_group_layout = QVBoxLayout()
        scene_manager_group_layout.addWidget(scene_clear_checkbox)
        scene_manager_group_layout.addWidget(channel_unpack_checkbox)
        scene_manager_group.setLayout(scene_manager_group_layout)
        scene_manager_group.setFixedHeight(100)

        viewer.window.add_dock_widget(
            scene_manager_group,
            area="right",
            name=AICSIMAGEIO_CHOICES,
        )

    # Create the list widget and populate with the ids & scenes in the file
    list_widget = QListWidget()
    for i, scene in enumerate(img.scenes):
        list_widget.addItem(f"{i}{SCENE_LABEL_DELIMITER}{scene}")

    # Add this files scenes widget to viewer
    viewer.window.add_dock_widget(
        list_widget,
        area="right",
        name=f"{Path(path).name}{SCENE_LABEL_DELIMITER}Scenes",
    )

    # Function to create image layer from a scene selected in the list widget
    def open_scene(item: QListWidgetItem) -> None:
        scene_text = item.text()

        # Use scene indexes to cover for duplicate names
        scene_index = int(scene_text.split(SCENE_LABEL_DELIMITER)[0])

        # Update scene on image and get data
        img.set_scene(scene_index)
        data = _get_full_image_data(img=img, in_memory=in_memory)

        # Get metadata and add to image
        meta = _get_meta(data, img)

        # Optionally clear layers
        if _widget_is_checked(CLEAR_LAYERS_ON_SELECT):
            viewer.layers.clear()

        # Optionally remove channel axis
        if not _widget_is_checked(UNPACK_CHANNELS_TO_LAYERS):
            meta["name"] = scene_text
            meta.pop("channel_axis", None)

        viewer.add_image(data, **meta)

    list_widget.currentItemChanged.connect(open_scene)  # type: ignore
Ejemplo n.º 29
0
class ApplicationsDialog(QDialog):
    """Dialog for selection of installed system/user applications."""
    def __init__(self, parent=None):
        """Dialog for selection of installed system/user applications."""
        super(ApplicationsDialog, self).__init__(parent=parent)

        # Widgets
        self.label = QLabel()
        self.label_browse = QLabel()
        self.edit_filter = QLineEdit()
        self.list = QListWidget()
        self.button_browse = QPushButton(_('Browse...'))
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                           | QDialogButtonBox.Cancel)
        self.button_ok = self.button_box.button(QDialogButtonBox.Ok)
        self.button_cancel = self.button_box.button(QDialogButtonBox.Cancel)

        # Widget setup
        self.setWindowTitle(_('Applications'))
        self.edit_filter.setPlaceholderText(_('Type to filter by name'))
        self.list.setIconSize(QSize(16, 16))  # FIXME: Use metrics

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.edit_filter)
        layout.addWidget(self.list)
        layout_browse = QHBoxLayout()
        layout_browse.addWidget(self.button_browse)
        layout_browse.addWidget(self.label_browse)
        layout.addLayout(layout_browse)
        layout.addSpacing(12)  # FIXME: Use metrics
        layout.addWidget(self.button_box)
        self.setLayout(layout)

        # Signals
        self.edit_filter.textChanged.connect(self.filter)
        self.button_browse.clicked.connect(lambda x: self.browse())
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.list.currentItemChanged.connect(self._refresh)

        self._refresh()
        self.setup()

    def setup(self, applications=None):
        """Load installed applications."""
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        self.list.clear()
        if applications is None:
            apps = get_installed_applications()
        else:
            apps = applications

        for app in sorted(apps, key=lambda x: x.lower()):
            fpath = apps[app]
            icon = get_application_icon(fpath)
            item = QListWidgetItem(icon, app)
            item.setToolTip(fpath)
            item.fpath = fpath
            self.list.addItem(item)

        # FIXME: Use metrics
        self.list.setMinimumWidth(self.list.sizeHintForColumn(0) + 24)
        QApplication.restoreOverrideCursor()
        self._refresh()

    def _refresh(self):
        """Refresh the status of buttons on widget."""
        self.button_ok.setEnabled(self.list.currentRow() != -1)

    def browse(self, fpath=None):
        """Prompt user to select an application not found on the list."""
        app = None
        item = None

        if sys.platform == 'darwin':
            if fpath is None:
                basedir = '/Applications/'
                filters = _('Applications (*.app)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath and fpath.endswith('.app') and os.path.isdir(fpath):
                app = os.path.basename(fpath).split('.app')[0]
                for row in range(self.list.count()):
                    item = self.list.item(row)
                    if app == item.text() and fpath == item.fpath:
                        break
                else:
                    item = None
        elif os.name == 'nt':
            if fpath is None:
                basedir = 'C:\\'
                filters = _('Applications (*.exe *.bat *.com)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath:
                check_1 = fpath.endswith('.bat') and is_text_file(fpath)
                check_2 = (fpath.endswith(('.exe', '.com'))
                           and not is_text_file(fpath))
                if check_1 or check_2:
                    app = os.path.basename(fpath).capitalize().rsplit('.')[0]
                    for row in range(self.list.count()):
                        item = self.list.item(row)
                        if app == item.text() and fpath == item.fpath:
                            break
                    else:
                        item = None
        else:
            if fpath is None:
                basedir = '/'
                filters = _('Applications (*.desktop)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath and fpath.endswith(('.desktop')) and is_text_file(fpath):
                entry_data = parse_linux_desktop_entry(fpath)
                app = entry_data['name']
                for row in range(self.list.count()):
                    item = self.list.item(row)
                    if app == item.text() and fpath == item.fpath:
                        break
                else:
                    item = None

        if fpath:
            if item:
                self.list.setCurrentItem(item)
            elif app:
                icon = get_application_icon(fpath)
                item = QListWidgetItem(icon, app)
                item.fpath = fpath
                self.list.addItem(item)
                self.list.setCurrentItem(item)

        self.list.setFocus()
        self._refresh()

    def filter(self, text):
        """Filter the list of applications based on text."""
        text = self.edit_filter.text().lower().strip()
        for row in range(self.list.count()):
            item = self.list.item(row)
            item.setHidden(text not in item.text().lower())
        self._refresh()

    def set_extension(self, extension):
        """Set the extension on the label of the dialog."""
        self.label.setText(
            _('Choose the application for files of type ') + extension)

    @property
    def application_path(self):
        """Return the selected application path to executable."""
        item = self.list.currentItem()
        path = item.fpath if item else ''
        return path

    @property
    def application_name(self):
        """Return the selected application name."""
        item = self.list.currentItem()
        text = item.text() if item else ''
        return text
Ejemplo n.º 30
0
class PathManager(QDialog):
    """Path manager dialog."""
    redirect_stdio = Signal(bool)
    sig_path_changed = Signal(object)

    def __init__(self,
                 parent,
                 path=None,
                 read_only_path=None,
                 not_active_path=None,
                 sync=True):
        """Path manager dialog."""
        super(PathManager, self).__init__(parent)
        assert isinstance(path, (tuple, None))

        self.path = path or ()
        self.read_only_path = read_only_path or ()
        self.not_active_path = not_active_path or ()
        self.last_path = getcwd_or_home()
        self.original_path_dict = None

        # Widgets
        self.add_button = None
        self.remove_button = None
        self.movetop_button = None
        self.moveup_button = None
        self.movedown_button = None
        self.movebottom_button = None
        self.sync_button = None
        self.selection_widgets = []
        self.top_toolbar_widgets = self._setup_top_toolbar()
        self.bottom_toolbar_widgets = self._setup_bottom_toolbar()
        self.listwidget = QListWidget(self)
        self.bbox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Cancel)
        self.button_ok = self.bbox.button(QDialogButtonBox.Ok)

        # Widget setup
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)
        self.sync_button.setVisible(os.name == 'nt' and sync)

        # Layouts
        top_layout = QHBoxLayout()
        self._add_widgets_to_layout(self.top_toolbar_widgets, top_layout)

        bottom_layout = QHBoxLayout()
        self._add_widgets_to_layout(self.bottom_toolbar_widgets, bottom_layout)
        bottom_layout.addWidget(self.bbox)

        layout = QVBoxLayout()
        layout.addLayout(top_layout)
        layout.addWidget(self.listwidget)
        layout.addLayout(bottom_layout)
        self.setLayout(layout)

        # Signals
        self.listwidget.currentRowChanged.connect(lambda x: self.refresh())
        self.listwidget.itemChanged.connect(lambda x: self.refresh())
        self.bbox.accepted.connect(self.accept)
        self.bbox.rejected.connect(self.reject)

        # Setup
        self.setup()

    def _add_widgets_to_layout(self, widgets, layout):
        """Helper to add toolbar widgets to top and bottom layout."""
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            if widget is None:
                layout.addStretch(1)
            else:
                layout.addWidget(widget)

    def _setup_top_toolbar(self):
        """Create top toolbar and actions."""
        self.movetop_button = create_toolbutton(
            self,
            text=_("Move to top"),
            icon=ima.icon('2uparrow'),
            triggered=lambda: self.move_to(absolute=0),
            text_beside_icon=True)
        self.moveup_button = create_toolbutton(
            self,
            text=_("Move up"),
            icon=ima.icon('1uparrow'),
            triggered=lambda: self.move_to(relative=-1),
            text_beside_icon=True)
        self.movedown_button = create_toolbutton(
            self,
            text=_("Move down"),
            icon=ima.icon('1downarrow'),
            triggered=lambda: self.move_to(relative=1),
            text_beside_icon=True)
        self.movebottom_button = create_toolbutton(
            self,
            text=_("Move to bottom"),
            icon=ima.icon('2downarrow'),
            triggered=lambda: self.move_to(absolute=1),
            text_beside_icon=True)

        toolbar = [
            self.movetop_button, self.moveup_button, self.movedown_button,
            self.movebottom_button
        ]
        self.selection_widgets.extend(toolbar)
        return toolbar

    def _setup_bottom_toolbar(self):
        """Create bottom toolbar and actions."""
        self.add_button = create_toolbutton(
            self,
            text=_('Add path'),
            icon=ima.icon('edit_add'),
            triggered=lambda x: self.add_path(),
            text_beside_icon=True)
        self.remove_button = create_toolbutton(
            self,
            text=_('Remove path'),
            icon=ima.icon('edit_remove'),
            triggered=lambda x: self.remove_path(),
            text_beside_icon=True)
        self.sync_button = create_toolbutton(
            self,
            text=_("Synchronize..."),
            icon=ima.icon('fileimport'),
            triggered=self.synchronize,
            tip=_("Synchronize Spyder's path list with PYTHONPATH "
                  "environment variable"),
            text_beside_icon=True)

        self.selection_widgets.append(self.remove_button)
        return [self.add_button, self.remove_button, None, self.sync_button]

    def _create_item(self, path):
        """Helper to create a new list item."""
        item = QListWidgetItem(path)
        item.setIcon(ima.icon('DirClosedIcon'))

        if path in self.read_only_path:
            item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
        elif path in self.not_active_path:
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Unchecked)
        else:
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)

        return item

    @property
    def editable_bottom_row(self):
        """Maximum bottom row count that is editable."""
        read_only_count = len(self.read_only_path)
        if read_only_count == 0:
            max_row = self.listwidget.count() - 1
        else:
            max_row = self.listwidget.count() - read_only_count - 1
        return max_row

    def setup(self):
        """Populate list widget."""
        self.listwidget.clear()
        for path in self.path + self.read_only_path:
            item = self._create_item(path)
            self.listwidget.addItem(item)
        self.listwidget.setCurrentRow(0)
        self.original_path_dict = self.get_path_dict()
        self.refresh()

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms.
        """
        answer = QMessageBox.question(
            self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
              "<b>PYTHONPATH</b> environment variable for the current user, "
              "allowing you to run your Python modules outside Spyder "
              "without having to configure sys.path. "
              "<br>"
              "Do you want to clear contents of PYTHONPATH before "
              "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False

        from spyder.utils.environ import (get_user_env, listdict2envdict,
                                          set_user_env)
        env = get_user_env()

        # Includes read only paths
        active_path = tuple(k for k, v in self.get_path_dict(True).items()
                            if v)

        if remove:
            ppath = active_path
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]

            ppath = tuple(p for p in ppath if p not in active_path)
            ppath = ppath + active_path

        env['PYTHONPATH'] = list(ppath)
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_dict(self, read_only=False):
        """
        Return an ordered dict with the path entries as keys and the active
        state as the value.

        If `read_only` is True, the read_only entries are also included.
        `read_only` entry refers to the project path entry.
        """
        odict = OrderedDict()
        for row in range(self.listwidget.count()):
            item = self.listwidget.item(row)
            path = item.text()
            if path in self.read_only_path and not read_only:
                continue
            odict[path] = item.checkState() == Qt.Checked
        return odict

    def refresh(self):
        """Refresh toolbar widgets."""
        enabled = self.listwidget.currentItem() is not None
        for widget in self.selection_widgets:
            widget.setEnabled(enabled)

        # Disable buttons based on row
        row = self.listwidget.currentRow()
        disable_widgets = []

        # Move up/top disabled for top item
        if row == 0:
            disable_widgets.extend([self.movetop_button, self.moveup_button])

        # Move down/bottom disabled for bottom item
        if row == self.editable_bottom_row:
            disable_widgets.extend(
                [self.movebottom_button, self.movedown_button])
        for widget in disable_widgets:
            widget.setEnabled(False)

        self.sync_button.setEnabled(self.listwidget.count() > 0)

        # Ok button only enabled if actual changes occur
        self.button_ok.setEnabled(
            self.original_path_dict != self.get_path_dict())

    def check_path(self, path):
        """Check that the path is not a [site|dist]-packages folder."""
        if os.name == 'nt':
            pat = re.compile(r'.*lib/(?:site|dist)-packages.*')
        else:
            pat = re.compile(r'.*lib/python.../(?:site|dist)-packages.*')

        path_norm = path.replace('\\', '/')
        return pat.match(path_norm) is None

    @Slot()
    def add_path(self, directory=None):
        """
        Add path to list widget.

        If `directory` is provided, the folder dialog is overridden.
        """
        if directory is None:
            self.redirect_stdio.emit(False)
            directory = getexistingdirectory(self, _("Select directory"),
                                             self.last_path)
            self.redirect_stdio.emit(True)

        if PY2:
            is_unicode = False
            try:
                directory.decode('ascii')
            except (UnicodeEncodeError, UnicodeDecodeError):
                is_unicode = True

            if is_unicode:
                QMessageBox.warning(
                    self, _("Add path"),
                    _("You are using Python 2 and the selected path has "
                      "Unicode characters."
                      "<br> "
                      "Therefore, this path will not be added."),
                    QMessageBox.Ok)
                return

        directory = osp.abspath(directory)
        self.last_path = directory

        if directory in self.get_path_dict():
            item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
            item.setCheckState(Qt.Checked)
            answer = QMessageBox.question(
                self, _("Add path"),
                _("This directory is already included in the list."
                  "<br> "
                  "Do you want to move it to the top of it?"),
                QMessageBox.Yes | QMessageBox.No)

            if answer == QMessageBox.Yes:
                item = self.listwidget.takeItem(self.listwidget.row(item))
                self.listwidget.insertItem(0, item)
                self.listwidget.setCurrentRow(0)
        else:
            if self.check_path(directory):
                item = self._create_item(directory)
                self.listwidget.insertItem(0, item)
                self.listwidget.setCurrentRow(0)
            else:
                answer = QMessageBox.warning(
                    self, _("Add path"),
                    _("This directory cannot be added to the path!"
                      "<br><br>"
                      "If you want to set a different Python interpreter, "
                      "please go to <tt>Preferences > Main interpreter</tt>"
                      "."), QMessageBox.Ok)

        self.refresh()

    @Slot()
    def remove_path(self, force=False):
        """
        Remove path from list widget.

        If `force` is True, the message box is overridden.
        """
        if self.listwidget.currentItem():
            if not force:
                answer = QMessageBox.warning(
                    self, _("Remove path"),
                    _("Do you really want to remove the selected path?"),
                    QMessageBox.Yes | QMessageBox.No)

            if force or answer == QMessageBox.Yes:
                self.listwidget.takeItem(self.listwidget.currentRow())
                self.refresh()

    def move_to(self, absolute=None, relative=None):
        """Move items of list widget."""
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = self.listwidget.count() - 1
            else:
                new_index = 0
        else:
            new_index = index + relative

        new_index = max(0, min(self.editable_bottom_row, new_index))
        item = self.listwidget.takeItem(index)
        self.listwidget.insertItem(new_index, item)
        self.listwidget.setCurrentRow(new_index)
        self.refresh()

    def current_row(self):
        """Returns the current row of the list."""
        return self.listwidget.currentRow()

    def set_current_row(self, row):
        """Set the current row of the list."""
        self.listwidget.setCurrentRow(row)

    def row_check_state(self, row):
        """Return the checked state for item in row."""
        item = self.listwidget.item(row)
        return item.checkState()

    def set_row_check_state(self, row, value):
        """Set the current checked state for item in row."""
        item = self.listwidget.item(row)
        item.setCheckState(value)

    def count(self):
        """Return the number of items."""
        return self.listwidget.count()

    def accept(self):
        """Override Qt method."""
        path_dict = self.get_path_dict()
        if self.original_path_dict != path_dict:
            self.sig_path_changed.emit(path_dict)
        super(PathManager, self).accept()
Ejemplo n.º 31
0
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int, object)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]
    MAX_WIDTH = 600

    def __init__(self, parent, plugin, tabs, data, icon):
        QDialog.__init__(self, parent)

        # Variables
        self.plugins_tabs = []
        self.plugins_data = []
        self.plugins_instances = []
        self.add_plugin(plugin, tabs, data, icon)
        self.plugin = None  # Last plugin with focus
        self.mode = self.FILE_MODE  # By default start in this mode
        self.initial_cursors = None  # {fullpath: QCursor}
        self.initial_path = None  # Fullpath of initial active editor
        self.initial_widget = None  # Initial active editor
        self.line_number = None  # Selected line number in filer
        self.is_visible = False  # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = FilesFilterLine(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Properties
    @property
    def widgets(self):
        widgets = []
        for plugin in self.plugins_instances:
            tabs = self.get_plugin_tabwidget(plugin)
            widgets += [(tabs.widget(index), plugin)
                        for index in range(tabs.count())]
        return widgets

    @property
    def line_count(self):
        line_count = []
        for widget in self.widgets:
            try:
                current_line_count = widget[0].get_line_count()
            except AttributeError:
                current_line_count = 0
            line_count.append(current_line_count)
        return line_count

    @property
    def save_status(self):
        save_status = []
        for da, icon in self.plugins_data:
            save_status += [getattr(td, 'newly_created', False) for td in da]
        return save_status

    @property
    def paths(self):
        paths = []
        for plugin in self.plugins_instances:
            da = self.get_plugin_data(plugin)
            paths += [getattr(td, 'filename', None) for td in da]
        return paths

    @property
    def filenames(self):
        filenames = []
        for plugin in self.plugins_instances:
            da = self.get_plugin_data(plugin)
            filenames += [
                os.path.basename(getattr(td, 'filename', None)) for td in da
            ]
        return filenames

    @property
    def icons(self):
        icons = []
        for da, icon in self.plugins_data:
            icons += [icon for td in da]
        return icons

    @property
    def current_path(self):
        return self.paths_by_widget[self.get_widget()]

    @property
    def paths_by_widget(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(widgets, self.paths))

    @property
    def widgets_by_path(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(self.paths, widgets))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, _str):
        self.edit.setText(_str)

    def save_initial_state(self):
        """Save initial cursors and initial active widget."""
        paths = self.paths
        self.initial_widget = self.get_widget()
        self.initial_cursors = {}

        for i, editor in enumerate(self.widgets):
            if editor is self.initial_widget:
                self.initial_path = paths[i]
            # This try is needed to make the fileswitcher work with
            # plugins that does not have a textCursor.
            try:
                self.initial_cursors[paths[i]] = editor.textCursor()
            except AttributeError:
                pass

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        widgets = self.widgets_by_path

        if not self.edit.clicked_outside:
            for path in self.initial_cursors:
                cursor = self.initial_cursors[path]
                if path in widgets:
                    self.set_editor_cursor(widgets[path], cursor)

            if self.initial_widget in self.paths_by_widget:
                index = self.paths.index(self.initial_path)
                self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup
        left = parent.geometry().width() / 2 - width / 2
        # Note: the +1 pixel on the top makes it look better
        if isinstance(parent, QMainWindow):
            top = (parent.toolbars_menu.geometry().height() +
                   parent.menuBar().geometry().height() + 1)
        else:
            top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1

        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        self.move(left, top)

    def get_item_size(self, content):
        """
        Get the max size (width and height) for the elements of a list of
        strings as a QLabel.
        """
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()

            return (max([fm.width(s) * 1.3 for s in strings]), fm.height())

    def fix_size(self, content):
        """
        Adjusts the width and height of the file switcher
        based on the relative size of the parent and content.
        """
        # Update size of dialog based on relative size of the parent
        if content:
            width, height = self.get_item_size(content)

            # Width
            parent = self.parent()
            relative_width = parent.geometry().width() * 0.65
            if relative_width > self.MAX_WIDTH:
                relative_width = self.MAX_WIDTH
            self.list.setMinimumWidth(relative_width)

            # Height
            if len(content) < 8:
                max_entries = len(content)
            else:
                max_entries = 8
            max_height = height * max_entries * 2.5
            self.list.setMinimumHeight(max_height)

            # Resize
            self.list.resize(relative_width, self.list.height())

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(-1)
            return
        prev_row = self.current_row() - 1
        if prev_row >= 0:
            title = self.list.item(prev_row).text()
        else:
            title = ''
        if prev_row == 0 and '</b></big><br>' in title:
            self.list.scrollToTop()
        elif '</b></big><br>' in title:
            # Select the next previous row, the one following is a title
            self.select_row(-2)
        else:
            self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(+1)
            return
        next_row = self.current_row() + 1
        if next_row < self.count():
            if '</b></big><br>' in self.list.item(next_row).text():
                # Select the next next row, the one following is a title
                self.select_row(+2)
            else:
                self.select_row(+1)

    def get_stack_index(self, stack_index, plugin_index):
        """Get the real index of the selected item."""
        other_plugins_count = sum([other_tabs[0].count() \
                                   for other_tabs in \
                                   self.plugins_tabs[:plugin_index]])
        real_index = stack_index - other_plugins_count

        return real_index

    # --- Helper methods: Widget
    def get_plugin_data(self, plugin):
        """Get the data object of the plugin's current tab manager."""
        # The data object is named "data" in the editor plugin while it is
        # named "clients" in the notebook plugin.
        try:
            data = plugin.get_current_tab_manager().data
        except AttributeError:
            data = plugin.get_current_tab_manager().clients

        return data

    def get_plugin_tabwidget(self, plugin):
        """Get the tabwidget of the plugin's current tab manager."""
        # The tab widget is named "tabs" in the editor plugin while it is
        # named "tabwidget" in the notebook plugin.
        try:
            tabwidget = plugin.get_current_tab_manager().tabs
        except AttributeError:
            tabwidget = plugin.get_current_tab_manager().tabwidget

        return tabwidget

    def get_widget(self, index=None, path=None, tabs=None):
        """Get widget by index.

        If no tabs and index specified the current active widget is returned.
        """
        if (index and tabs) or (path and tabs):
            return tabs.widget(index)
        elif self.plugin:
            return self.get_plugin_tabwidget(self.plugin).currentWidget()
        else:
            return self.plugins_tabs[0][0].currentWidget()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            try:
                self.plugin.go_to_line(line_number)
            except AttributeError:
                pass

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the list of symbols present in the file."""
        try:
            oedata = self.get_widget().get_outlineexplorer_data()
        except AttributeError:
            oedata = {}
        return oedata

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if '</b></big><br>' in self.list.currentItem().text() and row == 0:
                self.next_row()
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.plugin = self.widgets[stack_index][1]
                    plugin_index = self.plugins_instances.index(self.plugin)
                    # Count the real index in the tabWidget of the
                    # current plugin
                    real_index = self.get_stack_index(stack_index,
                                                      plugin_index)
                    self.sig_goto_file.emit(
                        real_index, self.plugin.get_current_tab_manager())
                    self.goto_line(self.line_number)
                    try:
                        self.plugin.switch_to_plugin()
                        self.raise_()
                    except AttributeError:
                        # The widget using the fileswitcher is not a plugin
                        pass
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        icons = self.icons
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
            if line_number == '':
                line_number = None
            # Get all the available filenames
            scores = get_search_scores('',
                                       self.filenames,
                                       template="<b>{0}</b>")
        else:
            line_number = None
            # Get all available filenames and get the scores for
            # "fuzzy" matching
            scores = get_search_scores(filter_text,
                                       self.filenames,
                                       template="<b>{0}</b>")

        # Get max width to determine if shortpaths should be used
        max_width = self.get_item_size(paths)[0]
        self.fix_size(paths)

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text.replace('&', '') + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                if max_width > self.list.width():
                    text_item += u"<br><i>{0:}</i>".format(short_paths[index])
                else:
                    text_item += u"<br><i>{0:}</i>".format(paths[index])
                if (trying_for_line_number and self.line_count[index] != 0
                        or not trying_for_line_number):
                    results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        plugin = None
        for result in sorted(results):
            index = result[1]
            path = paths[index]
            icon = icons[index]
            text = ''
            try:
                title = self.widgets[index][1].get_plugin_title().split(' - ')
                if plugin != title[0]:
                    plugin = title[0]
                    text += '<br><big><b>' + plugin + '</b></big><br>'
                    item = QListWidgetItem(text)
                    item.setToolTip(path)
                    item.setSizeHint(QSize(0, 25))
                    item.setFlags(Qt.ItemIsEditable)
                    self.list.addItem(item)
                    self.filtered_path.append(path)
            except:
                # The widget using the fileswitcher is not a plugin
                pass
            text = ''
            text += result[-1]
            item = QListWidgetItem(icon, text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = True

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        # The list of paths here is needed in order to have the same
        # point of measurement for the list widget size as in the file list
        # See issue 4648
        paths = self.paths
        # Update list size
        self.fix_size(paths)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text, fold_level,
                                icons[index], token))

        template = '{0}{1}'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;' * (fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template.format(fold_space, rich_text)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = False

        # Select edit line when using symbol search initially.
        # See issue 5661
        self.edit.setFocus()

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

    def setup(self):
        """Setup list widget content."""
        if len(self.plugins_tabs) == 0:
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)

        # Set position according to size
        self.set_dialog_position()

    def show(self):
        """
        Override Qt method to force an update of the fileswitcher before
        showing it. See Issue #5317 and PR #5389.
        """
        self.setup()
        super(FileSwitcher, self).show()

    def add_plugin(self, plugin, tabs, data, icon):
        """Add a plugin to display its files."""
        self.plugins_tabs.append((tabs, plugin))
        self.plugins_data.append((data, icon))
        self.plugins_instances.append(plugin)
Ejemplo n.º 32
0
class ProgressView(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, parent, batch_manager):
        QWidget.__init__(self, parent)
        self.calculation_manager = batch_manager
        self.whole_progress = QProgressBar(self)
        self.whole_progress.setMinimum(0)
        self.whole_progress.setMaximum(1)
        self.whole_progress.setFormat("%v of %m")
        self.whole_progress.setTextVisible(True)
        self.part_progress = QProgressBar(self)
        self.part_progress.setMinimum(0)
        self.part_progress.setMaximum(1)
        self.part_progress.setFormat("%v of %m")
        self.whole_label = QLabel("All batch progress:", self)
        self.part_label = QLabel("Single batch progress:", self)
        self.logs = ExceptionList(self)
        self.logs.setToolTip("Logs")
        self.task_que = QListWidget()
        self.process_num_timer = QTimer()
        self.process_num_timer.setInterval(1000)
        self.process_num_timer.setSingleShot(True)
        self.process_num_timer.timeout.connect(self.change_number_of_workers)
        self.number_of_process = QSpinBox(self)
        self.number_of_process.setRange(1, multiprocessing.cpu_count())
        self.number_of_process.setValue(1)
        self.number_of_process.setToolTip(
            "Number of process used in batch calculation")
        self.number_of_process.valueChanged.connect(
            self.process_num_timer_start)
        layout = QGridLayout()
        layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight)
        layout.addWidget(self.whole_progress, 0, 1, 1, 2)
        layout.addWidget(self.part_label, 1, 0, Qt.AlignRight)
        layout.addWidget(self.part_progress, 1, 1, 1, 2)
        lab = QLabel("Number of process:")
        lab.setToolTip("Number of process used in batch calculation")
        layout.addWidget(lab, 2, 0)
        layout.addWidget(self.number_of_process, 2, 1)
        layout.addWidget(self.logs, 3, 0, 1, 3)
        layout.addWidget(self.task_que, 0, 4, 0, 1)
        layout.setColumnMinimumWidth(2, 10)
        layout.setColumnStretch(2, 1)
        self.setLayout(layout)
        self.preview_timer = QTimer()
        self.preview_timer.setInterval(1000)
        self.preview_timer.timeout.connect(self.update_info)

    def new_task(self):
        self.whole_progress.setMaximum(
            self.calculation_manager.calculation_size)
        if not self.preview_timer.isActive():
            self.update_info()
            self.preview_timer.start()

    def update_info(self):
        res = self.calculation_manager.get_results()
        for el in res.errors:
            if el[0]:
                QListWidgetItem(el[0], self.logs)
            ExceptionListItem(el[1], self.logs)
            if (state_store.report_errors and parsed_version.is_devrelease
                    and not isinstance(el[1][0], SegmentationLimitException)
                    and isinstance(el[1][1], tuple)):
                with sentry_sdk.push_scope() as scope:
                    scope.set_tag("auto_report", "true")
                    sentry_sdk.capture_event(el[1][1][0])
        self.whole_progress.setValue(res.global_counter)
        working_search = True
        for i, (progress, total) in enumerate(res.jobs_status):
            if working_search and progress != total:
                self.part_progress.setMaximum(total)
                self.part_progress.setValue(progress)
                working_search = False
            if i < self.task_que.count():
                item = self.task_que.item(i)
                item.setText("Task {} ({}/{})".format(i, progress, total))
            else:
                self.task_que.addItem("Task {} ({}/{})".format(
                    i, progress, total))
        if not self.calculation_manager.has_work:
            print(
                "[ProgressView.update_info]",
                self.calculation_manager.has_work,
                self.calculation_manager.batch_manager.has_work,
                self.calculation_manager.writer.writing_finished(),
            )
            self.part_progress.setValue(self.part_progress.maximum())
            self.preview_timer.stop()
            logging.info("Progress stop")

    def process_num_timer_start(self):
        self.process_num_timer.start()

    def update_progress(self, total_progress, part_progress):
        self.whole_progress.setValue(total_progress)
        self.part_progress.setValue(part_progress)

    def set_total_size(self, size):
        self.whole_progress.setMaximum(size)

    def set_part_size(self, size):
        self.part_progress.setMaximum(size)

    def change_number_of_workers(self):
        self.calculation_manager.set_number_of_workers(
            self.number_of_process.value())
Ejemplo n.º 33
0
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int)
    sig_close_file = Signal(int)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]

    def __init__(self, parent, tabs, data):
        QDialog.__init__(self, parent)

        # Variables
        self.tabs = tabs                  # Editor stack tabs
        self.data = data                  # Editor data
        self.mode = self.FILE_MODE        # By default start in this mode
        self.initial_cursors = None       # {fullpath: QCursor}
        self.initial_path = None          # Fullpath of initial active editor
        self.initial_editor = None        # Initial active editor
        self.line_number = None           # Selected line number in filer
        self.is_visible = False           # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = QLineEdit(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

        # Setup
        self.save_initial_state()
        self.set_dialog_position()
        self.setup()

    # --- Properties
    @property
    def editors(self):
        return [self.tabs.widget(index) for index in range(self.tabs.count())]

    @property
    def line_count(self):
        return [editor.get_line_count() for editor in self.editors]

    @property
    def save_status(self):
        return [getattr(td, 'newly_created', False) for td in self.data]

    @property
    def paths(self):
        return [getattr(td, 'filename', None) for td in self.data]

    @property
    def filenames(self):
        return [self.tabs.tabText(index) for index in range(self.tabs.count())]

    @property
    def current_path(self):
        return self.paths_by_editor[self.get_editor()]

    @property
    def paths_by_editor(self):
        return dict(zip(self.editors, self.paths))

    @property
    def editors_by_path(self):
        return dict(zip(self.paths, self.editors))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def save_initial_state(self):
        """Saves initial cursors and initial active editor."""
        paths = self.paths
        self.initial_editor = self.get_editor()
        self.initial_cursors = {}

        for i, editor in enumerate(self.editors):
            if editor is self.initial_editor:
                self.initial_path = paths[i]
            self.initial_cursors[paths[i]] = editor.textCursor()

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        editors = self.editors_by_path

        for path in self.initial_cursors:
            cursor = self.initial_cursors[path]
            if path in editors:
                self.set_editor_cursor(editors[path], cursor)

        if self.initial_editor in self.paths_by_editor:
            index = self.paths.index(self.initial_path)
            self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog in the center of the editor."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup

        left = parent.geometry().width()/2 - width/2
        top = 0
        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        # Note: the +1 pixel on the top makes it look better
        self.move(left, top + self.tabs.tabBar().geometry().height() + 1)

    def fix_size(self, content, extra=50):
        """Adjusts the width of the file switcher, based on the content."""
        # Update size of dialog based on longest shortened path
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()
            max_width = max([fm.width(s)*1.3 for s in strings])
            self.list.setMinimumWidth(max_width + extra)
            self.set_dialog_position()

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        self.select_row(+1)

    # --- Helper methods: Editor
    def get_editor(self, index=None, path=None):
        """Get editor by index or path.
        
        If no path or index specified the current active editor is returned
        """
        if index:
            return self.tabs.widget(index)
        elif path:
            return self.tabs.widget(index)
        else:
            return self.parent().get_current_editor()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            editor = self.get_editor()
            editor.go_to_line(min(line_number, editor.get_line_count()))

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the object explorer data."""
        return self.get_editor().highlighter.get_outlineexplorer_data()

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.sig_goto_file.emit(stack_index)
                    self.goto_line(self.line_number)
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
        else:
            line_number = None

        # Get all available filenames and get the scores for "fuzzy" matching
        scores = get_search_scores(filter_text, self.filenames,
                                   template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                text_item += "<br><i>{0:}</i>".format(
                    short_paths[index])

                results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        for result in sorted(results):
            index = result[1]
            text = result[-1]
            path = paths[index]
            item = QListWidgetItem(self.tabs.tabIcon(index), text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)
        self.fix_size(short_paths)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text,
                                fold_level, icons[index], token))

        template_1 = '<code>{0}<big>{1} {2}</big></code>'
        template_2 = '<br><code>{0}</code><i>[Line {1}]</i>'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;'*(fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template_1.format(fold_space, token, rich_text)
            textline += template_2.format(fold_space, line_number)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

        # Update list size
        self.fix_size(lines, extra=125)

    def setup(self):
        """Setup list widget content."""
        if not self.tabs.count():
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)
Ejemplo n.º 34
0
class MainWindow(QMainWindow):
    FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = 0, 1, 2

    def __init__(self, label_file=None):
        super(MainWindow, self).__init__()
        self.showMaximized()
        self.setWindowTitle("VTCC.Labelling")

        self.file_dirs = []
        self.file_id = -1
        self.labels = []
        with open(label_file, 'r') as f:
            lines = f.read().splitlines()
            for label in lines:
                self.labels.append(label)

        # RIGHT DOCK
        self.label_dock = QDockWidget("Label List", self)
        self.label_list_widget = QListWidget(self)
        self.load_labels(label_file)
        self.label_dock.setWidget(self.label_list_widget)

        self.object_dock = QDockWidget("Object List", self)
        self.object_list_widget = QListWidget(self)
        self.object_list_widget.currentRowChanged.connect(self.change_object)
        self.object_dock.setWidget(self.object_list_widget)

        self.file_dock = QDockWidget("File List", self)
        self.file_list_widget = QListWidget(self)
        self.file_list_widget.currentRowChanged.connect(self.change_file)
        self.file_dock.setWidget(self.file_list_widget)

        self.addDockWidget(Qt.RightDockWidgetArea, self.label_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.object_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock)

        # MAIN CANVAS
        self.canvas = Canvas(self)

        self.canvas_area = QScrollArea()
        self.canvas_area.setWidget(self.canvas)
        self.canvas_area.setWidgetResizable(True)
        self.scrollBars = {
            Qt.Vertical: self.canvas_area.verticalScrollBar(),
            Qt.Horizontal: self.canvas_area.horizontalScrollBar(),
        }
        self.setCentralWidget(self.canvas_area)

        # LEFT DOCK
        self.open_action = QAction(QIcon('icons/open.png'), 'Open File', self)
        self.open_action.triggered.connect(self.open_file)
        self.open_action.setShortcut(QKeySequence("Ctrl+O"))
        self.open_dir_action = QAction(QIcon('icons/open.png'), 'Open Dir',
                                       self)
        self.open_dir_action.triggered.connect(self.open_dir)

        self.next_img_action = QAction(QIcon('icons/next.png'), 'Next Image',
                                       self)
        self.next_img_action.triggered.connect(self.next_img)
        self.next_img_action.setShortcut(QKeySequence("Right"))
        self.prev_img_action = QAction(QIcon('icons/prev.png'), 'Prev Image',
                                       self)
        self.prev_img_action.triggered.connect(self.prev_img)
        self.prev_img_action.setShortcut(QKeySequence("Left"))

        self.zoom_in_action = QAction(QIcon('icons/zoom-in.png'), 'Zoom In',
                                      self)
        self.zoom_in_action.triggered.connect(self.zoom_in)
        self.zoom_out_action = QAction(QIcon('icons/zoom-out.png'), 'Zoom Out',
                                       self)
        self.zoom_out_action.triggered.connect(self.zoom_out)
        self.zoom_org_action = QAction(QIcon('icons/fit-window.png'),
                                       'Fit Window', self)
        self.zoom_org_action.triggered.connect(self.zoom_org)

        self.rectangle_action = QAction(QIcon('icons/objects.png'),
                                        'New Rectangle', self)
        self.rectangle_action.triggered.connect(self.new_rectangle)
        self.auto_polygon_action = QAction(QIcon('icons/objects.png'),
                                           'New Auto-Polygon', self)
        self.auto_polygon_action.triggered.connect(self.new_auto_polygon)
        self.polygon_action = QAction(QIcon('icons/objects.png'),
                                      'New Polygon', self)
        self.polygon_action.triggered.connect(self.new_polygon)

        self.next_obj_action = QAction(QIcon('icons/next.png'), 'Next Object',
                                       self)
        self.next_obj_action.triggered.connect(self.canvas.next_obj)
        self.next_obj_action.setShortcut(QKeySequence("Down"))
        self.prev_obj_action = QAction(QIcon('icons/prev.png'), 'Prev Object',
                                       self)
        self.prev_obj_action.triggered.connect(self.canvas.prev_obj)
        self.prev_obj_action.setShortcut(QKeySequence("Up"))
        self.del_obj_action = QAction(QIcon('icons/delete.png'),
                                      'Delete Object', self)
        self.del_obj_action.triggered.connect(self.canvas.del_obj)
        self.del_obj_action.setShortcut(QKeySequence("Del"))

        self.toolbar = QToolBar(self)
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.addAction(self.open_action)
        self.toolbar.addAction(self.open_dir_action)
        self.toolbar.addAction(self.next_img_action)
        self.toolbar.addAction(self.prev_img_action)
        # self.toolbar.addAction(self.zoom_in_action)
        # self.toolbar.addAction(self.zoom_out_action)
        # self.toolbar.addAction(self.zoom_org_action)
        self.toolbar.addAction(self.rectangle_action)
        self.toolbar.addAction(self.auto_polygon_action)
        self.toolbar.addAction(self.polygon_action)
        self.toolbar.addAction(self.next_obj_action)
        self.toolbar.addAction(self.prev_obj_action)
        self.toolbar.addAction(self.del_obj_action)

        self.addToolBar(Qt.LeftToolBarArea, self.toolbar)

        self.scalers = {
            self.FIT_WINDOW:
            self.scaleFitWindow,
            self.FIT_WIDTH:
            self.scaleFitWidth,
            # Set to one to scale to 100% when loading files.
            self.MANUAL_ZOOM:
            lambda: 1,
        }

    def update_mode(self, mode_id):
        pass

    def change_object(self, row):
        if (row >= 0):
            self.canvas.cur_object = row
            self.canvas.repaint()

    def change_file(self, row):
        if (row >= 0):
            self.file_id = row
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)

    def open_file(self):
        path = '.'
        if len(self.file_dirs) > 0:
            path = os.path.dirname(str(self.file_dirs[0]))

        formats = [
            '*.{}'.format(fmt.data().decode())
            for fmt in QImageReader.supportedImageFormats()
        ]
        filters = "Image files (%s)" % ' '.join(formats)
        file_dir = QFileDialog.getOpenFileName(self, \
                    "Choose Image file", path, filters)[0]

        self.file_dirs = [file_dir]
        self.import_files()

    def open_dir(self):
        targetDirPath = str(
            QFileDialog.getExistingDirectory(
                self, 'Open Directory', '.',
                QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks))

        self.file_dirs = []
        for fmt in QImageReader.supportedImageFormats():
            pattern = os.path.join(targetDirPath, "*." + fmt.data().decode())
            self.file_dirs += glob.glob(pattern)
        self.import_files()

    def next_img(self):
        if (len(self.file_dirs) > 0):
            self.file_id = (self.file_id + 1) % len(self.file_dirs)
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)
            self.file_list_widget.setCurrentRow(self.file_id)

    def prev_img(self):
        if (len(self.file_dirs) > 0):
            self.file_id = (self.file_id + len(self.file_dirs) - 1) % len(
                self.file_dirs)
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)
            self.file_list_widget.setCurrentRow(self.file_id)

    def import_files(self):
        self.load_file_list()

        self.file_id = 0
        self.canvas.load_file(self.file_dirs[0])
        self.adjustScale(initial=True)
        self.file_list_widget.setCurrentRow(self.file_id)

    def load_labels(self, label_file):
        self.label_list_widget.clear()
        for label in self.labels:
            item = QListWidgetItem(label)
            self.label_list_widget.addItem(item)

    def load_object_list(self, objects):
        self.object_list_widget.clear()
        for obj in objects:
            item = QListWidgetItem(obj.label)
            self.object_list_widget.addItem(item)

    def load_file_list(self):
        self.file_list_widget.clear()

        for image_dir in self.file_dirs:
            if not QFile.exists(image_dir):
                continue
            item = QListWidgetItem(image_dir)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            label_dir = os.path.splitext(image_dir)[0] + '.json'
            if QFile.exists(label_dir):
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)
            self.file_list_widget.addItem(item)

    def scaleFitWindow(self):
        """Figure out the size of the pixmap to fit the main widget."""
        e = 2.0  # So that no scrollbars are generated.
        w1 = self.centralWidget().width() - e
        h1 = self.centralWidget().height() - e
        a1 = w1 / h1
        # Calculate a new scale value based on the pixmap's aspect ratio.
        w2 = self.canvas.pixmap.width() - 0.0
        h2 = self.canvas.pixmap.height() - 0.0
        a2 = w2 / h2
        return w1 / w2 if a2 >= a1 else h1 / h2

    def scaleFitWidth(self):
        # The epsilon does not seem to work too well here.
        w = self.centralWidget().width() - 2.0
        return w / self.canvas.pixmap.width()

    def adjustScale(self, initial=False):
        value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]()
        self.canvas.rescale(value)

    def zoom_in(self):
        value = self.canvas.scale
        self.canvas.rescale(value * 1.1)

    def zoom_out(self):
        value = self.canvas.scale
        self.canvas.rescale(value * 0.9)

    def zoom_org(self):
        print(self.centralWidget().width(), self.centralWidget().height())
        print(self.canvas.pixmap.width(), self.canvas.pixmap.height())
        print(self.canvas.width(), self.canvas.height())
        print(self.canvas_area.width(), self.canvas_area.height())
        self.adjustScale(initial=True)

    def new_rectangle(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.RECTANGLE

    def new_auto_polygon(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.AUTO_POLYGON

    def new_polygon(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.POLYGON