Example #1
0
    def __init__(self, name, a, b, callback):
        QWidget.__init__(self)
        self.name = name
        self.callback = callback
        self.a = a
        self.b = b
        self.manually_triggered = False

        self.slider = QSlider()
        self.slider.setRange(0, 1000)
        self.slider.setValue(500)
        self.slider.valueChanged.connect(self.slider_changed)

        self.name_label = QLabel()
        self.name_label.setText(self.name)
        self.name_label.setAlignment(QtCore.Qt.AlignCenter)

        self.value_label = QLabel()
        self.value_label.setText('%2.2f' % (self.slider.value() * self.a
                                             + self.b))
        self.value_label.setAlignment(QtCore.Qt.AlignCenter)

        self.layout = QGridLayout(self)
        self.layout.addWidget(self.name_label, 0, 0)
        self.layout.addWidget(self.slider, 1, 0, QtCore.Qt.AlignHCenter)
        self.layout.addWidget(self.value_label, 2, 0)
Example #2
0
    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()
Example #3
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.uid_label = QLabel()
        self.open_individually_button = QPushButton('Open individually')
        self.open_individually_button.hide()
        self.open_individually_button.clicked.connect(self._open_individually)
        self.open_overplotted_button = QPushButton('Open over-plotted')
        self.open_overplotted_button.hide()
        self.open_overplotted_button.clicked.connect(self._open_overplotted)
        self.open_overplotted_on_button = QPushButton('Add to tab...')
        self.open_overplotted_on_button.hide()
        self.open_overplotted_on_button.setEnabled(False)
        self.open_overplotted_on_button.clicked.connect(self._open_overplotted_on)
        self.copy_uid_button = QPushButton('Copy UID to Clipboard')
        self.copy_uid_button.hide()
        self.copy_uid_button.clicked.connect(self._copy_uid)
        self.streams = QLabel()
        self.entries = []

        uid_layout = QHBoxLayout()
        uid_layout.addWidget(self.uid_label)
        uid_layout.addWidget(self.copy_uid_button)
        layout = QVBoxLayout()
        layout.addWidget(self.open_individually_button)
        layout.addWidget(self.open_overplotted_button)
        layout.addWidget(self.open_overplotted_on_button)
        layout.addLayout(uid_layout)
        layout.addWidget(self.streams)
        self.setLayout(layout)

        self._tab_titles = ()
Example #4
0
 def rebuild(self):
     """ Clear out all existing widgets, and populate the list using the
     template file and data source."""
     self.clear()
     if (not self.templateFilename) or (not self.data):
         return
     self.setUpdatesEnabled(False)
     
     layout_class = layout_class_for_type[self.layoutType]
     if type(self.layout()) != layout_class:
         if self.layout() is not None:
             # Trick to remove the existing layout by re-parenting it in an empty widget.
             QWidget().setLayout(self.layout())
         l = layout_class(self)
         self.setLayout(l)
     with pydm.data_plugins.connection_queue():
         for i, variables in enumerate(self.data):
             if is_qt_designer() and i > self.countShownInDesigner - 1:
                 break
             w = self.open_template_file(variables)
             if w is None:
                 w = QLabel()
                 w.setText("No Template Loaded.  Data: {}".format(variables))
             w.setParent(self)
             self.layout().addWidget(w)
     self.setUpdatesEnabled(True)
    def _init_ui(self):
        # LINE 1: Data component drop down
        self.component_prompt = QLabel("Data Component:")
        self.component_prompt.setWordWrap(True)
        # Add the data component labels to the drop down, with the ComponentID
        # set as the userData:
        if self.parent is not None and hasattr(self.parent, 'data_components'):
            self.label_data = [(str(cid), cid) for cid in self.parent.data_components]
        else:
            self.label_data = [(str(cid), cid) for cid in self.data.visible_components]

        default_index = 0
        self.component_combo = QComboBox()
        self.component_combo.setFixedWidth(200)
        update_combobox(self.component_combo, self.label_data, default_index=default_index)
        self.component_combo.currentIndexChanged.connect(self.update_unit_layout)

        # hbl is short for Horizontal Box Layout
        hbl1 = QHBoxLayout()
        hbl1.addWidget(self.component_prompt)
        hbl1.addWidget(self.component_combo)
        hbl1.addStretch(1)

        # LINE 2: Unit conversion layout
        # This layout is filled by CubeVizUnit
        self.unit_layout = QHBoxLayout()  # this is hbl2

        # LINE 3: Message box
        self.message_box = QLabel("")
        hbl3 = QHBoxLayout()
        hbl3.addWidget(self.message_box)
        hbl3.addStretch(1)

        # Line 4: Buttons
        ok_text = "Convert Data" if self.convert_data else "Convert Displayed Units"
        ok_function = self.convert_data_units if self.convert_data else self.convert_displayed_units
        self.okButton = QPushButton(ok_text)
        self.okButton.clicked.connect(ok_function)
        self.okButton.setDefault(True)

        self.cancelButton = QPushButton("Cancel")
        self.cancelButton.clicked.connect(self.cancel)

        hbl4 = QHBoxLayout()
        hbl4.addStretch(1)
        hbl4.addWidget(self.cancelButton)
        hbl4.addWidget(self.okButton)

        vbl = QVBoxLayout()
        vbl.addLayout(hbl1)
        vbl.addLayout(self.unit_layout)
        vbl.addLayout(hbl3)
        vbl.addLayout(hbl4)
        self.setLayout(vbl)
        self.vbl = vbl

        self.update_unit_layout(default_index)

        self.show()
Example #6
0
    def setup_page(self):
        # Connections group
        connections_group = QGroupBox(_("Automatic connections"))
        connections_label = QLabel(_("This pane can automatically "
                                     "show an object's help information after "
                                     "a left parenthesis is written next to it. "
                                     "Below you can decide to which plugin "
                                     "you want to connect it to turn on this "
                                     "feature."))
        connections_label.setWordWrap(True)
        editor_box = self.create_checkbox(_("Editor"), 'connect/editor')
        rope_installed = programs.is_module_installed('rope')
        jedi_installed = programs.is_module_installed('jedi', '>=0.8.1')
        editor_box.setEnabled(rope_installed or jedi_installed)
        if not rope_installed and not jedi_installed:
            editor_tip = _("This feature requires the Rope or Jedi libraries.\n"
                           "It seems you don't have either installed.")
            editor_box.setToolTip(editor_tip)
        ipython_box = self.create_checkbox(_("IPython Console"),
                                           'connect/ipython_console')

        connections_layout = QVBoxLayout()
        connections_layout.addWidget(connections_label)
        connections_layout.addWidget(editor_box)
        connections_layout.addWidget(ipython_box)
        connections_group.setLayout(connections_layout)

        # Features group
        features_group = QGroupBox(_("Additional features"))
        math_box = self.create_checkbox(_("Render mathematical equations"),
                                        'math')
        req_sphinx = programs.is_module_installed('sphinx', '>=1.1')
        math_box.setEnabled(req_sphinx)
        if not req_sphinx:
            sphinx_ver = programs.get_module_version('sphinx')
            sphinx_tip = _("This feature requires Sphinx 1.1 or superior.")
            sphinx_tip += "\n" + _("Sphinx %s is currently installed.") % sphinx_ver
            math_box.setToolTip(sphinx_tip)

        features_layout = QVBoxLayout()
        features_layout.addWidget(math_box)
        features_group.setLayout(features_layout)

        # Source code group
        sourcecode_group = QGroupBox(_("Source code"))
        wrap_mode_box = self.create_checkbox(_("Wrap lines"), 'wrap')

        sourcecode_layout = QVBoxLayout()
        sourcecode_layout.addWidget(wrap_mode_box)
        sourcecode_group.setLayout(sourcecode_layout)

        # Final layout
        vlayout = QVBoxLayout()
        vlayout.addWidget(connections_group)
        vlayout.addWidget(features_group)
        vlayout.addWidget(sourcecode_group)
        vlayout.addStretch(1)
        self.setLayout(vlayout)
    def _init_ui(self):
        self.slit_type_label = QLabel('Slit Type')
        self.slit_type_combo = QComboBox()
        self.slit_type_combo.currentIndexChanged.connect(self.update_info)

        hbl1 = QHBoxLayout()
        hbl1.addWidget(self.slit_type_label)
        hbl1.addWidget(self.slit_type_combo)

        self.slit_width_label = QLabel('Slit Width')
        self.slit_width_input = QLineEdit()
        self.slit_width_combo = QComboBox()
        self.slit_width_units = QLabel('arcsec')

        hbl2 = QHBoxLayout()
        hbl2.addWidget(self.slit_width_label)
        hbl2.addWidget(self.slit_width_input)
        hbl2.addWidget(self.slit_width_combo)
        hbl2.addWidget(self.slit_width_units)

        self.slit_length_label = QLabel('Slit Length')
        self.slit_length_input = QLineEdit()
        self.slit_length_combo = QComboBox()
        self.slit_length_units = QLabel('arcsec')

        hbl3 = QHBoxLayout()
        hbl3.addWidget(self.slit_length_label)
        hbl3.addWidget(self.slit_length_input)
        hbl3.addWidget(self.slit_length_combo)
        hbl3.addWidget(self.slit_length_units)

        self.okButton = QPushButton('Apply')
        self.okButton.clicked.connect(self.apply)
        self.okButton.setDefault(True)

        self.cancelButton = QPushButton('Cancel')
        self.cancelButton.clicked.connect(self.cancel)

        hbl4 = QHBoxLayout()
        hbl4.addWidget(self.cancelButton)
        hbl4.addWidget(self.okButton)

        vbl = QVBoxLayout()
        vbl.addLayout(hbl1)
        vbl.addLayout(hbl2)
        vbl.addLayout(hbl3)
        vbl.addLayout(hbl4)
        self.setLayout(vbl)
        self.vbl = vbl

        self._load_selections()
        self._populate_combo()
        self.update_info(0)

        self.show()
Example #8
0
class EncodingStatus(StatusBarWidget):
    def __init__(self, parent, statusbar):
        StatusBarWidget.__init__(self, parent, statusbar)
        layout = self.layout()
        layout.addWidget(QLabel(_("Encoding:")))
        self.encoding = QLabel()
        self.encoding.setFont(self.label_font)
        layout.addWidget(self.encoding)
        layout.addSpacing(20)
        
    def encoding_changed(self, encoding):
        self.encoding.setText(str(encoding).upper().ljust(15))
Example #9
0
 def __init__(self, parent, statusbar):
     StatusBarWidget.__init__(self, parent, statusbar)
     layout = self.layout()
     layout.addWidget(QLabel(_("Line:")))
     self.line = QLabel()
     self.line.setFont(self.label_font)
     layout.addWidget(self.line)
     layout.addWidget(QLabel(_("Column:")))
     self.column = QLabel()
     self.column.setFont(self.label_font)
     layout.addWidget(self.column)
     self.setLayout(layout)
Example #10
0
class QLabeledSlider(QWidget):
    """
    A labeled slider widget
    """

    range = None
    integer = None

    def __init__(self, parent=None):

        super(QLabeledSlider, self).__init__(parent)

        self._range = range

        self._slider = QSlider()
        self._slider.setMinimum(0)
        self._slider.setMaximum(100)
        self._slider.setOrientation(Qt.Horizontal)

        self._label = QLabel('')
        self._layout = QHBoxLayout()
        self._layout.setContentsMargins(2, 2, 2, 2)
        self._layout.addWidget(self._slider)
        self._layout.addWidget(self._label)

        self._slider.valueChanged.connect(self._update_label)

        self.setLayout(self._layout)

    def _update_label(self, *args):
        self._label.setText(str(self.value()))

    @property
    def valueChanged(self):
        return self._slider.valueChanged

    def value(self, layer=None, view=None):
        value = self._slider.value() / 100. * (self.range[1] - self.range[0]) + self.range[0]
        if self.integer:
            return int(value)
        else:
            return(value)

    _in_set_value = False

    def setValue(self, value):
        if self._in_set_value:
            return
        self._in_set_value = True
        value = int(100 * (value - self.range[0]) / (self.range[1] - self.range[0]))
        self._slider.setValue(value)
        self._in_set_value = False
Example #11
0
 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()
Example #12
0
class ReadWriteStatus(StatusBarWidget):
    def __init__(self, parent, statusbar):
        StatusBarWidget.__init__(self, parent, statusbar)
        layout = self.layout()
        layout.addWidget(QLabel(_("Permissions:")))
        self.readwrite = QLabel()
        self.readwrite.setFont(self.label_font)
        layout.addWidget(self.readwrite)
        layout.addSpacing(20)
        
    def readonly_changed(self, readonly):
        readwrite = "R" if readonly else "RW"
        self.readwrite.setText(readwrite.ljust(3))
Example #13
0
class EOLStatus(StatusBarWidget):
    def __init__(self, parent, statusbar):
        StatusBarWidget.__init__(self, parent, statusbar)
        layout = self.layout()
        layout.addWidget(QLabel(_("End-of-lines:")))
        self.eol = QLabel()
        self.eol.setFont(self.label_font)
        layout.addWidget(self.eol)
        layout.addSpacing(20)
        
    def eol_changed(self, os_name):
        os_name = to_text_string(os_name)
        self.eol.setText({"nt": "CRLF", "posix": "LF"}.get(os_name, "CR"))
Example #14
0
class SearchLineEdit(QLineEdit):
    """Line edit search widget with icon and remove all button"""
    def __init__(self, parent=None, icon=True):
        super(SearchLineEdit, self).__init__(parent)
        self.setTextMargins(1, 0, 20, 0)

        if icon:
            self.setTextMargins(18, 0, 20, 0)
            self._label = QLabel(self)
            self._pixmap_icon = QPixmap(get_image_path('conda_search.png'))
            self._label.setPixmap(self._pixmap_icon)
            self._label.setStyleSheet('''border: 0px; padding-bottom: 0px;
                                      padding-left: 2px;''')

        self._pixmap = QPixmap(get_image_path('conda_del.png'))
        self.button_clear = QToolButton(self)
        self.button_clear.setIcon(QIcon(self._pixmap))
        self.button_clear.setIconSize(QSize(18, 18))
        self.button_clear.setCursor(Qt.ArrowCursor)
        self.button_clear.setStyleSheet("""QToolButton
            {background: transparent;
            padding-right: 2px; border: none; margin:0px; }""")
        self.button_clear.setVisible(False)

        # Layout
        self._layout = QHBoxLayout(self)
        self._layout.addWidget(self.button_clear, 0, Qt.AlignRight)
        self._layout.setSpacing(0)
        self._layout.setContentsMargins(0, 2, 2, 0)

        # Signals and slots
        self.button_clear.clicked.connect(self.clear_text)
        self.textChanged.connect(self._toggle_visibility)
        self.textEdited.connect(self._toggle_visibility)

    def _toggle_visibility(self):
        """ """
        if len(self.text()) == 0:
            self.button_clear.setVisible(False)
        else:
            self.button_clear.setVisible(True)

    def sizeHint(self):
        return QSize(200, self._pixmap_icon.height())

    # Public api
    # ----------
    def clear_text(self):
        """ """
        self.setText('')
        self.setFocus()
Example #15
0
class CursorPositionStatus(StatusBarWidget):
    """Status bar widget for the current file cursor postion."""

    def __init__(self, parent, statusbar):
        """Status bar widget for the current file cursor postion."""
        super(CursorPositionStatus, self).__init__(parent, statusbar)

        # Widget
        self.label_line = QLabel(_("Line:"))
        self.label_column = QLabel(_("Column:"))
        self.column = QLabel()
        self.line = QLabel()

        # Widget setup
        self.line.setFont(self.label_font)
        self.column.setFont(self.label_font)

        # Layout
        layout = self.layout()
        layout.addWidget(self.label_line)
        layout.addWidget(self.line)
        layout.addWidget(self.label_column)
        layout.addWidget(self.column)
        self.setLayout(layout)
        
    def cursor_position_changed(self, line, index):
        """Update cursos position."""
        self.line.setText("%-6d" % (line+1))
        self.column.setText("%-4d" % (index+1))
    def setup_page(self):
        settings_group = QGroupBox(_("Settings"))
        use_color_box = self.create_checkbox(
            _("Use deterministic colors to differentiate functions"),
            'use_colors', default=True)

        results_group = QGroupBox(_("Results"))
        results_label1 = QLabel(_("Memory profiler plugin results "
                                  "(the output of memory_profiler)\n"
                                  "is stored here:"))
        results_label1.setWordWrap(True)

        # 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 of Profiler plugon
        results_label2 = QLabel(MemoryProfilerWidget.DATAPATH)

        results_label2.setTextInteractionFlags(Qt.TextSelectableByMouse)
        results_label2.setWordWrap(True)

        settings_layout = QVBoxLayout()
        settings_layout.addWidget(use_color_box)
        settings_group.setLayout(settings_layout)

        results_layout = QVBoxLayout()
        results_layout.addWidget(results_label1)
        results_layout.addWidget(results_label2)
        results_group.setLayout(results_layout)

        vlayout = QVBoxLayout()
        vlayout.addWidget(settings_group)
        vlayout.addWidget(results_group)
        vlayout.addStretch(1)
        self.setLayout(vlayout)
Example #17
0
    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())
Example #18
0
class ConnectionInspector(QWidget):
    def __init__(self, parent=None):
        super(ConnectionInspector, self).__init__(parent, Qt.Window)
        connections = self.fetch_data()
        self.table_view = ConnectionTableView(connections, self)
        self.setLayout(QVBoxLayout(self))
        self.layout().addWidget(self.table_view)
        button_layout = QHBoxLayout()
        self.layout().addItem(button_layout)
        self.save_status_label = QLabel(self)
        button_layout.addWidget(self.save_status_label)
        button_layout.addStretch()
        self.save_button = QPushButton(self)
        self.save_button.setText("Save list to file...")
        self.save_button.clicked.connect(self.save_list_to_file)
        button_layout.addWidget(self.save_button)
        self.update_timer = QTimer(parent=self)
        self.update_timer.setInterval(1500)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.start()

    def update_data(self):
        self.table_view.model().connections = self.fetch_data()

    def fetch_data(self):
        plugins = data_plugins.plugin_modules
        return [connection
                for p in plugins.values()
                for connection in p.connections.values()
                ]

    @Slot()
    def save_list_to_file(self):
        filename, filters = QFileDialog.getSaveFileName(self,
                                                        "Save connection list",
                                                        "",
                                                        "Text Files (*.txt)")
        try:
            with open(filename, "w") as f:
                for conn in self.table_view.model().connections:
                    f.write(
                        "{p}://{a}\n".format(p=conn.protocol, a=conn.address))
            self.save_status_label.setText("File saved to {}".format(filename))
        except Exception as e:
            msgBox = QMessageBox()
            msgBox.setText("Couldn't save connection list to file.")
            msgBox.setInformativeText("Error: {}".format(str(e)))
            msgBox.setStandardButtons(QMessageBox.Ok)
            msgBox.exec_()
Example #19
0
class StatusBarWidget(QWidget):
    """Status bar widget base."""
    TIP = None

    def __init__(self, parent, statusbar, icon=None):
        """Status bar widget base."""
        super(StatusBarWidget, self).__init__(parent)

        # Variables
        self.value = None

        # Widget
        self._icon = icon
        self._pixmap = icon.pixmap(QSize(16, 16)) if icon is not None else None
        self.label_icon = QLabel() if icon is not None else None
        self.label_value = QLabel()

        # Widget setup
        if icon is not None:
            self.label_icon.setPixmap(self._pixmap)
        self.text_font = QFont(get_font(option='font'))  # See Issue #9044
        self.text_font.setPointSize(self.font().pointSize())
        self.text_font.setBold(True)
        self.label_value.setAlignment(Qt.AlignRight)
        self.label_value.setFont(self.text_font)

        if self.TIP:
            self.setToolTip(self.TIP)
            self.label_value.setToolTip(self.TIP)

        # Layout
        layout = QHBoxLayout()
        if icon is not None:
            layout.addWidget(self.label_icon)
        layout.addWidget(self.label_value)
        layout.addSpacing(20)

        # Layout setup
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        # Setup
        statusbar.addPermanentWidget(self)

    def set_value(self, value):
        """Set formatted text value."""
        self.value = value
        if self.isVisible():
            self.label_value.setText(value)
Example #20
0
 def __init__(self, parent=None, init_channel=None):
     QLabel.__init__(self, parent)
     PyDMWidget.__init__(self, init_channel=init_channel)
     if 'Text' not in PyDMLabel.RULE_PROPERTIES:
         PyDMLabel.RULE_PROPERTIES = PyDMWidget.RULE_PROPERTIES.copy()
         PyDMLabel.RULE_PROPERTIES.update(
             {'Text': ['value_changed', str]})
     self.app = QApplication.instance()
     self.setTextFormat(Qt.PlainText)
     self.setTextInteractionFlags(Qt.NoTextInteraction)
     self.setText("PyDMLabel")
     self._display_format_type = self.DisplayFormat.Default
     self._string_encoding = "utf_8"
     if is_pydm_app():
         self._string_encoding = self.app.get_string_encoding()
Example #21
0
class ProjectExplorerTest(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)

        self.explorer = ProjectExplorerWidget(None, show_all=True)
        self.explorer.setup_project(osp.dirname(osp.abspath(__file__)))
        vlayout.addWidget(self.explorer)

        hlayout1 = QHBoxLayout()
        vlayout.addLayout(hlayout1)
        label = QLabel("<b>Open file:</b>")
        label.setAlignment(Qt.AlignRight)
        hlayout1.addWidget(label)
        self.label1 = QLabel()
        hlayout1.addWidget(self.label1)
        self.explorer.sig_open_file.connect(self.label1.setText)

        hlayout3 = QHBoxLayout()
        vlayout.addLayout(hlayout3)
        label = QLabel("<b>Option changed:</b>")
        label.setAlignment(Qt.AlignRight)
        hlayout3.addWidget(label)
        self.label3 = QLabel()
        hlayout3.addWidget(self.label3)
        self.explorer.sig_option_changed.connect(
           lambda x, y: self.label3.setText('option_changed: %r, %r' % (x, y)))
Example #22
0
    def __init__(self, parent, statusbar):
        """Status bar widget base for widgets that update based on timers."""
        super(BaseTimerStatus, self).__init__(parent, statusbar)

        # Widgets
        self.label = QLabel(self.TITLE)
        self.value = QLabel()

        # Widget setup
        self.setToolTip(self.TIP)
        self.value.setAlignment(Qt.AlignRight)
        self.value.setFont(self.label_font)
        fm = self.value.fontMetrics()
        self.value.setMinimumWidth(fm.width('000%'))

        # Layout
        layout = self.layout()
        layout.addWidget(self.label)
        layout.addWidget(self.value)
        layout.addSpacing(20)

        # Setup
        if self.is_supported():
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_label)
            self.timer.start(2000)
        else:
            self.timer = None
            self.hide()
Example #23
0
    def __init__(self, parent=None, icon=True):
        super(SearchLineEdit, self).__init__(parent)
        self.setTextMargins(1, 0, 20, 0)

        if icon:
            self.setTextMargins(18, 0, 20, 0)
            self._label = QLabel(self)
            self._pixmap_icon = QPixmap(get_image_path('conda_search.png'))
            self._label.setPixmap(self._pixmap_icon)
            self._label.setStyleSheet('''border: 0px; padding-bottom: 0px;
                                      padding-left: 2px;''')

        self._pixmap = QPixmap(get_image_path('conda_del.png'))
        self.button_clear = QToolButton(self)
        self.button_clear.setIcon(QIcon(self._pixmap))
        self.button_clear.setIconSize(QSize(18, 18))
        self.button_clear.setCursor(Qt.ArrowCursor)
        self.button_clear.setStyleSheet("""QToolButton
            {background: transparent;
            padding-right: 2px; border: none; margin:0px; }""")
        self.button_clear.setVisible(False)

        # Layout
        self._layout = QHBoxLayout(self)
        self._layout.addWidget(self.button_clear, 0, Qt.AlignRight)
        self._layout.setSpacing(0)
        self._layout.setContentsMargins(0, 2, 2, 0)

        # Signals and slots
        self.button_clear.clicked.connect(self.clear_text)
        self.textChanged.connect(self._toggle_visibility)
        self.textEdited.connect(self._toggle_visibility)
Example #24
0
    def __init__(self, dim_info, number=0, state=State.NONE, parent=None):

        # hack in a number_of_bins for MDEventWorkspace
        dim_info['number_of_bins'] = 1000
        dim_info['width'] = (dim_info['maximum']-dim_info['minimum'])/1000

        self.spinBins = QSpinBox()
        self.spinBins.setRange(2,9999)
        self.spinBins.setValue(100)
        self.spinBins.hide()
        self.spinBins.setMinimumWidth(110)
        self.spinThick = QDoubleSpinBox()
        self.spinThick.setRange(0.001,999)
        self.spinThick.setValue(0.1)
        self.spinThick.setSingleStep(0.1)
        self.spinThick.setDecimals(3)
        self.spinThick.setMinimumWidth(110)
        self.rebinLabel = QLabel("thick")
        self.rebinLabel.setMinimumWidth(44)

        super(DimensionMDE, self).__init__(dim_info, number, state, parent)

        self.spinBins.valueChanged.connect(self.binningChanged)
        self.spinThick.valueChanged.connect(self.valueChanged)

        self.layout.addWidget(self.spinBins)
        self.layout.addWidget(self.spinThick)
        self.layout.addWidget(self.rebinLabel)
Example #25
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.settings = QSettings()

        # decimal
        self.decimalLabel = QLabel(self.tr("decimal:"))
        self.decimalComboBox = QComboBox()
        self.decimalLabel.setBuddy(self.decimalComboBox)
        self.decimalComboBox.addItems([",", "."])

        # separator
        self.separatorLabel = QLabel(self.tr("separator:"))
        self.separatorComboBox = QComboBox()
        self.separatorLabel.setBuddy(self.separatorComboBox)
        self.separatorComboBox.addItem("Semicolon ';'", ';')
        self.separatorComboBox.addItem("Comma ','", ',')
        self.separatorComboBox.addItem("Tabulator '\\t'", '\t')
        self.separatorComboBox.addItem("Whitespace ' '", ' ')

        # buttons
        self.buttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)

        # layout
        layout = QGridLayout()
        layout.addWidget(self.decimalLabel, 0, 0)
        layout.addWidget(self.decimalComboBox, 0, 1)
        layout.addWidget(self.separatorLabel, 1, 0)
        layout.addWidget(self.separatorComboBox, 1, 1)
        layout.addWidget(self.buttons, 2, 0, 1, 2)
        self.setLayout(layout)

        # settings
        self.decimalComboBox.setCurrentIndex(
            self.decimalComboBox.findText(
                self.settings.value(DECIMAL_SETTING, ","))
        )
        self.separatorComboBox.setCurrentIndex(
            self.separatorComboBox.findData(
                self.settings.value(SEPARATOR_SETTING, ";"))
        )

        self.setWindowTitle(self.tr("record settings"))
Example #26
0
 def __init__(self, parent, statusbar):
     StatusBarWidget.__init__(self, parent, statusbar)
     layout = self.layout()
     layout.addWidget(QLabel(_("End-of-lines:")))
     self.eol = QLabel()
     self.eol.setFont(self.label_font)
     layout.addWidget(self.eol)
     layout.addSpacing(20)
Example #27
0
    def __init__(self, parent, statusbar):
        """Status bar widget for the current file end of line."""
        super(EOLStatus, self).__init__(parent, statusbar)

        # Widget
        self.label = QLabel(_("End-of-lines:"))
        self.eol = QLabel()

        # Widget setup
        self.label.setAlignment(Qt.AlignRight)
        self.eol.setFont(self.label_font)

        # Layouts
        layout = self.layout()
        layout.addWidget(self.label)
        layout.addWidget(self.eol)
        layout.addSpacing(20)
Example #28
0
 def __init__(self, parent, statusbar):
     StatusBarWidget.__init__(self, parent, statusbar)
     layout = self.layout()
     layout.addWidget(QLabel(_("Encoding:")))
     self.encoding = QLabel()
     self.encoding.setFont(self.label_font)
     layout.addWidget(self.encoding)
     layout.addSpacing(20)
Example #29
0
    def __init__(self, parent, statusbar):
        """Status bar widget for the current file encoding."""
        super(EncodingStatus, self).__init__(parent, statusbar)

        # Widgets
        self.label = QLabel(_("Encoding:"))
        self.encoding = QLabel()

        # Widget setup
        self.label.setAlignment(Qt.AlignRight)
        self.encoding.setFont(self.label_font)

        # Layouts
        layout = self.layout()
        layout.addWidget(self.label)
        layout.addWidget(self.encoding)
        layout.addSpacing(20)
Example #30
0
    def __init__(self, parent, statusbar):
        """Status bar widget for current file read/write mode."""
        super(ReadWriteStatus, self).__init__(parent, statusbar)

        # Widget
        self.label = QLabel(_("Permissions:"))
        self.readwrite = QLabel()

        # Widget setup
        self.label.setAlignment(Qt.AlignRight)
        self.readwrite.setFont(self.label_font)

        # Layouts
        layout = self.layout()
        layout.addWidget(self.label)
        layout.addWidget(self.readwrite)
        layout.addSpacing(20)
Example #31
0
    def _get_acq_commom_params_grpbx(self):
        grp_bx = QGroupBox('Common Parameters', self)
        fbl = QFormLayout(grp_bx)

        lbl = QLabel('Non-linear Corr.', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair_butled(grp_bx, 'PolyCalibration')
        fbl.addRow(lbl, wid)
        self._set_detailed([lbl, wid])

        lbl = QLabel('Channel Rate', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair_sel(grp_bx, 'TrigAcqChan')
        fbl.addRow(lbl, wid)

        lbl = QLabel('Trigger Type', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair_sel(grp_bx, 'TrigAcqTrigger')
        fbl.addRow(lbl, wid)
        self._set_detailed([lbl, wid])

        lbl = QLabel('Repeat', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair_butled(grp_bx, 'TrigAcqRepeat')
        fbl.addRow(lbl, wid)
        self._set_detailed([lbl, wid])

        if self.isring:
            lbl = QLabel('Nr of Shots', grp_bx, alignment=Qt.AlignCenter)
            wid = self.create_pair(grp_bx, 'TrigNrShots')
            fbl.addRow(lbl, wid)
            self._set_detailed([lbl, wid])

        lbl = QLabel('SamplesPre', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair(grp_bx, 'TrigNrSamplesPre')
        fbl.addRow(lbl, wid)
        lbl = QLabel('SamplesPost', grp_bx, alignment=Qt.AlignCenter)
        wid = self.create_pair(grp_bx, 'TrigNrSamplesPost')
        fbl.addRow(lbl, wid)

        lbl = QLabel('Acquisition:', grp_bx, alignment=Qt.AlignCenter)
        strt = PyDMPushButton(
            grp_bx,
            label='',
            init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'),
            pressValue=self._csorb.TrigAcqCtrl.Start)
        strt.setToolTip('Start Acquisition')
        strt.setIcon(qta.icon('fa5s.play'))
        strt.setObjectName('strt')
        strt.setStyleSheet(
            '#strt{min-width:25px; max-width:25px; icon-size:20px;}')
        stop = PyDMPushButton(
            grp_bx,
            label='',
            init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'),
            pressValue=self._csorb.TrigAcqCtrl.Stop)
        stop.setToolTip('Stop Acquisition')
        stop.setIcon(qta.icon('fa5s.stop'))
        stop.setObjectName('stop')
        stop.setStyleSheet(
            '#stop{min-width:25px; max-width:25px; icon-size:20px;}')
        abrt = PyDMPushButton(
            grp_bx,
            label='',
            init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'),
            pressValue=self._csorb.TrigAcqCtrl.Abort)
        abrt.setToolTip('Abort Acquisition')
        abrt.setIcon(qta.icon('fa5s.ban'))
        abrt.setObjectName('abrt')
        abrt.setStyleSheet(
            '#abrt{min-width:25px; max-width:25px; icon-size:20px;}')

        pdmlbl = PyDMLabel(grp_bx,
                           self.devpref.substitute(propty='TrigAcqCtrl-Sts'))
        pdmlbl.setObjectName('pdmlbl')
        pdmlbl.setStyleSheet('#pdmlbl{min-width:6em; max-width:6em;}')
        pdmlbl.setAlignment(Qt.AlignCenter)
        hbl = QHBoxLayout()
        fbl.addRow(hbl)
        hbl.addStretch()
        hbl.addWidget(lbl)
        hbl.addWidget(strt)
        hbl.addWidget(stop)
        hbl.addWidget(abrt)
        hbl.addWidget(pdmlbl)

        conf = PyDMPushButton(
            grp_bx,
            pressValue=1,
            init_channel=self.devpref.substitute(propty='TrigAcqConfig-Cmd'))
        conf.setToolTip('Resend Configurations')
        conf.setIcon(qta.icon('fa5s.sync'))
        conf.setObjectName('conf')
        conf.setStyleSheet(
            '#conf{min-width:25px; max-width:25px; icon-size:20px;}')

        sts = QPushButton('', grp_bx)
        sts.setIcon(qta.icon('fa5s.list-ul'))
        sts.setToolTip('Open Detailed Status View')
        sts.setObjectName('sts')
        sts.setStyleSheet(
            '#sts{min-width:25px; max-width:25px; icon-size:20px;}')
        Window = create_window_from_widget(StatusWidget, title='Orbit Status')
        connect_window(sts,
                       Window,
                       grp_bx,
                       device=self.device,
                       prefix=self.prefix,
                       acc=self.acc,
                       is_orb=True)

        pdm_led = SiriusLedAlert(
            grp_bx, self.devpref.substitute(propty='OrbStatus-Mon'))

        lbl = QLabel('Status:', grp_bx)
        hbl = QHBoxLayout()
        hbl.setSpacing(9)
        hbl.addStretch()
        hbl.addWidget(lbl)
        hbl.addWidget(pdm_led)
        hbl.addWidget(sts)
        hbl.addWidget(conf)
        fbl.addRow(hbl)

        return grp_bx
Example #32
0
    def __init__(self, name=None, plugin=None, parent=None):
        super().__init__(name, plugin, parent=parent)
        self.set_conf('text_color', MAIN_TEXT_COLOR)
        self.set_conf('hist_limit', MAX_PATH_HISTORY)

        # Attributes
        self.text_color = self.get_conf('text_color')
        self.supported_encodings = self.get_conf('supported_encodings')
        self.search_thread = None
        self.running = False
        self.more_options_action = None
        self.extras_toolbar = None

        search_text = self.get_conf('search_text', '')
        path_history = self.get_conf('path_history', [])
        exclude = self.get_conf('exclude')

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

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

        if not isinstance(path_history, (list, tuple)):
            path_history = [path_history]

        # Widgets
        self.search_text_edit = PatternComboBox(
            self,
            search_text,
            id_=FindInFilesWidgetToolbarItems.SearchPatternCombo)

        self.search_text_edit.lineEdit().setPlaceholderText(
            _('Write text to search'))

        self.search_in_label = QLabel(_('Search in:'))
        self.search_in_label.ID = FindInFilesWidgetToolbarItems.SearchInLabel

        self.exclude_label = QLabel(_('Exclude:'))
        self.exclude_label.ID = FindInFilesWidgetToolbarItems.ExcludeLabel

        self.path_selection_combo = SearchInComboBox(
            path_history,
            self,
            id_=FindInFilesWidgetToolbarItems.SearchInCombo)

        self.exclude_pattern_edit = PatternComboBox(
            self,
            exclude,
            _("Exclude pattern"),
            id_=FindInFilesWidgetToolbarItems.ExcludePatternCombo)

        self.result_browser = ResultsBrowser(
            self,
            text_color=self.text_color,
            max_results=self.get_conf('max_results'),
        )

        # Setup
        self.exclude_label.setBuddy(self.exclude_pattern_edit)
        exclude_idx = self.get_conf('exclude_index', None)
        if (exclude_idx is not None and exclude_idx >= 0
                and exclude_idx < self.exclude_pattern_edit.count()):
            self.exclude_pattern_edit.setCurrentIndex(exclude_idx)

        search_in_index = self.get_conf('search_in_index', None)
        self.path_selection_combo.set_current_searchpath_index(search_in_index)

        # Layout
        layout = QHBoxLayout()
        layout.addWidget(self.result_browser)
        self.setLayout(layout)

        # Signals
        self.path_selection_combo.sig_redirect_stdio_requested.connect(
            self.sig_redirect_stdio_requested)
        self.search_text_edit.valid.connect(lambda valid: self.find())
        self.exclude_pattern_edit.valid.connect(lambda valid: self.find())
        self.result_browser.sig_edit_goto_requested.connect(
            self.sig_edit_goto_requested)
        self.result_browser.sig_max_results_reached.connect(
            self.sig_max_results_reached)
        self.result_browser.sig_max_results_reached.connect(
            self._stop_and_reset_thread)
        self.search_text_edit.sig_resized.connect(self._update_size)
Example #33
0
    def __init__(self, parent):
        super(KiteIntegrationInfo, self).__init__(parent)
        # Images
        images_layout = QHBoxLayout()
        icon_filename = 'kite_completions'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        screen = QApplication.primaryScreen()
        image_label = QLabel()
        image_height = int(image.height() * self.ICON_SCALE_FACTOR)
        image_width = int(image.width() * self.ICON_SCALE_FACTOR)
        image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                             Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()

        ilayout = QHBoxLayout()
        ilayout.addLayout(images_layout)

        # Label
        integration_label_title = QLabel(
            "Get better code completions in Spyder")
        integration_label_title.setStyleSheet(
            f"font-size: {self.TITLE_FONT_SIZE}")
        integration_label_title.setWordWrap(True)
        integration_label = QLabel(
            _("Now Spyder can use Kite to provide better code "
              "completions for key packages in the scientific Python "
              "Ecosystem. Install Kite for a better editor experience in "
              "Spyder. <br><br>Kite is free to use but is not open "
              "source. <a href=\"{kite_url}\">Learn more about Kite </a>").
            format(kite_url=KITE_SPYDER_URL))
        integration_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}")
        integration_label.setOpenExternalLinks(True)
        integration_label.setWordWrap(True)
        integration_label.setFixedWidth(360)
        label_layout = QVBoxLayout()
        label_layout.addWidget(integration_label_title)
        label_layout.addWidget(integration_label)

        # Buttons
        buttons_layout = QHBoxLayout()
        install_button = QPushButton(_('Install Kite'))
        install_button.setAutoDefault(False)
        install_button.setStyleSheet(
            f"background-color: {QStylePalette.COLOR_ACCENT_3};"
            f"font-size: {self.BUTTONS_FONT_SIZE};"
            f"padding: {self.BUTTONS_PADDING}")
        dismiss_button = QPushButton(_('Dismiss'))
        dismiss_button.setAutoDefault(False)
        dismiss_button.setStyleSheet(
            f"background-color: {QStylePalette.COLOR_BACKGROUND_6};"
            f"font-size: {self.BUTTONS_FONT_SIZE};"
            f"padding: {self.BUTTONS_PADDING}")
        buttons_layout.addStretch()
        buttons_layout.addWidget(install_button)
        if not MAC:
            buttons_layout.addSpacing(10)
        buttons_layout.addWidget(dismiss_button)

        # Buttons with label
        vertical_layout = QVBoxLayout()
        if not MAC:
            vertical_layout.addStretch()
            vertical_layout.addLayout(label_layout)
            vertical_layout.addSpacing(20)
            vertical_layout.addLayout(buttons_layout)
            vertical_layout.addStretch()
        else:
            vertical_layout.addLayout(label_layout)
            vertical_layout.addLayout(buttons_layout)

        general_layout = QHBoxLayout()
        general_layout.addStretch()
        general_layout.addLayout(ilayout)
        general_layout.addSpacing(15)
        general_layout.addLayout(vertical_layout)
        general_layout.addStretch()

        self.setLayout(general_layout)

        # Signals
        install_button.clicked.connect(self.sig_install_button_clicked)
        dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)

        self.setStyleSheet(
            f"background-color: {QStylePalette.COLOR_BACKGROUND_2}")
        self.setContentsMargins(18, 40, 18, 40)
        if not MAC:
            self.setFixedSize(800, 350)
class ParamSweepWidget(QDialog):
    """
    A dialog box containing a workflow finished message. Also includes a button to open the output folder
    and a button to close the dialog box

    Params:
        output_folder (Path):       The output folder to open when the corresponding button is clicked by the user.
    """
    def __init__(self, param_set: Dict[str, Any], step_number, controller):
        super().__init__()
        # Track UI elements
        self.live_count: QLabel = None
        self.progress_bar: QProgressBar = None
        self.inputs: Dict[str, QFrame] = dict()
        self.default_sweep_values: Dict = dict()
        self.layout: QVBoxLayout = QVBoxLayout()
        # State
        self.controller = controller
        self.step_number: int = step_number
        self.param_set: Dict[str, Any] = param_set
        rows: List[FormRow] = self._create_sweep_ui()

        # Format UI on init
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self._create_buttons())
        self.setLayout(Form(rows))
        self.setStyleSheet(get_stylesheet(self.controller.viewer.get_theme()))
        self.setWindowTitle("Parameter Sweep")

    def _create_sweep_ui(self) -> List[FormRow]:
        """
        Populate the sweep widget with this workflow step's parameters and their default values as well as other
        UI elements needed to run a sweep.

         Params:
            none
        """
        rows: List[FormRow] = list()
        # add ui elements in order
        rows.append(FormRow("", widget=self.create_sweep_headers()))

        # convert parameter set to form rows
        default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[
            self.step_number].function.parameters
        if self.param_set:
            for key, value in self.param_set.items():
                # sometimes multiple unique params are in one list, need to separate out for UI
                if isinstance(value, list):
                    if not isinstance(value[0], str):
                        i = 1
                        for _ in value:
                            sweep_inputs: QFrame = QFrame()
                            sweep_inputs.setLayout(QHBoxLayout())
                            # get default value
                            min_value: int = default_params[key][i -
                                                                 1].min_value
                            max_value: int = default_params[key][i -
                                                                 1].max_value
                            step_size: float = (max_value - min_value) / 2

                            # Create UI Elements and populate with default values
                            min_input: QLineEdit = QLineEdit()
                            min_input.setText(str(min_value))
                            min_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(min_input)

                            max_input: QLineEdit = QLineEdit()
                            max_input.setText(str(max_value))
                            max_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(max_input)

                            step_input = QLineEdit()
                            step_input.setText(str(step_size))
                            step_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(step_input)

                            # Reset button for row (reset to default values)
                            reset_button: QPushButton = QPushButton("reset")
                            reset_button.setStyleSheet("border: none;")
                            # pass the key and value as values for calling function later on
                            reset_button.clicked.connect(
                                partial(
                                    lambda k, val: self._reset_row_to_default(
                                        k, val), key, i))
                            sweep_inputs.layout().addWidget(reset_button)

                            # store the index of this parameter in its list appended to the parameter key
                            self.inputs[key + str(i)] = sweep_inputs
                            rows.append(
                                FormRow(f"{key} {i}", widget=sweep_inputs))
                            i = i + 1
                else:
                    # most params are single entries in the param dictionary
                    # for params that are a dropdown
                    if default_params[key][0].widget_type.name == "DROPDOWN":
                        dropdown = QComboBox()
                        dropdown.setStyleSheet(
                            "QComboBox { combobox-popup: 0; }")
                        dropdown.addItems(default_params[key][0].options)
                        self.inputs[key] = dropdown
                        rows.append(FormRow(key, widget=dropdown))
                    else:
                        # for typical sweep params
                        sweep_inputs: QFrame = QFrame()
                        sweep_inputs.setLayout(QHBoxLayout())
                        # get the default values
                        min_value: int = default_params[key][0].min_value
                        max_value: int = default_params[key][0].max_value
                        step_size: float = (max_value - min_value) / 2

                        # Create UI elements and populate with default values
                        min_input: QLineEdit = QLineEdit()
                        min_input.setText(str(min_value))
                        min_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(min_input)

                        max_input: QLineEdit = QLineEdit()
                        max_input.setText(str(max_value))
                        max_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(max_input)

                        step_input: QLineEdit = QLineEdit()
                        step_input.setText(str(step_size))
                        step_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(step_input)
                        self.inputs[key] = sweep_inputs

                        # Create button to reset back to default values
                        reset_button: QPushButton = QPushButton("reset")
                        reset_button.setStyleSheet("border: none;")
                        reset_button.clicked.connect(
                            lambda: self._reset_row_to_default(key))
                        sweep_inputs.layout().addWidget(reset_button)
                        rows.append(FormRow(key, widget=sweep_inputs))

        # Grab user input as str
        def_params_values: List[List[str]] = self.grab_ui_values(
            grab_combo=False)

        # Display how many images will be create with this sweep
        def_count: int = self.get_live_count(def_params_values)
        self.live_count = QLabel(f"{def_count} images will be created")
        self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter)

        # Create progress bar with length of sweep
        self.create_progress_bar(bar_len=def_count)

        rows.append(FormRow("", widget=self.live_count))
        rows.append(FormRow("", widget=self.progress_bar))
        rows.append(FormRow("", widget=self._create_buttons()))
        return rows

    def _create_buttons(self) -> QFrame:
        """
        Creates buttons for the bottom of the dialog box, one for opening the output folder, and one for
            closing the dialog box

        Params:
            None

        Returns:
            (QFrame): A QFrame that has two horizontally laid out buttons, first button is Open output directory,
                second button is close.
        """
        buttons: QFrame = QFrame()
        buttons.setLayout(QHBoxLayout())
        self.run_sweep_button = QPushButton("Start Sweep")
        self.run_sweep_button.setToolTip(
            "Start sweep using the selected napari layer as the input image.")
        self.run_sweep_button.clicked.connect(self._run_sweep)
        self.run_sweep_button.setAutoDefault(False)
        close_button: QPushButton = QPushButton("Cancel")
        close_button.setToolTip("Cancel an active parameter sweep.")
        close_button.clicked.connect(self.cancel)
        close_button.setAutoDefault(False)
        buttons.layout().addWidget(self.run_sweep_button)
        buttons.layout().addWidget(close_button)
        return buttons

    def _run_sweep(self) -> None:
        """
        Initiate a sweep (called when run sweep button is pressed) with the values provided in the UI

        Params:
           none
        """
        inputs: List[List[str]] = self.grab_ui_values(grab_combo=False)
        count: int = self.get_live_count(inputs)
        if count > 20:
            # warn if too many images will be generated
            if self.warn_images_created(count) == 1024:
                self.set_run_in_progress()
                self.controller.run_step_sweep(self, self.grab_ui_values())
        else:
            self.set_run_in_progress()
            self.controller.run_step_sweep(self, self.grab_ui_values())

    def sanitize_ui_inputs(self, ui_inputs: List[List[str]]) -> None:
        """
        Check that all user inputs in the UI (passed as str) can be converted to a valid float
        Params:
           ui_inputs (List[List[str]]): inputs recieved from the UI as str
        """
        for i in ui_inputs:
            for j in i:
                try:
                    # ensure user input (passed as str) is a valid number
                    float(j)
                except ValueError:
                    raise ValueError(
                        "Please enter a single number or the min:step:max notation for sweeps"
                    )

    def warn_images_created(self, count: int) -> None:
        """
        Create a popup to warn a user that they will be creating a large number of layers by running a sweep.

        Params:
           count (int): number of images that user will create for warning
        """
        message: QMessageBox = QMessageBox()
        message.setText(f"{int(count)} result image layers will be created.")
        message.setStyleSheet(
            get_stylesheet(self.controller.viewer.get_theme()))
        message.setWindowTitle("Running Sweep")
        message.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        return message.exec_()

    def update_live_count(self, count: int) -> None:
        """
        Update the Live count on the param sweep widget

        Params:
            count (int): new live count to update with
        """
        if self.live_count:
            self.live_count.setText(f"{count } images will be created")

    def get_live_count(self, inputs: List[List[str]]) -> int:
        """
        Calculate how many images will be created with the user provided values for this sweep

        Params:
            inputs (List[List[str]]): user inputs from the paramsweepwidget to calculate how many total result layers
            will be created
        """
        # make sure all user inputs are valid numbers
        self.sanitize_ui_inputs(inputs)
        length: int = 1
        for sweeps in inputs:
            # multiplying the result layers for all parameters will give total result layers
            # (ex: if param1 creates 5 combinations and param2 creates 5 combinations, there will be
            # 25 result layers created 5x5=25)
            length = length * self.get_sweep_len(float(
                sweeps[0]), float(sweeps[1]), float(sweeps[2]))
        return length

    def get_sweep_len(self, min: float, step: float, max: float) -> int:
        """
        Calculate how many result layers will be created for one parameter's sweep values

        Params:
            min (float): starting range of sweep provided by user
            step (float): increment value of sweep provided by user
            max (float): end value of sweep provided by user
        """
        i: float = min
        count: int = 0
        while i <= max:
            i = i + step
            count = count + 1
        return count

    def grab_ui_values(self, grab_combo=True) -> List[List[str]]:
        """
        Update the Live count on the param sweep widget

        Params:
            count (int): new live count to update with
        """
        inputs: List[List[str]] = list()
        for widget in self.inputs.values():
            # grab values from combobox (when calling run_sweep function)
            if grab_combo:
                # ui is min, max, step
                # transform into min, step, max
                try:
                    # is a set of numbers for sweep
                    inputs.append([
                        widget.children()[1].text(),
                        widget.children()[3].text(),
                        widget.children()[2].text()
                    ])
                except IndexError:
                    # is a combobox(string parameters)
                    inputs.append(widget.currentText())
                except AttributeError:
                    inputs.append(widget.currentText())
            else:
                # do not grab values from combobox (for checking inputs and getting size of sweeps)
                try:
                    inputs.append([
                        widget.children()[1].text(),
                        widget.children()[3].text(),
                        widget.children()[2].text()
                    ])
                except Exception:
                    pass
        return inputs

    def create_progress_bar(self, bar_len: int = 10) -> QProgressBar:
        """
        Create a progress bar with the given length

        Params:
            bar_len (int): length of progress bar to be created
        """
        self.progress_bar: QProgressBar = QProgressBar()
        self.progress_bar.setRange(0, bar_len)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(False)
        return self.progress_bar

    def update_progress_bar_len(self, new_len: int) -> None:
        """
        Update the progress bar so it has the given length

        Params:
            new_len (int): new length to update progress bar
        """
        if self.progress_bar:
            self.progress_bar.setRange(0, new_len)
            self.progress_bar.setValue(0)

    def increment_progress_bar(self) -> None:
        """
        Increment the progress bar by one step

        Params:
            none
        """
        if self.controller.run_lock:
            self.progress_bar.setValue(self.progress_bar.value() + 1)

    def reset_progress_bar(self) -> None:
        """
        Reset the progress bar to 0

        Params:
            none
        """
        self.progress_bar.setValue(0)

    def set_progress_bar(self, val: int) -> None:
        """
        Set the progress bar to the value provided

        Params:
            val: value to set the progess bar to
        """
        self.progress_bar.setValue(val)

    def create_sweep_headers(self) -> QFrame:
        """
        Create headers for the sweep UI

        Params:
            none
        """
        # headers
        header: QFrame = QFrame()
        header.setLayout(QHBoxLayout())
        label: QLabel = QLabel("min")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        label: QLabel = QLabel("max")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        label: QLabel = QLabel("step size")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        return header

    def _on_change_textbox(self) -> None:
        """
        Event listener called when textboxes in widget are edited

        Params:
            none
        """
        # grab new values
        inputs: List[List[str]] = self.grab_ui_values(grab_combo=False)
        # calculate new count
        new_count: int = self.get_live_count(inputs)
        # update ui
        self.update_live_count(new_count)
        self.update_progress_bar_len(new_count)

    def set_run_in_progress(self) -> None:
        """
        function called when a run is in progress

        Params:
            none
        """
        self.run_sweep_button.setText("Cancel")

    def set_run_finished(self) -> None:
        """
        function called when a run is finished

        Params:
            none
        """
        self.run_sweep_button.setText("Run Sweep")
        self.run_sweep_button.clicked.connect(self._run_sweep)

    def cancel(self) -> None:
        """
        Cancel the current sweep

        Params:
            none
        """
        if self.controller.run_lock:
            # if running, cancel
            self.controller.cancel_run_all()
        else:
            # if not running, close window
            self.close()

    def _reset_row_to_default(self, key_of_row: str, list_index: int = None):
        """
        Reset a parameter row to its default values

        Params:
            key_of_row (str): Name of row in ui to change back to default values
            list_index (int): index of parameter if there are multiple with the same name
        """
        default_param_set: Dict = self.controller.model.active_workflow.workflow_definition.steps[
            self.step_number].function.parameters

        if list_index:
            input_boxes: List = self.inputs[key_of_row +
                                            str(list_index)].children()
        else:
            input_boxes = self.inputs[key_of_row].children()

        if list_index:
            default_params = default_param_set[key_of_row][list_index - 1]
        else:
            default_params = default_param_set[key_of_row][0]

        # reset min value
        input_boxes[1].setText(str(default_params.min_value))
        # reset max value
        input_boxes[2].setText(str(default_params.max_value))
        # reset step_size value
        input_boxes[3].setText(
            str((default_params.max_value - default_params.min_value) / 2))

        updated_values: List[List[str]] = self.grab_ui_values(grab_combo=False)
        self.update_live_count(self.get_live_count(updated_values))
    def _create_sweep_ui(self) -> List[FormRow]:
        """
        Populate the sweep widget with this workflow step's parameters and their default values as well as other
        UI elements needed to run a sweep.

         Params:
            none
        """
        rows: List[FormRow] = list()
        # add ui elements in order
        rows.append(FormRow("", widget=self.create_sweep_headers()))

        # convert parameter set to form rows
        default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[
            self.step_number].function.parameters
        if self.param_set:
            for key, value in self.param_set.items():
                # sometimes multiple unique params are in one list, need to separate out for UI
                if isinstance(value, list):
                    if not isinstance(value[0], str):
                        i = 1
                        for _ in value:
                            sweep_inputs: QFrame = QFrame()
                            sweep_inputs.setLayout(QHBoxLayout())
                            # get default value
                            min_value: int = default_params[key][i -
                                                                 1].min_value
                            max_value: int = default_params[key][i -
                                                                 1].max_value
                            step_size: float = (max_value - min_value) / 2

                            # Create UI Elements and populate with default values
                            min_input: QLineEdit = QLineEdit()
                            min_input.setText(str(min_value))
                            min_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(min_input)

                            max_input: QLineEdit = QLineEdit()
                            max_input.setText(str(max_value))
                            max_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(max_input)

                            step_input = QLineEdit()
                            step_input.setText(str(step_size))
                            step_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(step_input)

                            # Reset button for row (reset to default values)
                            reset_button: QPushButton = QPushButton("reset")
                            reset_button.setStyleSheet("border: none;")
                            # pass the key and value as values for calling function later on
                            reset_button.clicked.connect(
                                partial(
                                    lambda k, val: self._reset_row_to_default(
                                        k, val), key, i))
                            sweep_inputs.layout().addWidget(reset_button)

                            # store the index of this parameter in its list appended to the parameter key
                            self.inputs[key + str(i)] = sweep_inputs
                            rows.append(
                                FormRow(f"{key} {i}", widget=sweep_inputs))
                            i = i + 1
                else:
                    # most params are single entries in the param dictionary
                    # for params that are a dropdown
                    if default_params[key][0].widget_type.name == "DROPDOWN":
                        dropdown = QComboBox()
                        dropdown.setStyleSheet(
                            "QComboBox { combobox-popup: 0; }")
                        dropdown.addItems(default_params[key][0].options)
                        self.inputs[key] = dropdown
                        rows.append(FormRow(key, widget=dropdown))
                    else:
                        # for typical sweep params
                        sweep_inputs: QFrame = QFrame()
                        sweep_inputs.setLayout(QHBoxLayout())
                        # get the default values
                        min_value: int = default_params[key][0].min_value
                        max_value: int = default_params[key][0].max_value
                        step_size: float = (max_value - min_value) / 2

                        # Create UI elements and populate with default values
                        min_input: QLineEdit = QLineEdit()
                        min_input.setText(str(min_value))
                        min_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(min_input)

                        max_input: QLineEdit = QLineEdit()
                        max_input.setText(str(max_value))
                        max_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(max_input)

                        step_input: QLineEdit = QLineEdit()
                        step_input.setText(str(step_size))
                        step_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(step_input)
                        self.inputs[key] = sweep_inputs

                        # Create button to reset back to default values
                        reset_button: QPushButton = QPushButton("reset")
                        reset_button.setStyleSheet("border: none;")
                        reset_button.clicked.connect(
                            lambda: self._reset_row_to_default(key))
                        sweep_inputs.layout().addWidget(reset_button)
                        rows.append(FormRow(key, widget=sweep_inputs))

        # Grab user input as str
        def_params_values: List[List[str]] = self.grab_ui_values(
            grab_combo=False)

        # Display how many images will be create with this sweep
        def_count: int = self.get_live_count(def_params_values)
        self.live_count = QLabel(f"{def_count} images will be created")
        self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter)

        # Create progress bar with length of sweep
        self.create_progress_bar(bar_len=def_count)

        rows.append(FormRow("", widget=self.live_count))
        rows.append(FormRow("", widget=self.progress_bar))
        rows.append(FormRow("", widget=self._create_buttons()))
        return rows
Example #36
0
class MainWindow(QMainWindow):
    """MNELAB main window."""
    def __init__(self, model):
        """Initialize MNELAB main window.

        Parameters
        ----------
        model : mnelab.model.Model instance
            The main window needs to connect to a model containing all data
            sets. This decouples the GUI from the data (model/view).
        """
        super().__init__()
        self.model = model  # data model
        self.setWindowTitle("MNELAB")

        # restore settings
        settings = read_settings()
        self.recent = settings["recent"]  # list of recent files
        self.resize(settings["size"])
        self.move(settings["pos"])

        # trigger theme setting
        QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")])
        self.event(QEvent(QEvent.PaletteChange))

        self.actions = {}  # contains all actions

        # initialize menus
        file_menu = self.menuBar().addMenu("&File")
        icon = QIcon.fromTheme("open-file")
        self.actions["open_file"] = file_menu.addAction(
            icon, "&Open...", self.open_data, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.actions["close_file"] = file_menu.addAction(
            "&Close", self.model.remove_data, QKeySequence.Close)
        self.actions["close_all"] = file_menu.addAction(
            "Close all", self.close_all)
        file_menu.addSeparator()
        icon = QIcon.fromTheme("meta-info")
        self.actions["meta_info"] = file_menu.addAction(
            icon, "Show information...", self.meta_info)
        file_menu.addSeparator()
        self.actions["import_bads"] = file_menu.addAction(
            "Import bad channels...", lambda: self.import_file(
                model.import_bads, "Import bad channels", "*.csv"))
        self.actions["import_events"] = file_menu.addAction(
            "Import events...", lambda: self.import_file(
                model.import_events, "Import events", "*.csv"))
        self.actions["import_annotations"] = file_menu.addAction(
            "Import annotations...", lambda: self.import_file(
                model.import_annotations, "Import annotations", "*.csv"))
        self.actions["import_ica"] = file_menu.addAction(
            "Import &ICA...", lambda: self.open_file(
                model.import_ica, "Import ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.export_menu = file_menu.addMenu("Export data")
        for ext, description in writers.items():
            action = "export_data" + ext.replace(".", "_")
            self.actions[action] = self.export_menu.addAction(
                f"{ext[1:].upper()} ({description[1]})...",
                partial(self.export_file, model.export_data, "Export data",
                        ext))
        self.actions["export_bads"] = file_menu.addAction(
            "Export &bad channels...", lambda: self.export_file(
                model.export_bads, "Export bad channels", "*.csv"))
        self.actions["export_events"] = file_menu.addAction(
            "Export &events...", lambda: self.export_file(
                model.export_events, "Export events", "*.csv"))
        self.actions["export_annotations"] = file_menu.addAction(
            "Export &annotations...", lambda: self.export_file(
                model.export_annotations, "Export annotations", "*.csv"))
        self.actions["export_ica"] = file_menu.addAction(
            "Export ICA...", lambda: self.export_file(
                model.export_ica, "Export ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.actions["quit"] = file_menu.addAction("&Quit", self.close,
                                                   QKeySequence.Quit)

        edit_menu = self.menuBar().addMenu("&Edit")
        self.actions["pick_chans"] = edit_menu.addAction(
            "P&ick channels...", self.pick_channels)
        icon = QIcon.fromTheme("chan-props")
        self.actions["chan_props"] = edit_menu.addAction(
            icon, "Channel &properties...", self.channel_properties)
        self.actions["set_montage"] = edit_menu.addAction(
            "Set &montage...", self.set_montage)
        edit_menu.addSeparator()
        self.actions["set_ref"] = edit_menu.addAction("Set &reference...",
                                                      self.set_reference)
        edit_menu.addSeparator()
        self.actions["annotations"] = edit_menu.addAction(
            "&Annotations...", self.edit_annotations)
        self.actions["events"] = edit_menu.addAction("&Events...",
                                                     self.edit_events)

        edit_menu.addSeparator()
        self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop)
        self.actions["append_data"] = edit_menu.addAction(
            "Appen&d data...", self.append_data)

        plot_menu = self.menuBar().addMenu("&Plot")
        icon = QIcon.fromTheme("plot-data")
        self.actions["plot_data"] = plot_menu.addAction(
            icon, "&Data...", self.plot_data)
        icon = QIcon.fromTheme("plot-psd")
        self.actions["plot_psd"] = plot_menu.addAction(
            icon, "&Power spectral density...", self.plot_psd)
        icon = QIcon.fromTheme("plot-locations")
        self.actions["plot_locations"] = plot_menu.addAction(
            icon, "&Channel locations...", self.plot_locations)
        self.actions["plot_erds"] = plot_menu.addAction(
            "&ERDS maps...", self.plot_erds)
        plot_menu.addSeparator()
        self.actions["plot_ica_components"] = plot_menu.addAction(
            "ICA &components...", self.plot_ica_components)
        self.actions["plot_ica_sources"] = plot_menu.addAction(
            "ICA &sources...", self.plot_ica_sources)

        tools_menu = self.menuBar().addMenu("&Tools")
        icon = QIcon.fromTheme("filter-data")
        self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...",
                                                      self.filter_data)
        icon = QIcon.fromTheme("find-events")
        self.actions["find_events"] = tools_menu.addAction(
            icon, "Find &events...", self.find_events)
        self.actions["events_from_annotations"] = tools_menu.addAction(
            "Create events from annotations", self.events_from_annotations)
        self.actions["annotations_from_events"] = tools_menu.addAction(
            "Create annotations from events", self.annotations_from_events)
        tools_menu.addSeparator()
        nirs_menu = tools_menu.addMenu("NIRS")
        self.actions["convert_od"] = nirs_menu.addAction(
            "Convert to &optical density", self.convert_od)
        self.actions["convert_bl"] = nirs_menu.addAction(
            "Convert to &haemoglobin", self.convert_bl)

        tools_menu.addSeparator()
        icon = QIcon.fromTheme("run-ica")
        self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...",
                                                       self.run_ica)
        self.actions["apply_ica"] = tools_menu.addAction(
            "Apply &ICA", self.apply_ica)
        tools_menu.addSeparator()
        self.actions["interpolate_bads"] = tools_menu.addAction(
            "Interpolate bad channels...", self.interpolate_bads)
        tools_menu.addSeparator()
        icon = QIcon.fromTheme("epoch-data")
        self.actions["epoch_data"] = tools_menu.addAction(
            icon, "Create Epochs...", self.epoch_data)

        view_menu = self.menuBar().addMenu("&View")
        self.actions["history"] = view_menu.addAction("&History...",
                                                      self.show_history)
        self.actions["toolbar"] = view_menu.addAction("&Toolbar",
                                                      self._toggle_toolbar)
        self.actions["toolbar"].setCheckable(True)
        self.actions["statusbar"] = view_menu.addAction(
            "&Statusbar", self._toggle_statusbar)
        self.actions["statusbar"].setCheckable(True)

        help_menu = self.menuBar().addMenu("&Help")
        self.actions["about"] = help_menu.addAction("&About", self.show_about)
        self.actions["about_qt"] = help_menu.addAction("About &Qt",
                                                       self.show_about_qt)

        # actions that are always enabled
        self.always_enabled = [
            "open_file", "about", "about_qt", "quit", "toolbar", "statusbar"
        ]

        # set up toolbar
        self.toolbar = self.addToolBar("toolbar")
        self.toolbar.setObjectName("toolbar")
        self.toolbar.addAction(self.actions["open_file"])
        self.toolbar.addAction(self.actions["meta_info"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["chan_props"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["plot_data"])
        self.toolbar.addAction(self.actions["plot_psd"])
        self.toolbar.addAction(self.actions["plot_locations"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["filter"])
        self.toolbar.addAction(self.actions["find_events"])
        self.toolbar.addAction(self.actions["epoch_data"])
        self.toolbar.addAction(self.actions["run_ica"])

        self.setUnifiedTitleAndToolBarOnMac(True)
        if settings["toolbar"]:
            self.toolbar.show()
            self.actions["toolbar"].setChecked(True)
        else:
            self.toolbar.hide()
            self.actions["toolbar"].setChecked(False)

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((int(width * 0.3), int(width * 0.7)))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            self.actions["statusbar"].setChecked(True)
        else:
            self.statusBar().hide()
            self.actions["statusbar"].setChecked(False)

        self.setAcceptDrops(True)
        self.data_changed()

    def data_changed(self):
        # update sidebar
        self.names.setStringList(self.model.names)
        self.sidebar.setCurrentIndex(self.names.index(self.model.index))

        # update info widget
        if self.model.data:
            self.infowidget.set_values(self.model.get_info())
        else:
            self.infowidget.clear()

        # update status bar
        if self.model.data:
            mb = self.model.nbytes / 1024**2
            self.status_label.setText(f"Total Memory: {mb:.2f} MB")
        else:
            self.status_label.clear()

        # toggle actions
        if len(self.model) == 0:  # disable if no data sets are currently open
            enabled = False
        else:
            enabled = True

        for name, action in self.actions.items():  # toggle
            if name not in self.always_enabled:
                action.setEnabled(enabled)

        if self.model.data:  # toggle if specific conditions are met
            bads = bool(self.model.current["data"].info["bads"])
            self.actions["export_bads"].setEnabled(enabled and bads)
            events = self.model.current["events"] is not None
            self.actions["export_events"].setEnabled(enabled and events)
            if self.model.current["dtype"] == "raw":
                annot = bool(self.model.current["data"].annotations)
            else:
                annot = False
            self.actions["export_annotations"].setEnabled(enabled and annot)
            self.actions["annotations"].setEnabled(enabled and annot)
            locations = has_locations(self.model.current["data"].info)
            self.actions["plot_locations"].setEnabled(enabled and locations)
            ica = bool(self.model.current["ica"])
            self.actions["apply_ica"].setEnabled(enabled and ica)
            self.actions["export_ica"].setEnabled(enabled and ica)
            self.actions["plot_erds"].setEnabled(
                enabled and self.model.current["dtype"] == "epochs")
            self.actions["plot_ica_components"].setEnabled(enabled and ica
                                                           and locations)
            self.actions["plot_ica_sources"].setEnabled(enabled and ica)
            self.actions["interpolate_bads"].setEnabled(enabled and locations
                                                        and bads)
            self.actions["events"].setEnabled(enabled and events)
            self.actions["events_from_annotations"].setEnabled(enabled
                                                               and annot)
            self.actions["annotations_from_events"].setEnabled(enabled
                                                               and events)
            self.actions["find_events"].setEnabled(
                enabled and self.model.current["dtype"] == "raw")
            self.actions["epoch_data"].setEnabled(
                enabled and events and self.model.current["dtype"] == "raw")
            self.actions["crop"].setEnabled(
                enabled and self.model.current["dtype"] == "raw")
            append = bool(self.model.get_compatibles())
            self.actions["append_data"].setEnabled(
                enabled and append
                and (self.model.current["dtype"] in ("raw", "epochs")))
            self.actions["meta_info"].setEnabled(
                enabled
                and self.model.current["ftype"] in ["XDF", "XDFZ", "XDF.GZ"])
            self.actions["convert_od"].setEnabled(
                len(
                    mne.pick_types(self.model.current["data"].info,
                                   fnirs="fnirs_raw")))
            self.actions["convert_bl"].setEnabled(
                len(
                    mne.pick_types(self.model.current["data"].info,
                                   fnirs="fnirs_od")))
            # disable unsupported exporters for epochs (all must support raw)
            if self.model.current["dtype"] == "epochs":
                for ext, description in writers.items():
                    action = "export_data" + ext.replace(".", "_")
                    if "epoch" in description[2]:
                        self.actions[action].setEnabled(True)
                    else:
                        self.actions[action].setEnabled(False)
        # add to recent files
        if len(self.model) > 0:
            self._add_recent(self.model.current["fname"])

    def open_data(self, fname=None):
        """Open raw file."""
        if fname is None:
            fname = QFileDialog.getOpenFileName(self, "Open raw")[0]
        if fname:
            if not (Path(fname).is_file() or Path(fname).is_dir()):
                self._remove_recent(fname)
                QMessageBox.critical(self, "File does not exist",
                                     f"File {fname} does not exist anymore.")
                return

            ext = "".join(Path(fname).suffixes)

            if ext in [".xdf", ".xdfz", ".xdf.gz"]:
                rows, disabled = [], []
                for idx, s in enumerate(get_streams(fname)):
                    rows.append([
                        s["stream_id"], s["name"], s["type"],
                        s["channel_count"], s["channel_format"],
                        s["nominal_srate"]
                    ])
                    is_marker = (s["nominal_srate"] == 0
                                 or s["channel_format"] == "string")
                    if is_marker:  # disable marker streams
                        disabled.append(idx)

                enabled = list(set(range(len(rows))) - set(disabled))
                if enabled:
                    selected = enabled[0]
                else:
                    selected = None
                dialog = XDFStreamsDialog(self,
                                          rows,
                                          selected=selected,
                                          disabled=disabled)
                if dialog.exec_():
                    row = dialog.view.selectionModel().selectedRows()[0].row()
                    stream_id = dialog.model.data(dialog.model.index(row, 0))
                    self.model.load(fname, stream_id=stream_id)
            else:  # all other file formats
                try:
                    self.model.load(fname)
                except FileNotFoundError as e:
                    QMessageBox.critical(self, "File not found", str(e))
                except ValueError as e:
                    QMessageBox.critical(self, "Unknown file type", str(e))

    def open_file(self, f, text, ffilter="*"):
        """Open file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            f(fname)

    def export_file(self, f, text, ffilter="*"):
        """Export to file."""
        fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0]
        if fname:
            try:
                f(fname, ffilter)
            except TypeError:
                f(fname)

    def import_file(self, f, text, ffilter="*"):
        """Import file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            try:
                f(fname)
            except LabelsNotFoundError as e:
                QMessageBox.critical(self, "Channel labels not found", str(e))
            except InvalidAnnotationsError as e:
                QMessageBox.critical(self, "Invalid annotations", str(e))

    def close_all(self):
        """Close all currently open data sets."""
        msg = QMessageBox.question(self, "Close all data sets",
                                   "Close all data sets?")
        if msg == QMessageBox.Yes:
            while len(self.model) > 0:
                self.model.remove_data()

    def meta_info(self):
        """Show XDF meta info."""
        xml = get_xml(self.model.current["fname"])
        dialog = MetaInfoDialog(self, xml)
        dialog.exec_()

    def pick_channels(self):
        """Pick channels in current data set."""
        channels = self.model.current["data"].info["ch_names"]
        dialog = PickChannelsDialog(self, channels, selected=channels)
        if dialog.exec_():
            picks = [item.data(0) for item in dialog.channels.selectedItems()]
            drops = set(channels) - set(picks)
            if drops:
                self.auto_duplicate()
                self.model.drop_channels(drops)
                self.model.history.append(f"raw.drop({drops})")

    def channel_properties(self):
        """Show channel properties dialog."""
        info = self.model.current["data"].info
        dialog = ChannelPropertiesDialog(self, info)
        if dialog.exec_():
            dialog.model.sort(0)
            bads = []
            renamed = {}
            types = {}
            for i in range(dialog.model.rowCount()):
                new_label = dialog.model.item(i, 1).data(Qt.DisplayRole)
                old_label = info["ch_names"][i]
                if new_label != old_label:
                    renamed[old_label] = new_label
                new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower()
                old_type = channel_type(info, i).lower()
                if new_type != old_type:
                    types[new_label] = new_type
                if dialog.model.item(i, 3).checkState() == Qt.Checked:
                    bads.append(info["ch_names"][i])
            self.model.set_channel_properties(bads, renamed, types)

    def set_montage(self):
        """Set montage."""
        montages = mne.channels.get_builtin_montages()
        # TODO: currently it is not possible to remove an existing montage
        dialog = MontageDialog(self, montages)
        if dialog.exec_():
            name = dialog.montages.selectedItems()[0].data(0)
            montage = mne.channels.make_standard_montage(name)
            ch_names = self.model.current["data"].info["ch_names"]
            # check if at least one channel name matches a name in the montage
            if set(ch_names) & set(montage.ch_names):
                self.model.set_montage(name)
            else:
                QMessageBox.critical(
                    self, "No matching channel names",
                    "Channel names defined in the montage do "
                    "not match any channel name in the data.")

    def edit_annotations(self):
        fs = self.model.current["data"].info["sfreq"]
        pos = self.model.current["data"].annotations.onset
        pos = (pos * fs).astype(int).tolist()
        dur = self.model.current["data"].annotations.duration
        dur = (dur * fs).astype(int).tolist()
        desc = self.model.current["data"].annotations.description.tolist()
        dialog = AnnotationsDialog(self, pos, dur, desc)
        if dialog.exec_():
            rows = dialog.table.rowCount()
            onset, duration, description = [], [], []
            for i in range(rows):
                data = dialog.table.item(i, 0).data(Qt.DisplayRole)
                onset.append(float(data) / fs)
                data = dialog.table.item(i, 1).data(Qt.DisplayRole)
                duration.append(float(data) / fs)
                data = dialog.table.item(i, 2).data(Qt.DisplayRole)
                description.append(data)
            self.model.set_annotations(onset, duration, description)

    def edit_events(self):
        pos = self.model.current["events"][:, 0].tolist()
        desc = self.model.current["events"][:, 2].tolist()
        dialog = EventsDialog(self, pos, desc)
        if dialog.exec_():
            rows = dialog.table.rowCount()
            events = np.zeros((rows, 3), dtype=int)
            for i in range(rows):
                pos = int(dialog.table.item(i, 0).data(Qt.DisplayRole))
                desc = int(dialog.table.item(i, 1).data(Qt.DisplayRole))
                events[i] = pos, 0, desc
            self.model.set_events(events)

    def crop(self):
        """Crop data."""
        fs = self.model.current["data"].info["sfreq"]
        length = self.model.current["data"].n_times / fs
        dialog = CropDialog(self, 0, length)
        if dialog.exec_():
            self.auto_duplicate()
            self.model.crop(dialog.start or 0, dialog.stop)

    def append_data(self):
        """Concatenate raw data objects to current one."""
        compatibles = self.model.get_compatibles()
        dialog = AppendDialog(self, compatibles)
        if dialog.exec_():
            self.auto_duplicate()
            self.model.append_data(dialog.names)

    def plot_data(self):
        """Plot data."""
        # self.bad is needed to update history if bad channels are selected in
        # the interactive plot window (see also self.eventFilter)
        self.bads = self.model.current["data"].info["bads"]
        events = self.model.current["events"]
        nchan = self.model.current["data"].info["nchan"]
        fig = self.model.current["data"].plot(events=events,
                                              n_channels=nchan,
                                              title=self.model.current["name"],
                                              scalings="auto",
                                              show=False)
        if events is not None:
            hist = f"data.plot(events=events, n_channels={nchan})"
        else:
            hist = f"data.plot(n_channels={nchan})"
        self.model.history.append(hist)
        win = fig.canvas.manager.window
        win.setWindowTitle(self.model.current["name"])
        win.statusBar().hide()  # not necessary since matplotlib 3.3
        win.installEventFilter(self)  # detect if the figure is closed

        # prevent closing the window with the escape key
        try:
            fig._mne_params["close_key"] = None
        except AttributeError:  # does not exist in older MNE versions
            pass

        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD)."""
        kwds = {}
        if self.model.current["dtype"] == "raw":
            kwds.update({"average": False, "spatial_colors": False})
        fig = self.model.current["data"].plot_psd(show=False, **kwds)
        if kwds:
            tmp = ", ".join(f"{key}={value}" for key, value in kwds.items())
            hist = f"data.plot_psd({tmp})"
        else:
            hist = "data.plot_psd()"
        self.model.history.append(hist)
        win = fig.canvas.manager.window
        win.setWindowTitle("Power spectral density")
        fig.show()

    def plot_locations(self):
        """Plot current montage."""
        fig = self.model.current["data"].plot_sensors(show_names=True,
                                                      show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Montage")
        win.statusBar().hide()  # not necessary since matplotlib 3.3
        fig.show()

    def plot_ica_components(self):
        self.model.current["ica"].plot_components(
            inst=self.model.current["data"])

    def plot_ica_sources(self):
        self.model.current["ica"].plot_sources(inst=self.model.current["data"])

    def plot_erds(self):
        """Plot ERDS maps."""
        data = self.model.current["data"]
        t_range = [data.tmin, data.tmax]
        f_range = [1, data.info["sfreq"] / 2]

        dialog = ERDSDialog(self, t_range, f_range)

        if dialog.exec_():
            freqs = np.arange(dialog.f1, dialog.f2, dialog.step)
            baseline = [dialog.b1, dialog.b2]
            times = [dialog.t1, dialog.t2]
            figs = plot_erds(data, freqs, freqs, baseline, times)
            for fig in figs:
                fig.show()

    def run_ica(self):
        """Run ICA calculation."""

        methods = ["Infomax"]
        if have["picard"]:
            methods.insert(0, "Picard")
        if have["sklearn"]:
            methods.append("FastICA")

        dialog = RunICADialog(self, self.model.current["data"].info["nchan"],
                              methods)

        if dialog.exec_():
            calc = CalcDialog(self, "Calculating ICA", "Calculating ICA.")

            method = dialog.method.currentText().lower()
            exclude_bad_segments = dialog.exclude_bad_segments.isChecked()

            fit_params = {}
            if dialog.extended.isEnabled():
                fit_params["extended"] = dialog.extended.isChecked()
            if dialog.ortho.isEnabled():
                fit_params["ortho"] = dialog.ortho.isChecked()

            ica = mne.preprocessing.ICA(method=method, fit_params=fit_params)
            history = f"ica = mne.preprocessing.ICA(method='{method}'"
            if fit_params:
                history += f", fit_params={fit_params})"
            else:
                history += ")"
            self.model.history.append(history)

            pool = mp.Pool(processes=1)
            res = pool.apply_async(
                func=ica.fit,
                args=(self.model.current["data"], ),
                kwds={"reject_by_annotation": exclude_bad_segments},
                callback=lambda x: calc.accept())
            pool.close()

            if not calc.exec_():
                pool.terminate()
            else:
                self.model.current["ica"] = res.get(timeout=1)
                self.model.history.append(f"ica.fit(inst=raw, "
                                          f"reject_by_annotation="
                                          f"{exclude_bad_segments})")
                self.data_changed()

    def apply_ica(self):
        """Apply current fitted ICA."""
        self.auto_duplicate()
        self.model.apply_ica()

    def interpolate_bads(self):
        """Interpolate bad channels"""
        dialog = InterpolateBadsDialog(self)
        if dialog.exec_():
            duplicated = self.auto_duplicate()
            try:
                self.model.interpolate_bads(dialog.reset_bads, dialog.mode,
                                            dialog.origin)
            except ValueError as e:
                if duplicated:  # undo
                    self.model.remove_data()
                    self.model.index -= 1
                    self.data_changed()
                msgbox = ErrorMessageBox(self,
                                         "Could not interpolate bad channels",
                                         str(e), traceback.format_exc())
                msgbox.show()

    def filter_data(self):
        """Filter data."""
        dialog = FilterDialog(self)
        if dialog.exec_():
            self.auto_duplicate()
            self.model.filter(dialog.low, dialog.high)

    def find_events(self):
        info = self.model.current["data"].info

        # use first stim channel as default in dialog
        default_stim = 0
        for i in range(info["nchan"]):
            if mne.io.pick.channel_type(info, i) == "stim":
                default_stim = i
                break
        dialog = FindEventsDialog(self, info["ch_names"], default_stim)
        if dialog.exec_():
            stim_channel = dialog.stimchan.currentText()
            consecutive = dialog.consecutive.isChecked()
            initial_event = dialog.initial_event.isChecked()
            uint_cast = dialog.uint_cast.isChecked()
            min_dur = dialog.minduredit.value()
            shortest_event = dialog.shortesteventedit.value()
            self.model.find_events(stim_channel=stim_channel,
                                   consecutive=consecutive,
                                   initial_event=initial_event,
                                   uint_cast=uint_cast,
                                   min_duration=min_dur,
                                   shortest_event=shortest_event)

    def events_from_annotations(self):
        self.model.events_from_annotations()

    def annotations_from_events(self):
        self.model.annotations_from_events()

    def epoch_data(self):
        """Epoch raw data."""
        dialog = EpochDialog(self, self.model.current["events"])
        if dialog.exec_():
            events = [
                int(item.text()) for item in dialog.events.selectedItems()
            ]
            tmin = dialog.tmin.value()
            tmax = dialog.tmax.value()

            if dialog.baseline.isChecked():
                baseline = dialog.a.value(), dialog.b.value()
            else:
                baseline = None

            duplicated = self.auto_duplicate()
            try:
                self.model.epoch_data(events, tmin, tmax, baseline)
            except ValueError as e:
                if duplicated:  # undo
                    self.model.remove_data()
                    self.model.index -= 1
                    self.data_changed()
                msgbox = ErrorMessageBox(self, "Could not create epochs",
                                         str(e), traceback.format_exc())
                msgbox.show()

    def convert_od(self):
        """Convert to optical density."""
        self.auto_duplicate()
        self.model.convert_od()

    def convert_bl(self):
        """Convert to haemoglobin."""
        self.auto_duplicate()
        self.model.convert_beer_lambert()

    def set_reference(self):
        """Set reference."""
        dialog = ReferenceDialog(self)
        if dialog.exec_():
            self.auto_duplicate()
            if dialog.average.isChecked():
                self.model.set_reference("average")
            else:
                ref = [c.strip() for c in dialog.channellist.text().split(",")]
                self.model.set_reference(ref)

    def show_history(self):
        """Show history."""
        dialog = HistoryDialog(self, "\n".join(self.model.history))
        dialog.exec_()

    def show_about(self):
        """Show About dialog."""
        msg_box = QMessageBox(self)
        text = (f"<img src='{image_path('mnelab_logo.png')}'>"
                f"<p>MNELAB {__version__}</p>")
        msg_box.setText(text)

        mnelab_url = "github.com/cbrnr/mnelab"
        mne_url = "github.com/mne-tools/mne-python"

        pkgs = []
        for key, value in have.items():
            if value:
                pkgs.append(f"{key}&nbsp;({value})")
            else:
                pkgs.append(f"{key}&nbsp;(not installed)")

        text = (f'<nobr><p>This program uses Python '
                f'{".".join(str(k) for k in version_info[:3])} and the '
                f'following packages:</p></nobr>'
                f'<p>{", ".join(pkgs)}</p>'
                f'<nobr><p>MNELAB repository: '
                f'<a href=https://{mnelab_url}>{mnelab_url}</a></p></nobr>'
                f'<nobr><p>MNE repository: '
                f'<a href=https://{mne_url}>{mne_url}</a></p></nobr>'
                f'<p>Licensed under the BSD 3-clause license.</p>'
                f'<p>Copyright 2017-2020 by Clemens Brunner.</p>')
        msg_box.setInformativeText(text)
        msg_box.exec_()

    def show_about_qt(self):
        """Show About Qt dialog."""
        QMessageBox.aboutQt(self, "About Qt")

    def auto_duplicate(self):
        """Automatically duplicate current data set.

        If the current data set is stored in a file (i.e. was loaded directly
        from a file), a new data set is automatically created. If the current
        data set is not stored in a file (i.e. was created by operations in
        MNELAB), a dialog box asks the user if the current data set should be
        overwritten or duplicated.

        Returns
        -------
        duplicated : bool
            True if the current data set was automatically duplicated, False if
            the current data set was overwritten.
        """
        # if current data is stored in a file create a new data set
        if self.model.current["fname"]:
            self.model.duplicate_data()
            return True
        # otherwise ask the user
        else:
            msg = QMessageBox.question(self, "Overwrite existing data set",
                                       "Overwrite existing data set?")
            if msg == QMessageBox.No:  # create new data set
                self.model.duplicate_data()
                return True
        return False

    def _add_recent(self, fname):
        """Add a file to recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > MAX_RECENT:  # prune list
            self.recent.pop()
        write_settings(recent=self.recent)
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _remove_recent(self, fname):
        """Remove file from recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:
            self.recent.remove(fname)
            write_settings(recent=self.recent)
            if not self.recent:
                self.recent_menu.setEnabled(False)

    @Slot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.

        Parameters
        ----------
        selected : QModelIndex
            Index of the selected row.
        """
        if selected.row() != self.model.index:
            self.model.index = selected.row()
            self.data_changed()
            self.model.history.append(f"data = datasets[{self.model.index}]")

    @Slot(QModelIndex, QModelIndex)
    def _update_names(self, start, stop):
        """Update names in DataSets after changes in sidebar."""
        for index in range(start.row(), stop.row() + 1):
            self.model.data[index]["name"] = self.names.stringList()[index]

    @Slot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @Slot(QAction)
    def _load_recent(self, action):
        self.open_data(fname=action.text())

    @Slot()
    def _toggle_toolbar(self):
        if self.toolbar.isHidden():
            self.toolbar.show()
        else:
            self.toolbar.hide()
        write_settings(toolbar=not self.toolbar.isHidden())

    @Slot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        write_settings(statusbar=not self.statusBar().isHidden())

    @Slot(QDropEvent)
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    @Slot(QDropEvent)
    def dropEvent(self, event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                try:
                    self.open_data(url.toLocalFile())
                except FileNotFoundError as e:
                    QMessageBox.critical(self, "File not found", str(e))

    @Slot(QEvent)
    def closeEvent(self, event):
        """Close application.

        Parameters
        ----------
        event : QEvent
            Close event.
        """
        write_settings(size=self.size(), pos=self.pos())
        if self.model.history:
            print("\nCommand History")
            print("===============")
            print("\n".join(self.model.history))
        QApplication.quit()

    def eventFilter(self, source, event):
        # currently the only source is the raw plot window
        if event.type() == QEvent.Close:
            self.data_changed()
            bads = self.model.current["data"].info["bads"]
            if self.bads != bads:
                self.model.history.append(f"data.info['bads'] = {bads}")
        return QObject.eventFilter(self, source, event)

    def event(self, ev):
        """Catch system events."""
        if ev.type() == QEvent.PaletteChange:  # detect theme switches
            style = interface_style()  # light or dark
            if style is not None:
                QIcon.setThemeName(style)
            else:
                QIcon.setThemeName("light")  # fallback
        return super().event(ev)
Example #37
0
    def __init__(self, model):
        """Initialize MNELAB main window.

        Parameters
        ----------
        model : mnelab.model.Model instance
            The main window needs to connect to a model containing all data
            sets. This decouples the GUI from the data (model/view).
        """
        super().__init__()
        self.model = model  # data model
        self.setWindowTitle("MNELAB")

        # restore settings
        settings = read_settings()
        self.recent = settings["recent"]  # list of recent files
        self.resize(settings["size"])
        self.move(settings["pos"])

        # trigger theme setting
        QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")])
        self.event(QEvent(QEvent.PaletteChange))

        self.actions = {}  # contains all actions

        # initialize menus
        file_menu = self.menuBar().addMenu("&File")
        icon = QIcon.fromTheme("open-file")
        self.actions["open_file"] = file_menu.addAction(
            icon, "&Open...", self.open_data, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.actions["close_file"] = file_menu.addAction(
            "&Close", self.model.remove_data, QKeySequence.Close)
        self.actions["close_all"] = file_menu.addAction(
            "Close all", self.close_all)
        file_menu.addSeparator()
        icon = QIcon.fromTheme("meta-info")
        self.actions["meta_info"] = file_menu.addAction(
            icon, "Show information...", self.meta_info)
        file_menu.addSeparator()
        self.actions["import_bads"] = file_menu.addAction(
            "Import bad channels...", lambda: self.import_file(
                model.import_bads, "Import bad channels", "*.csv"))
        self.actions["import_events"] = file_menu.addAction(
            "Import events...", lambda: self.import_file(
                model.import_events, "Import events", "*.csv"))
        self.actions["import_annotations"] = file_menu.addAction(
            "Import annotations...", lambda: self.import_file(
                model.import_annotations, "Import annotations", "*.csv"))
        self.actions["import_ica"] = file_menu.addAction(
            "Import &ICA...", lambda: self.open_file(
                model.import_ica, "Import ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.export_menu = file_menu.addMenu("Export data")
        for ext, description in writers.items():
            action = "export_data" + ext.replace(".", "_")
            self.actions[action] = self.export_menu.addAction(
                f"{ext[1:].upper()} ({description[1]})...",
                partial(self.export_file, model.export_data, "Export data",
                        ext))
        self.actions["export_bads"] = file_menu.addAction(
            "Export &bad channels...", lambda: self.export_file(
                model.export_bads, "Export bad channels", "*.csv"))
        self.actions["export_events"] = file_menu.addAction(
            "Export &events...", lambda: self.export_file(
                model.export_events, "Export events", "*.csv"))
        self.actions["export_annotations"] = file_menu.addAction(
            "Export &annotations...", lambda: self.export_file(
                model.export_annotations, "Export annotations", "*.csv"))
        self.actions["export_ica"] = file_menu.addAction(
            "Export ICA...", lambda: self.export_file(
                model.export_ica, "Export ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.actions["quit"] = file_menu.addAction("&Quit", self.close,
                                                   QKeySequence.Quit)

        edit_menu = self.menuBar().addMenu("&Edit")
        self.actions["pick_chans"] = edit_menu.addAction(
            "P&ick channels...", self.pick_channels)
        icon = QIcon.fromTheme("chan-props")
        self.actions["chan_props"] = edit_menu.addAction(
            icon, "Channel &properties...", self.channel_properties)
        self.actions["set_montage"] = edit_menu.addAction(
            "Set &montage...", self.set_montage)
        edit_menu.addSeparator()
        self.actions["set_ref"] = edit_menu.addAction("Set &reference...",
                                                      self.set_reference)
        edit_menu.addSeparator()
        self.actions["annotations"] = edit_menu.addAction(
            "&Annotations...", self.edit_annotations)
        self.actions["events"] = edit_menu.addAction("&Events...",
                                                     self.edit_events)

        edit_menu.addSeparator()
        self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop)
        self.actions["append_data"] = edit_menu.addAction(
            "Appen&d data...", self.append_data)

        plot_menu = self.menuBar().addMenu("&Plot")
        icon = QIcon.fromTheme("plot-data")
        self.actions["plot_data"] = plot_menu.addAction(
            icon, "&Data...", self.plot_data)
        icon = QIcon.fromTheme("plot-psd")
        self.actions["plot_psd"] = plot_menu.addAction(
            icon, "&Power spectral density...", self.plot_psd)
        icon = QIcon.fromTheme("plot-locations")
        self.actions["plot_locations"] = plot_menu.addAction(
            icon, "&Channel locations...", self.plot_locations)
        self.actions["plot_erds"] = plot_menu.addAction(
            "&ERDS maps...", self.plot_erds)
        plot_menu.addSeparator()
        self.actions["plot_ica_components"] = plot_menu.addAction(
            "ICA &components...", self.plot_ica_components)
        self.actions["plot_ica_sources"] = plot_menu.addAction(
            "ICA &sources...", self.plot_ica_sources)

        tools_menu = self.menuBar().addMenu("&Tools")
        icon = QIcon.fromTheme("filter-data")
        self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...",
                                                      self.filter_data)
        icon = QIcon.fromTheme("find-events")
        self.actions["find_events"] = tools_menu.addAction(
            icon, "Find &events...", self.find_events)
        self.actions["events_from_annotations"] = tools_menu.addAction(
            "Create events from annotations", self.events_from_annotations)
        self.actions["annotations_from_events"] = tools_menu.addAction(
            "Create annotations from events", self.annotations_from_events)
        tools_menu.addSeparator()
        nirs_menu = tools_menu.addMenu("NIRS")
        self.actions["convert_od"] = nirs_menu.addAction(
            "Convert to &optical density", self.convert_od)
        self.actions["convert_bl"] = nirs_menu.addAction(
            "Convert to &haemoglobin", self.convert_bl)

        tools_menu.addSeparator()
        icon = QIcon.fromTheme("run-ica")
        self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...",
                                                       self.run_ica)
        self.actions["apply_ica"] = tools_menu.addAction(
            "Apply &ICA", self.apply_ica)
        tools_menu.addSeparator()
        self.actions["interpolate_bads"] = tools_menu.addAction(
            "Interpolate bad channels...", self.interpolate_bads)
        tools_menu.addSeparator()
        icon = QIcon.fromTheme("epoch-data")
        self.actions["epoch_data"] = tools_menu.addAction(
            icon, "Create Epochs...", self.epoch_data)

        view_menu = self.menuBar().addMenu("&View")
        self.actions["history"] = view_menu.addAction("&History...",
                                                      self.show_history)
        self.actions["toolbar"] = view_menu.addAction("&Toolbar",
                                                      self._toggle_toolbar)
        self.actions["toolbar"].setCheckable(True)
        self.actions["statusbar"] = view_menu.addAction(
            "&Statusbar", self._toggle_statusbar)
        self.actions["statusbar"].setCheckable(True)

        help_menu = self.menuBar().addMenu("&Help")
        self.actions["about"] = help_menu.addAction("&About", self.show_about)
        self.actions["about_qt"] = help_menu.addAction("About &Qt",
                                                       self.show_about_qt)

        # actions that are always enabled
        self.always_enabled = [
            "open_file", "about", "about_qt", "quit", "toolbar", "statusbar"
        ]

        # set up toolbar
        self.toolbar = self.addToolBar("toolbar")
        self.toolbar.setObjectName("toolbar")
        self.toolbar.addAction(self.actions["open_file"])
        self.toolbar.addAction(self.actions["meta_info"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["chan_props"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["plot_data"])
        self.toolbar.addAction(self.actions["plot_psd"])
        self.toolbar.addAction(self.actions["plot_locations"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["filter"])
        self.toolbar.addAction(self.actions["find_events"])
        self.toolbar.addAction(self.actions["epoch_data"])
        self.toolbar.addAction(self.actions["run_ica"])

        self.setUnifiedTitleAndToolBarOnMac(True)
        if settings["toolbar"]:
            self.toolbar.show()
            self.actions["toolbar"].setChecked(True)
        else:
            self.toolbar.hide()
            self.actions["toolbar"].setChecked(False)

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((int(width * 0.3), int(width * 0.7)))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            self.actions["statusbar"].setChecked(True)
        else:
            self.statusBar().hide()
            self.actions["statusbar"].setChecked(False)

        self.setAcceptDrops(True)
        self.data_changed()
Example #38
0
def addLabel(x):
    label = QLabel("A", toolbar)
    toolbar.addWidget(label)
Example #39
0
class StatusBarWidget(QWidget):
    """Status bar widget base."""
    # Signals
    sig_clicked = Signal()

    def __init__(self, parent, statusbar, icon=None, spinner=False):
        """Status bar widget base."""
        super(StatusBarWidget, self).__init__(parent)

        # Variables
        self.value = None

        # Widget
        self._status_bar = statusbar
        self._icon = None
        self._pixmap = None
        self._icon_size = QSize(16, 16)  # Should this be adjustable?
        self.label_icon = QLabel()
        self.label_value = QLabel()
        self.spinner = None
        if spinner:
            self.spinner = create_waitspinner(size=14, parent=self)

        # Layout setup
        layout = QHBoxLayout(self)
        layout.setSpacing(0)  # Reduce space between icon and label
        layout.addWidget(self.label_icon)
        layout.addWidget(self.label_value)
        if spinner:
            layout.addWidget(self.spinner)
            self.spinner.hide()
        layout.addSpacing(20)
        layout.setContentsMargins(0, 0, 0, 0)

        # Widget setup
        self.set_icon(icon)

        # See spyder-ide/spyder#9044.
        self.text_font = QFont(QFont().defaultFamily(), weight=QFont.Normal)
        self.label_value.setAlignment(Qt.AlignRight)
        self.label_value.setFont(self.text_font)

        # Setup
        statusbar.addPermanentWidget(self)
        self.set_value('')
        self.update_tooltip()

    # --- Status bar widget API
    # ------------------------------------------------------------------------
    def set_icon(self, icon):
        """Set the icon for the status bar widget."""
        self.label_icon.setVisible(icon is not None)
        if icon is not None and isinstance(icon, QIcon):
            self._icon = icon
            self._pixmap = icon.pixmap(self._icon_size)
            self.label_icon.setPixmap(self._pixmap)

    def set_value(self, value):
        """Set formatted text value."""
        self.value = value
        self.label_value.setText(value)

    def update_tooltip(self):
        """Update tooltip for widget."""
        tooltip = self.get_tooltip()
        if tooltip:
            self.label_value.setToolTip(tooltip)
            if self.label_icon:
                self.label_icon.setToolTip(tooltip)
            self.setToolTip(tooltip)

    def mouseReleaseEvent(self, event):
        """Override Qt method to allow for click signal."""
        super(StatusBarWidget, self).mousePressEvent(event)
        self.sig_clicked.emit()

    # --- API to be defined by user
    # ------------------------------------------------------------------------
    def get_tooltip(self):
        """Return the widget tooltip text."""
        return ''

    def get_icon(self):
        """Return the widget tooltip text."""
        return None
Example #40
0
class MessageBox(DialogBase):
    """Base message box dialog."""

    QUESTION_BOX = 100
    INFORMATION_BOX = 101
    ERROR_BOX = 102
    REMOVE_BOX = 103

    sig_url_clicked = Signal(object)

    def __init__(self, type_, error='', title='', text='', learn_more=None):
        """Base message box dialog."""
        super(MessageBox, self).__init__()
        from anaconda_navigator.utils.analytics import GATracker

        self.tracker = GATracker()
        self.label_text = QLabel(to_text_string(text))
        self.textbox_error = QTextEdit()
        self.button_ok = ButtonPrimary('Ok')
        self.button_yes = ButtonPrimary('Yes')
        self.button_no = ButtonNormal('No')
        self.button_copy = ButtonNormal('Copy text')
        self.button_learn = ButtonNormal('Learn more')
        self.button_remove = ButtonDanger('Remove')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_send = ButtonNormal('Report Issue', parent=self)

        self.label_text.setOpenExternalLinks(False)
        self.label_text.setWordWrap(True)
        self.label_text.linkActivated.connect(self.url_clicked)
        self.textbox_error.setReadOnly(True)
        self.textbox_error.setFrameStyle(QTextEdit.Plain)
        self.textbox_error.setFrameShape(QTextEdit.NoFrame)
        self.setMinimumWidth(260)
        self.textbox_error.verticalScrollBar().show()
        self.setWindowTitle(to_text_string(title))

        error = to_text_string(error).split('\n')
        error = '<br>'.join(error)
        self.textbox_error.setText(error)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.label_text)
        layout.addWidget(SpacerVertical())
        if error:
            layout.addWidget(self.textbox_error)
            layout.addWidget(SpacerVertical())
            layout.addWidget(self.button_copy)
            layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()

        layout.addLayout(layout_buttons)

        self.layout = layout
        self.setLayout(layout)

        # Signals
        self.button_copy.clicked.connect(self.copy_text)
        self.button_ok.clicked.connect(self.accept)
        self.button_yes.clicked.connect(self.accept)
        self.button_no.clicked.connect(self.reject)
        self.button_remove.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_send.clicked.connect(self.send)

        # Setup
        self.button_learn.setVisible(bool(learn_more))
        if bool(learn_more):
            layout_buttons.addWidget(self.button_learn)
            layout_buttons.addWidget(SpacerHorizontal())
            self.button_learn.clicked.connect(
                lambda: self.show_url(learn_more))

        if type_ == self.ERROR_BOX:
            layout_buttons.addWidget(self.button_send)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.INFORMATION_BOX:
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.textbox_error.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.QUESTION_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_no)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_yes)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.REMOVE_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_cancel)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_remove)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)

        self.button_send.setVisible(False)
        self.layout_buttons = layout_buttons

    def url_clicked(self, url):
        """Emit url interaction."""
        self.sig_url_clicked.emit(url)

    def copy_text(self):
        """Copy all the content of the displayed error message."""
        self.textbox_error.selectAll()
        self.textbox_error.copy()

    def show_url(self, url=None):
        """Open url in default browser."""
        if url:
            qurl = QUrl(url)
            QDesktopServices.openUrl(qurl)
            self.tracker.track_event('help', 'documentation', url)

    def send(self):
        """Send error report to github and create an issue with a template."""
        import webbrowser
        from anaconda_navigator.utils.analytics import GATracker
        base = "https://github.com/ContinuumIO/anaconda-issues/issues/new?{0}"
        template = '''
## Main error
{text}
## Traceback
```
{trace}
```
## System information
```
{info}
```
'''
        info = GATracker().info
        info = '\n'.join('{}: {}'.format(k, v) for k, v in info.items())
        query = parse.urlencode({
            'title':
            "Navigator Error",
            'labels':
            "tag:navigator",
            'body':
            template.format(text=self.text, trace=self.error, info=info)
        })
        url = base.format(query)
        webbrowser.open_new_tab(url)
Example #41
0
class StatusBarWidget(QWidget, SpyderWidgetMixin):
    """
    Base class for status bar widgets.

    These widgets consist by default of an icon, a label and a spinner,
    which are organized from left to right on that order.

    You can also add any other QWidget to this layout by setting the
    CUSTOM_WIDGET_CLASS class attribute. It'll be put between the label
    and the spinner.
    """

    ID = None
    """
    Unique string widget identifier.
    """

    CUSTOM_WIDGET_CLASS = None
    """
    Custom widget class to add to the default layout.
    """

    sig_clicked = Signal()
    """
    This signal is emmitted when the widget is clicked.
    """

    def __init__(self, parent=None, show_icon=True, show_label=True,
                 show_spinner=False):
        """
        Base class for status bar widgets.

        These are composed of the following widgets, which are arranged
        in a QHBoxLayout from left to right:

        * Icon
        * Label
        * Custom QWidget
        * Spinner

        Parameters
        ----------
        show_icon: bool
            Show an icon in the widget.
        show_label: bool
            Show a label in the widget.
        show_spinner: bool
            Show a spinner.

        Notes
        -----
        1. To use an icon, you need to redefine the ``get_icon`` method.
        2. To use a label, you need to call ``set_value``.
        """
        super().__init__(parent, class_parent=parent)
        self._parent = parent

        self.show_icon = show_icon
        self.show_label = show_label
        self.show_spinner = show_spinner

        self.value = None
        self.label_icon = None
        self.label_value = None
        self.spinner = None
        self.custom_widget = None

        self.set_layout()
        self.setStyleSheet(self._stylesheet())

    def set_layout(self):
        """Set layout for default widgets."""
        # Icon
        if self.show_icon:
            self._icon = self.get_icon()
            self._pixmap = None
            self._icon_size = QSize(16, 16)  # Should this be adjustable?
            self.label_icon = QLabel()
            self.set_icon()

        # Label
        if self.show_label:
            self.label_value = QLabel()
            self.set_value('')

            # See spyder-ide/spyder#9044.
            self.text_font = QFont(QFont().defaultFamily(),
                                   weight=QFont.Normal)
            self.label_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            self.label_value.setFont(self.text_font)

        # Custom widget
        if self.CUSTOM_WIDGET_CLASS:
            if not issubclass(self.CUSTOM_WIDGET_CLASS, QWidget):
                raise SpyderAPIError(
                    'Any custom status widget must subclass QWidget!'
                )
            self.custom_widget = self.CUSTOM_WIDGET_CLASS(self._parent)

        # Spinner
        if self.show_spinner:
            self.spinner = create_waitspinner(size=14, parent=self)
            self.spinner.hide()

        # Layout setup
        layout = QHBoxLayout(self)
        layout.setSpacing(0)  # Reduce space between icon and label
        if self.show_icon:
            layout.addWidget(self.label_icon)
        if self.show_label:
            layout.addWidget(self.label_value)
        if self.custom_widget:
            layout.addWidget(self.custom_widget)
        if self.show_spinner:
            layout.addWidget(self.spinner)

        layout.addSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setAlignment(Qt.AlignVCenter)

        # Setup
        self.update_tooltip()

    # ---- Status bar widget API
    def set_icon(self):
        """Set the icon for the status bar widget."""
        if self.label_icon:
            icon = self._icon
            self.label_icon.setVisible(icon is not None)
            if icon is not None and isinstance(icon, QIcon):
                self._pixmap = icon.pixmap(self._icon_size)
                self.label_icon.setPixmap(self._pixmap)

    def set_value(self, value):
        """Set formatted text value."""
        if self.label_value:
            self.value = value
            self.label_value.setText(value)

    def update_tooltip(self):
        """Update tooltip for widget."""
        tooltip = self.get_tooltip()
        if tooltip:
            if self.label_value:
                self.label_value.setToolTip(tooltip)
            if self.label_icon:
                self.label_icon.setToolTip(tooltip)
            self.setToolTip(tooltip)

    def mouseReleaseEvent(self, event):
        """Override Qt method to allow for click signal."""
        super(StatusBarWidget, self).mousePressEvent(event)
        self.sig_clicked.emit()

    # ---- API to be defined by user
    def get_tooltip(self):
        """Return the widget tooltip text."""
        return ''

    def get_icon(self):
        """Return the widget tooltip text."""
        return None

    def _stylesheet(self):
        stylesheet = ("QToolTip {{background-color: {background_color};"
                      "color: {color};"
                      "border: none}}").format(
                      background_color=QStylePalette.COLOR_ACCENT_2,
                      color=QStylePalette.COLOR_TEXT_1
                      )
        return stylesheet
Example #42
0
    def __init__(self, type_, error='', title='', text='', learn_more=None):
        """Base message box dialog."""
        super(MessageBox, self).__init__()
        from anaconda_navigator.utils.analytics import GATracker

        self.tracker = GATracker()
        self.label_text = QLabel(to_text_string(text))
        self.textbox_error = QTextEdit()
        self.button_ok = ButtonPrimary('Ok')
        self.button_yes = ButtonPrimary('Yes')
        self.button_no = ButtonNormal('No')
        self.button_copy = ButtonNormal('Copy text')
        self.button_learn = ButtonNormal('Learn more')
        self.button_remove = ButtonDanger('Remove')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_send = ButtonNormal('Report Issue', parent=self)

        self.label_text.setOpenExternalLinks(False)
        self.label_text.setWordWrap(True)
        self.label_text.linkActivated.connect(self.url_clicked)
        self.textbox_error.setReadOnly(True)
        self.textbox_error.setFrameStyle(QTextEdit.Plain)
        self.textbox_error.setFrameShape(QTextEdit.NoFrame)
        self.setMinimumWidth(260)
        self.textbox_error.verticalScrollBar().show()
        self.setWindowTitle(to_text_string(title))

        error = to_text_string(error).split('\n')
        error = '<br>'.join(error)
        self.textbox_error.setText(error)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.label_text)
        layout.addWidget(SpacerVertical())
        if error:
            layout.addWidget(self.textbox_error)
            layout.addWidget(SpacerVertical())
            layout.addWidget(self.button_copy)
            layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()

        layout.addLayout(layout_buttons)

        self.layout = layout
        self.setLayout(layout)

        # Signals
        self.button_copy.clicked.connect(self.copy_text)
        self.button_ok.clicked.connect(self.accept)
        self.button_yes.clicked.connect(self.accept)
        self.button_no.clicked.connect(self.reject)
        self.button_remove.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_send.clicked.connect(self.send)

        # Setup
        self.button_learn.setVisible(bool(learn_more))
        if bool(learn_more):
            layout_buttons.addWidget(self.button_learn)
            layout_buttons.addWidget(SpacerHorizontal())
            self.button_learn.clicked.connect(
                lambda: self.show_url(learn_more))

        if type_ == self.ERROR_BOX:
            layout_buttons.addWidget(self.button_send)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.INFORMATION_BOX:
            layout_buttons.addWidget(self.button_ok)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)
            self.textbox_error.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.QUESTION_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_no)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_yes)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_remove.setVisible(False)
            self.button_cancel.setVisible(False)
        elif type_ == self.REMOVE_BOX:
            layout_buttons.addStretch()
            layout_buttons.addWidget(self.button_cancel)
            layout_buttons.addWidget(SpacerHorizontal())
            layout_buttons.addWidget(self.button_remove)
            layout_buttons.addWidget(SpacerHorizontal())
            self.textbox_error.setVisible(False)
            self.button_ok.setVisible(False)
            self.button_copy.setVisible(False)
            self.button_yes.setVisible(False)
            self.button_no.setVisible(False)

        self.button_send.setVisible(False)
        self.layout_buttons = layout_buttons
Example #43
0
class QtImageControls(QtBaseImageControls):
    """Qt view and controls for the napari Image layer.

    Parameters
    ----------
    layer : napari.layers.Image
        An instance of a napari Image layer.

    Attributes
    ----------
    attenuationSlider : qtpy.QtWidgets.QSlider
        Slider controlling attenuation rate for `attenuated_mip` mode.
    attenuationLabel : qtpy.QtWidgets.QLabel
        Label for the attenuation slider widget.
    grid_layout : qtpy.QtWidgets.QGridLayout
        Layout of Qt widget controls for the layer.
    interpComboBox : qtpy.QtWidgets.QComboBox
        Dropdown menu to select the interpolation mode for image display.
    interpLabel : qtpy.QtWidgets.QLabel
        Label for the interpolation dropdown menu.
    isoThresholdSlider : qtpy.QtWidgets.QSlider
        Slider controlling the isosurface threshold value for rendering.
    isoThresholdLabel : qtpy.QtWidgets.QLabel
        Label for the isosurface threshold slider widget.
    layer : napari.layers.Image
        An instance of a napari Image layer.
    renderComboBox : qtpy.QtWidgets.QComboBox
        Dropdown menu to select the rendering mode for image display.
    renderLabel : qtpy.QtWidgets.QLabel
        Label for the rendering mode dropdown menu.
    """
    def __init__(self, layer):
        super().__init__(layer)

        self.layer.events.interpolation.connect(self._on_interpolation_change)
        self.layer.events.rendering.connect(self._on_rendering_change)
        self.layer.events.iso_threshold.connect(self._on_iso_threshold_change)
        self.layer.events.attenuation.connect(self._on_attenuation_change)
        self.layer._dims.events.ndisplay.connect(self._on_ndisplay_change)

        self.interpComboBox = QComboBox(self)
        self.interpComboBox.activated[str].connect(self.changeInterpolation)
        self.interpLabel = QLabel('interpolation:')

        renderComboBox = QComboBox(self)
        renderComboBox.addItems(Rendering.keys())
        index = renderComboBox.findText(self.layer.rendering,
                                        Qt.MatchFixedString)
        renderComboBox.setCurrentIndex(index)
        renderComboBox.activated[str].connect(self.changeRendering)
        self.renderComboBox = renderComboBox
        self.renderLabel = QLabel('rendering:')

        sld = QSlider(Qt.Horizontal, parent=self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setMinimum(0)
        sld.setMaximum(100)
        sld.setSingleStep(1)
        sld.setValue(int(self.layer.iso_threshold * 100))
        sld.valueChanged.connect(self.changeIsoThreshold)
        self.isoThresholdSlider = sld
        self.isoThresholdLabel = QLabel('iso threshold:')

        sld = QSlider(Qt.Horizontal, parent=self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setMinimum(0)
        sld.setMaximum(100)
        sld.setSingleStep(1)
        sld.setValue(int(self.layer.attenuation * 200))
        sld.valueChanged.connect(self.changeAttenuation)
        self.attenuationSlider = sld
        self.attenuationLabel = QLabel('attenuation:')
        self._on_ndisplay_change()

        colormap_layout = QHBoxLayout()
        if hasattr(self.layer, 'rgb') and self.layer.rgb:
            colormap_layout.addWidget(QLabel("RGB"))
            self.colormapComboBox.setVisible(False)
            self.colorbarLabel.setVisible(False)
        else:
            colormap_layout.addWidget(self.colorbarLabel)
            colormap_layout.addWidget(self.colormapComboBox)
        colormap_layout.addStretch(1)

        # grid_layout created in QtLayerControls
        # addWidget(widget, row, column, [row_span, column_span])
        self.grid_layout.addWidget(QLabel('opacity:'), 0, 0)
        self.grid_layout.addWidget(self.opacitySlider, 0, 1)
        self.grid_layout.addWidget(QLabel('contrast limits:'), 1, 0)
        self.grid_layout.addWidget(self.contrastLimitsSlider, 1, 1)
        self.grid_layout.addWidget(QLabel('gamma:'), 2, 0)
        self.grid_layout.addWidget(self.gammaSlider, 2, 1)
        self.grid_layout.addWidget(QLabel('colormap:'), 3, 0)
        self.grid_layout.addLayout(colormap_layout, 3, 1)
        self.grid_layout.addWidget(QLabel('blending:'), 4, 0)
        self.grid_layout.addWidget(self.blendComboBox, 4, 1)
        self.grid_layout.addWidget(self.interpLabel, 5, 0)
        self.grid_layout.addWidget(self.interpComboBox, 5, 1)
        self.grid_layout.addWidget(self.renderLabel, 6, 0)
        self.grid_layout.addWidget(self.renderComboBox, 6, 1)
        self.grid_layout.addWidget(self.isoThresholdLabel, 7, 0)
        self.grid_layout.addWidget(self.isoThresholdSlider, 7, 1)
        self.grid_layout.addWidget(self.attenuationLabel, 8, 0)
        self.grid_layout.addWidget(self.attenuationSlider, 8, 1)
        self.grid_layout.setRowStretch(9, 1)
        self.grid_layout.setColumnStretch(1, 1)
        self.grid_layout.setSpacing(4)

    def changeInterpolation(self, text):
        """Change interpolation mode for image display.

        Parameters
        ----------
        text : str
            Interpolation mode used by vispy. Must be one of our supported
            modes:
            'bessel', 'bicubic', 'bilinear', 'blackman', 'catrom', 'gaussian',
            'hamming', 'hanning', 'hermite', 'kaiser', 'lanczos', 'mitchell',
            'nearest', 'spline16', 'spline36'
        """
        self.layer.interpolation = text

    def changeRendering(self, text):
        """Change rendering mode for image display.

        Parameters
        ----------
        text : str
            Rendering mode used by vispy.
            Selects a preset rendering mode in vispy that determines how
            volume is displayed:
            * translucent: voxel colors are blended along the view ray until
              the result is opaque.
            * mip: maximum intensity projection. Cast a ray and display the
              maximum value that was encountered.
            * additive: voxel colors are added along the view ray until
              the result is saturated.
            * iso: isosurface. Cast a ray until a certain threshold is
              encountered. At that location, lighning calculations are
              performed to give the visual appearance of a surface.
            * attenuated_mip: attenuated maximum intensity projection. Cast a
              ray and attenuate values based on integral of encountered values,
              display the maximum value that was encountered after attenuation.
              This will make nearer objects appear more prominent.
        """
        self.layer.rendering = text
        self._toggle_rendering_parameter_visbility()

    def changeIsoThreshold(self, value):
        """Change isosurface threshold on the layer model.

        Parameters
        ----------
        value : float
            Threshold for isosurface.
        """
        with self.layer.events.blocker(self._on_iso_threshold_change):
            self.layer.iso_threshold = value / 100

    def _on_iso_threshold_change(self, event):
        """Receive layer model isosurface change event and update the slider.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        with self.layer.events.iso_threshold.blocker():
            self.isoThresholdSlider.setValue(
                int(self.layer.iso_threshold * 100))

    def changeAttenuation(self, value):
        """Change attenuation rate for attenuated maximum intensity projection.

        Parameters
        ----------
        value : Float
            Attenuation rate for attenuated maximum intensity projection.
        """
        with self.layer.events.blocker(self._on_attenuation_change):
            self.layer.attenuation = value / 200

    def _on_attenuation_change(self, event):
        """Receive layer model attenuation change event and update the slider.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        with self.layer.events.attenuation.blocker():
            self.attenuationSlider.setValue(int(self.layer.attenuation * 200))

    def _on_interpolation_change(self, event):
        """Receive layer interpolation change event and update dropdown menu.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        with self.layer.events.interpolation.blocker():
            index = self.interpComboBox.findText(self.layer.interpolation,
                                                 Qt.MatchFixedString)
            self.interpComboBox.setCurrentIndex(index)

    def _on_rendering_change(self, event):
        """Receive layer model rendering change event and update dropdown menu.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        with self.layer.events.rendering.blocker():
            index = self.renderComboBox.findText(self.layer.rendering,
                                                 Qt.MatchFixedString)
            self.renderComboBox.setCurrentIndex(index)
            self._toggle_rendering_parameter_visbility()

    def _toggle_rendering_parameter_visbility(self):
        """Hide isosurface rendering parameters if they aren't needed."""
        rendering = Rendering(self.layer.rendering)
        if rendering == Rendering.ISO:
            self.isoThresholdSlider.show()
            self.isoThresholdLabel.show()
        else:
            self.isoThresholdSlider.hide()
            self.isoThresholdLabel.hide()
        if rendering == Rendering.ATTENUATED_MIP:
            self.attenuationSlider.show()
            self.attenuationLabel.show()
        else:
            self.attenuationSlider.hide()
            self.attenuationLabel.hide()

    def _update_interpolation_combo(self):
        self.interpComboBox.clear()
        interp_enum = (Interpolation3D
                       if self.layer._dims.ndisplay == 3 else Interpolation)
        self.interpComboBox.addItems(interp_enum.keys())
        index = self.interpComboBox.findText(self.layer.interpolation,
                                             Qt.MatchFixedString)
        self.interpComboBox.setCurrentIndex(index)

    def _on_ndisplay_change(self, event=None):
        """Toggle between 2D and 3D visualization modes.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method, default is None.
        """
        self._update_interpolation_combo()
        if self.layer._dims.ndisplay == 2:
            self.isoThresholdSlider.hide()
            self.isoThresholdLabel.hide()
            self.attenuationSlider.hide()
            self.attenuationLabel.hide()
            self.renderComboBox.hide()
            self.renderLabel.hide()
        else:
            self.renderComboBox.show()
            self.renderLabel.show()
            self._toggle_rendering_parameter_visbility()
Example #44
0
class DialogUpdateApplication(DialogBase):
    """Update application dialog."""

    WIDTH = 460

    def __init__(self, version, config=CONF, startup=False):
        """
        Update application dialog.

        Parameter
        ---------
        version: str
            New version of update available.
        """
        super(DialogUpdateApplication, self).__init__()
        self.tracker = GATracker()

        self.label = QLabel(
            "There's a new version of Anaconda Navigator available. "
            "We strongly recommend you to update. <br><br>"
            "If you click yes, you Anaconda Navigator will close and the "
            "Anaconda Navigator Updater will start.<br><br><br>"
            "Do you wish to update to <b>Anaconda Navigator {0}</b> now?"
            "<br><br>".format(version))
        self.button_yes = ButtonPrimary('Yes')
        self.button_no = ButtonNormal('No, remind me later')
        self.button_no_show = ButtonNormal("No, don't show again")
        self.config = config

        if not startup:
            self.button_no_show.setVisible(False)
            self.button_no.setText('No')

        # Widgets setup
        self.label.setWordWrap(True)
        self.setMinimumWidth(self.WIDTH)
        self.setMaximumWidth(self.WIDTH)
        self.setWindowTitle('Update Application')

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_no_show)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_no)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_yes)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout_buttons.addWidget(SpacerVertical())
        layout_buttons.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)
        self.setLayout(layout)

        # Signals
        self.button_yes.clicked.connect(self.accept)
        self.button_no.clicked.connect(self.reject)
        self.button_no_show.clicked.connect(self.no_show)

        self.button_yes.setFocus()

    def no_show(self):
        """Handle not showing updates on startup."""
        self.config.set('main', 'hide_update_dialog', True)
        self.reject()
Example #45
0
    def _init_selection_ui(self):
        # LINE 1: Radio box spatial vs spectral axis
        self.axes_prompt = QLabel("Smoothing Axis:")
        self.axes_prompt.setMinimumWidth(150)

        self.spatial_radio = QRadioButton("Spatial")
        self.spatial_radio.setChecked(True)
        self.current_axis = "spatial"
        self.spatial_radio.toggled.connect(self.spatial_radio_checked)

        self.spectral_radio = QRadioButton("Spectral")
        self.spectral_radio.toggled.connect(self.spectral_radio_checked)

        # hbl is short for Horizontal Box Layout
        hbl1 = QHBoxLayout()
        hbl1.addWidget(self.axes_prompt)
        hbl1.addWidget(self.spatial_radio)
        hbl1.addWidget(self.spectral_radio)

        # LINE 2: Kernel Type prompt
        self.k_type_prompt = QLabel("Kernel Type:")
        self.k_type_prompt.setMinimumWidth(150)
        # Load kernel types + names and add to drop down
        self._load_options()
        self.combo = QComboBox()
        self.combo.setMinimumWidth(150)
        self.combo.addItems(self.options[self.current_axis])

        hbl2 = QHBoxLayout()
        hbl2.addWidget(self.k_type_prompt)
        hbl2.addWidget(self.combo)

        # LINE 3: Kernel size
        self.size_prompt = QLabel(
            self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type))
        self.size_prompt.setWordWrap(True)
        self.size_prompt.setMinimumWidth(150)
        self.unit_label = QLabel(
            self.smooth_cube.get_kernel_unit(self.current_kernel_type))
        self.k_size = QLineEdit("1")  # Default Kernel size set here

        hbl3 = QHBoxLayout()
        hbl3.addWidget(self.size_prompt)
        hbl3.addWidget(self.k_size)
        hbl3.addWidget(self.unit_label)

        # LINE 4: Data component drop down
        self.component_prompt = QLabel("Data Component:")
        self.component_prompt.setWordWrap(True)
        self.component_prompt.setMinimumWidth(150)
        # Load component_ids and add to drop down

        # Add the data component labels to the drop down, with the ComponentID
        # set as the userData:

        if self.parent is not None and hasattr(self.parent, 'data_components'):
            labeldata = [(str(cid), cid)
                         for cid in self.parent.data_components]
        else:
            labeldata = [(str(cid), cid)
                         for cid in self.data.main_components()]

        self.component_combo = QComboBox()
        update_combobox(self.component_combo, labeldata)

        self.component_combo.setMaximumWidth(150)
        self.component_combo.setCurrentIndex(0)
        if self.allow_preview:
            self.component_combo.currentIndexChanged.connect(
                self.update_preview_button)

        hbl4 = QHBoxLayout()
        hbl4.addWidget(self.component_prompt)
        hbl4.addWidget(self.component_combo)

        # Line 5: Preview Message
        message = "Info: Smoothing previews are displayed on " \
                  "CubeViz's left and single image viewers."
        self.preview_message = QLabel(message)
        self.preview_message.setWordWrap(True)
        self.preview_message.hide()
        hbl5 = QHBoxLayout()
        hbl5.addWidget(self.preview_message)

        # LINE 6: preview ok cancel buttons
        self.previewButton = QPushButton("Preview Slice")
        self.previewButton.clicked.connect(self.call_preview)

        self.okButton = QPushButton("Smooth Cube")
        self.okButton.clicked.connect(self.call_main)
        self.okButton.setDefault(True)

        self.cancelButton = QPushButton("Cancel")
        self.cancelButton.clicked.connect(self.cancel)

        hbl6 = QHBoxLayout()
        hbl6.addStretch(1)
        if self.allow_preview:
            hbl6.addWidget(self.previewButton)
        hbl6.addWidget(self.cancelButton)
        hbl6.addWidget(self.okButton)

        # Add Lines to Vertical Layout
        # vbl is short for Vertical Box Layout
        vbl = QVBoxLayout()
        if self.allow_spectral_axes:
            vbl.addLayout(hbl1)
        vbl.addLayout(hbl2)
        vbl.addLayout(hbl3)
        vbl.addLayout(hbl4)
        vbl.addLayout(hbl5)
        vbl.addLayout(hbl6)

        self.setLayout(vbl)
        self.setMaximumWidth(330)

        # Connect kernel combo box to event handler
        self.combo.currentIndexChanged.connect(self.selection_changed)
        self.selection_changed(0)

        self.show()
Example #46
0
class ShortcutEditor(QDialog):
    """A dialog for entering key sequences."""
    def __init__(self, parent, context, name, sequence, shortcuts):
        super(ShortcutEditor, self).__init__(parent)
        self._parent = parent

        self.context = context
        self.name = name
        self.shortcuts = shortcuts
        self.current_sequence = sequence
        self._qsequences = list()

        self.setup()
        self.update_warning()

    @property
    def new_sequence(self):
        """Return a string representation of the new key sequence."""
        return ', '.join(self._qsequences)

    @property
    def new_qsequence(self):
        """Return the QKeySequence object of the new key sequence."""
        return QKeySequence(self.new_sequence)

    def setup(self):
        """Setup the ShortcutEditor with the provided arguments."""
        # Widgets
        icon_info = HelperToolButton()
        icon_info.setIcon(get_std_icon('MessageBoxInformation'))
        layout_icon_info = QVBoxLayout()
        layout_icon_info.setContentsMargins(0, 0, 0, 0)
        layout_icon_info.setSpacing(0)
        layout_icon_info.addWidget(icon_info)
        layout_icon_info.addStretch(100)

        self.label_info = QLabel()
        self.label_info.setText(
            _("Press the new shortcut and select 'Ok' to confirm, "
              "click 'Cancel' to revert to the previous state, "
              "or use 'Clear' to unbind the command from a shortcut."))
        self.label_info.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.label_info.setWordWrap(True)
        layout_info = QHBoxLayout()
        layout_info.setContentsMargins(0, 0, 0, 0)
        layout_info.addLayout(layout_icon_info)
        layout_info.addWidget(self.label_info)
        layout_info.setStretch(1, 100)

        self.label_current_sequence = QLabel(_("Current shortcut:"))
        self.text_current_sequence = QLabel(self.current_sequence)

        self.label_new_sequence = QLabel(_("New shortcut:"))
        self.text_new_sequence = ShortcutLineEdit(self)
        self.text_new_sequence.setPlaceholderText(_("Press shortcut."))

        self.helper_button = HelperToolButton()
        self.helper_button.setIcon(QIcon())
        self.label_warning = QLabel()
        self.label_warning.setWordWrap(True)
        self.label_warning.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.button_default = QPushButton(_('Default'))
        self.button_ok = QPushButton(_('Ok'))
        self.button_ok.setEnabled(False)
        self.button_clear = QPushButton(_('Clear'))
        self.button_cancel = QPushButton(_('Cancel'))
        button_box = QHBoxLayout()
        button_box.addWidget(self.button_default)
        button_box.addStretch(100)
        button_box.addWidget(self.button_ok)
        button_box.addWidget(self.button_clear)
        button_box.addWidget(self.button_cancel)

        # New Sequence button box
        self.btn_clear_sequence = create_toolbutton(
            self,
            icon=ima.icon('editclear'),
            tip=_("Clear all entered key sequences"),
            triggered=self.clear_new_sequence)
        self.button_back_sequence = create_toolbutton(
            self,
            icon=ima.icon('ArrowBack'),
            tip=_("Remove last key sequence entered"),
            triggered=self.back_new_sequence)

        newseq_btnbar = QHBoxLayout()
        newseq_btnbar.setSpacing(0)
        newseq_btnbar.setContentsMargins(0, 0, 0, 0)
        newseq_btnbar.addWidget(self.button_back_sequence)
        newseq_btnbar.addWidget(self.btn_clear_sequence)

        # Setup widgets
        self.setWindowTitle(_('Shortcut: {0}').format(self.name))
        self.helper_button.setToolTip('')
        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self.helper_button.setStyleSheet(style)
        icon_info.setToolTip('')
        icon_info.setStyleSheet(style)

        # Layout
        layout_sequence = QGridLayout()
        layout_sequence.setContentsMargins(0, 0, 0, 0)
        layout_sequence.addLayout(layout_info, 0, 0, 1, 4)
        layout_sequence.addItem(QSpacerItem(15, 15), 1, 0, 1, 4)
        layout_sequence.addWidget(self.label_current_sequence, 2, 0)
        layout_sequence.addWidget(self.text_current_sequence, 2, 2)
        layout_sequence.addWidget(self.label_new_sequence, 3, 0)
        layout_sequence.addWidget(self.helper_button, 3, 1)
        layout_sequence.addWidget(self.text_new_sequence, 3, 2)
        layout_sequence.addLayout(newseq_btnbar, 3, 3)
        layout_sequence.addWidget(self.label_warning, 4, 2, 1, 2)
        layout_sequence.setColumnStretch(2, 100)
        layout_sequence.setRowStretch(4, 100)

        layout = QVBoxLayout()
        layout.addLayout(layout_sequence)
        layout.addSpacing(5)
        layout.addLayout(button_box)
        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept_override)
        self.button_clear.clicked.connect(self.unbind_shortcut)
        self.button_cancel.clicked.connect(self.reject)
        self.button_default.clicked.connect(self.set_sequence_to_default)

        # Set all widget to no focus so that we can register <Tab> key
        # press event.
        widgets = (self.label_warning, self.helper_button,
                   self.text_new_sequence, self.button_clear,
                   self.button_default, self.button_cancel, self.button_ok,
                   self.btn_clear_sequence, self.button_back_sequence)
        for w in widgets:
            w.setFocusPolicy(Qt.NoFocus)
            w.clearFocus()

    @Slot()
    def reject(self):
        """Slot for rejected signal."""
        # Added for spyder-ide/spyder#5426.  Due to the focusPolicy of
        # Qt.NoFocus for the buttons, if the cancel button was clicked without
        # first setting focus to the button, it would cause a seg fault crash.
        self.button_cancel.setFocus()
        super(ShortcutEditor, self).reject()

    @Slot()
    def accept(self):
        """Slot for accepted signal."""
        # Added for spyder-ide/spyder#5426.  Due to the focusPolicy of
        # Qt.NoFocus for the buttons, if the cancel button was clicked without
        # first setting focus to the button, it would cause a seg fault crash.
        self.button_ok.setFocus()
        super(ShortcutEditor, self).accept()

    def event(self, event):
        """Qt method override."""
        if event.type() in (QEvent.Shortcut, QEvent.ShortcutOverride):
            return True
        else:
            return super(ShortcutEditor, self).event(event)

    def keyPressEvent(self, event):
        """Qt method override."""
        event_key = event.key()
        if not event_key or event_key == Qt.Key_unknown:
            return
        if len(self._qsequences) == 4:
            # QKeySequence accepts a maximum of 4 different sequences.
            return
        if event_key in [
                Qt.Key_Control, Qt.Key_Shift, Qt.Key_Alt, Qt.Key_Meta
        ]:
            # The event corresponds to just and only a special key.
            return

        translator = ShortcutTranslator()
        event_keyseq = translator.keyevent_to_keyseq(event)
        event_keystr = event_keyseq.toString(QKeySequence.PortableText)
        self._qsequences.append(event_keystr)
        self.update_warning()

    def check_conflicts(self):
        """Check shortcuts for conflicts."""
        conflicts = []
        if len(self._qsequences) == 0:
            return conflicts

        new_qsequence = self.new_qsequence
        for shortcut in self.shortcuts:
            shortcut_qsequence = QKeySequence.fromString(str(shortcut.key))
            if shortcut_qsequence.isEmpty():
                continue
            if (shortcut.context, shortcut.name) == (self.context, self.name):
                continue
            if shortcut.context in [self.context, '_'] or self.context == '_':
                if (shortcut_qsequence.matches(new_qsequence)
                        or new_qsequence.matches(shortcut_qsequence)):
                    conflicts.append(shortcut)
        return conflicts

    def check_ascii(self):
        """
        Check that all characters in the new sequence are ascii or else the
        shortcut will not work.
        """
        try:
            self.new_sequence.encode('ascii')
        except UnicodeEncodeError:
            return False
        else:
            return True

    def check_singlekey(self):
        """Check if the first sub-sequence of the new key sequence is valid."""
        if len(self._qsequences) == 0:
            return True
        else:
            keystr = self._qsequences[0]
            valid_single_keys = (EDITOR_SINGLE_KEYS
                                 if self.context == 'editor' else SINGLE_KEYS)
            if any((m in keystr for m in ('Ctrl', 'Alt', 'Shift', 'Meta'))):
                return True
            else:
                # This means that the the first subsequence is composed of
                # a single key with no modifier.
                valid_single_keys = (EDITOR_SINGLE_KEYS if self.context
                                     == 'editor' else SINGLE_KEYS)
                if any((k == keystr for k in valid_single_keys)):
                    return True
                else:
                    return False

    def update_warning(self):
        """Update the warning label, buttons state and sequence text."""
        new_qsequence = self.new_qsequence
        new_sequence = self.new_sequence
        self.text_new_sequence.setText(
            new_qsequence.toString(QKeySequence.NativeText))

        conflicts = self.check_conflicts()
        if len(self._qsequences) == 0:
            warning = SEQUENCE_EMPTY
            tip = ''
            icon = QIcon()
        elif conflicts:
            warning = SEQUENCE_CONFLICT
            template = '<i>{0}<b>{1}</b>{2}</i>'
            tip_title = _('The new shortcut conflicts with:') + '<br>'
            tip_body = ''
            for s in conflicts:
                tip_body += ' - {0}: {1}<br>'.format(s.context, s.name)
            tip_body = tip_body[:-4]  # Removing last <br>
            tip_override = '<br>Press <b>OK</b> to unbind '
            tip_override += 'it' if len(conflicts) == 1 else 'them'
            tip_override += ' and assign it to <b>{}</b>'.format(self.name)
            tip = template.format(tip_title, tip_body, tip_override)
            icon = get_std_icon('MessageBoxWarning')
        elif new_sequence in BLACKLIST:
            warning = IN_BLACKLIST
            template = '<i>{0}<b>{1}</b></i>'
            tip_title = _('Forbidden key sequence!') + '<br>'
            tip_body = ''
            use = BLACKLIST[new_sequence]
            if use is not None:
                tip_body = use
            tip = template.format(tip_title, tip_body)
            icon = get_std_icon('MessageBoxWarning')
        elif self.check_singlekey() is False or self.check_ascii() is False:
            warning = INVALID_KEY
            template = '<i>{0}</i>'
            tip = _('Invalid key sequence entered') + '<br>'
            icon = get_std_icon('MessageBoxWarning')
        else:
            warning = NO_WARNING
            tip = 'This shortcut is valid.'
            icon = get_std_icon('DialogApplyButton')

        self.warning = warning
        self.conflicts = conflicts

        self.helper_button.setIcon(icon)
        self.button_ok.setEnabled(
            self.warning in [NO_WARNING, SEQUENCE_CONFLICT])
        self.label_warning.setText(tip)
        # Everytime after update warning message, update the label height
        new_height = self.label_warning.sizeHint().height()
        self.label_warning.setMaximumHeight(new_height)

    def set_sequence_from_str(self, sequence):
        """
        This is a convenience method to set the new QKeySequence of the
        shortcut editor from a string.
        """
        self._qsequences = [QKeySequence(s) for s in sequence.split(', ')]
        self.update_warning()

    def set_sequence_to_default(self):
        """Set the new sequence to the default value defined in the config."""
        sequence = CONF.get_default('shortcuts',
                                    "{}/{}".format(self.context, self.name))
        self._qsequences = sequence.split(', ')
        self.update_warning()

    def back_new_sequence(self):
        """Remove the last subsequence from the sequence compound."""
        self._qsequences = self._qsequences[:-1]
        self.update_warning()

    def clear_new_sequence(self):
        """Clear the new sequence."""
        self._qsequences = []
        self.update_warning()

    def unbind_shortcut(self):
        """Unbind the shortcut."""
        self._qsequences = []
        self.accept()

    def accept_override(self):
        """Unbind all conflicted shortcuts, and accept the new one"""
        conflicts = self.check_conflicts()
        if conflicts:
            for shortcut in conflicts:
                shortcut.key = ''
        self.accept()
Example #47
0
class SelectSmoothing(QDialog):
    """
    SelectSmoothing launches a GUI and executes smoothing.
    Any output is added to the input data as a new component.
    """
    def __init__(self,
                 data,
                 parent=None,
                 smooth_cube=None,
                 allow_preview=False,
                 allow_spectral_axes=False):
        super(SelectSmoothing, self).__init__(parent)
        self.setWindowFlags(self.windowFlags() | Qt.Tool)
        self.parent = parent
        self.title = "Smoothing Selection"

        self.data = data  # Glue data to be smoothed

        # Check if smooth object is the caller
        if smooth_cube is None:
            self.smooth_cube = SmoothCube(data=self.data)
        else:
            self.smooth_cube = smooth_cube

        self.allow_spectral_axes = allow_spectral_axes

        self.allow_preview = allow_preview
        self.is_preview_active = False  # Flag to show if smoothing preview is active

        self.abort_window = None  # Small window pop up when smoothing.

        self.component_id = None  # Glue data component to smooth over
        self.current_axis = None  # Selected smoothing_axis
        self.current_kernel_type = None  # Selected kernel type, a key in SmoothCube.kernel_registry
        self.current_kernel_name = None  # Name of selected kernel

        self._init_selection_ui()  # Format and show gui

    def _init_selection_ui(self):
        # LINE 1: Radio box spatial vs spectral axis
        self.axes_prompt = QLabel("Smoothing Axis:")
        self.axes_prompt.setMinimumWidth(150)

        self.spatial_radio = QRadioButton("Spatial")
        self.spatial_radio.setChecked(True)
        self.current_axis = "spatial"
        self.spatial_radio.toggled.connect(self.spatial_radio_checked)

        self.spectral_radio = QRadioButton("Spectral")
        self.spectral_radio.toggled.connect(self.spectral_radio_checked)

        # hbl is short for Horizontal Box Layout
        hbl1 = QHBoxLayout()
        hbl1.addWidget(self.axes_prompt)
        hbl1.addWidget(self.spatial_radio)
        hbl1.addWidget(self.spectral_radio)

        # LINE 2: Kernel Type prompt
        self.k_type_prompt = QLabel("Kernel Type:")
        self.k_type_prompt.setMinimumWidth(150)
        # Load kernel types + names and add to drop down
        self._load_options()
        self.combo = QComboBox()
        self.combo.setMinimumWidth(150)
        self.combo.addItems(self.options[self.current_axis])

        hbl2 = QHBoxLayout()
        hbl2.addWidget(self.k_type_prompt)
        hbl2.addWidget(self.combo)

        # LINE 3: Kernel size
        self.size_prompt = QLabel(
            self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type))
        self.size_prompt.setWordWrap(True)
        self.size_prompt.setMinimumWidth(150)
        self.unit_label = QLabel(
            self.smooth_cube.get_kernel_unit(self.current_kernel_type))
        self.k_size = QLineEdit("1")  # Default Kernel size set here

        hbl3 = QHBoxLayout()
        hbl3.addWidget(self.size_prompt)
        hbl3.addWidget(self.k_size)
        hbl3.addWidget(self.unit_label)

        # LINE 4: Data component drop down
        self.component_prompt = QLabel("Data Component:")
        self.component_prompt.setWordWrap(True)
        self.component_prompt.setMinimumWidth(150)
        # Load component_ids and add to drop down

        # Add the data component labels to the drop down, with the ComponentID
        # set as the userData:

        if self.parent is not None and hasattr(self.parent, 'data_components'):
            labeldata = [(str(cid), cid)
                         for cid in self.parent.data_components]
        else:
            labeldata = [(str(cid), cid)
                         for cid in self.data.main_components()]

        self.component_combo = QComboBox()
        update_combobox(self.component_combo, labeldata)

        self.component_combo.setMaximumWidth(150)
        self.component_combo.setCurrentIndex(0)
        if self.allow_preview:
            self.component_combo.currentIndexChanged.connect(
                self.update_preview_button)

        hbl4 = QHBoxLayout()
        hbl4.addWidget(self.component_prompt)
        hbl4.addWidget(self.component_combo)

        # Line 5: Preview Message
        message = "Info: Smoothing previews are displayed on " \
                  "CubeViz's left and single image viewers."
        self.preview_message = QLabel(message)
        self.preview_message.setWordWrap(True)
        self.preview_message.hide()
        hbl5 = QHBoxLayout()
        hbl5.addWidget(self.preview_message)

        # LINE 6: preview ok cancel buttons
        self.previewButton = QPushButton("Preview Slice")
        self.previewButton.clicked.connect(self.call_preview)

        self.okButton = QPushButton("Smooth Cube")
        self.okButton.clicked.connect(self.call_main)
        self.okButton.setDefault(True)

        self.cancelButton = QPushButton("Cancel")
        self.cancelButton.clicked.connect(self.cancel)

        hbl6 = QHBoxLayout()
        hbl6.addStretch(1)
        if self.allow_preview:
            hbl6.addWidget(self.previewButton)
        hbl6.addWidget(self.cancelButton)
        hbl6.addWidget(self.okButton)

        # Add Lines to Vertical Layout
        # vbl is short for Vertical Box Layout
        vbl = QVBoxLayout()
        if self.allow_spectral_axes:
            vbl.addLayout(hbl1)
        vbl.addLayout(hbl2)
        vbl.addLayout(hbl3)
        vbl.addLayout(hbl4)
        vbl.addLayout(hbl5)
        vbl.addLayout(hbl6)

        self.setLayout(vbl)
        self.setMaximumWidth(330)

        # Connect kernel combo box to event handler
        self.combo.currentIndexChanged.connect(self.selection_changed)
        self.selection_changed(0)

        self.show()

    def _load_options(self):
        """Extract names + types of kernels from SmoothCube.kernel_registry"""
        kernel_registry = self.smooth_cube.get_kernel_registry()

        self.options = {"spatial": [], "spectral": []}
        for k in kernel_registry:
            axis = kernel_registry[k]["axis"]
            for a in axis:
                if "spatial" == a:
                    self.options["spatial"].append(kernel_registry[k]["name"])
                elif "spectral" == a:
                    self.options["spectral"].append(kernel_registry[k]["name"])
        self.options["spectral"].sort()
        self.options["spatial"].sort()
        self.current_kernel_name = self.options[self.current_axis][0]
        self.current_kernel_type = self.smooth_cube.name_to_kernel_type(
            self.options[self.current_axis][0])

    def selection_changed(self, i):
        """
        Update kernel type, units, etc... when
        kernel name changes in combo box.
        """
        keys = self.options[self.current_axis]
        name = keys[i]
        self.current_kernel_name = name
        self.current_kernel_type = self.smooth_cube.name_to_kernel_type(name)
        self.unit_label.setText(
            self.smooth_cube.get_kernel_unit(self.current_kernel_type))
        self.size_prompt.setText(
            self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type))

    def spatial_radio_checked(self):
        self.current_axis = "spatial"
        self.update_preview_button()
        self.combo.clear()
        self.combo.addItems(self.options[self.current_axis])

    def spectral_radio_checked(self):
        self.current_axis = "spectral"
        self.update_preview_button()
        self.combo.clear()
        self.combo.addItems(self.options[self.current_axis])

    def input_validation(self):
        """
        Check if input will break Smoothing
        :return: bool: True if no errors
        """
        red = "background-color: rgba(255, 0, 0, 128);"
        success = True

        # Check 1: k_size
        if self.k_size == "":
            self.k_size.setStyleSheet(red)
            success = False
        else:
            try:
                if self.current_kernel_type == "median":
                    k_size = int(self.k_size.text())
                else:
                    k_size = float(self.k_size.text())
                if k_size <= 0:
                    self.k_size.setStyleSheet(red)
                    success = False
                else:
                    self.k_size.setStyleSheet("")
            except ValueError:
                if self.current_kernel_type == "median":
                    info = QMessageBox.critical(
                        self, "Error",
                        "Kernel size must be integer for median")
                self.k_size.setStyleSheet(red)
                success = False

        return success

    def call_main(self):
        try:
            self.main()
        except Exception as e:
            info = QMessageBox.critical(self, "Error", str(e))
            self.cancel()
            raise

    def main(self):
        """
        Main function to process input and call smoothing function
        """
        success = self.input_validation()

        if not success:
            return

        self.hide()
        self.abort_window = AbortWindow(self)
        QApplication.processEvents()

        # Add smoothing parameters

        self.smooth_cube.abort_window = self.abort_window
        if self.smooth_cube.parent is None and self.parent is not self.smooth_cube:
            self.smooth_cube.parent = self.parent
        if self.parent is not self.smooth_cube:
            self.smooth_cube.data = self.data
        self.smooth_cube.smoothing_axis = self.current_axis
        self.smooth_cube.kernel_type = self.current_kernel_type
        if self.current_kernel_type == "median":
            self.smooth_cube.kernel_size = int(self.k_size.text())
        else:
            self.smooth_cube.kernel_size = float(self.k_size.text())
        self.smooth_cube.component_id = str(self.component_combo.currentText())
        self.smooth_cube.output_as_component = True

        if self.is_preview_active:
            self.parent.end_smoothing_preview()
            self.is_preview_active = False
        self.smooth_cube.multi_threading_smooth()
        return

    def update_preview_button(self):
        if self.parent is None or "spatial" != self.current_axis:
            self.previewButton.setDisabled(True)
            return
        self.previewButton.setDisabled(False)
        return

    def call_preview(self):
        try:
            self.preview()
        except Exception as e:
            info = QMessageBox.critical(self, "Error", str(e))
            self.cancel()
            raise

    def preview(self):
        """Preview current options"""
        success = self.input_validation()

        if not success:
            return

        if self.smooth_cube.parent is None and self.parent is not self.smooth_cube:
            self.smooth_cube.parent = self.parent
        self.smooth_cube.smoothing_axis = self.current_axis
        self.smooth_cube.kernel_type = self.current_kernel_type
        if self.current_kernel_type == "median":
            self.smooth_cube.kernel_size = int(self.k_size.text())
        else:
            self.smooth_cube.kernel_size = float(self.k_size.text())

        preview_function = self.smooth_cube.preview_smoothing
        preview_title = self.smooth_cube.get_preview_title()
        component_id = self.component_combo.currentData()
        self.parent.start_smoothing_preview(preview_function, component_id,
                                            preview_title)

        self.is_preview_active = True
        self.preview_message.show()

    def cancel(self):
        self.clean_up()

    def clean_up(self):
        self.close()
        if self.abort_window is not None:
            self.abort_window.close()
        if self.is_preview_active:
            self.parent.end_smoothing_preview()
            self.is_preview_active = False

    def closeEvent(self, event):
        if self.is_preview_active:
            self.parent.end_smoothing_preview()
            self.is_preview_active = False

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.clean_up()
Example #48
0
    def __init__(self):
        super().__init__()

        self.setWindowTitle("About PyXRF")
        self.setFixedSize(500, 500)

        text_name = "PyXRF"
        text_description = "X-Ray Fluorescence Analysis Tool"

        text_ver = f"Version: {pyxrf.__version__}"
        text_latest_ver = "Latest stable version:"

        text_credit = "Credits:"
        text_credit_org = (
            "Data Acquisition, Management and Analysis Group\n"
            "National Synchrontron Light Source II\n"
            "Brookhaven National Laboratory"
        )

        text_copyright = f"\u00A92015\u2014{datetime.now().year} Brookhaven National Laboratory"

        label_name = QLabel(text_name)
        label_name.setStyleSheet("QLabel {font-weight: bold; font-size: 32px}")

        label_description = QLabel(text_description)
        label_description.setStyleSheet("QLabel {font-style: italic; font-size: 18px}")

        label_ver = QLabel(text_ver)
        label_latest_ver = QLabel(text_latest_ver)
        label_credit = QLabel(text_credit)
        label_org = QLabel(text_credit_org)
        label_copyright = QLabel(text_copyright)

        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

        vbox = QVBoxLayout()

        vbox.addStretch(1)

        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(label_name)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(label_description)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addStretch(1)

        hbox = QHBoxLayout()
        hbox.addSpacing(30)
        hbox.addWidget(label_ver)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        hbox = QHBoxLayout()
        hbox.addSpacing(30)
        hbox.addWidget(label_latest_ver)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addStretch(1)

        hbox = QHBoxLayout()
        hbox.addSpacing(30)
        hbox.addWidget(label_credit, 0, Qt.AlignTop)
        hbox.addWidget(label_org, 0, Qt.AlignTop)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addSpacing(20)

        hbox = QHBoxLayout()
        hbox.addSpacing(30)
        hbox.addWidget(label_copyright)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addSpacing(20)

        vbox.addWidget(button_box)

        self.setLayout(vbox)
Example #49
0
    def ui(self):
        """
            Define UI's elements
        """
        self.setGeometry(self.left, self.top, self.width, self.height)

        layout = QVBoxLayout()

        # Alpha
        alpha_label = QLabel(self)
        alpha_label.setObjectName("PopupLabel")
        alpha_label.setText("Alpha")

        self.alpha_textbox = QLineEdit(self)
        self.alpha_textbox.setText(str(1.0))

        # Color
        color_label = QLabel(self)
        color_label.setObjectName("PopupLabel")
        color_label.setText("Color")

        self.color_textbox = QLineEdit(self)
        self.color_textbox.setText("default")

        # Create a button in the window
        self.button = QPushButton("Ok", self)
        self.button.clicked.connect(self.on_click)
        self.button.setObjectName("RegionsButton")

        layout.addWidget(alpha_label)
        layout.addWidget(self.alpha_textbox)

        layout.addWidget(color_label)
        layout.addWidget(self.color_textbox)

        layout.addWidget(self.button)

        self.setLayout(layout)
        self.show()
Example #50
0
def get_image_label(name, default="not_found.png"):
    """Return image inside a QLabel object"""
    label = QLabel()
    label.setPixmap(QPixmap(get_image_path(name, default)))
    return label
Example #51
0
    def setup(self):
        """Setup the ShortcutEditor with the provided arguments."""
        # Widgets
        icon_info = HelperToolButton()
        icon_info.setIcon(get_std_icon('MessageBoxInformation'))
        layout_icon_info = QVBoxLayout()
        layout_icon_info.setContentsMargins(0, 0, 0, 0)
        layout_icon_info.setSpacing(0)
        layout_icon_info.addWidget(icon_info)
        layout_icon_info.addStretch(100)

        self.label_info = QLabel()
        self.label_info.setText(
            _("Press the new shortcut and select 'Ok' to confirm, "
              "click 'Cancel' to revert to the previous state, "
              "or use 'Clear' to unbind the command from a shortcut."))
        self.label_info.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.label_info.setWordWrap(True)
        layout_info = QHBoxLayout()
        layout_info.setContentsMargins(0, 0, 0, 0)
        layout_info.addLayout(layout_icon_info)
        layout_info.addWidget(self.label_info)
        layout_info.setStretch(1, 100)

        self.label_current_sequence = QLabel(_("Current shortcut:"))
        self.text_current_sequence = QLabel(self.current_sequence)

        self.label_new_sequence = QLabel(_("New shortcut:"))
        self.text_new_sequence = ShortcutLineEdit(self)
        self.text_new_sequence.setPlaceholderText(_("Press shortcut."))

        self.helper_button = HelperToolButton()
        self.helper_button.setIcon(QIcon())
        self.label_warning = QLabel()
        self.label_warning.setWordWrap(True)
        self.label_warning.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.button_default = QPushButton(_('Default'))
        self.button_ok = QPushButton(_('Ok'))
        self.button_ok.setEnabled(False)
        self.button_clear = QPushButton(_('Clear'))
        self.button_cancel = QPushButton(_('Cancel'))
        button_box = QHBoxLayout()
        button_box.addWidget(self.button_default)
        button_box.addStretch(100)
        button_box.addWidget(self.button_ok)
        button_box.addWidget(self.button_clear)
        button_box.addWidget(self.button_cancel)

        # New Sequence button box
        self.btn_clear_sequence = create_toolbutton(
            self,
            icon=ima.icon('editclear'),
            tip=_("Clear all entered key sequences"),
            triggered=self.clear_new_sequence)
        self.button_back_sequence = create_toolbutton(
            self,
            icon=ima.icon('ArrowBack'),
            tip=_("Remove last key sequence entered"),
            triggered=self.back_new_sequence)

        newseq_btnbar = QHBoxLayout()
        newseq_btnbar.setSpacing(0)
        newseq_btnbar.setContentsMargins(0, 0, 0, 0)
        newseq_btnbar.addWidget(self.button_back_sequence)
        newseq_btnbar.addWidget(self.btn_clear_sequence)

        # Setup widgets
        self.setWindowTitle(_('Shortcut: {0}').format(self.name))
        self.helper_button.setToolTip('')
        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self.helper_button.setStyleSheet(style)
        icon_info.setToolTip('')
        icon_info.setStyleSheet(style)

        # Layout
        layout_sequence = QGridLayout()
        layout_sequence.setContentsMargins(0, 0, 0, 0)
        layout_sequence.addLayout(layout_info, 0, 0, 1, 4)
        layout_sequence.addItem(QSpacerItem(15, 15), 1, 0, 1, 4)
        layout_sequence.addWidget(self.label_current_sequence, 2, 0)
        layout_sequence.addWidget(self.text_current_sequence, 2, 2)
        layout_sequence.addWidget(self.label_new_sequence, 3, 0)
        layout_sequence.addWidget(self.helper_button, 3, 1)
        layout_sequence.addWidget(self.text_new_sequence, 3, 2)
        layout_sequence.addLayout(newseq_btnbar, 3, 3)
        layout_sequence.addWidget(self.label_warning, 4, 2, 1, 2)
        layout_sequence.setColumnStretch(2, 100)
        layout_sequence.setRowStretch(4, 100)

        layout = QVBoxLayout()
        layout.addLayout(layout_sequence)
        layout.addSpacing(5)
        layout.addLayout(button_box)
        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept_override)
        self.button_clear.clicked.connect(self.unbind_shortcut)
        self.button_cancel.clicked.connect(self.reject)
        self.button_default.clicked.connect(self.set_sequence_to_default)

        # Set all widget to no focus so that we can register <Tab> key
        # press event.
        widgets = (self.label_warning, self.helper_button,
                   self.text_new_sequence, self.button_clear,
                   self.button_default, self.button_cancel, self.button_ok,
                   self.btn_clear_sequence, self.button_back_sequence)
        for w in widgets:
            w.setFocusPolicy(Qt.NoFocus)
            w.clearFocus()
Example #52
0
    def __init__(self, parent=None, *, file_path=None):

        super().__init__(parent)

        self.__file_path = ""
        self.__distance_to_sample = 0.0
        self.__overwrite_existing = False
        self.__preview = ("", {})  # str - information, dict - warnings

        self.setWindowTitle("Save Quantitative Calibration")
        self.setMinimumHeight(600)
        self.setMinimumWidth(600)
        self.resize(600, 600)

        self.text_edit = QTextEdit()
        set_tooltip(
            self.text_edit,
            "Preview the <b>quantitative calibration data</b> to be saved. The displayed "
            "warnings will not be saved, but need to be addressed in order to keep "
            "data integrity. The parameter <b>distance-to-sample</b> is optional, "
            "but desirable. If <b>distance-to-sample</b> is zero then no scaling will be "
            "applied to data to compensate for changing distance.",
        )
        self.text_edit.setReadOnly(True)

        self.le_file_path = LineEditReadOnly()
        set_tooltip(
            self.le_file_path,
            "Full path to the file used to <b>save the calibration data</b>. The path "
            "can be changed in file selection dialog box.",
        )
        self.pb_file_path = PushButtonMinimumWidth("..")
        set_tooltip(
            self.pb_file_path,
            "Change <b>file path</b> for saving the calibration data.")
        self.pb_file_path.clicked.connect(self.pb_file_path_clicked)
        self.pb_file_path.setDefault(False)
        self.pb_file_path.setAutoDefault(False)

        self.le_distance_to_sample = LineEditExtended()
        self.le_distance_to_sample.textChanged.connect(
            self.le_distance_to_sample_text_changed)
        self.le_distance_to_sample.editingFinished.connect(
            self.le_distance_to_sample_editing_finished)
        self._le_distance_to_sample_validator = DoubleValidatorStrict()
        self._le_distance_to_sample_validator.setBottom(0.0)
        set_tooltip(
            self.le_distance_to_sample,
            "<b>Distance</b> between the detector and the sample during calibration. If the value "
            "is 0, then no scaling is applied to data to correct the data if distance-to-sample "
            "is changed between calibration and measurement.",
        )

        self.cb_overwrite = QCheckBox("Overwrite Existing")
        self.cb_overwrite.stateChanged.connect(self.cb_overwrite_state_changed)
        set_tooltip(
            self.cb_overwrite,
            "Overwrite the <b>existing</b> file. This is a safety feature implemented to protect "
            "valuable results from accidental deletion.",
        )

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(
            QLabel("The following data will be saved to JSON file:"))
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        vbox.addWidget(self.text_edit)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Path: "))
        hbox.addWidget(self.pb_file_path)
        hbox.addWidget(self.le_file_path)
        vbox.addLayout(hbox)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Distance-to-sample:"))
        hbox.addWidget(self.le_distance_to_sample)
        hbox.addStretch(1)
        hbox.addWidget(self.cb_overwrite)
        vbox.addLayout(hbox)

        button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                      | QDialogButtonBox.Cancel)
        self.pb_ok = button_box.button(QDialogButtonBox.Ok)
        self.pb_ok.setDefault(False)
        self.pb_ok.setAutoDefault(False)
        self.pb_cancel = button_box.button(QDialogButtonBox.Cancel)
        self.pb_cancel.setDefault(True)
        self.pb_cancel.setAutoDefault(True)

        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        vbox.addWidget(button_box)

        self.setLayout(vbox)

        self._show_distance_to_sample()
        self._show_preview()
        self._show_overwrite_existing()

        # Set and display file path
        if file_path is not None:
            self.file_path = file_path
Example #53
0
class BibleExplorer(QWidget):
    def __init__(self, parent, bcvTextTuple):
        super().__init__()

        self.parent = parent
        self.b, self.c, self.v, self.text = bcvTextTuple
        self.bcvChain = False
        self.biblesSqlite = BiblesSqlite()
        bibleVerseParser = BibleVerseParser(config.parserStandarisation)
        self.bookNo2Abb = bibleVerseParser.standardAbbreviation
        self.bookNo2Name = bibleVerseParser.standardFullBookName
        self.bookLabel = QLabel("")
        self.chapterLabel = QLabel("")
        self.verseLabel = QLabel("")

        # set title
        self.setWindowTitle(config.thisTranslation["menu_bible"])
        # setup interface
        self.setupUI()

    # setup ui
    def setupUI(self):
        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.navigationWidget())
        mainLayout.addWidget(self.featuresWidget())
        self.setLayout(mainLayout)

    def navigationWidget(self):
        navigation = QWidget()

        navigationLayouts = QVBoxLayout()
        navigationLayouts.setSpacing(10)

        navigationLayoutsSub1 = QVBoxLayout()
        navigationLayoutsSub1.setSpacing(3)
        navigationLayout1 = self.navigationLayout1()
        navigationLayoutsSub1.addLayout(navigationLayout1)
        navigationLayout2 = self.navigationLayout2()
        navigationLayoutsSub1.addLayout(navigationLayout2)
        navigationLayouts.addLayout(navigationLayoutsSub1)

        navigationLayout3 = self.navigationLayout3()
        navigationLayouts.addLayout(navigationLayout3)

        navigationLayout3b = self.navigationLayout3b()
        navigationLayouts.addLayout(navigationLayout3b)

        navigationLayout4 = self.navigationLayout4()
        navigationLayouts.addLayout(navigationLayout4)

        navigationLayout5 = self.navigationLayout5()
        navigationLayouts.addLayout(navigationLayout5)

        if len(config.bibleCollections) > 0:
            navigationLayout6 = self.navigationLayout6()
            navigationLayouts.addWidget(navigationLayout6)

        navigationLayout7 = self.navigationLayout7()
        navigationLayouts.addWidget(navigationLayout7)

        navigationLayouts.addStretch()

        navigation.setLayout(navigationLayouts)
        return navigation

    def navigationLayout1(self):
        navigationLayout1 = QBoxLayout(QBoxLayout.LeftToRight)
        navigationLayout1.setSpacing(5)
        # Version selection
        self.versionCombo = QComboBox()
        #self.textList = self.biblesSqlite.getBibleList()
        self.textList = self.parent.textList
        self.versionCombo.addItems(self.textList)
        for index, fullName in enumerate(self.parent.textFullNameList):
            self.versionCombo.setItemData(index, fullName, Qt.ToolTipRole)
        initialIndex = 0
        if self.text in self.textList:
            initialIndex = self.textList.index(self.text)
        self.versionCombo.setCurrentIndex(initialIndex)
        navigationLayout1.addWidget(self.versionCombo)
        # Book / Chapter / Verse selection
        self.bookCombo = QComboBox()
        navigationLayout1.addWidget(self.bookCombo)
        self.chapterCombo = QComboBox()
        navigationLayout1.addWidget(self.chapterCombo)
        self.verseCombo = QComboBox()
        navigationLayout1.addWidget(self.verseCombo)
        # Initial setup
        self.updateBookCombo()
        # Interactive update in response to users selection
        self.versionCombo.currentIndexChanged.connect(self.updateBookCombo)
        self.bookCombo.currentIndexChanged.connect(
            lambda index: self.updateChapterCombo(self.bookList[index], True))
        self.chapterCombo.currentIndexChanged.connect(
            lambda index: self.updateVerseCombo(self.chapterList[index], True))
        self.verseCombo.currentIndexChanged.connect(self.updateV)
        return navigationLayout1

    def navigationLayout2(self):
        buttonElementTuple = (
            ("bar1_menu", lambda: self.openInWindow("BIBLE")),
            ("bar2_menu", lambda: self.openInWindow("STUDY")),
            ("addToCommand", self.addToCommand),
            ("presentation", self.present),
        )
        return self.parent.buttonsLayout(buttonElementTuple)

    def navigationLayout3(self):
        feature = "html_showParallel"
        action = lambda: self.versionsAction("PARALLEL")
        items = self.textList
        initialItems = list(
            {config.mainText, config.studyText, config.favouriteBible})
        self.parallelCombo = CheckableComboBox(
            items, initialItems, toolTips=self.parent.textFullNameList)
        return self.parent.comboFeatureLayout(feature, self.parallelCombo,
                                              action)

    def navigationLayout3b(self):
        feature = "parallelVerses"
        action = lambda: self.versionsAction("PARALLELVERSES")
        items = self.textList
        initialItems = list(
            {config.mainText, config.studyText, config.favouriteBible})
        self.parallelVersesCombo = CheckableComboBox(
            items, initialItems, toolTips=self.parent.textFullNameList)
        return self.parent.comboFeatureLayout(feature,
                                              self.parallelVersesCombo, action)

    def navigationLayout4(self):
        feature = "html_showCompare"
        action = lambda: self.versionsAction("COMPARE")
        items = self.textList
        initialItems = list(
            {config.mainText, config.studyText, config.favouriteBible})
        self.compareCombo = CheckableComboBox(
            items, initialItems, toolTips=self.parent.textFullNameList)
        return self.parent.comboFeatureLayout(feature, self.compareCombo,
                                              action)

    def navigationLayout5(self):
        feature = "html_showDifference"
        action = lambda: self.versionsAction("DIFFERENCE")
        items = self.textList
        initialItems = list(
            {config.mainText, config.studyText, config.favouriteBible})
        self.differenceCombo = CheckableComboBox(
            items, initialItems, toolTips=self.parent.textFullNameList)
        return self.parent.comboFeatureLayout(feature, self.differenceCombo,
                                              action)

    def navigationLayout6(self):
        rows = []
        row = [
            ("All", lambda: self.selectCollection("All")),
            ("None", lambda: self.selectCollection("None")),
        ]
        count = len(row)
        for collection in sorted(config.bibleCollections.keys()):
            row.append((collection, partial(self.selectCollection,
                                            collection)))
            count += 1
            if count % 6 == 0:
                rows.append(row)
                row = []
        if len(row) > 0:
            rows.append(row)
        return self.parent.buttonsWidget(rows, False, False)

    def navigationLayout7(self):
        buttonRow1 = (
            ("MOB", lambda: self.openInWindow("BIBLE", "MOB")),
            ("MIB", lambda: self.openInWindow("BIBLE", "MIB")),
            ("MTB", lambda: self.openInWindow("BIBLE", "MTB")),
            ("MPB", lambda: self.openInWindow("BIBLE", "MPB")),
            ("MAB", lambda: self.openInWindow("BIBLE", "MAB")),
            ("SBLGNTl", lambda: self.openInWindow("BIBLE", "SBLGNTl")),
        )
        buttonRow2 = (
            ("LXX1", lambda: self.openInWindow("BIBLE", "LXX1")),
            ("LXX1i", lambda: self.openInWindow("BIBLE", "LXX1i")),
            ("LXX2", lambda: self.openInWindow("BIBLE", "LXX2")),
            ("LXX2i", lambda: self.openInWindow("BIBLE", "LXX2i")),
            ("OHGB", lambda: self.openInWindow("BIBLE", "OHGB")),
            ("OHGBi", lambda: self.openInWindow("BIBLE", "OHGBi")),
        )
        buttonElementTupleTuple = (buttonRow1, buttonRow2)
        return self.parent.buttonsWidget(buttonElementTupleTuple, False, False)

    def updateBCVText(self, b, c, v, text):
        if text in self.textList:
            self.versionCombo.setCurrentIndex(self.textList.index(text))
            self.text = text
        if b in self.bookList:
            self.bookCombo.setCurrentIndex(self.bookList.index(b))
            self.b = b
        if c in self.chapterList:
            self.chapterCombo.setCurrentIndex(self.chapterList.index(c))
            self.c = c
        if v in self.verseList:
            self.verseCombo.setCurrentIndex(self.verseList.index(v))
            self.v = v
        self.updateBcvLabels()

    def updateBookCombo(self, textIndex=None, reset=False):
        if textIndex is None or ((textIndex is not None) and textIndex >= 0):
            self.bcvChain = True

            if textIndex is not None:
                self.text = self.textList[textIndex]
            self.bookCombo.clear()
            self.bookList = self.biblesSqlite.getBookList(self.text)
            if self.bookList:
                # Add only those are recognised by UBA parser
                for index, b in enumerate(self.bookList):
                    strB = str(b)
                    if strB in self.bookNo2Abb:
                        self.bookCombo.addItem(self.bookNo2Abb[str(b)])
                        self.bookCombo.setItemData(index,
                                                   self.bookNo2Name[str(b)],
                                                   Qt.ToolTipRole)
                index = 0
                if not reset and self.b in self.bookList:
                    index = self.bookList.index(self.b)
                else:
                    self.b = self.bookList[index]
                    reset = True
                self.bookCombo.setCurrentIndex(index)
                # check / update
                self.updateChapterCombo(self.b, reset, False)

    def updateChapterCombo(self, b=None, reset=False, head=True):
        if b is None or ((b is not None) and b >= 0):
            if (head and not self.bcvChain) or (self.bcvChain and not head):
                self.bcvChain = True

                if b is not None:
                    self.b = b
                self.chapterCombo.clear()
                self.chapterList = self.biblesSqlite.getChapterList(
                    self.b, self.text)
                self.chapterCombo.addItems([str(c) for c in self.chapterList])
                index = 0
                if not reset and self.c in self.chapterList:
                    index = self.chapterList.index(self.c)
                else:
                    self.c = self.chapterList[index]
                    reset = True
                self.chapterCombo.setCurrentIndex(index)
                # check / update
                self.updateVerseCombo(self.c, reset, False)

    def updateVerseCombo(self, c=None, reset=False, head=True):
        if c is None or ((c is not None) and c >= 0):
            if (head and not self.bcvChain) or (self.bcvChain and not head):
                self.bcvChain = True

                if c is not None:
                    self.c = c
                self.verseCombo.clear()
                self.verseList = self.biblesSqlite.getVerseList(
                    self.b, self.c, self.text)
                self.verseCombo.addItems([str(v) for v in self.verseList])
                index = 0
                if not reset and self.v in self.verseList:
                    index = self.verseList.index(self.v)
                else:
                    self.v = self.verseList[index]
                self.verseCombo.setCurrentIndex(index)
                # Complete update
                self.updateBcvLabels()
                self.bcvChain = False

    def updateV(self, index):
        if not self.bcvChain and (index >= 0):
            self.v = self.verseList[index]
            self.verseLabel.setText(self.getSelectedReference())
            self.parent.updateBibleTabText("{0}:::{1}".format(
                self.text, self.getSelectedReference()))

    def featuresWidget(self):
        features = QWidget()
        featuresLayout = QFormLayout()
        featuresLayout.setSpacing(5)
        featuresLayout.addRow(self.bookLabel, self.bookFeatures())
        featuresLayout.addRow(self.chapterLabel, self.chapterFeatures())
        featuresLayout.addRow(self.verseLabel, self.verseFeatures())
        features.setLayout(featuresLayout)
        return features

    def updateBcvLabels(self):
        self.bookLabel.setText(self.getSelectedReferenceBook())
        self.chapterLabel.setText(self.getSelectedReferenceChapter())
        self.verseLabel.setText(self.getSelectedReference())
        self.parent.updateBibleTabText("{0}:::{1}".format(
            self.text, self.getSelectedReference()))

    def bookFeatures(self):
        buttonRow1 = (
            ("readNotes", lambda: self.openBibleNotes("book")),
            ("editNotes", lambda: self.editBibleNotes("book")),
        )
        buttonRow2 = (
            ("html_introduction",
             lambda: self.searchBookChapter("Tidwell_The_Bible_Book_by_Book")),
            ("html_timelines", lambda: self.searchBookChapter("Timelines")),
            ("context1_dict", lambda: self.searchBookName(True)),
            ("context1_encyclopedia", lambda: self.searchBookName(False)),
        )
        buttonElementTupleTuple = (buttonRow1, buttonRow2)
        return self.parent.buttonsWidget(buttonElementTupleTuple)

    def chapterFeatures(self):
        buttonRow1 = (
            ("readNotes", lambda: self.openBibleNotes("chapter")),
            ("editNotes", lambda: self.editBibleNotes("chapter")),
        )
        buttonRow2 = (
            ("html_overview", lambda: self.chapterAction("OVERVIEW")),
            ("html_chapterIndex", lambda: self.chapterAction("CHAPTERINDEX")),
            ("html_summary", lambda: self.chapterAction("SUMMARY")),
            ("menu4_commentary", lambda: self.chapterAction("COMMENTARY")),
        )
        buttonElementTupleTuple = (buttonRow1, buttonRow2)
        return self.parent.buttonsWidget(buttonElementTupleTuple)

    def verseFeatures(self):
        buttonRow1 = (
            ("readNotes", lambda: self.openBibleNotes("verse")),
            ("editNotes", lambda: self.editBibleNotes("verse")),
        )
        buttonRow2 = (
            ("menu4_compareAll", lambda: self.verseAction("COMPARE")),
            ("menu4_crossRef", lambda: self.verseAction("CROSSREFERENCE")),
            ("menu4_tske", lambda: self.verseAction("TSKE")),
        )
        buttonRow3 = (
            ("menu4_traslations", lambda: self.verseAction("TRANSLATION")),
            ("menu4_discourse", lambda: self.verseAction("DISCOURSE")),
            ("menu4_words", lambda: self.verseAction("WORDS")),
            ("menu4_tdw", lambda: self.verseAction("COMBO")),
        )
        buttonRow4 = (
            ("menu4_indexes", lambda: self.verseAction("INDEX")),
            ("menu4_commentary", lambda: self.verseAction("COMMENTARY")),
        )
        buttonElementTupleTuple = (buttonRow1, buttonRow2, buttonRow3,
                                   buttonRow4)
        return self.parent.buttonsWidget(buttonElementTupleTuple)

    # Selected Reference

    def getSelectedReference(self):
        return "{0} {1}:{2}".format(self.bookNo2Abb[str(self.b)], self.c,
                                    self.v)

    def getSelectedReferenceBook(self):
        return self.getSelectedReference().split(" ")[0]

    def getSelectedReferenceChapter(self):
        return self.getSelectedReference().split(":")[0]

    # Button actions

    def addToCommand(self):
        self.parent.commandField.setText("{0} {1}".format(
            self.parent.commandField.text(), self.getSelectedReference()))

    def openInWindow(self, window, text=""):
        if window == "STUDY" and config.openBibleInMainViewOnly:
            self.parent.parent.enableStudyBibleButtonClicked()
        command = "{0}:::{1}:::{2}".format(window, text if text else self.text,
                                           self.getSelectedReference())
        self.parent.runTextCommand(command)

    def present(self):
        config.mainText = self.text
        command = "SCREEN:::{0}".format(self.getSelectedReference())
        self.parent.runTextCommand(command)

    def openBibleNotes(self, noteType):
        keywords = {
            "book": "_openbooknote",
            "chapter": "_openchapternote",
            "verse": "_openversenote",
        }
        command = "{0}:::{1}.{2}.{3}".format(keywords[noteType], self.b,
                                             self.c, self.v)
        self.parent.runTextCommand(command)

    def versionsAction(self, keyword):
        selectedVersionsMap = {
            "PARALLEL": self.parallelCombo.checkItems,
            "PARALLELVERSES": self.parallelVersesCombo.checkItems,
            "COMPARE": self.compareCombo.checkItems,
            "DIFFERENCE": self.differenceCombo.checkItems,
        }
        selectedVersions = "_".join(selectedVersionsMap[keyword])
        command = "{0}:::{1}:::{2}".format(keyword, selectedVersions,
                                           self.getSelectedReference())
        self.parent.runTextCommand(command)

    def editBibleNotes(self, noteType):
        keywords = {
            "book": "_editbooknote",
            "chapter": "_editchapternote",
            "verse": "_editversenote",
        }
        command = "{0}:::{1}.{2}.{3}".format(keywords[noteType], self.b,
                                             self.c, self.v)
        self.parent.runTextCommand(command)

    def searchBookName(self, dictionary):
        engFullBookName = BibleBooks().eng[str(self.b)][1]
        matches = re.match("^[0-9]+? (.*?)$", engFullBookName)
        if matches:
            engFullBookName = matches.group(1)
        command = "SEARCHTOOL:::{0}:::{1}".format(
            config.dictionary if dictionary else config.encyclopedia,
            engFullBookName)
        self.parent.runTextCommand(command)

    def searchBookChapter(self, resource):
        engFullBookName = BibleBooks().eng[str(self.b)][1]
        command = "SEARCHBOOKCHAPTER:::{0}:::{1}".format(
            resource, engFullBookName)
        self.parent.runTextCommand(command)

    def chapterAction(self, keyword):
        command = "{0}:::{1}".format(keyword,
                                     self.getSelectedReferenceChapter())
        self.parent.runTextCommand(command)

    def verseAction(self, keyword):
        command = "{0}:::{1}".format(keyword, self.getSelectedReference())
        self.parent.runTextCommand(command)

    def selectCollection(self, collection):
        if collection == "All":
            self.versionCombo.clear()
            self.versionCombo.addItems(self.textList)
            for index, fullName in enumerate(self.parent.textFullNameList):
                self.versionCombo.setItemData(index, fullName, Qt.ToolTipRole)
            self.parallelCombo.checkAll()
            self.parallelVersesCombo.checkAll()
            self.compareCombo.checkAll()
            self.differenceCombo.checkAll()
        elif collection == "None":
            self.parallelCombo.clearAll()
            self.parallelVersesCombo.clearAll()
            self.compareCombo.clearAll()
            self.differenceCombo.clearAll()
        else:
            self.versionCombo.clear()
            self.versionCombo.addItems(config.bibleCollections[collection])
            for i in range(self.versionCombo.model().rowCount()):
                text = self.versionCombo.model().item(i).text()
                fullName = Bible(text).bibleInfo()
                self.versionCombo.setItemData(i, fullName, Qt.ToolTipRole)
            self.parallelCombo.checkFromList(
                config.bibleCollections[collection])
            self.parallelVersesCombo.checkFromList(
                config.bibleCollections[collection])
            self.compareCombo.checkFromList(
                config.bibleCollections[collection])
            self.differenceCombo.checkFromList(
                config.bibleCollections[collection])
Example #54
0
    def __init__(self, parent):
        super(KiteInstallation, self).__init__(parent)

        # Left side
        action_layout = QVBoxLayout()
        progress_layout = QHBoxLayout()
        self._progress_widget = QWidget(self)
        self._progress_widget.setFixedHeight(50)
        self._progress_filter = HoverEventFilter()
        self._progress_bar = QProgressBar(self)
        self._progress_bar.setFixedWidth(180)
        self._progress_widget.installEventFilter(self._progress_filter)
        self.cancel_button = QPushButton()
        self.cancel_button.setIcon(ima.icon('DialogCloseButton'))
        self.cancel_button.hide()
        progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft)
        progress_layout.addWidget(self.cancel_button)
        self._progress_widget.setLayout(progress_layout)

        self._progress_label = QLabel(_('Downloading'))
        install_info = QLabel(
            _("Kite comes with a native app called the Copilot <br>"
              "which provides you with real time <br>"
              "documentation as you code.<br><br>"
              "When Kite is done installing, the Copilot will <br>"
              "launch automatically and guide you throught the <br>"
              "rest of the setup process."))

        button_layout = QHBoxLayout()
        self.ok_button = QPushButton(_('OK'))
        button_layout.addStretch()
        button_layout.addWidget(self.ok_button)
        button_layout.addStretch()

        action_layout.addStretch()
        action_layout.addWidget(self._progress_label)
        action_layout.addWidget(self._progress_widget)
        action_layout.addWidget(install_info)
        action_layout.addSpacing(10)
        action_layout.addLayout(button_layout)
        action_layout.addStretch()

        # Right side
        copilot_image_source = get_image_path('kite_copilot')

        copilot_image = QPixmap(copilot_image_source)
        copilot_label = QLabel()
        screen = QApplication.primaryScreen()
        device_pixel_ratio = screen.devicePixelRatio()
        if device_pixel_ratio > 1:
            copilot_image.setDevicePixelRatio(device_pixel_ratio)
            copilot_label.setPixmap(copilot_image)
        else:
            image_height = int(copilot_image.height() * 0.4)
            image_width = int(copilot_image.width() * 0.4)
            copilot_label.setPixmap(
                copilot_image.scaled(image_width, image_height,
                                     Qt.KeepAspectRatio,
                                     Qt.SmoothTransformation))

        # Layout
        general_layout = QHBoxLayout()
        general_layout.addLayout(action_layout)
        general_layout.addWidget(copilot_label)

        self.setLayout(general_layout)

        # Signals
        self._progress_filter.sig_hover_enter.connect(
            lambda: self.cancel_button.show())
        self._progress_filter.sig_hover_leave.connect(
            lambda: self.cancel_button.hide())
Example #55
0
    def initialize(self):

        self.resize(_main_window_geometry["initial_width"], _main_window_geometry["initial_height"])

        self.setMinimumWidth(_main_window_geometry["min_width"])
        self.setMinimumHeight(_main_window_geometry["min_height"])

        self.setWindowTitle(self.gpc.get_window_title())

        self.central_widget = TwoPanelWidget(gpc=self.gpc, gui_vars=self.gui_vars)
        self.setCentralWidget(self.central_widget)

        # Status bar
        self.statusLabel = QLabel()
        self.statusBar().addWidget(self.statusLabel)
        self.statusProgressBar = QProgressBar()
        self.statusProgressBar.setFixedWidth(200)
        self.statusBar().addPermanentWidget(self.statusProgressBar)

        self.statusLabelDefaultText = "No data is loaded"
        self.statusLabel.setText(self.statusLabelDefaultText)

        # 'Scan Data' menu item
        self.action_read_file = QAction("&Read File...", self)
        self.action_read_file.setStatusTip("Load data from HDF5 file")
        self.action_read_file.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_file.clicked)

        self.action_load_run = QAction("&Load Run...", self)
        self.action_load_run.setEnabled(self.gui_vars["gui_state"]["databroker_available"])
        self.action_load_run.setStatusTip("Load data from database (Databroker)")
        self.action_load_run.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_dbase.clicked)

        self.action_view_metadata = QAction("View Metadata...", self)
        self.action_view_metadata.setEnabled(self.gpc.is_scan_metadata_available())
        self.action_view_metadata.setStatusTip("View metadata for loaded run")
        self.action_view_metadata.triggered.connect(
            self.central_widget.left_panel.load_data_widget.pb_view_metadata.clicked
        )

        # Main menu
        menubar = self.menuBar()
        # Disable native menu bar (it doesn't work on MacOS 10.15 with PyQt<=5.11)
        #   It may work with later versions of PyQt when they become available.
        menubar.setNativeMenuBar(False)
        loadData = menubar.addMenu("Scan &Data")
        loadData.addAction(self.action_read_file)
        loadData.addAction(self.action_load_run)
        loadData.addSeparator()
        loadData.addAction(self.action_view_metadata)

        # 'Fitting Model' menu item
        self.action_lines_find_automatically = QAction("Find &Automatically...", self)
        self.action_lines_find_automatically.setStatusTip("Automatically find emission lines in total spectrum")
        self.action_lines_find_automatically.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_find_elines.clicked
        )

        self.action_lines_load_from_file = QAction("Load From &File...", self)
        self.action_lines_load_from_file.setStatusTip(
            "Load processing parameters, including selected emission lines, from JSON file"
        )
        self.action_lines_load_from_file.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_load_elines.clicked
        )

        self.action_lines_load_quant_standard = QAction("Load &Quantitative Standards...", self)
        self.action_lines_load_quant_standard.setStatusTip(
            "Load quantitative standard. The emission lines from the standard are automatically selected"
        )
        self.action_lines_load_quant_standard.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_load_qstandard.clicked
        )

        self.action_add_remove_emission_lines = QAction("&Add/Remove Emission Lines...", self)
        self.action_add_remove_emission_lines.setStatusTip("Manually add and remove emission lines")
        self.action_add_remove_emission_lines.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_manage_emission_lines.clicked
        )

        self.action_save_model_params = QAction("&Save Model Parameters...", self)
        self.action_save_model_params.setStatusTip("Save model parameters to JSON file")
        self.action_save_model_params.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_save_elines.clicked
        )

        self.action_add_remove_emission_lines = QAction("Start Model &Fitting", self)
        self.action_add_remove_emission_lines.setStatusTip("Run computations: start fitting for total spectrum")
        self.action_add_remove_emission_lines.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_start_fitting.clicked
        )

        fittingModel = menubar.addMenu("Fitting &Model")
        emissionLines = fittingModel.addMenu("&Emission Lines")
        emissionLines.addAction(self.action_lines_find_automatically)
        emissionLines.addAction(self.action_lines_load_from_file)
        emissionLines.addAction(self.action_lines_load_quant_standard)
        fittingModel.addAction(self.action_add_remove_emission_lines)
        fittingModel.addSeparator()
        fittingModel.addAction(self.action_save_model_params)
        fittingModel.addSeparator()
        fittingModel.addAction(self.action_add_remove_emission_lines)

        # "XRF Maps" menu item
        self.action_start_xrf_map_fitting = QAction("Start XRF Map &Fitting", self)
        self.action_start_xrf_map_fitting.setStatusTip("Run computations: start fitting for XRF maps")
        self.action_start_xrf_map_fitting.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_start_map_fitting.clicked
        )

        self.action_compute_rois = QAction("Compute &ROIs...", self)
        self.action_compute_rois.setStatusTip("Compute XRF Maps based on spectral ROIs")
        self.action_compute_rois.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_compute_roi_maps.clicked
        )

        self.action_load_quant_calibration = QAction("&Load Quantitative Calibration...", self)
        self.action_load_quant_calibration.setStatusTip(
            "Load quantitative calibration from JSON file. Calibration is used for scaling of XRF Maps"
        )
        self.action_load_quant_calibration.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_load_quant_calib.clicked
        )

        self.action_save_quant_calibration = QAction("&Save Quantitative Calibration...", self)
        self.action_save_quant_calibration.setStatusTip(
            "Save Quantitative Calibration based on XRF map of the standard sample"
        )
        self.action_save_quant_calibration.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_save_q_calibration.clicked
        )

        self.action_export_to_tiff_and_txt = QAction("&Export to TIFF and TXT...", self)
        self.action_export_to_tiff_and_txt.setStatusTip("Export XRF Maps as TIFF and/or TXT files")
        self.action_export_to_tiff_and_txt.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_export_to_tiff_and_txt.clicked
        )

        xrfMaps = menubar.addMenu("XRF &Maps")
        xrfMaps.addAction(self.action_start_xrf_map_fitting)
        xrfMaps.addAction(self.action_compute_rois)
        xrfMaps.addSeparator()
        xrfMaps.addAction(self.action_load_quant_calibration)
        xrfMaps.addSeparator()
        xrfMaps.addAction(self.action_save_quant_calibration)
        xrfMaps.addAction(self.action_export_to_tiff_and_txt)

        # "View" menu item
        self.action_show_matplotlib_toolbar = QAction("Show &Matplotlib Toolbar", self)
        self.action_show_matplotlib_toolbar.setCheckable(True)
        self.action_show_matplotlib_toolbar.setChecked(True)
        self.action_show_matplotlib_toolbar.setStatusTip("Show Matplotlib Toolbar on the plots")
        self.action_show_matplotlib_toolbar.toggled.connect(self.action_show_matplotlib_toolbar_toggled)

        self.action_show_widget_tooltips = QAction("Show &Tooltips", self)
        self.action_show_widget_tooltips.setCheckable(True)
        self.action_show_widget_tooltips.setChecked(self.gui_vars["show_tooltip"])
        self.action_show_widget_tooltips.setStatusTip("Show widget tooltips")
        self.action_show_widget_tooltips.toggled.connect(self.action_show_widget_tooltips_toggled)

        options = menubar.addMenu("&Options")
        options.addAction(self.action_show_widget_tooltips)
        options.addAction(self.action_show_matplotlib_toolbar)

        # "Help" menu item
        self.action_online_docs = QAction("Online &Documentation", self)
        self.action_online_docs.setStatusTip("Open online documentation in the default browser")
        self.action_online_docs.triggered.connect(self.action_online_docs_triggered)

        self.action_about = QAction("&About PyXRF", self)
        self.action_about.setStatusTip("Show information about this program")
        self.action_about.triggered.connect(self.action_about_triggered)

        help = menubar.addMenu("&Help")
        help.addAction(self.action_online_docs)
        help.addSeparator()
        help.addAction(self.action_about)

        self.update_widget_state()

        # Connect signals
        self.central_widget.left_panel.load_data_widget.update_preview_map_range.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_count.update_map_range
        )

        # Before loading a new file or run
        self.central_widget.left_panel.load_data_widget.signal_loading_new_run.connect(
            self.central_widget.right_panel.slot_activate_tab_preview
        )

        # Open a new file or run
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.slot_activate_load_data_tab
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.slot_activate_tab_preview
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(self.slot_new_run_loaded)
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.wnd_image_wizard.slot_update_table
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.wnd_load_quantitative_calibration.update_all_data
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.fit_maps_widget.slot_update_for_new_loaded_run
        )

        # New model is loaded or processing parameters (incident energy) was changed
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.wnd_manage_emission_lines.update_widget_data
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.redraw_plot_fit
        )

        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.slot_activate_tab_fitting_model
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.update_controls
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.wnd_manage_emission_lines.update_widget_data
        )

        # XRF Maps dataset changed
        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect(
            self.wnd_image_wizard.slot_update_table
        )

        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.combo_select_dataset_update_current_index
        )
        self.central_widget.right_panel.tab_plot_rgb_maps.signal_rgb_maps_dataset_selection_changed.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.combo_select_dataset_update_current_index
        )

        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_norm_changed.connect(
            self.wnd_image_wizard.slot_update_ranges
        )

        # Quantitative calibration changed
        self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_ranges
        )
        self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect(
            self.wnd_image_wizard.slot_update_ranges
        )

        # Selected element is changed (tools for emission line selection)
        self.wnd_manage_emission_lines.signal_selected_element_changed.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect(
            self.wnd_manage_emission_lines.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_add_line.connect(
            self.wnd_manage_emission_lines.pb_add_eline_clicked
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_remove_line.connect(
            self.wnd_manage_emission_lines.pb_remove_eline_clicked
        )
        self.wnd_manage_emission_lines.signal_update_element_selection_list.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_update_eline_selection_list
        )
        self.wnd_manage_emission_lines.signal_update_add_remove_btn_state.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_update_add_remove_btn_state
        )

        self.wnd_manage_emission_lines.signal_selected_element_changed.connect(
            self.central_widget.left_panel.model_widget.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect(
            self.central_widget.left_panel.model_widget.slot_selection_item_changed
        )

        # Total spectrum fitting completed
        self.central_widget.left_panel.model_widget.signal_total_spectrum_fitting_completed.connect(
            self.wnd_manage_emission_lines.update_eline_table
        )
        # Total spectrum invalidated
        self.wnd_manage_emission_lines.signal_parameters_changed.connect(
            self.central_widget.left_panel.model_widget.update_fit_status
        )
        # New dataset loaded or different channel selected. Compute fit parameters.
        self.central_widget.left_panel.load_data_widget.signal_data_channel_changed.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )

        # Update map datasets (Fitted maps)
        self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.fit_maps_widget.signal_activate_tab_xrf_maps.connect(
            self.central_widget.right_panel.slot_activate_tab_xrf_maps
        )

        # Update map datasets (ROI maps)
        self.wnd_compute_roi_maps.signal_roi_computation_complete.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.wnd_compute_roi_maps.signal_roi_computation_complete.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.wnd_compute_roi_maps.signal_activate_tab_xrf_maps.connect(
            self.central_widget.right_panel.slot_activate_tab_xrf_maps
        )

        self.signal_fitting_parameters_changed.connect(self.wnd_general_fitting_settings.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_shared.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_lines.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_manage_emission_lines.update_widget_data)
Example #56
0
    def _setupUi(self):
        # status
        label_status = QLabel('Status: ',
                              self,
                              alignment=Qt.AlignRight | Qt.AlignVCenter)
        channels2values = {
            self.device.substitute(propty='ForceComplete-Mon'): 1,
            self.device.substitute(propty='NegativeDoneMov-Mon'): 1,
            self.device.substitute(propty='PositiveDoneMov-Mon'): 1
        }
        self.multiled_status = PyDMLedMultiChannel(self, channels2values)
        self.multiled_status.setStyleSheet('max-width: 1.29em;')

        self.pb_details = QPushButton(qta.icon('fa5s.ellipsis-h'), '', self)
        self.pb_details.setToolTip('Open details')
        self.pb_details.setObjectName('detail')
        self.pb_details.setStyleSheet(
            "#detail{min-width:25px; max-width:25px; icon-size:20px;}")
        util.connect_window(self.pb_details,
                            _DiffCtrlDetails,
                            parent=self,
                            prefix=self.prefix,
                            device=self.device)

        self.lb_descCtrl1 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl1 = PyDMSpinbox(self)
        self.sb_Ctrl1.showStepExponent = False
        self.lb_Ctrl1 = PyDMLabel(self)
        self.lb_descCtrl2 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl2 = PyDMSpinbox(self)
        self.sb_Ctrl2.showStepExponent = False
        self.lb_Ctrl2 = PyDMLabel(self)

        self.pb_open = PyDMPushButton(
            parent=self,
            label='Open',
            pressValue=1,
            init_channel=self.device.substitute(propty='Home-Cmd'))

        tmp_file = _substitute_in_file(
            _os.path.abspath(_os.path.dirname(__file__)) + '/ui_as_ap_dev' +
            self.orientation.lower() + 'mon.ui', {'PREFIX': self.prefix})
        self.dev_widget = loadUi(tmp_file)
        self.dev_widget.setObjectName('dev')
        self.dev_widget_scrarea = QScrollArea()
        self.dev_widget_scrarea.setObjectName('scrarea')
        self.dev_widget_scrarea.setStyleSheet(
            '#scrarea{background-color: transparent; max-width: 15em;}'
            '#dev{background-color:transparent;}')
        self.dev_widget_scrarea.setWidget(self.dev_widget)

        lay = QGridLayout(self)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(label_status, 0, 0)
        lay.addWidget(self.multiled_status, 0, 1)
        lay.addWidget(self.pb_details, 0, 2, alignment=Qt.AlignRight)
        lay.addWidget(self.lb_descCtrl1, 1, 0)
        lay.addWidget(self.sb_Ctrl1, 1, 1)
        lay.addWidget(self.lb_Ctrl1, 1, 2)
        lay.addWidget(self.lb_descCtrl2, 2, 0)
        lay.addWidget(self.sb_Ctrl2, 2, 1)
        lay.addWidget(self.lb_Ctrl2, 2, 2)
        lay.addWidget(self.pb_open, 3, 1, 1, 2)
        lay.addWidget(self.dev_widget_scrarea, 0, 3, 4, 1)
Example #57
0
class MainWindow(QMainWindow):

    signal_fitting_parameters_changed = Signal()

    def __init__(self, *, gpc):
        """
        Parameters
        ----------
        gpc: object
            reference to a class that holds references to processing classes.
        """
        super().__init__()

        self._cursor_set = False  # Indicates if temporary 'wait' cursor is set

        self.gpc = gpc
        self.gui_vars = global_gui_variables
        self.gui_vars["ref_main_window"] = self

        self.wnd_manage_emission_lines = WndManageEmissionLines(gpc=self.gpc, gui_vars=self.gui_vars)
        self.wnd_compute_roi_maps = WndComputeRoiMaps(gpc=self.gpc, gui_vars=self.gui_vars)
        self.wnd_image_wizard = WndImageWizard(gpc=self.gpc, gui_vars=self.gui_vars)
        self.wnd_load_quantitative_calibration = WndLoadQuantitativeCalibration(
            gpc=self.gpc, gui_vars=self.gui_vars
        )
        self.wnd_general_fitting_settings = WndGeneralFittingSettings(gpc=self.gpc, gui_vars=self.gui_vars)
        self.wnd_fitting_parameters_shared = WndDetailedFittingParamsShared(gpc=self.gpc, gui_vars=self.gui_vars)
        self.wnd_fitting_parameters_lines = WndDetailedFittingParamsLines(gpc=self.gpc, gui_vars=self.gui_vars)
        # Indicates that the window was closed (used mostly for testing)
        self._is_closed = False

        global_gui_variables["gui_state"]["databroker_available"] = self.gpc.is_databroker_available()

        self.initialize()

        self.central_widget.left_panel.load_data_widget.update_main_window_title.connect(self.update_window_title)

        # Set the callback for update forms with fitting parameters
        def update_fitting_parameter_forms():
            self.signal_fitting_parameters_changed.emit()

        gpc.add_parameters_changed_cb(update_fitting_parameter_forms)
        gpc.param_model.parameters_changed()

    def initialize(self):

        self.resize(_main_window_geometry["initial_width"], _main_window_geometry["initial_height"])

        self.setMinimumWidth(_main_window_geometry["min_width"])
        self.setMinimumHeight(_main_window_geometry["min_height"])

        self.setWindowTitle(self.gpc.get_window_title())

        self.central_widget = TwoPanelWidget(gpc=self.gpc, gui_vars=self.gui_vars)
        self.setCentralWidget(self.central_widget)

        # Status bar
        self.statusLabel = QLabel()
        self.statusBar().addWidget(self.statusLabel)
        self.statusProgressBar = QProgressBar()
        self.statusProgressBar.setFixedWidth(200)
        self.statusBar().addPermanentWidget(self.statusProgressBar)

        self.statusLabelDefaultText = "No data is loaded"
        self.statusLabel.setText(self.statusLabelDefaultText)

        # 'Scan Data' menu item
        self.action_read_file = QAction("&Read File...", self)
        self.action_read_file.setStatusTip("Load data from HDF5 file")
        self.action_read_file.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_file.clicked)

        self.action_load_run = QAction("&Load Run...", self)
        self.action_load_run.setEnabled(self.gui_vars["gui_state"]["databroker_available"])
        self.action_load_run.setStatusTip("Load data from database (Databroker)")
        self.action_load_run.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_dbase.clicked)

        self.action_view_metadata = QAction("View Metadata...", self)
        self.action_view_metadata.setEnabled(self.gpc.is_scan_metadata_available())
        self.action_view_metadata.setStatusTip("View metadata for loaded run")
        self.action_view_metadata.triggered.connect(
            self.central_widget.left_panel.load_data_widget.pb_view_metadata.clicked
        )

        # Main menu
        menubar = self.menuBar()
        # Disable native menu bar (it doesn't work on MacOS 10.15 with PyQt<=5.11)
        #   It may work with later versions of PyQt when they become available.
        menubar.setNativeMenuBar(False)
        loadData = menubar.addMenu("Scan &Data")
        loadData.addAction(self.action_read_file)
        loadData.addAction(self.action_load_run)
        loadData.addSeparator()
        loadData.addAction(self.action_view_metadata)

        # 'Fitting Model' menu item
        self.action_lines_find_automatically = QAction("Find &Automatically...", self)
        self.action_lines_find_automatically.setStatusTip("Automatically find emission lines in total spectrum")
        self.action_lines_find_automatically.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_find_elines.clicked
        )

        self.action_lines_load_from_file = QAction("Load From &File...", self)
        self.action_lines_load_from_file.setStatusTip(
            "Load processing parameters, including selected emission lines, from JSON file"
        )
        self.action_lines_load_from_file.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_load_elines.clicked
        )

        self.action_lines_load_quant_standard = QAction("Load &Quantitative Standards...", self)
        self.action_lines_load_quant_standard.setStatusTip(
            "Load quantitative standard. The emission lines from the standard are automatically selected"
        )
        self.action_lines_load_quant_standard.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_load_qstandard.clicked
        )

        self.action_add_remove_emission_lines = QAction("&Add/Remove Emission Lines...", self)
        self.action_add_remove_emission_lines.setStatusTip("Manually add and remove emission lines")
        self.action_add_remove_emission_lines.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_manage_emission_lines.clicked
        )

        self.action_save_model_params = QAction("&Save Model Parameters...", self)
        self.action_save_model_params.setStatusTip("Save model parameters to JSON file")
        self.action_save_model_params.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_save_elines.clicked
        )

        self.action_add_remove_emission_lines = QAction("Start Model &Fitting", self)
        self.action_add_remove_emission_lines.setStatusTip("Run computations: start fitting for total spectrum")
        self.action_add_remove_emission_lines.triggered.connect(
            self.central_widget.left_panel.model_widget.pb_start_fitting.clicked
        )

        fittingModel = menubar.addMenu("Fitting &Model")
        emissionLines = fittingModel.addMenu("&Emission Lines")
        emissionLines.addAction(self.action_lines_find_automatically)
        emissionLines.addAction(self.action_lines_load_from_file)
        emissionLines.addAction(self.action_lines_load_quant_standard)
        fittingModel.addAction(self.action_add_remove_emission_lines)
        fittingModel.addSeparator()
        fittingModel.addAction(self.action_save_model_params)
        fittingModel.addSeparator()
        fittingModel.addAction(self.action_add_remove_emission_lines)

        # "XRF Maps" menu item
        self.action_start_xrf_map_fitting = QAction("Start XRF Map &Fitting", self)
        self.action_start_xrf_map_fitting.setStatusTip("Run computations: start fitting for XRF maps")
        self.action_start_xrf_map_fitting.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_start_map_fitting.clicked
        )

        self.action_compute_rois = QAction("Compute &ROIs...", self)
        self.action_compute_rois.setStatusTip("Compute XRF Maps based on spectral ROIs")
        self.action_compute_rois.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_compute_roi_maps.clicked
        )

        self.action_load_quant_calibration = QAction("&Load Quantitative Calibration...", self)
        self.action_load_quant_calibration.setStatusTip(
            "Load quantitative calibration from JSON file. Calibration is used for scaling of XRF Maps"
        )
        self.action_load_quant_calibration.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_load_quant_calib.clicked
        )

        self.action_save_quant_calibration = QAction("&Save Quantitative Calibration...", self)
        self.action_save_quant_calibration.setStatusTip(
            "Save Quantitative Calibration based on XRF map of the standard sample"
        )
        self.action_save_quant_calibration.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_save_q_calibration.clicked
        )

        self.action_export_to_tiff_and_txt = QAction("&Export to TIFF and TXT...", self)
        self.action_export_to_tiff_and_txt.setStatusTip("Export XRF Maps as TIFF and/or TXT files")
        self.action_export_to_tiff_and_txt.triggered.connect(
            self.central_widget.left_panel.fit_maps_widget.pb_export_to_tiff_and_txt.clicked
        )

        xrfMaps = menubar.addMenu("XRF &Maps")
        xrfMaps.addAction(self.action_start_xrf_map_fitting)
        xrfMaps.addAction(self.action_compute_rois)
        xrfMaps.addSeparator()
        xrfMaps.addAction(self.action_load_quant_calibration)
        xrfMaps.addSeparator()
        xrfMaps.addAction(self.action_save_quant_calibration)
        xrfMaps.addAction(self.action_export_to_tiff_and_txt)

        # "View" menu item
        self.action_show_matplotlib_toolbar = QAction("Show &Matplotlib Toolbar", self)
        self.action_show_matplotlib_toolbar.setCheckable(True)
        self.action_show_matplotlib_toolbar.setChecked(True)
        self.action_show_matplotlib_toolbar.setStatusTip("Show Matplotlib Toolbar on the plots")
        self.action_show_matplotlib_toolbar.toggled.connect(self.action_show_matplotlib_toolbar_toggled)

        self.action_show_widget_tooltips = QAction("Show &Tooltips", self)
        self.action_show_widget_tooltips.setCheckable(True)
        self.action_show_widget_tooltips.setChecked(self.gui_vars["show_tooltip"])
        self.action_show_widget_tooltips.setStatusTip("Show widget tooltips")
        self.action_show_widget_tooltips.toggled.connect(self.action_show_widget_tooltips_toggled)

        options = menubar.addMenu("&Options")
        options.addAction(self.action_show_widget_tooltips)
        options.addAction(self.action_show_matplotlib_toolbar)

        # "Help" menu item
        self.action_online_docs = QAction("Online &Documentation", self)
        self.action_online_docs.setStatusTip("Open online documentation in the default browser")
        self.action_online_docs.triggered.connect(self.action_online_docs_triggered)

        self.action_about = QAction("&About PyXRF", self)
        self.action_about.setStatusTip("Show information about this program")
        self.action_about.triggered.connect(self.action_about_triggered)

        help = menubar.addMenu("&Help")
        help.addAction(self.action_online_docs)
        help.addSeparator()
        help.addAction(self.action_about)

        self.update_widget_state()

        # Connect signals
        self.central_widget.left_panel.load_data_widget.update_preview_map_range.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_count.update_map_range
        )

        # Before loading a new file or run
        self.central_widget.left_panel.load_data_widget.signal_loading_new_run.connect(
            self.central_widget.right_panel.slot_activate_tab_preview
        )

        # Open a new file or run
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.slot_activate_load_data_tab
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.slot_activate_tab_preview
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(self.slot_new_run_loaded)
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.wnd_image_wizard.slot_update_table
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.wnd_load_quantitative_calibration.update_all_data
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.fit_maps_widget.slot_update_for_new_loaded_run
        )

        # New model is loaded or processing parameters (incident energy) was changed
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.wnd_manage_emission_lines.update_widget_data
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.redraw_plot_fit
        )

        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.slot_activate_tab_fitting_model
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.update_controls
        )
        self.central_widget.left_panel.model_widget.signal_model_loaded.connect(
            self.wnd_manage_emission_lines.update_widget_data
        )

        # XRF Maps dataset changed
        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect(
            self.wnd_image_wizard.slot_update_table
        )

        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.combo_select_dataset_update_current_index
        )
        self.central_widget.right_panel.tab_plot_rgb_maps.signal_rgb_maps_dataset_selection_changed.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.combo_select_dataset_update_current_index
        )

        self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_norm_changed.connect(
            self.wnd_image_wizard.slot_update_ranges
        )

        # Quantitative calibration changed
        self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_ranges
        )
        self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect(
            self.wnd_image_wizard.slot_update_ranges
        )

        # Selected element is changed (tools for emission line selection)
        self.wnd_manage_emission_lines.signal_selected_element_changed.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect(
            self.wnd_manage_emission_lines.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_add_line.connect(
            self.wnd_manage_emission_lines.pb_add_eline_clicked
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_remove_line.connect(
            self.wnd_manage_emission_lines.pb_remove_eline_clicked
        )
        self.wnd_manage_emission_lines.signal_update_element_selection_list.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_update_eline_selection_list
        )
        self.wnd_manage_emission_lines.signal_update_add_remove_btn_state.connect(
            self.central_widget.right_panel.tab_plot_fitting_model.slot_update_add_remove_btn_state
        )

        self.wnd_manage_emission_lines.signal_selected_element_changed.connect(
            self.central_widget.left_panel.model_widget.slot_selection_item_changed
        )
        self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect(
            self.central_widget.left_panel.model_widget.slot_selection_item_changed
        )

        # Total spectrum fitting completed
        self.central_widget.left_panel.model_widget.signal_total_spectrum_fitting_completed.connect(
            self.wnd_manage_emission_lines.update_eline_table
        )
        # Total spectrum invalidated
        self.wnd_manage_emission_lines.signal_parameters_changed.connect(
            self.central_widget.left_panel.model_widget.update_fit_status
        )
        # New dataset loaded or different channel selected. Compute fit parameters.
        self.central_widget.left_panel.load_data_widget.signal_data_channel_changed.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )
        self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )
        self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect(
            self.central_widget.left_panel.model_widget.clear_fit_status
        )

        # Update map datasets (Fitted maps)
        self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.central_widget.left_panel.fit_maps_widget.signal_activate_tab_xrf_maps.connect(
            self.central_widget.right_panel.slot_activate_tab_xrf_maps
        )

        # Update map datasets (ROI maps)
        self.wnd_compute_roi_maps.signal_roi_computation_complete.connect(
            self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info
        )
        self.wnd_compute_roi_maps.signal_roi_computation_complete.connect(
            self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info
        )
        self.wnd_compute_roi_maps.signal_activate_tab_xrf_maps.connect(
            self.central_widget.right_panel.slot_activate_tab_xrf_maps
        )

        self.signal_fitting_parameters_changed.connect(self.wnd_general_fitting_settings.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_shared.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_lines.update_form_data)
        self.signal_fitting_parameters_changed.connect(self.wnd_manage_emission_lines.update_widget_data)

    @Slot()
    @Slot(str)
    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.menuBar().setEnabled(state)

        state_computations = self.gui_vars["gui_state"]["running_computations"]
        if state_computations:
            if not self._cursor_set:
                QGuiApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
                self._cursor_set = True
        else:
            if self._cursor_set:
                QGuiApplication.restoreOverrideCursor()
                self._cursor_set = False

        # Forward to children
        self.central_widget.update_widget_state(condition)

        # Forward the updates to open windows
        self.wnd_manage_emission_lines.update_widget_state(condition)
        self.wnd_compute_roi_maps.update_widget_state(condition)
        self.wnd_image_wizard.update_widget_state(condition)
        self.wnd_load_quantitative_calibration.update_widget_state(condition)
        self.wnd_general_fitting_settings.update_widget_state(condition)
        self.wnd_fitting_parameters_shared.update_widget_state(condition)
        self.wnd_fitting_parameters_lines.update_widget_state(condition)

    def closeEvent(self, event):
        mb_close = QMessageBox(
            QMessageBox.Question,
            "Exit",
            "Are you sure you want to EXIT the program?",
            QMessageBox.Yes | QMessageBox.No,
            parent=self,
        )
        mb_close.setDefaultButton(QMessageBox.No)

        if mb_close.exec() == QMessageBox.Yes:
            event.accept()

            self.wnd_manage_emission_lines.close()
            self.wnd_compute_roi_maps.close()
            self.wnd_image_wizard.close()
            self.wnd_load_quantitative_calibration.close()
            self.wnd_general_fitting_settings.close()
            self.wnd_fitting_parameters_shared.close()
            self.wnd_fitting_parameters_lines.close()

            # This flag is used for CI tests
            self._is_closed = True
        else:
            event.ignore()

    def action_online_docs_triggered(self):
        """
        Display online documentation: open the URL in the default browser.
        """
        doc_url = "http://nsls-ii.github.io/PyXRF/"
        try:
            webbrowser.open(doc_url, autoraise=True)
        except Exception as ex:
            logger.error(f"Error occurred while opening URL '{doc_url}' in the default browser")
            msg = f"Failed to Open Online Documentation. \n  Exception: {str(ex)}"
            msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self)
            msgbox.exec()

    def action_about_triggered(self):
        """
        Display 'About' dialog box
        """
        dlg = DialogAbout()
        dlg.exec()

    def action_show_matplotlib_toolbar_toggled(self, state):
        """
        Turn tooltips on or off
        """
        self.gui_vars["show_matplotlib_toolbar"] = state
        self.update_widget_state()

    def action_show_widget_tooltips_toggled(self, state):
        """
        Turn tooltips on or off
        """
        self.gui_vars["show_tooltip"] = state
        self.update_widget_state("tooltips")

    @Slot()
    def update_window_title(self):
        self.setWindowTitle(self.gpc.get_window_title())

    @Slot(bool)
    def slot_new_run_loaded(self, success):
        if success:
            # Update status bar
            file_name = self.gpc.get_loaded_file_name()
            run_id, run_uid = "", ""
            if self.gpc.is_scan_metadata_available():
                try:
                    run_id = self.gpc.get_metadata_scan_id()
                except Exception:
                    pass
                try:
                    run_uid = self.gpc.get_metadata_scan_uid()
                except Exception:
                    pass
            else:
                if self.gpc.get_current_run_id() >= 0:
                    run_id = self.gpc.get_current_run_id()
            s = ""
            if run_id:
                s += f"ID: {run_id}  "
            if run_uid:
                if run_id:
                    s += f"({run_uid})  "
                else:
                    s += f"UID: {run_uid}  "
            if file_name:
                s += f"File: '{file_name}'"
            self._set_status_bar_text(s)

            # Activate/deactivate "View Metadata ..." menu item
            if self.gpc.is_scan_metadata_available():
                self.action_view_metadata.setEnabled(True)
            else:
                self.action_view_metadata.setEnabled(False)

        else:
            self._set_status_bar_text()

    def _set_status_bar_text(self, text=None):
        if text is None:
            text = self.statusLabelDefaultText
        self.statusLabel.setText(text)
Example #58
0
class FindInFilesWidget(PluginMainWidget):
    """
    Find in files main widget.
    """

    ENABLE_SPINNER = True
    REGEX_INVALID = f"background-color:{SpyderPalette.COLOR_ERROR_2};"
    REGEX_ERROR = _("Regular expression error")

    # Signals
    sig_edit_goto_requested = Signal(str, int, str, int, int)
    """
    This signal will request to open a file in a given row and column
    using a code editor.

    Parameters
    ----------
    path: str
        Path to file.
    row: int
        Cursor starting row position.
    word: str
        Word to select on given row.
    start_column: int
        Starting column of found word.
    end_column:
        Ending column of found word.
    """

    sig_finished = Signal()
    """
    This signal is emitted to inform the search process has finished.
    """

    sig_max_results_reached = Signal()
    """
    This signal is emitted to inform the search process has finished due
    to reaching the maximum number of results.
    """
    def __init__(self, name=None, plugin=None, parent=None):
        super().__init__(name, plugin, parent=parent)
        self.set_conf('text_color', MAIN_TEXT_COLOR)
        self.set_conf('hist_limit', MAX_PATH_HISTORY)

        # Attributes
        self.text_color = self.get_conf('text_color')
        self.supported_encodings = self.get_conf('supported_encodings')
        self.search_thread = None
        self.running = False
        self.more_options_action = None
        self.extras_toolbar = None

        search_text = self.get_conf('search_text', '')
        path_history = self.get_conf('path_history', [])
        exclude = self.get_conf('exclude')

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

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

        if not isinstance(path_history, (list, tuple)):
            path_history = [path_history]

        # Widgets
        self.search_text_edit = PatternComboBox(
            self,
            search_text,
            id_=FindInFilesWidgetToolbarItems.SearchPatternCombo)

        self.search_text_edit.lineEdit().setPlaceholderText(
            _('Write text to search'))

        self.search_in_label = QLabel(_('Search in:'))
        self.search_in_label.ID = FindInFilesWidgetToolbarItems.SearchInLabel

        self.exclude_label = QLabel(_('Exclude:'))
        self.exclude_label.ID = FindInFilesWidgetToolbarItems.ExcludeLabel

        self.path_selection_combo = SearchInComboBox(
            path_history,
            self,
            id_=FindInFilesWidgetToolbarItems.SearchInCombo)

        self.exclude_pattern_edit = PatternComboBox(
            self,
            exclude,
            _("Exclude pattern"),
            id_=FindInFilesWidgetToolbarItems.ExcludePatternCombo)

        self.result_browser = ResultsBrowser(
            self,
            text_color=self.text_color,
            max_results=self.get_conf('max_results'),
        )

        # Setup
        self.exclude_label.setBuddy(self.exclude_pattern_edit)
        exclude_idx = self.get_conf('exclude_index', None)
        if (exclude_idx is not None and exclude_idx >= 0
                and exclude_idx < self.exclude_pattern_edit.count()):
            self.exclude_pattern_edit.setCurrentIndex(exclude_idx)

        search_in_index = self.get_conf('search_in_index', None)
        self.path_selection_combo.set_current_searchpath_index(search_in_index)

        # Layout
        layout = QHBoxLayout()
        layout.addWidget(self.result_browser)
        self.setLayout(layout)

        # Signals
        self.path_selection_combo.sig_redirect_stdio_requested.connect(
            self.sig_redirect_stdio_requested)
        self.search_text_edit.valid.connect(lambda valid: self.find())
        self.exclude_pattern_edit.valid.connect(lambda valid: self.find())
        self.result_browser.sig_edit_goto_requested.connect(
            self.sig_edit_goto_requested)
        self.result_browser.sig_max_results_reached.connect(
            self.sig_max_results_reached)
        self.result_browser.sig_max_results_reached.connect(
            self._stop_and_reset_thread)
        self.search_text_edit.sig_resized.connect(self._update_size)

    # --- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _("Find")

    def get_focus_widget(self):
        return self.search_text_edit

    def setup(self):
        self.search_regexp_action = self.create_action(
            FindInFilesWidgetActions.ToggleSearchRegex,
            text=_('Regular expression'),
            tip=_('Use regular expressions'),
            icon=self.create_icon('regex'),
            toggled=True,
            initial=self.get_conf('search_text_regexp'),
            option='search_text_regexp')
        self.case_action = self.create_action(
            FindInFilesWidgetActions.ToggleExcludeCase,
            text=_("Case sensitive"),
            tip=_("Case sensitive search"),
            icon=self.create_icon("format_letter_case"),
            toggled=True,
            initial=self.get_conf('case_sensitive'),
            option='case_sensitive')
        self.find_action = self.create_action(
            FindInFilesWidgetActions.Find,
            text=_("&Find in files"),
            tip=_("Search text"),
            icon=self.create_icon('find'),
            triggered=self.find,
            register_shortcut=False,
        )
        self.exclude_regexp_action = self.create_action(
            FindInFilesWidgetActions.ToggleExcludeRegex,
            text=_('Regular expression'),
            tip=_('Use regular expressions'),
            icon=self.create_icon('regex'),
            toggled=True,
            initial=self.get_conf('exclude_regexp'),
            option='exclude_regexp')
        self.exclude_case_action = self.create_action(
            FindInFilesWidgetActions.ToggleCase,
            text=_("Exclude case sensitive"),
            tip=_("Exclude case sensitive"),
            icon=self.create_icon("format_letter_case"),
            toggled=True,
            initial=self.get_conf('exclude_case_sensitive'),
            option='exclude_case_sensitive')
        self.more_options_action = self.create_action(
            FindInFilesWidgetActions.ToggleMoreOptions,
            text=_('Show advanced options'),
            tip=_('Show advanced options'),
            icon=self.create_icon("options_more"),
            toggled=True,
            initial=self.get_conf('more_options'),
            option='more_options')
        self.set_max_results_action = self.create_action(
            FindInFilesWidgetActions.MaxResults,
            text=_('Set maximum number of results'),
            tip=_('Set maximum number of results'),
            triggered=lambda x=None: self.set_max_results(),
        )

        # Toolbar
        toolbar = self.get_main_toolbar()
        for item in [
                self.search_text_edit, self.find_action,
                self.search_regexp_action, self.case_action,
                self.more_options_action
        ]:
            self.add_item_to_toolbar(
                item,
                toolbar=toolbar,
                section=FindInFilesWidgetMainToolbarSections.Main,
            )

        # Exclude Toolbar
        self.extras_toolbar = self.create_toolbar(
            FindInFilesWidgetToolbars.Exclude)

        stretcher = self.create_stretcher()
        stretcher.ID = FindInFilesWidgetToolbarItems.Stretcher1
        for item in [
                self.exclude_label, self.exclude_pattern_edit,
                self.exclude_regexp_action, stretcher
        ]:
            self.add_item_to_toolbar(
                item,
                toolbar=self.extras_toolbar,
                section=FindInFilesWidgetExcludeToolbarSections.Main,
            )

        # Location toolbar
        location_toolbar = self.create_toolbar(
            FindInFilesWidgetToolbars.Location)
        for item in [self.search_in_label, self.path_selection_combo]:
            self.add_item_to_toolbar(
                item,
                toolbar=location_toolbar,
                section=FindInFilesWidgetLocationToolbarSections.Main,
            )

        menu = self.get_options_menu()
        self.add_item_to_menu(
            self.set_max_results_action,
            menu=menu,
        )

    def update_actions(self):
        self.find_action.setIcon(
            self.create_icon('stop' if self.running else 'find'))

        if self.extras_toolbar and self.more_options_action:
            self.extras_toolbar.setVisible(
                self.more_options_action.isChecked())

    @on_conf_change(option='more_options')
    def on_more_options_update(self, value):
        self.exclude_pattern_edit.setMinimumWidth(
            self.search_text_edit.width())

        if value:
            icon = self.create_icon('options_less')
            tip = _('Hide advanced options')
        else:
            icon = self.create_icon('options_more')
            tip = _('Show advanced options')

        if self.extras_toolbar:
            self.extras_toolbar.setVisible(value)

        if self.more_options_action:
            self.more_options_action.setIcon(icon)
            self.more_options_action.setToolTip(tip)

    @on_conf_change(option='max_results')
    def on_max_results_update(self, value):
        self.result_browser.set_max_results(value)

    # --- Private API
    # ------------------------------------------------------------------------
    def _update_size(self, size, old_size):
        self.exclude_pattern_edit.setMinimumWidth(size.width())

    def _get_options(self):
        """
        Get search options.
        """
        text_re = self.search_regexp_action.isChecked()
        exclude_re = self.exclude_regexp_action.isChecked()
        case_sensitive = self.case_action.isChecked()

        # Clear fields
        self.search_text_edit.lineEdit().setStyleSheet("")
        self.exclude_pattern_edit.lineEdit().setStyleSheet("")
        self.exclude_pattern_edit.setToolTip("")
        self.search_text_edit.setToolTip("")

        utext = str(self.search_text_edit.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

        exclude = str(self.exclude_pattern_edit.currentText())

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

        file_search = self.path_selection_combo.is_file_search()
        path = self.path_selection_combo.get_current_searchpath()

        if not exclude_re:
            items = [
                fnmatch.translate(item.strip()) for item in exclude.split(",")
                if item.strip() != ''
            ]
            exclude = '|'.join(items)

        # Validate exclude regular expression
        if exclude:
            error_msg = regexp_error_msg(exclude)
            if error_msg:
                exclude_edit = self.exclude_pattern_edit.lineEdit()
                exclude_edit.setStyleSheet(self.REGEX_INVALID)
                tooltip = self.REGEX_ERROR + ': ' + str(error_msg)
                self.exclude_pattern_edit.setToolTip(tooltip)
                return None
            else:
                exclude = re.compile(exclude)

        # Validate text regular expression
        if text_re:
            error_msg = regexp_error_msg(texts[0][0])
            if error_msg:
                self.search_text_edit.lineEdit().setStyleSheet(
                    self.REGEX_INVALID)
                tooltip = self.REGEX_ERROR + ': ' + str(error_msg)
                self.search_text_edit.setToolTip(tooltip)
                return None
            else:
                texts = [(re.compile(x[0]), x[1]) for x in texts]

        return (path, file_search, exclude, texts, text_re, case_sensitive)

    def _update_options(self):
        """
        Extract search options from widgets and set the corresponding option.
        """
        hist_limit = self.get_conf('hist_limit')
        search_texts = [
            str(self.search_text_edit.itemText(index))
            for index in range(self.search_text_edit.count())
        ]
        excludes = [
            str(self.exclude_pattern_edit.itemText(index))
            for index in range(self.exclude_pattern_edit.count())
        ]
        path_history = self.path_selection_combo.get_external_paths()

        self.set_conf('path_history', path_history)
        self.set_conf('search_text', search_texts[:hist_limit])
        self.set_conf('exclude', excludes[:hist_limit])
        self.set_conf('path_history', path_history[-hist_limit:])
        self.set_conf('exclude_index',
                      self.exclude_pattern_edit.currentIndex())
        self.set_conf('search_in_index',
                      self.path_selection_combo.currentIndex())

    def _handle_search_complete(self, completed):
        """
        Current search thread has finished.
        """
        self.result_browser.set_sorting(ON)
        self.result_browser.set_width()
        self.result_browser.expandAll()
        if self.search_thread is None:
            return

        self.sig_finished.emit()
        found = self.search_thread.get_results()
        self._stop_and_reset_thread()
        if found is not None:
            self.result_browser.show()

        self.stop_spinner()
        self.update_actions()

    def _stop_and_reset_thread(self, ignore_results=False):
        """Stop current search thread and clean-up."""
        if self.search_thread is not None:
            if self.search_thread.isRunning():
                if ignore_results:
                    self.search_thread.sig_finished.disconnect(
                        self.search_complete)
                self.search_thread.stop()
                self.search_thread.wait()

            self.search_thread.setParent(None)
            self.search_thread = None

        self.running = False
        self.stop_spinner()
        self.update_actions()

    # --- Public API
    # ------------------------------------------------------------------------
    @property
    def path(self):
        """Return the current path."""
        return self.path_selection_combo.path

    @property
    def project_path(self):
        """Return the current project path."""
        return self.path_selection_combo.project_path

    @property
    def file_path(self):
        """Return the current file path."""
        return self.path_selection_combo.file_path

    def set_directory(self, directory):
        """
        Set directory as current path.

        Parameters
        ----------
        directory: str
            Directory path string.
        """
        self.path_selection_combo.path = osp.abspath(directory)

    def set_project_path(self, path):
        """
        Set path as current project path.

        Parameters
        ----------
        path: str
            Project path string.
        """
        self.path_selection_combo.set_project_path(path)

    def disable_project_search(self):
        """Disable project search path in combobox."""
        self.path_selection_combo.set_project_path(None)

    def set_file_path(self, path):
        """
        Set path as current file path.

        Parameters
        ----------
        path: str
            File path string.
        """
        self.path_selection_combo.file_path = path

    def set_search_text(self, text):
        """
        Set current search text.

        Parameters
        ----------
        text: str
            Search string.

        Notes
        -----
        If `text` is empty, focus will be given to the search lineedit and no
        search will be performed.
        """
        if text:
            self.search_text_edit.add_text(text)
            self.search_text_edit.lineEdit().selectAll()

        self.search_text_edit.setFocus()

    def find(self):
        """
        Start/stop find action.

        Notes
        -----
        If there is no search running, this will start the search. If there is
        a search running, this will stop it.
        """
        if self.running:
            self.stop()
        else:
            self.start()

    def stop(self):
        """Stop find thread."""
        self._stop_and_reset_thread()

    def start(self):
        """Start find thread."""
        options = self._get_options()
        if options is None:
            return

        self._stop_and_reset_thread(ignore_results=True)
        search_text = self.search_text_edit.currentText()

        # Update and set options
        self._update_options()

        # Setup result_browser
        self.result_browser.set_path(options[0])
        self.result_browser.longest_file_item = ''
        self.result_browser.longest_line_item = ''

        # Start
        self.running = True
        self.start_spinner()
        self.search_thread = SearchThread(self, search_text, self.text_color)
        self.search_thread.sig_finished.connect(self._handle_search_complete)
        self.search_thread.sig_file_match.connect(
            self.result_browser.append_file_result)
        self.search_thread.sig_line_match.connect(
            self.result_browser.append_result)
        self.result_browser.clear_title(search_text)
        self.search_thread.initialize(*self._get_options())
        self.search_thread.start()
        self.update_actions()

    def add_external_path(self, path):
        """
        Parameters
        ----------
        path: str
            Path to add to combobox.
        """
        self.path_selection_combo.add_external_path(path)

    def set_max_results(self, value=None):
        """
        Set maximum amount of results to add to the result browser.

        Parameters
        ----------
        value: int, optional
            Number of results. If None an input dialog will be used.
            Default is None.
        """
        if value is None:
            # Create dialog
            dialog = QInputDialog(self)

            # Set dialog properties
            dialog.setModal(False)
            dialog.setWindowTitle(_('Max results'))
            dialog.setLabelText(_('Set maximum number of results: '))
            dialog.setInputMode(QInputDialog.IntInput)
            dialog.setIntRange(1, 10000)
            dialog.setIntStep(1)
            dialog.setIntValue(self.get_conf('max_results'))

            # Connect slot
            dialog.intValueSelected.connect(
                lambda value: self.set_conf('max_results', value))

            dialog.show()
        else:
            self.set_conf('max_results', value)
Example #59
0
    def __init__(self, layer):
        super().__init__(layer)

        self.layer.events.interpolation.connect(self._on_interpolation_change)
        self.layer.events.rendering.connect(self._on_rendering_change)
        self.layer.events.iso_threshold.connect(self._on_iso_threshold_change)
        self.layer.events.attenuation.connect(self._on_attenuation_change)
        self.layer._dims.events.ndisplay.connect(self._on_ndisplay_change)

        self.interpComboBox = QComboBox(self)
        self.interpComboBox.activated[str].connect(self.changeInterpolation)
        self.interpLabel = QLabel('interpolation:')

        renderComboBox = QComboBox(self)
        renderComboBox.addItems(Rendering.keys())
        index = renderComboBox.findText(self.layer.rendering,
                                        Qt.MatchFixedString)
        renderComboBox.setCurrentIndex(index)
        renderComboBox.activated[str].connect(self.changeRendering)
        self.renderComboBox = renderComboBox
        self.renderLabel = QLabel('rendering:')

        sld = QSlider(Qt.Horizontal, parent=self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setMinimum(0)
        sld.setMaximum(100)
        sld.setSingleStep(1)
        sld.setValue(int(self.layer.iso_threshold * 100))
        sld.valueChanged.connect(self.changeIsoThreshold)
        self.isoThresholdSlider = sld
        self.isoThresholdLabel = QLabel('iso threshold:')

        sld = QSlider(Qt.Horizontal, parent=self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setMinimum(0)
        sld.setMaximum(100)
        sld.setSingleStep(1)
        sld.setValue(int(self.layer.attenuation * 200))
        sld.valueChanged.connect(self.changeAttenuation)
        self.attenuationSlider = sld
        self.attenuationLabel = QLabel('attenuation:')
        self._on_ndisplay_change()

        colormap_layout = QHBoxLayout()
        if hasattr(self.layer, 'rgb') and self.layer.rgb:
            colormap_layout.addWidget(QLabel("RGB"))
            self.colormapComboBox.setVisible(False)
            self.colorbarLabel.setVisible(False)
        else:
            colormap_layout.addWidget(self.colorbarLabel)
            colormap_layout.addWidget(self.colormapComboBox)
        colormap_layout.addStretch(1)

        # grid_layout created in QtLayerControls
        # addWidget(widget, row, column, [row_span, column_span])
        self.grid_layout.addWidget(QLabel('opacity:'), 0, 0)
        self.grid_layout.addWidget(self.opacitySlider, 0, 1)
        self.grid_layout.addWidget(QLabel('contrast limits:'), 1, 0)
        self.grid_layout.addWidget(self.contrastLimitsSlider, 1, 1)
        self.grid_layout.addWidget(QLabel('gamma:'), 2, 0)
        self.grid_layout.addWidget(self.gammaSlider, 2, 1)
        self.grid_layout.addWidget(QLabel('colormap:'), 3, 0)
        self.grid_layout.addLayout(colormap_layout, 3, 1)
        self.grid_layout.addWidget(QLabel('blending:'), 4, 0)
        self.grid_layout.addWidget(self.blendComboBox, 4, 1)
        self.grid_layout.addWidget(self.interpLabel, 5, 0)
        self.grid_layout.addWidget(self.interpComboBox, 5, 1)
        self.grid_layout.addWidget(self.renderLabel, 6, 0)
        self.grid_layout.addWidget(self.renderComboBox, 6, 1)
        self.grid_layout.addWidget(self.isoThresholdLabel, 7, 0)
        self.grid_layout.addWidget(self.isoThresholdSlider, 7, 1)
        self.grid_layout.addWidget(self.attenuationLabel, 8, 0)
        self.grid_layout.addWidget(self.attenuationSlider, 8, 1)
        self.grid_layout.setRowStretch(9, 1)
        self.grid_layout.setColumnStretch(1, 1)
        self.grid_layout.setSpacing(4)
Example #60
0
    def __init__(self, directory=None):
        QWidget.__init__(self)
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)

        self.explorer = ProjectExplorerWidget(self, show_all=True)
        if directory is not None:
            self.directory = directory
        else:
            self.directory = osp.dirname(osp.abspath(__file__))
        self.explorer.setup_project(self.directory)
        vlayout.addWidget(self.explorer)

        hlayout1 = QHBoxLayout()
        vlayout.addLayout(hlayout1)
        label = QLabel("<b>Open file:</b>")
        label.setAlignment(Qt.AlignRight)
        hlayout1.addWidget(label)
        self.label1 = QLabel()
        hlayout1.addWidget(self.label1)
        self.explorer.sig_open_file.connect(self.label1.setText)

        hlayout3 = QHBoxLayout()
        vlayout.addLayout(hlayout3)
        label = QLabel("<b>Option changed:</b>")
        label.setAlignment(Qt.AlignRight)
        hlayout3.addWidget(label)
        self.label3 = QLabel()
        hlayout3.addWidget(self.label3)
        self.explorer.sig_option_changed.connect(
            lambda x, y: self.label3.setText('option_changed: %r, %r' %
                                             (x, y)))