def create_file_combobox(self, text, choices, option, default=NoDefault, tip=None, restart=False, filters=None, adjust_to_contents=False, default_line_edit=False): """choices: couples (name, key)""" combobox = FileComboBox(self, adjust_to_contents=adjust_to_contents, default_line_edit=default_line_edit) combobox.restart_required = restart combobox.label_text = text edit = combobox.lineEdit() edit.label_text = text edit.restart_required = restart self.lineedits[edit] = (option, default) if tip is not None: combobox.setToolTip(tip) combobox.addItems(choices) msg = _('Invalid file path') self.validate_data[edit] = (osp.isfile, msg) browse_btn = QPushButton(ima.icon('FileIcon'), '', self) browse_btn.setToolTip(_("Select file")) browse_btn.clicked.connect(lambda: self.select_file(edit, filters)) layout = QGridLayout() layout.addWidget(combobox, 0, 0, 0, 9) layout.addWidget(browse_btn, 0, 10) layout.setContentsMargins(0, 0, 0, 0) widget = QWidget(self) widget.combobox = combobox widget.browse_btn = browse_btn widget.setLayout(layout) return widget
def setup_gui(self): """Setup the main layout of the widget.""" layout = QGridLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.canvas, 0, 1) layout.addLayout(self.setup_toolbar(), 0, 3, 2, 1) layout.setColumnStretch(0, 100) layout.setColumnStretch(2, 100) layout.setRowStretch(1, 100)
def _setupFeedbackSettings(self): ld_fbenbl = QLabel('Enable', self) pb_fbenbl = PyDMStateButton(self, self.dev_pref+':FBCTRL') ld_coefsel = QLabel('Coeficient Set', self) cb_coefsel = PyDMEnumComboBox(self, self.dev_pref+':SETSEL') ld_sftgain = QLabel('Shift Gain', self) sb_sftgain = PyDMSpinbox(self, self.dev_pref+':SHIFTGAIN') sb_sftgain.showStepExponent = False ld_downspl = QLabel('Downsampling', self) sb_downspl = PyDMSpinbox(self, self.dev_pref+':PROC_DS') sb_downspl.showStepExponent = False ld_satthrs = QLabel('Sat. Threshold [%]', self) sb_satthrs = PyDMSpinbox(self, self.dev_pref+':SAT_THRESHOLD') sb_satthrs.showStepExponent = False lay = QGridLayout() lay.addWidget(ld_fbenbl, 1, 0) lay.addWidget(pb_fbenbl, 1, 1) lay.addWidget(ld_coefsel, 2, 0) lay.addWidget(cb_coefsel, 2, 1) lay.addWidget(ld_sftgain, 3, 0) lay.addWidget(sb_sftgain, 3, 1) lay.addWidget(ld_downspl, 4, 0) lay.addWidget(sb_downspl, 4, 1) lay.addWidget(ld_satthrs, 5, 0) lay.addWidget(sb_satthrs, 5, 1) if self._is_resumed: wid = QWidget() wid.setLayout(lay) fb_label = QLabel( '<h4>Feedback Settings</h4>', self, alignment=Qt.AlignCenter) lay.setContentsMargins(0, 0, 0, 0) lay.setVerticalSpacing(12) lay.addWidget(fb_label, 0, 0, 1, 2) else: wid = QGroupBox('Feedback Settings', self) wid.setLayout(lay) ld_gensts = QLabel('Setup Status', self) led_gensts = SiriusLedAlert(self, self.dev_pref+':ERRSUM') lay.addWidget(ld_gensts, 6, 0) lay.addWidget(led_gensts, 6, 1) return wid
def setTempHeader(self, item_list, layout): ''' Display Temperature header labels ''' widget = QWidget() hd_glay = QGridLayout() pos = 0 for item in item_list: lbl_header = QLabel('<h4>' + item + '</h4>') lbl_header.setAlignment(Qt.AlignCenter) hd_glay.addWidget(lbl_header, 0, pos, 1, 3) pos += 3 hd_glay = self.setParamLabel(hd_glay) hd_glay.setContentsMargins(0, 0, 0, 0) widget.setLayout(hd_glay) layout.addWidget(widget, 0, 1, 1, 6) return layout
def _setupUi(self): fbsett_wid = self._setupFeedbackSettings() status_wid = self._setupStatusWidget() lay = QGridLayout(self) lay.setAlignment(Qt.AlignTop | Qt.AlignCenter) if self._is_resumed: led_gensts = SiriusLedAlert(self, self.dev_pref+':ERRSUM') dev_label = QLabel( '<h3>'+self._label+'</h3>', self, alignment=Qt.AlignCenter) self.pb_detail = QPushButton(qta.icon('fa5s.ellipsis-v'), '', self) self.pb_detail.setObjectName('dtls') self.pb_detail.setStyleSheet( '#dtls{min-width:20px;max-width:20px;icon-size:15px;}') cmd = ['sirius-hla-si-di-bbb.py', '-dev', self.dev_pref] if self._prefix: cmd.extend(['-p', self._prefix]) connect_newprocess(self.pb_detail, cmd, self) hbox_label = QHBoxLayout() hbox_label.setContentsMargins(0, 0, 0, 0) hbox_label.addWidget(led_gensts, alignment=Qt.AlignLeft) hbox_label.addWidget(dev_label) hbox_label.addWidget(self.pb_detail, alignment=Qt.AlignRight) hbox_label.setStretch(0, 1) hbox_label.setStretch(1, 10) hbox_label.setStretch(2, 1) wid = QWidget(self) wid.setObjectName('box') wid.setStyleSheet(""" #box{border: 2px solid gray;}""") lay_box = QGridLayout(wid) lay_box.setVerticalSpacing(15) lay_box.addLayout(hbox_label, 0, 0) lay_box.addWidget(fbsett_wid, 1, 0) lay_box.addWidget(status_wid, 2, 0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(wid) else: info_wid = BbBInfoWidget(self, self._prefix, self._device) lay.addWidget(fbsett_wid, 0, 1) lay.addWidget(status_wid, 0, 2) lay.addWidget(info_wid, 0, 3) lay.setColumnStretch(0, 3) lay.setColumnStretch(4, 3) lay.setRowStretch(1, 3)
def create_file_combobox(self, text, choices, option, default=NoDefault, tip=None, restart=False, filters=None, adjust_to_contents=False, default_line_edit=False, section=None, validate_callback=None): """choices: couples (name, key)""" if section is not None and section != self.CONF_SECTION: self.cross_section_options[option] = section combobox = FileComboBox(self, adjust_to_contents=adjust_to_contents, default_line_edit=default_line_edit) combobox.restart_required = restart combobox.label_text = text edit = combobox.lineEdit() edit.label_text = text edit.restart_required = restart self.lineedits[edit] = (section, option, default) if tip is not None: combobox.setToolTip(tip) combobox.addItems(choices) combobox.choices = choices msg = _('Invalid file path') self.validate_data[edit] = (validate_callback if validate_callback else osp.isfile, msg) browse_btn = QPushButton(ima.icon('FileIcon'), '', self) browse_btn.setToolTip(_("Select file")) browse_btn.clicked.connect(lambda: self.select_file(edit, filters)) layout = QGridLayout() layout.addWidget(combobox, 0, 0, 0, 9) layout.addWidget(browse_btn, 0, 10) layout.setContentsMargins(0, 0, 0, 0) widget = QWidget(self) widget.combobox = combobox widget.browse_btn = browse_btn widget.setLayout(layout) return widget
def _create_corr_summwidget(self, corr): """Create and return a corrector detail widget.""" wid = QWidget() wid.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Maximum) lay = QGridLayout(wid) lay.setContentsMargins(0, 0, 0, 0) lay.setAlignment(Qt.AlignCenter) propty_sp = 'Current-SP' if corr.sec == 'LI' else 'Kick-SP' propty_mon = propty_sp.replace('SP', 'Mon') led = SiriusLedState( self, corr.substitute(prefix=self.prefix, propty='PwrState-Sts')) led.setStyleSheet("max-width:1.29em;") lay.addWidget(led, 1, 1) nickname = corr.get_nickname(sec=corr.sec == 'LI', dev=True) pb = QPushButton(nickname, self) if corr.dis == 'PU': util.connect_window(pb, PUDetailWindow, parent=self, devname=corr) else: util.connect_window(pb, PSDetailWindow, parent=self, psname=corr) pb.setStyleSheet(""" min-width:6em; max-width:6em; min-height:1.29em;""") lay.addWidget(pb, 1, 2) sp_kick = PyDMSpinboxScrollbar( self, corr.substitute(prefix=self.prefix, propty=propty_sp)) sp_kick.setStyleSheet("QDoubleSpinBox{min-width:4em; max-width:4em; }" "QScrollBar{max-width:4em;}") sp_kick.spinbox.precisionFromPV = False sp_kick.spinbox.precision = 1 sp_kick.scrollbar.limitsFromPV = True lay.addWidget(sp_kick, 1, 3, 2, 1) lb_kick = PyDMLabel( self, corr.substitute(prefix=self.prefix, propty=propty_mon)) lb_kick.setStyleSheet(""" min-width:5em; max-width:5em; min-height:1.29em;""") lb_kick.showUnits = True lb_kick.precisionFromPV = False lb_kick.precision = 1 lb_kick.setAlignment(Qt.AlignCenter) lay.addWidget(lb_kick, 1, 4) return wid
def __init__(self, dims: Dims, parent=None): super().__init__(parent=parent) # We keep a reference to the view: self.dims = dims # list of sliders self.sliders = [] self._slider_axis = [] # Initialises the layout: layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # Update the number of sliders now that the dims have been added self._update_nsliders() # The next lines connect events coming from the model to the Qt event # system: We need to go through Qt signals so that these events are run # in the Qt event loop thread. This is all about changing thread # context for thread-safety purposes # axis change listener def update_axis_listener(event): self.update_axis.emit(event.axis) self.dims.events.axis.connect(update_axis_listener) # What to do with the axis change events in terms of UI calls to the # widget self.update_axis.connect(self._update_slider) # ndim change listener def update_ndim_listener(event): self.update_ndim.emit() self.dims.events.ndim.connect(update_ndim_listener) # What to do with the ndim change events in terms of UI calls to the # widget self.update_ndim.connect(self._update_nsliders)
def _setupUi(self): self._label_dev = QLabel('Power supply: ', self) self._label_dev.setStyleSheet( 'min-width: 8em; max-width: 8em;') self.cb_sec = QComboBox(self) for item in self._choose_sec: self.cb_sec.addItem(item) self.cb_sec.setCurrentText('SI') self.cb_sec.currentTextChanged.connect( self._handle_cb_visibility) self.cb_sec.currentTextChanged.connect( self._set_psnames) self.cb_sub = QComboBox(self) self.cb_sub.setEditable(True) self.cb_sub.setMaxVisibleItems(10) for item in self._choose_sub: self.cb_sub.addItem(item) self.cb_sub.currentTextChanged.connect( self._set_psnames) glay_choose = QGridLayout() glay_choose.addWidget(self.cb_sub, 0, 0) self.cb_dev = dict() for sec in self._choose_sec: visible = sec == 'SI' self.cb_dev[sec] = QComboBox(self) self.cb_dev[sec].setMaxVisibleItems(10) self.cb_dev[sec].setVisible(visible) for item in self._choose_dev[sec]: self.cb_dev[sec].addItem(item) self.cb_dev[sec].currentTextChanged.connect( self._set_psnames) glay_choose.addWidget(self.cb_dev[sec], 0, 1) lay = QGridLayout(self) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self._label_dev, 0, 0) lay.addWidget(self.cb_sec, 0, 1) lay.addLayout(glay_choose, 0, 2)
def _create_workdir_manager(self): self.workdir_ledit = QLineEdit() self.workdir_ledit.setReadOnly(True) self.workdir_btn = QToolButton() self.workdir_btn.setIcon(get_icon('folder_open')) self.workdir_btn.setAutoRaise(True) self.workdir_btn.setToolTip("Browse a working directory...") self.workdir_btn.clicked.connect(self.select_working_directory) workdir_widget = QWidget() workdir_layout = QGridLayout(workdir_widget) workdir_layout.setContentsMargins(0, 0, 0, 0) workdir_layout.setSpacing(1) workdir_layout.addWidget(QLabel('Working Directory:'), 0, 0) workdir_layout.addWidget(self.workdir_ledit, 0, 1) workdir_layout.addWidget(self.workdir_btn, 0, 2) return workdir_widget
def _setupUi(self): self._label_prop = QLabel('Properties: ', self) self._label_prop.setStyleSheet( 'min-width: 8em; max-width: 8em;') self._label_symb = QLabel() icon = qta.icon('mdi.record-circle-outline') pixmap = icon.pixmap(icon.actualSize(QSize(20, 20))) self._label_symb.setPixmap(pixmap) self._label_symb.setSizePolicy(QSzPlcy.Fixed, QSzPlcy.Fixed) self.cb_prop_symb = QComboBox(self) self.cb_prop_symb.currentTextChanged.connect( self.propty_symb_changed.emit) self.cb_prop_symb.setSizePolicy( QSzPlcy.Expanding, QSzPlcy.Preferred) self.cb_prop_symb.setMaxVisibleItems(10) self.cb_prop_symb.addItems(self._choose_prop_symb) hbox_prop_symb = QHBoxLayout() hbox_prop_symb.addWidget(self._label_symb) hbox_prop_symb.addWidget(self.cb_prop_symb) self._label_line = QLabel() icon = qta.icon('mdi.pulse') pixmap = icon.pixmap(icon.actualSize(QSize(20, 20))) self._label_line.setPixmap(pixmap) self._label_line.setSizePolicy(QSzPlcy.Fixed, QSzPlcy.Fixed) self.cb_prop_line = QComboBox(self) self.cb_prop_line.currentTextChanged.connect( self.propty_line_changed.emit) self.cb_prop_line.setSizePolicy( QSzPlcy.Expanding, QSzPlcy.Preferred) self.cb_prop_line.setMaxVisibleItems(10) self.cb_prop_line.addItems(self._choose_prop_line) hbox_prop_line = QHBoxLayout() hbox_prop_line.addWidget(self._label_line) hbox_prop_line.addWidget(self.cb_prop_line) lay = QGridLayout(self) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self._label_prop, 0, 0) lay.addLayout(hbox_prop_symb, 0, 1) lay.addLayout(hbox_prop_line, 0, 2)
class QuadHistogram(QFrame): '''A class which uses ColorHistogram to draw the 4 histograms of an image. R, G, B, and Value. The 4 histograms are layout out in a grid, and can be specified horizontal or vertical, and in which order ie. ['R', 'G', 'B', 'V'] ''' def __init__(self, img, layout='vertical', order=['R', 'G', 'B', 'V']): QFrame.__init__(self) r, g, b, v = histograms(img, 100) self.r_hist = ColorHistogram(r, (255, 0, 0)) self.g_hist = ColorHistogram(g, (0, 255, 0)) self.b_hist = ColorHistogram(b, (0, 0, 255)) self.v_hist = ColorHistogram(v, (0, 0, 0)) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.layout = QGridLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) order_map = { 'R': self.r_hist, 'G': self.g_hist, 'B': self.b_hist, 'V': self.v_hist } if layout == 'vertical': for i in range(len(order)): self.layout.addWidget(order_map[order[i]], i, 0) elif layout == 'horizontal': for i in range(len(order)): self.layout.addWidget(order_map[order[i]], 0, i) def update_hists(self, img): r, g, b, v = histograms(img, 100) self.r_hist.update_hist(r, (255, 0, 0)) self.g_hist.update_hist(g, (0, 255, 0)) self.b_hist.update_hist(b, (0, 0, 255)) self.v_hist.update_hist(v, (0, 0, 0))
def _setupTopUpModeWidget(self): self._ld_tuperd = QLabel('Period', self) self._sb_tuperd = SiriusSpinbox( self, self._inj_prefix.substitute(propty='TopUpPeriod-SP')) self._sb_tuperd.showStepExponent = False self._lb_tuperd = PyDMLabel( self, self._inj_prefix.substitute(propty='TopUpPeriod-RB')) self._lb_tuperd.showUnits = True self._ld_tumaxpu = QLabel('Max.Nr.Pulses', self) self._sb_tumaxpu = SiriusSpinbox( self, self._inj_prefix.substitute(propty='TopUpMaxNrPulses-SP')) self._sb_tumaxpu.showStepExponent = False self._lb_tumaxpu = PyDMLabel( self, self._inj_prefix.substitute(propty='TopUpMaxNrPulses-RB')) self._lb_tumaxpu.showUnits = True wid = QWidget() lay = QGridLayout(wid) lay.setContentsMargins(0, 6, 0, 0) lay.setAlignment(Qt.AlignTop) lay.addWidget(self._ld_tuperd, 0, 0) lay.addWidget(self._sb_tuperd, 0, 1) lay.addWidget(self._lb_tuperd, 0, 2) lay.addWidget(self._ld_tumaxpu, 1, 0) lay.addWidget(self._sb_tumaxpu, 1, 1) lay.addWidget(self._lb_tumaxpu, 1, 2) lay.setColumnStretch(0, 3) lay.setColumnStretch(1, 2) lay.setColumnStretch(2, 2) wid.setStyleSheet(""" .QLabel{ min-width: 6.5em; max-width: 6.5em; min-height: 1.5em; qproperty-alignment: 'AlignRight | AlignVCenter'; } PyDMLabel{ qproperty-alignment: AlignCenter; }""") return wid
class QuadHistogram(QFrame): '''A class which uses ColorHistogram to draw the 4 histograms of an image. R, G, B, and Value. The 4 histograms are layout out in a grid, and can be specified horizontal or vertical, and in which order ie. ['R', 'G', 'B', 'V'] ''' def __init__(self, img, layout='vertical', order=['R', 'G', 'B', 'V']): QFrame.__init__(self) r, g, b, v = histograms(img, 100) self.r_hist = ColorHistogram(r, (255, 0, 0)) self.g_hist = ColorHistogram(g, (0, 255, 0)) self.b_hist = ColorHistogram(b, (0, 0, 255)) self.v_hist = ColorHistogram(v, (0, 0, 0)) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.layout = QGridLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) order_map = {'R': self.r_hist, 'G': self.g_hist, 'B': self.b_hist, 'V': self.v_hist} if layout == 'vertical': for i in range(len(order)): self.layout.addWidget(order_map[order[i]], i, 0) elif layout == 'horizontal': for i in range(len(order)): self.layout.addWidget(order_map[order[i]], 0, i) def update_hists(self, img): r, g, b, v = histograms(img, 100) self.r_hist.update_hist(r, (255, 0, 0)) self.g_hist.update_hist(g, (0, 255, 0)) self.b_hist.update_hist(b, (0, 0, 255)) self.v_hist.update_hist(v, (0, 0, 0))
def __init__(self, parent=None, init_channel=None): """Init.""" super().__init__(parent) self._init_channel = init_channel layout = QGridLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) self.lineedit = SiriusLineEdit( parent=self, init_channel=init_channel) self.lineedit.setAlignment(Qt.AlignCenter) self.lineedit.setStyleSheet("SiriusLineEdit{min-height:1.29em;}") self.lineedit.setSizePolicy(QSzPol.Expanding, QSzPol.Preferred) self.scrollbar = PyDMScrollBar( parent=self, init_channel=init_channel) self.scrollbar.wheelEvent = lambda event: event.ignore() self.scrollbar.setTracking(False) self.scrollbar.setStyleSheet("PyDMScrollBar{max-height:0.7em;}") layout.addWidget(self.lineedit, 0, 0, 2, 1) layout.addWidget(self.scrollbar, 2, 0, 1, 1)
def __init__(self, plot, *args, **kwargs): super().__init__(*args, **kwargs) self.plot = plot ly = QGridLayout() # remove annoying padding ly.setContentsMargins(0, 0, 0, 0) self.setLayout(ly) self.label = QLabel('Binning:') ly.addWidget(self.label, 0, 0) self.auto = QCheckBox('auto', checked=False) ly.addWidget(self.auto, 0, 1) self.spinbox = QSpinBox() ly.addWidget(self.spinbox, 1, 0, 1, 2) self.spinbox.setRange(1, 100000) self.spinbox.setValue(10) self.spinbox.valueChanged.connect(self._on_binning_change) self.auto.clicked.connect(self._on_auto_change) self.auto.click()
def setup_ui(self): self.host = QLineEdit(self) self.host.setMinimumWidth(170) self.port = QLineEdit(self) self.port.setValidator(QIntValidator(0, 99999, self)) self.username = QLineEdit(self) self.password = QLineEdit(self) self.password.setEchoMode(QLineEdit.Password) self.password.editingFinished.connect( lambda: [self._connect() if self.password.text() else None]) host, user, uuid, port = self.gateway.get_current() self.host.setText(self.gateway._host or host or "") self.port.setText(self.gateway._port or port or "") self.username.setText(self.gateway._user or user or "") self.password.setText(os.getenv("OMERO_PASSWORD")) self.status = QLabel(self) self.status.setWordWrap(True) self.connect_btn = QPushButton("connect", self) self.connect_btn.clicked.connect(self._connect) self.login_form = QWidget(self) login_layout = QGridLayout(self.login_form) login_layout.setContentsMargins(0, 0, 0, 0) login_layout.addWidget(QLabel("host", self), 0, 0) login_layout.addWidget(self.host, 0, 1) login_layout.addWidget(QLabel("port", self), 1, 0) login_layout.addWidget(self.port, 1, 1) login_layout.addWidget(QLabel("username", self), 2, 0) login_layout.addWidget(self.username, 2, 1) login_layout.addWidget(QLabel("password", self), 3, 0) login_layout.addWidget(self.password, 3, 1) login_layout.addWidget(self.connect_btn, 4, 1) layout = QVBoxLayout(self) layout.addWidget(self.login_form) layout.addWidget(self.status)
def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton( self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.return_shift_pressed.connect( lambda: self.find(changed=False, forward=False, rehighlight=False, multiline_replace_check=False)) self.return_pressed.connect( lambda: self.find(changed=False, forward=True, rehighlight=False, multiline_replace_check=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.number_matches_text = QLabel(self) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp'), tip=_("Find previous")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown'), tip=_("Find next")) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('regex'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton( self, icon=ima.icon("format_letter_case"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton( self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.number_matches_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_text.valid.connect( lambda _: self.replace_find(focus_replace_text=True)) self.replace_button = create_toolbutton( self, text=_('Replace/find next'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_sel_button = create_toolbutton( self, text=_('Replace in selection'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find_selection, text_beside_icon=True) self.replace_sel_button.clicked.connect(self.update_replace_combo) self.replace_sel_button.clicked.connect(self.update_search_combo) self.replace_all_button = create_toolbutton( self, text=_('Replace all'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find_all, text_beside_icon=True) self.replace_all_button.clicked.connect(self.update_replace_combo) self.replace_all_button.clicked.connect(self.update_search_combo) self.replace_layout = QHBoxLayout() widgets = [ replace_with, self.replace_text, self.replace_button, self.replace_sel_button, self.replace_all_button ] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) self.search_text.installEventFilter(self)
class QtLayerProperties(QFrame): def __init__(self, layer): super().__init__() self.layer = layer layer.events.select.connect(lambda v: self.setSelected(True)) layer.events.deselect.connect(lambda v: self.setSelected(False)) layer.events.name.connect(self._on_layer_name_change) layer.events.blending.connect(self._on_blending_change) layer.events.opacity.connect(self._on_opacity_change) layer.events.visible.connect(self._on_visible_change) layer.events.thumbnail.connect(self._on_thumbnail_change) self.setObjectName('layer') self.vbox_layout = QVBoxLayout() self.top = QFrame() self.top_layout = QHBoxLayout() self.grid = QFrame() self.grid_layout = QGridLayout() self.vbox_layout.addWidget(self.top) self.vbox_layout.addWidget(self.grid) self.vbox_layout.setSpacing(0) self.top.setFixedHeight(38) self.top_layout.setContentsMargins(0, 0, 0, 0) self.grid_layout.setContentsMargins(0, 0, 0, 0) self.top_layout.setAlignment(Qt.AlignCenter) self.top.setLayout(self.top_layout) self.grid.setLayout(self.grid_layout) self.setLayout(self.vbox_layout) self.name_column = 0 self.property_column = 1 cb = QCheckBox(self) cb.setObjectName('visibility') cb.setToolTip('Layer visibility') cb.setChecked(self.layer.visible) cb.setProperty('mode', 'visibility') cb.stateChanged.connect(lambda state=cb: self.changeVisible(state)) self.visibleCheckBox = cb self.top_layout.addWidget(cb) tb = QLabel(self) tb.setObjectName('thumbmnail') tb.setToolTip('Layer thumbmnail') self.thumbnail_label = tb self._on_thumbnail_change(None) self.top_layout.addWidget(tb) textbox = QLineEdit(self) textbox.setText(layer.name) textbox.home(False) textbox.setToolTip('Layer name') textbox.setAcceptDrops(False) textbox.setEnabled(True) textbox.editingFinished.connect(self.changeText) self.nameTextBox = textbox self.top_layout.addWidget(textbox) pb = QPushButton(self) pb.setToolTip('Expand properties') pb.clicked.connect(self.changeExpanded) pb.setObjectName('expand') self.expand_button = pb self.top_layout.addWidget(pb) row = self.grid_layout.rowCount() sld = QSlider(Qt.Horizontal, self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(self.layer.opacity * 100) sld.valueChanged[int].connect( lambda value=sld: self.changeOpacity(value)) self.opacitySilder = sld row = self.grid_layout.rowCount() self.grid_layout.addWidget(QLabel('opacity:'), row, self.name_column) self.grid_layout.addWidget(sld, row, self.property_column) row = self.grid_layout.rowCount() blend_comboBox = QComboBox() for blend in Blending: blend_comboBox.addItem(str(blend)) index = blend_comboBox.findText(self.layer.blending, Qt.MatchFixedString) blend_comboBox.setCurrentIndex(index) blend_comboBox.activated[str].connect( lambda text=blend_comboBox: self.changeBlending(text)) self.blendComboBox = blend_comboBox self.grid_layout.addWidget(QLabel('blending:'), row, self.name_column) self.grid_layout.addWidget(blend_comboBox, row, self.property_column) msg = 'Click to select\nDrag to rearrange\nDouble click to expand' self.setToolTip(msg) self.setExpanded(False) self.setFixedWidth(250) self.grid_layout.setColumnMinimumWidth(0, 100) self.grid_layout.setColumnMinimumWidth(1, 100) self.setSelected(self.layer.selected) def setSelected(self, state): self.setProperty('selected', state) self.nameTextBox.setEnabled(state) self.style().unpolish(self) self.style().polish(self) def changeOpacity(self, value): with self.layer.events.blocker(self._on_opacity_change): self.layer.opacity = value / 100 def changeVisible(self, state): if state == Qt.Checked: self.layer.visible = True else: self.layer.visible = False def changeText(self): self.layer.name = self.nameTextBox.text() def changeBlending(self, text): self.layer.blending = text def mouseReleaseEvent(self, event): event.ignore() def mousePressEvent(self, event): event.ignore() def mouseMoveEvent(self, event): event.ignore() def mouseDoubleClickEvent(self, event): self.setExpanded(not self.expanded) def changeExpanded(self): self.setExpanded(not self.expanded) def setExpanded(self, bool): if bool: self.expanded = True self.expand_button.setProperty('expanded', True) rows = self.grid_layout.rowCount() self.setFixedHeight(38 + 30 * rows) self.grid.show() else: self.expanded = False self.expand_button.setProperty('expanded', False) self.setFixedHeight(60) self.grid.hide() self.expand_button.style().unpolish(self.expand_button) self.expand_button.style().polish(self.expand_button) def _on_layer_name_change(self, event): with self.layer.events.name.blocker(): self.nameTextBox.setText(self.layer.name) self.nameTextBox.home(False) def _on_opacity_change(self, event): with self.layer.events.opacity.blocker(): self.opacitySilder.setValue(self.layer.opacity * 100) def _on_blending_change(self, event): with self.layer.events.blending.blocker(): index = self.blendComboBox.findText(self.layer.blending, Qt.MatchFixedString) self.blendComboBox.setCurrentIndex(index) def _on_visible_change(self, event): with self.layer.events.visible.blocker(): self.visibleCheckBox.setChecked(self.layer.visible) def _on_thumbnail_change(self, event): thumbnail = self.layer.thumbnail # Note that QImage expects the image width followed by height image = QImage( thumbnail, thumbnail.shape[1], thumbnail.shape[0], QImage.Format_RGBA8888, ) self.thumbnail_label.setPixmap(QPixmap.fromImage(image))
def __init__(self, parent, adjacency): QSplitter.__init__(self, parent, orientation=Qt.Vertical) self.plugin = parent.plugin self.adjacency = adjacency treepane = QWidget(parent=self) # Create main widget self.model = MxAnalyzerModel( adjacency=adjacency, root=None, parent=self # parent must be self, # because self.model access self.tab ) # from .modeltest import ModelTest # self.modeltest = ModelTest(self.model, self) self.tree = MxAnalyzerTree(treepane, self.model) self.shellwidget = None # Set by parent button_group = QButtonGroup(parent=self) self.object_radio = object_radio = QRadioButton("Object") self.expr_radio = expr_radio = QRadioButton("Expression") button_group.addButton(object_radio) button_group.addButton(expr_radio) object_radio.setChecked(True) object_radio.toggled.connect(self.toggleObject) # Layout of the top area in the plugin widget expr_layout = QHBoxLayout() expr_layout.setContentsMargins(0, 0, 0, 0) # Add Object textbox expr_layout.addSpacing(10) txt = _("Object") if sys.platform == 'darwin': obj_label = QLabel(" " + txt) else: obj_label = QLabel(txt) expr_layout.addWidget(obj_label) if spyder.version_info < (4,): font = parent.plugin.get_plugin_font() else: font = parent.plugin.get_font() self.objbox = QLabel(parent=self) self.argbox = MxPyExprLineEdit(self, font=font) self.attrdict = None objbox_layout = QHBoxLayout() objbox_layout.addWidget(self.objbox) objbox_layout.addWidget(self.argbox) objbox_layout.setStretch(0, 3) # 3:1 objbox_layout.setStretch(1, 1) self.exprobjbox = MxPyExprLineEdit(treepane, font=font) expr_layout.addWidget(self.exprobjbox) expr_layout.addSpacing(10) # Add Object textbox txt = _("Args") if sys.platform == 'darwin': arg_label = QLabel(" " + txt) else: arg_label = QLabel(txt) expr_layout.addWidget(arg_label) self.exprargbox = MxPyExprLineEdit(treepane, font=font) expr_layout.addWidget(self.exprargbox) # expr_layout.addSpacing(5) top_layout = QGridLayout() top_layout.addWidget(object_radio, 0, 0) top_layout.addWidget(expr_radio, 1, 0) top_layout.addLayout(objbox_layout, 0, 1) objbox_layout.setContentsMargins(0, 0, 0, 5) top_layout.addLayout(expr_layout, 1, 1) top_layout.setContentsMargins(5, 5, 5, 5) # Main layout of this widget layout = create_plugin_layout(top_layout, self.tree) treepane.setLayout(layout) self.status = QLabel() layout.addWidget(self.status) self.codepane = AnalyzerCodePane(parent=self)
class MxDataWidget(QWidget): """ Dialog for displaying and editing DataFrame and related objects. Based on the gtabview project (ExtTableView). For more information please see: https://github.com/wavexx/gtabview/blob/master/gtabview/viewer.py Signals ------- sig_option_changed(str, object): Raised if an option is changed. Arguments are name of option and its new value. """ sig_option_changed = Signal(str, object) def __init__(self, parent=None, data=DataFrame()): QWidget.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.is_series = False self.layout = None self.setup_and_check(data) def setup_and_check(self, data, title=''): """ Setup DataFrameEditor: return False if data is not supported, True otherwise. Supported types for data are DataFrame, Series and Index. """ self._selection_rec = False self._model = None self.layout = QGridLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - %s" % data.__class__.__name__ else: title = _("%s editor") % data.__class__.__name__ if isinstance(data, Series): self.is_series = True data = data.to_frame() elif isinstance(data, Index): data = DataFrame(data) self.setWindowTitle(title) # self.resize(600, 500) self.hscroll = QScrollBar(Qt.Horizontal) self.vscroll = QScrollBar(Qt.Vertical) # Create the view for the level self.create_table_level() # Create the view for the horizontal header self.create_table_header() # Create the view for the vertical index self.create_table_index() # Create the model and view of the data self.dataModel = MxDataModel(data, parent=self) # self.dataModel.dataChanged.connect(self.save_and_close_enable) self.create_data_table() self.layout.addWidget(self.hscroll, 2, 0, 1, 2) self.layout.addWidget(self.vscroll, 0, 2, 2, 1) # autosize columns on-demand self._autosized_cols = set() self._max_autosize_ms = None self.dataTable.installEventFilter(self) avg_width = self.fontMetrics().averageCharWidth() self.min_trunc = avg_width * 8 # Minimum size for columns self.max_width = avg_width * 64 # Maximum size for columns self.setLayout(self.layout) # Make the dialog act as a window # self.setWindowFlags(Qt.Window) self.setModel(self.dataModel) self.resizeColumnsToContents() return True def create_table_level(self): """Create the QTableView that will hold the level model.""" self.table_level = QTableView() self.table_level.setEditTriggers(QTableWidget.NoEditTriggers) self.table_level.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_level.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_level.setFrameStyle(QFrame.Plain) self.table_level.horizontalHeader().sectionResized.connect( self._index_resized) self.table_level.verticalHeader().sectionResized.connect( self._header_resized) # self.table_level.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_level, 0, 0) self.table_level.setContentsMargins(0, 0, 0, 0) self.table_level.horizontalHeader().sectionClicked.connect( self.sortByIndex) def create_table_header(self): """Create the QTableView that will hold the header model.""" self.table_header = QTableView() self.table_header.verticalHeader().hide() self.table_header.setEditTriggers(QTableWidget.NoEditTriggers) self.table_header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_header.setHorizontalScrollMode(QTableView.ScrollPerPixel) self.table_header.setHorizontalScrollBar(self.hscroll) self.table_header.setFrameStyle(QFrame.Plain) self.table_header.horizontalHeader().sectionResized.connect( self._column_resized) # self.table_header.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_header, 0, 1) def create_table_index(self): """Create the QTableView that will hold the index model.""" self.table_index = QTableView() self.table_index.horizontalHeader().hide() self.table_index.setEditTriggers(QTableWidget.NoEditTriggers) self.table_index.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_index.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_index.setVerticalScrollMode(QTableView.ScrollPerPixel) self.table_index.setVerticalScrollBar(self.vscroll) self.table_index.setFrameStyle(QFrame.Plain) self.table_index.verticalHeader().sectionResized.connect( self._row_resized) # self.table_index.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_index, 1, 0) self.table_index.setContentsMargins(0, 0, 0, 0) def create_data_table(self): """Create the QTableView that will hold the data model.""" self.dataTable = MxDataTable(self, self.dataModel, self.table_header.horizontalHeader(), self.hscroll, self.vscroll) self.dataTable.verticalHeader().hide() self.dataTable.horizontalHeader().hide() self.dataTable.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.dataTable.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.dataTable.setHorizontalScrollMode(QTableView.ScrollPerPixel) self.dataTable.setVerticalScrollMode(QTableView.ScrollPerPixel) self.dataTable.setFrameStyle(QFrame.Plain) # self.dataTable.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.dataTable, 1, 1) self.setFocusProxy(self.dataTable) self.dataTable.sig_sort_by_column.connect(self._sort_update) self.dataTable.sig_fetch_more_columns.connect(self._fetch_more_columns) self.dataTable.sig_fetch_more_rows.connect(self._fetch_more_rows) def sortByIndex(self, index): """Implement a Index sort.""" self.table_level.horizontalHeader().setSortIndicatorShown(True) sort_order = self.table_level.horizontalHeader().sortIndicatorOrder() self.table_index.model().sort(index, sort_order) self._sort_update() def model(self): """Get the model of the dataframe.""" return self._model def _column_resized(self, col, old_width, new_width): """Update the column width.""" self.dataTable.setColumnWidth(col, new_width) self._update_layout() def _row_resized(self, row, old_height, new_height): """Update the row height.""" self.dataTable.setRowHeight(row, new_height) self._update_layout() def _index_resized(self, col, old_width, new_width): """Resize the corresponding column of the index section selected.""" self.table_index.setColumnWidth(col, new_width) self._update_layout() def _header_resized(self, row, old_height, new_height): """Resize the corresponding row of the header section selected.""" self.table_header.setRowHeight(row, new_height) self._update_layout() def _update_layout(self): """Set the width and height of the QTableViews and hide rows.""" h_width = max(self.table_level.verticalHeader().sizeHint().width(), self.table_index.verticalHeader().sizeHint().width()) self.table_level.verticalHeader().setFixedWidth(h_width) self.table_index.verticalHeader().setFixedWidth(h_width) last_row = self._model.header_shape[0] - 1 if last_row < 0: hdr_height = self.table_level.horizontalHeader().height() else: # Check if the header shape has only one row (which display the # same info than the horizontal header). if last_row == 0: self.table_level.setRowHidden(0, True) self.table_header.setRowHidden(0, True) else: self.table_level.setRowHidden(0, False) self.table_header.setRowHidden(0, False) hdr_height = self.table_level.rowViewportPosition(last_row) + \ self.table_level.rowHeight(last_row) + \ self.table_level.horizontalHeader().height() self.table_header.setFixedHeight(hdr_height) self.table_level.setFixedHeight(hdr_height) last_col = self._model.header_shape[1] - 1 if last_col < 0: idx_width = self.table_level.verticalHeader().width() else: idx_width = self.table_level.columnViewportPosition(last_col) + \ self.table_level.columnWidth(last_col) + \ self.table_level.verticalHeader().width() self.table_index.setFixedWidth(idx_width) self.table_level.setFixedWidth(idx_width) self._resizeVisibleColumnsToContents() def _reset_model(self, table, model): """Set the model in the given table.""" old_sel_model = table.selectionModel() table.setModel(model) if old_sel_model: del old_sel_model def setAutosizeLimit(self, limit_ms): """Set maximum size for columns.""" self._max_autosize_ms = limit_ms def setModel(self, model, relayout=True): """Set the model for the data, header/index and level views.""" self._model = model # sel_model = self.dataTable.selectionModel() # sel_model.currentColumnChanged.connect( # self._resizeCurrentColumnToContents) self._reset_model(self.dataTable, model) # Asociate the models (level, vertical index and horizontal header) # with its corresponding view. self._reset_model( self.table_level, DataFrameLevelModel(model, self.palette(), self.font())) self._reset_model(self.table_header, DataFrameHeaderModel(model, 0, self.palette())) self._reset_model(self.table_index, DataFrameHeaderModel(model, 1, self.palette())) # Needs to be called after setting all table models if relayout: self._update_layout() def setCurrentIndex(self, y, x): """Set current selection.""" self.dataTable.selectionModel().setCurrentIndex( self.dataTable.model().index(y, x), QItemSelectionModel.ClearAndSelect) def _sizeHintForColumn(self, table, col, limit_ms=None): """Get the size hint for a given column in a table.""" max_row = table.model().rowCount() lm_start = time.perf_counter() lm_row = 64 if limit_ms else max_row max_width = 0 for row in range(max_row): v = table.sizeHintForIndex(table.model().index(row, col)) max_width = max(max_width, v.width()) if row > lm_row: lm_now = time.perf_counter() lm_elapsed = (lm_now - lm_start) * 1000 if lm_elapsed >= limit_ms: break lm_row = int((row / lm_elapsed) * limit_ms) return max_width def _resizeColumnToContents(self, header, data, col, limit_ms): """Resize a column by its contents.""" hdr_width = self._sizeHintForColumn(header, col, limit_ms) data_width = self._sizeHintForColumn(data, col, limit_ms) if data_width > hdr_width: width = min(self.max_width, data_width) elif hdr_width > data_width * 2: width = max(min(hdr_width, self.min_trunc), min(self.max_width, data_width)) else: width = min(self.max_width, hdr_width) header.setColumnWidth(col, width) def _resizeColumnsToContents(self, header, data, limit_ms): """Resize all the colummns to its contents.""" max_col = data.model().columnCount() if limit_ms is None: max_col_ms = None else: max_col_ms = limit_ms / max(1, max_col) for col in range(max_col): self._resizeColumnToContents(header, data, col, max_col_ms) def eventFilter(self, obj, event): """Override eventFilter to catch resize event.""" if obj == self.dataTable and event.type() == QEvent.Resize: self._resizeVisibleColumnsToContents() return False def _resizeVisibleColumnsToContents(self): """Resize the columns that are in the view.""" index_column = self.dataTable.rect().topLeft().x() start = col = self.dataTable.columnAt(index_column) width = self._model.shape[1] end = self.dataTable.columnAt(self.dataTable.rect().bottomRight().x()) end = width if end == -1 else end + 1 if self._max_autosize_ms is None: max_col_ms = None else: max_col_ms = self._max_autosize_ms / max(1, end - start) while col < end: resized = False if col not in self._autosized_cols: self._autosized_cols.add(col) resized = True self._resizeColumnToContents(self.table_header, self.dataTable, col, max_col_ms) col += 1 if resized: # As we resize columns, the boundary will change index_column = self.dataTable.rect().bottomRight().x() end = self.dataTable.columnAt(index_column) end = width if end == -1 else end + 1 if max_col_ms is not None: max_col_ms = self._max_autosize_ms / max(1, end - start) def _resizeCurrentColumnToContents(self, new_index, old_index): """Resize the current column to its contents.""" if new_index.column() not in self._autosized_cols: # Ensure the requested column is fully into view after resizing self._resizeVisibleColumnsToContents() self.dataTable.scrollTo(new_index) def resizeColumnsToContents(self): """Resize the columns to its contents.""" self._autosized_cols = set() self._resizeColumnsToContents(self.table_level, self.table_index, self._max_autosize_ms) self._update_layout() self.table_level.resizeColumnsToContents() def change_format(self): """ Ask user for display format for floats and use it. This function also checks whether the format is valid and emits `sig_option_changed`. """ format, valid = QInputDialog.getText(self, _('Format'), _("Float formatting"), QLineEdit.Normal, self.dataModel.get_format()) if valid: format = str(format) try: format % 1.1 except: msg = _("Format ({}) is incorrect").format(format) QMessageBox.critical(self, _("Error"), msg) return if not format.startswith('%'): msg = _("Format ({}) should start with '%'").format(format) QMessageBox.critical(self, _("Error"), msg) return self.dataModel.set_format(format) self.sig_option_changed.emit('dataframe_format', format) def get_value(self): """Return modified Dataframe -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute df = self.dataModel.get_data() if self.is_series: return df.iloc[:, 0] else: return df def _update_header_size(self): """Update the column width of the header.""" column_count = self.table_header.model().columnCount() for index in range(0, column_count): if index < column_count: column_width = self.dataTable.columnWidth(index) self.table_header.setColumnWidth(index, column_width) else: break def _sort_update(self): """ Update the model for all the QTableView objects. Uses the model of the dataTable as the base. """ self.setModel(self.dataTable.model()) def _fetch_more_columns(self): """Fetch more data for the header (columns).""" self.table_header.model().fetch_more() def _fetch_more_rows(self): """Fetch more data for the index (rows).""" self.table_index.model().fetch_more() def resize_to_contents(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.dataTable.resizeColumnsToContents() self.dataModel.fetch_more(columns=True) self.dataTable.resizeColumnsToContents() self._update_header_size() QApplication.restoreOverrideCursor() # --- mx specific --- def process_remote_view(self, data): if data is None: data = DataFrame() # Empty DataFrame self.setModel(MxDataModel(data, parent=self))
class FramesWidget(BaseFrame): rejected = Signal() accepted = Signal() back = Signal() def __init__(self, parent=None): super(FramesWidget, self).__init__(parent) self.config_frames = [] self.frames_instances = [] self.current_frame = 0 self.widget_layout = QVBoxLayout(self) self.setLayout(self.widget_layout) self.frames_widget = QWidget(self) self.frames_widget.setContentsMargins(0, 0, 0, 0) self.frames_widget_layout = QGridLayout(self.frames_widget) self.frames_widget_layout.setContentsMargins(0, 0, 0, 0) self.widget_layout.addWidget(self.frames_widget) self.btn_box = QDialogButtonBox(self) self.btn_box.setObjectName("btn_box") self.btn_box.setStandardButtons(QDialogButtonBox.Reset | QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.btn_box.button(QDialogButtonBox.Reset).setText("Back") self.btn_box.button(QDialogButtonBox.Ok).setText("Next") self.widget_layout.addWidget(self.btn_box) self.btn_box.button(QDialogButtonBox.Reset).clicked.connect( self.on_btn_box_resetted) QMetaObject.connectSlotsByName(self) def add_frames(self, frames): for frame in frames: self.add_frame(frame) def add_frame(self, frame): f = frame(self.frames_widget) f.setVisible(False) f.set_next_enabled.connect( self.btn_box.button(QDialogButtonBox.Ok).setEnabled) self.frames_widget_layout.addWidget(f, 0, 0, 1, 1) self.frames_instances.append(f) if len(self.frames_instances) > 0: self.frames_instances[0].setVisible(True) if self.frames_instances[0].disable_next_on_enter: self.btn_box.button(QDialogButtonBox.Ok).setEnabled(False) @Slot(dict) def set_options(self, options): for frame in self.frames_instances: frame.set_options(options) @Slot() def collect_info(self) -> dict: """Get info from every page""" settings = {} for frame in self.frames_instances: settings = settings | frame.collect_info() return settings @Slot() def on_btn_box_rejected(self): """When user clicks cancel button""" self.rejected.emit() @Slot() def on_btn_box_accepted(self): """On Next button clicked""" if self.current_frame + 1 < len(self.frames_instances): # if not last self.frames_instances[self.current_frame].setVisible(False) self.current_frame = self.current_frame + 1 self.frames_instances[self.current_frame].setVisible(True) # if disable on enter page self.btn_box.button( QDialogButtonBox.Ok).setEnabled(not self.frames_instances[ self.current_frame].disable_next_on_enter) # if next page is last self._change_next_finish() # if last page elif self.current_frame + 1 == len(self.frames_instances): self.accepted.emit() @Slot() def on_btn_box_resetted(self): """On Back button clicked.""" if self.current_frame > 0: self.frames_instances[self.current_frame].setVisible(False) self.current_frame = self.current_frame - 1 self.frames_instances[self.current_frame].setVisible(True) self._change_next_finish() self.btn_box.button( QDialogButtonBox.Ok).setEnabled(not self.frames_instances[ self.current_frame].disable_next_on_enter) else: self.back.emit() def _change_next_finish(self): if self.current_frame + 1 == len(self.frames_instances): self.btn_box.button(QDialogButtonBox.Ok).setText("Finish") else: self.btn_box.button(QDialogButtonBox.Ok).setText("Next")
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(self) layout.addLayout(layout_sequence) layout.addSpacing(10) layout.addLayout(button_box) layout.setSizeConstraint(layout.SetFixedSize) # 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()
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()
def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.edge_width.connect(self._on_edge_width_change) self.layer.events.current_edge_color.connect( self._on_current_edge_color_change) self.layer.events.current_face_color.connect( self._on_current_face_color_change) self.layer.events.editable.connect(self._on_editable_change) self.layer.text.events.visible.connect(self._on_text_visibility_change) sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(40) sld.setSingleStep(1) value = self.layer.current_edge_width if isinstance(value, Iterable): if isinstance(value, list): value = np.asarray(value) value = value.mean() sld.setValue(int(value)) sld.valueChanged.connect(self.changeWidth) self.widthSlider = sld self.select_button = QtModeRadioButton(layer, 'select', Mode.SELECT, tooltip='Select shapes') self.direct_button = QtModeRadioButton(layer, 'direct', Mode.DIRECT, tooltip='Select vertices') self.panzoom_button = QtModeRadioButton(layer, 'zoom', Mode.PAN_ZOOM, tooltip='Pan/zoom', checked=True) self.rectangle_button = QtModeRadioButton(layer, 'rectangle', Mode.ADD_RECTANGLE, tooltip='Add rectangles') self.ellipse_button = QtModeRadioButton(layer, 'ellipse', Mode.ADD_ELLIPSE, tooltip='Add ellipses') self.line_button = QtModeRadioButton(layer, 'line', Mode.ADD_LINE, tooltip='Add lines') self.path_button = QtModeRadioButton(layer, 'path', Mode.ADD_PATH, tooltip='Add paths') self.polygon_button = QtModeRadioButton(layer, 'polygon', Mode.ADD_POLYGON, tooltip='Add polygons') self.vertex_insert_button = QtModeRadioButton(layer, 'vertex_insert', Mode.VERTEX_INSERT, tooltip='Insert vertex') self.vertex_remove_button = QtModeRadioButton(layer, 'vertex_remove', Mode.VERTEX_REMOVE, tooltip='Remove vertex') self.move_front_button = QtModePushButton( layer, 'move_front', slot=self.layer.move_to_front, tooltip='Move to front', ) self.move_back_button = QtModePushButton( layer, 'move_back', slot=self.layer.move_to_back, tooltip='Move to back', ) self.delete_button = QtModePushButton( layer, 'delete_shape', slot=self.layer.remove_selected, tooltip='Delete selected shapes', ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.select_button) self.button_group.addButton(self.direct_button) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.rectangle_button) self.button_group.addButton(self.ellipse_button) self.button_group.addButton(self.line_button) self.button_group.addButton(self.path_button) self.button_group.addButton(self.polygon_button) self.button_group.addButton(self.vertex_insert_button) self.button_group.addButton(self.vertex_remove_button) button_grid = QGridLayout() button_grid.addWidget(self.vertex_remove_button, 0, 2) button_grid.addWidget(self.vertex_insert_button, 0, 3) button_grid.addWidget(self.delete_button, 0, 4) button_grid.addWidget(self.direct_button, 0, 5) button_grid.addWidget(self.select_button, 0, 6) button_grid.addWidget(self.panzoom_button, 0, 7) button_grid.addWidget(self.move_back_button, 1, 1) button_grid.addWidget(self.move_front_button, 1, 2) button_grid.addWidget(self.ellipse_button, 1, 3) button_grid.addWidget(self.rectangle_button, 1, 4) button_grid.addWidget(self.polygon_button, 1, 5) button_grid.addWidget(self.line_button, 1, 6) button_grid.addWidget(self.path_button, 1, 7) button_grid.setContentsMargins(5, 0, 0, 5) button_grid.setColumnStretch(0, 1) button_grid.setSpacing(4) self.faceColorEdit = QColorSwatchEdit( initial_color=self.layer.current_face_color, tooltip='click to set current face color', ) self._on_current_face_color_change() self.edgeColorEdit = QColorSwatchEdit( initial_color=self.layer.current_edge_color, tooltip='click to set current edge color', ) self._on_current_edge_color_change() self.faceColorEdit.color_changed.connect(self.changeFaceColor) self.edgeColorEdit.color_changed.connect(self.changeEdgeColor) text_disp_cb = QCheckBox() text_disp_cb.setToolTip('toggle text visibility') text_disp_cb.setChecked(self.layer.text.visible) text_disp_cb.stateChanged.connect(self.change_text_visibility) self.textDispCheckBox = text_disp_cb # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_grid, 0, 0, 1, 2) self.grid_layout.addWidget(QLabel('opacity:'), 1, 0) self.grid_layout.addWidget(self.opacitySlider, 1, 1) self.grid_layout.addWidget(QLabel('edge width:'), 2, 0) self.grid_layout.addWidget(self.widthSlider, 2, 1) self.grid_layout.addWidget(QLabel('blending:'), 3, 0) self.grid_layout.addWidget(self.blendComboBox, 3, 1) self.grid_layout.addWidget(QLabel('face color:'), 4, 0) self.grid_layout.addWidget(self.faceColorEdit, 4, 1) self.grid_layout.addWidget(QLabel('edge color:'), 5, 0) self.grid_layout.addWidget(self.edgeColorEdit, 5, 1) self.grid_layout.addWidget(QLabel('display text:'), 6, 0) self.grid_layout.addWidget(self.textDispCheckBox, 6, 1) self.grid_layout.setRowStretch(7, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4)
class PSContainer(QWidget): """PSContainer.""" def __init__(self, widget, parent=None): super().__init__(parent) self._widget = widget self.name = widget.devname self.bbbname = widget.bbbname self.udcname = widget.udcname self.dclinks = list() self.dclinks_type = '' self.dclink_widgets = list() self.dclinksbbbname = set() self.dclinksudcname = set() dclinks = PSSearch.conv_psname_2_dclink(self.name) if dclinks: self.dclinks = dclinks self.dclinks_type = PSSearch.conv_psname_2_psmodel(dclinks[0]) if self.dclinks_type != 'REGATRON_DCLink': for dc in dclinks: self.dclinksbbbname.add(PSSearch.conv_psname_2_bbbname(dc)) self.dclinksudcname.add(PSSearch.conv_psname_2_udc(dc)) self.all_props = get_prop2label(PVName(dclinks[0])) self.visible_props = sort_propties( ['detail', 'state', 'intlk', 'setpoint', 'monitor']) self._setup_ui() self._create_actions() self._enable_actions() self.setStyleSheet(""" #HideButton { min-width: 10px; max-width: 10px; } #DCLinkContainer { background-color: lightgrey; } """) def _setup_ui(self): """Setup widget UI.""" self._layout = QGridLayout() self._layout.setSpacing(10) self._layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._layout) self._dclink_container = QWidget(self) self._dclink_container.setObjectName('DCLinkContainer') self._dclink_container.setLayout(QVBoxLayout()) self._dclink_is_filled = False if self.dclinks: self._hide = QPushButton(qta.icon('mdi.plus'), '', self) else: self._hide = QPushButton('', self) self._hide.setEnabled(False) self._hide.setObjectName('HideButton') self._hide.setSizePolicy(QSzPlcy.Maximum, QSzPlcy.Maximum) self._hide.setFlat(True) self._layout.addWidget(self._hide, 0, 0, Qt.AlignCenter) self._layout.addWidget(self._widget, 0, 1) self._layout.addWidget(self._dclink_container, 1, 1) # Configure self._dclink_container.setHidden(True) self._hide.clicked.connect(self._toggle_dclink) def _toggle_dclink(self): if self._dclink_container.isHidden(): if not self._dclink_is_filled: self._fill_dclink_container() self._enable_actions() self._hide.setIcon(qta.icon('mdi.minus')) self._dclink_container.setHidden(False) else: self._hide.setIcon(qta.icon('mdi.plus')) self._dclink_container.setHidden(True) def _fill_dclink_container(self): self._dclink_is_filled = True self._dclink_container.layout().addWidget( SummaryHeader(self.dclinks[0], self.visible_props, self)) for dclink_name in self.dclinks: w = SummaryWidget(dclink_name, self.visible_props, self) if self.dclinks_type == 'REGATRON_DCLink': connect_newprocess(w.detail_bt, [ 'sirius-hla-as-ps-regatron-individual', '-dev', dclink_name ], parent=self, is_pydm=True) else: connect_window(w.detail_bt, PSDetailWindow, self, psname=dclink_name) self._dclink_container.layout().addWidget(w) self.dclink_widgets.append(w) def update_visible_props(self, new_value): self.visible_props = sort_propties(new_value) self._enable_actions() # Action methods def _create_actions(self): self._turn_on_action = QAction('Turn DCLinks On', self) self._turn_on_action.triggered.connect( lambda: self._set_dclink_pwrstate(True)) self._turn_on_action.setEnabled(False) self._turn_off_action = QAction('Turn DCLinks Off', self) self._turn_off_action.triggered.connect( lambda: self._set_dclink_pwrstate(False)) self._turn_off_action.setEnabled(False) self._open_loop_action = QAction('Open DCLinks Control Loop', self) self._open_loop_action.triggered.connect( lambda: self._set_dclink_control_loop(False)) self._open_loop_action.setEnabled(False) self._close_loop_action = QAction('Close DCLinks Control Loop', self) self._close_loop_action.triggered.connect( lambda: self._set_dclink_control_loop(True)) self._close_loop_action.setEnabled(False) self._set_setpoint_action = QAction('Set DCLinks Voltage', self) self._set_setpoint_action.triggered.connect(self._set_setpoint) self._set_setpoint_action.setEnabled(False) self._reset_intlk_action = QAction('Reset DCLinks Interlocks', self) self._reset_intlk_action.triggered.connect(self._reset_intlk) self._reset_intlk_action.setEnabled(False) def _enable_actions(self): if 'state' in self.visible_props and \ not self._turn_on_action.isEnabled(): self._turn_on_action.setEnabled(True) self._turn_off_action.setEnabled(True) if 'ctrlloop' in self.visible_props and \ not self._open_loop_action.isEnabled(): self._open_loop_action.setEnabled(True) self._close_loop_action.setEnabled(True) if 'setpoint' in self.visible_props and \ not self._set_setpoint_action.isEnabled(): self._set_setpoint_action.setEnabled(True) if 'reset' in self.visible_props and \ not self._reset_intlk_action.isEnabled(): self._reset_intlk_action.setEnabled(True) def _set_dclink_pwrstate(self, value): for dclink in self.dclink_widgets: if value: dclink.turn_on() else: dclink.turn_off() def _set_dclink_control_loop(self, value): for dclink in self.dclink_widgets: btn = dclink.ctrlloop_bt if value: if btn._bit_val: btn.send_value() else: if not btn._bit_val: btn.send_value() def _set_setpoint(self): """Set current setpoint for every visible widget.""" dlg = QInputDialog(self) dlg.setLocale(QLocale(QLocale.English)) new_value, ok = dlg.getDouble(self, "New setpoint", "Value") if ok: for dclink in self.dclink_widgets: sp = dclink.setpoint.sp_lineedit sp.setText(str(new_value)) try: sp.send_value() except TypeError: pass def _reset_intlk(self): for dclink in self.dclink_widgets: dclink.reset() # Overloaded method def contextMenuEvent(self, event): """Overload to create a custom context menu.""" widget = self.childAt(event.pos()) parent = widget.parent() grand_parent = parent.parent() if widget.objectName() == 'DCLinkContainer' or \ parent.objectName() == 'DCLinkContainer' or \ grand_parent.objectName() == 'DCLinkContainer': menu = QMenu(self) menu.addAction(self._turn_on_action) menu.addAction(self._turn_off_action) menu.addSeparator() menu.addAction(self._close_loop_action) menu.addAction(self._open_loop_action) menu.addSeparator() menu.addAction(self._set_setpoint_action) menu.addSeparator() menu.addAction(self._reset_intlk_action) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(event.globalPos()) else: super().contextMenuEvent(event) def show_connections(self, checked): """.""" _ = checked c = ConnectionInspector(self) c.show()
def setup_page(self): self.ICON = ima.icon('eyedropper') names = self.get_option("names") try: names.pop(names.index(u'Custom')) except ValueError: pass custom_names = self.get_option("custom_names", []) # Interface options theme_group = QGroupBox(_("Main interface")) # Interface Widgets ui_themes = ['Automatic', 'Light', 'Dark'] ui_theme_choices = list(zip(ui_themes, [ui_theme.lower() for ui_theme in ui_themes])) ui_theme_combo = self.create_combobox(_('Interface theme'), ui_theme_choices, 'ui_theme', restart=True) styles = [str(txt) for txt in list(QStyleFactory.keys())] # Don't offer users the possibility to change to a different # style in Gtk-based desktops # Fixes Issue 2036 if is_gtk_desktop() and ('GTK+' in styles): styles = ['GTK+'] choices = list(zip(styles, [style.lower() for style in styles])) style_combo = self.create_combobox(_('Qt windows style'), choices, 'windows_style', default=self.main.default_style) self.style_combobox = style_combo.combobox themes = ['Spyder 2', 'Spyder 3'] icon_choices = list(zip(themes, [theme.lower() for theme in themes])) icons_combo = self.create_combobox(_('Icon theme'), icon_choices, 'icon_theme', restart=True) theme_comboboxes_layout = QGridLayout() theme_comboboxes_layout.addWidget(ui_theme_combo.label, 0, 0) theme_comboboxes_layout.addWidget(ui_theme_combo.combobox, 0, 1) theme_comboboxes_layout.addWidget(style_combo.label, 1, 0) theme_comboboxes_layout.addWidget(self.style_combobox, 1, 1) theme_comboboxes_layout.addWidget(icons_combo.label, 2, 0) theme_comboboxes_layout.addWidget(icons_combo.combobox, 2, 1) theme_layout = QVBoxLayout() theme_layout.addLayout(theme_comboboxes_layout) theme_group.setLayout(theme_layout) # Syntax coloring options syntax_group = QGroupBox(_("Syntax highlighting theme")) # Syntax Widgets edit_button = QPushButton(_("Edit selected scheme")) create_button = QPushButton(_("Create new scheme")) self.delete_button = QPushButton(_("Delete scheme")) self.reset_button = QPushButton(_("Reset to defaults")) self.preview_editor = CodeEditor(self) self.stacked_widget = QStackedWidget(self) self.scheme_editor_dialog = SchemeEditor(parent=self, stack=self.stacked_widget) self.scheme_choices_dict = {} schemes_combobox_widget = self.create_combobox('', [('', '')], 'selected') self.schemes_combobox = schemes_combobox_widget.combobox # Syntax layout syntax_layout = QGridLayout(syntax_group) btns = [self.schemes_combobox, edit_button, self.reset_button, create_button, self.delete_button] for i, btn in enumerate(btns): syntax_layout.addWidget(btn, i, 1) syntax_layout.setColumnStretch(0, 1) syntax_layout.setColumnStretch(1, 2) syntax_layout.setColumnStretch(2, 1) syntax_layout.setContentsMargins(0, 12, 0, 12) # Fonts options fonts_group = QGroupBox(_("Fonts")) # Fonts widgets plain_text_font = self.create_fontgroup( option='font', title=_("Plain text"), fontfilters=QFontComboBox.MonospacedFonts, without_group=True) rich_text_font = self.create_fontgroup( option='rich_font', title=_("Rich text"), without_group=True) # Fonts layouts fonts_layout = QGridLayout() fonts_layout.addWidget(plain_text_font.fontlabel, 0, 0) fonts_layout.addWidget(plain_text_font.fontbox, 0, 1) fonts_layout.addWidget(plain_text_font.sizelabel, 0, 2) fonts_layout.addWidget(plain_text_font.sizebox, 0, 3) fonts_layout.addWidget(rich_text_font.fontlabel, 1, 0) fonts_layout.addWidget(rich_text_font.fontbox, 1, 1) fonts_layout.addWidget(rich_text_font.sizelabel, 1, 2) fonts_layout.addWidget(rich_text_font.sizebox, 1, 3) fonts_group.setLayout(fonts_layout) # Left options layout options_layout = QVBoxLayout() options_layout.addWidget(theme_group) options_layout.addWidget(syntax_group) options_layout.addWidget(fonts_group) # Right preview layout preview_group = QGroupBox(_("Preview")) preview_layout = QVBoxLayout() preview_layout.addWidget(self.preview_editor) preview_group.setLayout(preview_layout) # Combined layout combined_layout = QGridLayout() combined_layout.setRowStretch(0, 1) combined_layout.setColumnStretch(1, 100) combined_layout.addLayout(options_layout, 0, 0) combined_layout.addWidget(preview_group, 0, 1) self.setLayout(combined_layout) # Signals and slots create_button.clicked.connect(self.create_new_scheme) edit_button.clicked.connect(self.edit_scheme) self.reset_button.clicked.connect(self.reset_to_default) self.delete_button.clicked.connect(self.delete_scheme) self.schemes_combobox.currentIndexChanged.connect(self.update_preview) self.schemes_combobox.currentIndexChanged.connect(self.update_buttons) # Setup for name in names: self.scheme_editor_dialog.add_color_scheme_stack(name) for name in custom_names: self.scheme_editor_dialog.add_color_scheme_stack(name, custom=True) self.update_combobox() self.update_preview() self.update_qt_style_combobox()
def setup_page(self): self.ICON = ima.icon('eyedropper') names = self.get_option("names") try: names.pop(names.index(u'Custom')) except ValueError: pass custom_names = self.get_option("custom_names", []) # Interface options theme_group = QGroupBox(_("Main interface")) # Interface Widgets ui_themes = ['Automatic', 'Light', 'Dark'] ui_theme_choices = list( zip(ui_themes, [ui_theme.lower() for ui_theme in ui_themes])) ui_theme_combo = self.create_combobox(_('Interface theme'), ui_theme_choices, 'ui_theme', restart=True) styles = [str(txt) for txt in list(QStyleFactory.keys())] # Don't offer users the possibility to change to a different # style in Gtk-based desktops # See spyder-ide/spyder#2036. if is_gtk_desktop() and ('GTK+' in styles): styles = ['GTK+'] choices = list(zip(styles, [style.lower() for style in styles])) style_combo = self.create_combobox(_('Qt windows style'), choices, 'windows_style', default=self.main.default_style) self.style_combobox = style_combo.combobox themes = ['Spyder 2', 'Spyder 3'] icon_choices = list(zip(themes, [theme.lower() for theme in themes])) icons_combo = self.create_combobox(_('Icon theme'), icon_choices, 'icon_theme', restart=True) theme_comboboxes_layout = QGridLayout() theme_comboboxes_layout.addWidget(ui_theme_combo.label, 0, 0) theme_comboboxes_layout.addWidget(ui_theme_combo.combobox, 0, 1) theme_comboboxes_layout.addWidget(style_combo.label, 1, 0) theme_comboboxes_layout.addWidget(self.style_combobox, 1, 1) theme_comboboxes_layout.addWidget(icons_combo.label, 2, 0) theme_comboboxes_layout.addWidget(icons_combo.combobox, 2, 1) theme_layout = QVBoxLayout() theme_layout.addLayout(theme_comboboxes_layout) theme_group.setLayout(theme_layout) # Syntax coloring options syntax_group = QGroupBox(_("Syntax highlighting theme")) # Syntax Widgets edit_button = QPushButton(_("Edit selected scheme")) create_button = QPushButton(_("Create new scheme")) self.delete_button = QPushButton(_("Delete scheme")) self.reset_button = QPushButton(_("Reset to defaults")) self.preview_editor = CodeEditor(self) self.stacked_widget = QStackedWidget(self) self.scheme_editor_dialog = SchemeEditor(parent=self, stack=self.stacked_widget) self.scheme_choices_dict = {} schemes_combobox_widget = self.create_combobox('', [('', '')], 'selected') self.schemes_combobox = schemes_combobox_widget.combobox # Syntax layout syntax_layout = QGridLayout(syntax_group) btns = [ self.schemes_combobox, edit_button, self.reset_button, create_button, self.delete_button ] for i, btn in enumerate(btns): syntax_layout.addWidget(btn, i, 1) syntax_layout.setColumnStretch(0, 1) syntax_layout.setColumnStretch(1, 2) syntax_layout.setColumnStretch(2, 1) syntax_layout.setContentsMargins(0, 12, 0, 12) # Fonts options fonts_group = QGroupBox(_("Fonts")) # Fonts widgets plain_text_font = self.create_fontgroup( option='font', title=_("Plain text"), fontfilters=QFontComboBox.MonospacedFonts, without_group=True) rich_text_font = self.create_fontgroup(option='rich_font', title=_("Rich text"), without_group=True) # Fonts layouts fonts_layout = QGridLayout(fonts_group) fonts_layout.addWidget(plain_text_font.fontlabel, 0, 0) fonts_layout.addWidget(plain_text_font.fontbox, 0, 1) fonts_layout.addWidget(plain_text_font.sizelabel, 0, 2) fonts_layout.addWidget(plain_text_font.sizebox, 0, 3) fonts_layout.addWidget(rich_text_font.fontlabel, 1, 0) fonts_layout.addWidget(rich_text_font.fontbox, 1, 1) fonts_layout.addWidget(rich_text_font.sizelabel, 1, 2) fonts_layout.addWidget(rich_text_font.sizebox, 1, 3) fonts_layout.setRowStretch(fonts_layout.rowCount(), 1) # Left options layout options_layout = QVBoxLayout() options_layout.addWidget(theme_group) options_layout.addWidget(syntax_group) options_layout.addWidget(fonts_group) # Right preview layout preview_group = QGroupBox(_("Preview")) preview_layout = QVBoxLayout() preview_layout.addWidget(self.preview_editor) preview_group.setLayout(preview_layout) # Combined layout combined_layout = QGridLayout() combined_layout.setRowStretch(0, 1) combined_layout.setColumnStretch(1, 100) combined_layout.addLayout(options_layout, 0, 0) combined_layout.addWidget(preview_group, 0, 1) self.setLayout(combined_layout) # Signals and slots create_button.clicked.connect(self.create_new_scheme) edit_button.clicked.connect(self.edit_scheme) self.reset_button.clicked.connect(self.reset_to_default) self.delete_button.clicked.connect(self.delete_scheme) self.schemes_combobox.currentIndexChanged.connect(self.update_preview) self.schemes_combobox.currentIndexChanged.connect(self.update_buttons) # Setup for name in names: self.scheme_editor_dialog.add_color_scheme_stack(name) for name in custom_names: self.scheme_editor_dialog.add_color_scheme_stack(name, custom=True) self.update_combobox() self.update_preview() self.update_qt_style_combobox()
def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect( lambda state: self.find(changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp')) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown')) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('advanced'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_button = create_toolbutton(self, text=_('Replace/find'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches)
class ThumbnailScrollBar(QFrame): """ A widget that manages the display of the FigureThumbnails that are created when a figure is sent to the IPython console by the kernel and that controls what is displayed in the FigureViewer. """ redirect_stdio = Signal(bool) _min_scrollbar_width = 100 def __init__(self, figure_viewer, parent=None, background_color=None): super(ThumbnailScrollBar, self).__init__(parent) self._thumbnails = [] self.background_color = background_color self.current_thumbnail = None self.set_figureviewer(figure_viewer) self.setup_gui() def setup_gui(self): """Setup the main layout of the widget.""" scrollarea = self.setup_scrollarea() up_btn, down_btn = self.setup_arrow_buttons() layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(up_btn) layout.addWidget(scrollarea) layout.addWidget(down_btn) def setup_scrollarea(self): """Setup the scrollarea that will contain the FigureThumbnails.""" self.view = QWidget() self.scene = QGridLayout(self.view) self.scene.setContentsMargins(0, 0, 0, 0) self.scrollarea = QScrollArea() self.scrollarea.setWidget(self.view) self.scrollarea.setWidgetResizable(True) self.scrollarea.setFrameStyle(0) self.scrollarea.setViewportMargins(2, 2, 2, 2) self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollarea.setMinimumWidth(self._min_scrollbar_width) # Set the vertical scrollbar explicitely. # This is required to avoid a "RuntimeError: no access to protected # functions or signals for objects not created from Python" in Linux. self.scrollarea.setVerticalScrollBar(QScrollBar()) # Install an event filter on the scrollbar. self.scrollarea.installEventFilter(self) return self.scrollarea def setup_arrow_buttons(self): """ Setup the up and down arrow buttons that are placed at the top and bottom of the scrollarea. """ # Get the size hint height of the horizontal scrollbar. height = self.scrollarea.horizontalScrollBar().sizeHint().height() # Setup the up and down arrow button. up_btn = up_btn = QPushButton(icon=ima.icon('last_edit_location')) up_btn.setFlat(True) up_btn.setFixedHeight(height) up_btn.clicked.connect(self.go_up) down_btn = QPushButton(icon=ima.icon('folding.arrow_down_on')) down_btn.setFlat(True) down_btn.setFixedHeight(height) down_btn.clicked.connect(self.go_down) return up_btn, down_btn def set_figureviewer(self, figure_viewer): """Set the bamespace for the FigureViewer.""" self.figure_viewer = figure_viewer def eventFilter(self, widget, event): """ An event filter to trigger an update of the thumbnails size so that their width fit that of the scrollarea. """ if event.type() == QEvent.Resize: self._update_thumbnail_size() return super(ThumbnailScrollBar, self).eventFilter(widget, event) # ---- Save Figure def save_all_figures_as(self): """Save all the figures to a file.""" self.redirect_stdio.emit(False) dirname = getexistingdirectory(self, caption='Save all figures', basedir=getcwd_or_home()) self.redirect_stdio.emit(True) if dirname: return self.save_all_figures_todir(dirname) def save_all_figures_todir(self, dirname): """Save all figure in dirname.""" fignames = [] for thumbnail in self._thumbnails: fig = thumbnail.canvas.fig fmt = thumbnail.canvas.fmt fext = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/svg+xml': '.svg' }[fmt] figname = get_unique_figname(dirname, 'Figure', fext) save_figure_tofile(fig, fmt, figname) fignames.append(figname) return fignames def save_current_figure_as(self): """Save the currently selected figure.""" if self.current_thumbnail is not None: self.save_figure_as(self.current_thumbnail.canvas.fig, self.current_thumbnail.canvas.fmt) def save_figure_as(self, fig, fmt): """Save the figure to a file.""" fext, ffilt = { 'image/png': ('.png', 'PNG (*.png)'), 'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'), 'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)') }[fmt] figname = get_unique_figname(getcwd_or_home(), 'Figure', fext) self.redirect_stdio.emit(False) fname, fext = getsavefilename(parent=self.parent(), caption='Save Figure', basedir=figname, filters=ffilt, selectedfilter='', options=None) self.redirect_stdio.emit(True) if fname: save_figure_tofile(fig, fmt, fname) # ---- Thumbails Handlers def _calculate_figure_canvas_width(self, thumbnail): """ Calculate the witdh the thumbnail's figure canvas need to have for the thumbnail to fit the scrollarea. """ extra_padding = 10 if sys.platform == 'darwin' else 0 figure_canvas_width = (self.scrollarea.width() - 2 * self.lineWidth() - self.scrollarea.viewportMargins().left() - self.scrollarea.viewportMargins().right() - thumbnail.savefig_btn.width() - thumbnail.layout().spacing() - extra_padding) if is_dark_interface(): # This is required to take into account some hard-coded padding # and margin in qdarkstyle. figure_canvas_width = figure_canvas_width - 6 return figure_canvas_width def _setup_thumbnail_size(self, thumbnail): """ Scale the thumbnail's canvas size so that it fits the thumbnail scrollbar's width. """ max_canvas_size = self._calculate_figure_canvas_width(thumbnail) thumbnail.scale_canvas_size(max_canvas_size) def _update_thumbnail_size(self): """ Update the thumbnails size so that their width fit that of the scrollarea. """ # NOTE: We hide temporarily the thumbnails to prevent a repaint of # each thumbnail as soon as their size is updated in the loop, which # causes some flickering of the thumbnail scrollbar resizing animation. # Once the size of all the thumbnails has been updated, we show them # back so that they are repainted all at once instead of one after the # other. This is just a trick to make the resizing animation of the # thumbnail scrollbar look smoother. self.view.hide() for thumbnail in self._thumbnails: self._setup_thumbnail_size(thumbnail) self.view.show() def add_thumbnail(self, fig, fmt): """ Add a new thumbnail to that thumbnail scrollbar. """ thumbnail = FigureThumbnail(parent=self, background_color=self.background_color) thumbnail.canvas.load_figure(fig, fmt) thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail) thumbnail.sig_remove_figure.connect(self.remove_thumbnail) thumbnail.sig_save_figure.connect(self.save_figure_as) self._thumbnails.append(thumbnail) self.scene.setRowStretch(self.scene.rowCount() - 1, 0) self.scene.addWidget(thumbnail, self.scene.rowCount() - 1, 0) self.scene.setRowStretch(self.scene.rowCount(), 100) self.set_current_thumbnail(thumbnail) thumbnail.show() self._setup_thumbnail_size(thumbnail) def remove_current_thumbnail(self): """Remove the currently selected thumbnail.""" if self.current_thumbnail is not None: self.remove_thumbnail(self.current_thumbnail) def remove_all_thumbnails(self): """Remove all thumbnails.""" for thumbnail in self._thumbnails: self.layout().removeWidget(thumbnail) thumbnail.sig_canvas_clicked.disconnect() thumbnail.sig_remove_figure.disconnect() thumbnail.sig_save_figure.disconnect() self._thumbnails = [] self.current_thumbnail = None self.figure_viewer.figcanvas.clear_canvas() def remove_thumbnail(self, thumbnail): """Remove thumbnail.""" if thumbnail in self._thumbnails: index = self._thumbnails.index(thumbnail) self._thumbnails.remove(thumbnail) self.layout().removeWidget(thumbnail) thumbnail.sig_canvas_clicked.disconnect() thumbnail.sig_remove_figure.disconnect() thumbnail.sig_save_figure.disconnect() # Select a new thumbnail if any : if thumbnail == self.current_thumbnail: if len(self._thumbnails) > 0: self.set_current_index(min(index, len(self._thumbnails) - 1)) else: self.current_thumbnail = None self.figure_viewer.figcanvas.clear_canvas() def set_current_index(self, index): """Set the currently selected thumbnail by its index.""" self.set_current_thumbnail(self._thumbnails[index]) def get_current_index(self): """Return the index of the currently selected thumbnail.""" try: return self._thumbnails.index(self.current_thumbnail) except ValueError: return -1 def set_current_thumbnail(self, thumbnail): """Set the currently selected thumbnail.""" self.current_thumbnail = thumbnail self.figure_viewer.load_figure(thumbnail.canvas.fig, thumbnail.canvas.fmt) for thumbnail in self._thumbnails: thumbnail.highlight_canvas(thumbnail == self.current_thumbnail) def go_previous_thumbnail(self): """Select the thumbnail previous to the currently selected one.""" if self.current_thumbnail is not None: index = self._thumbnails.index(self.current_thumbnail) - 1 index = index if index >= 0 else len(self._thumbnails) - 1 self.set_current_index(index) self.scroll_to_item(index) def go_next_thumbnail(self): """Select thumbnail next to the currently selected one.""" if self.current_thumbnail is not None: index = self._thumbnails.index(self.current_thumbnail) + 1 index = 0 if index >= len(self._thumbnails) else index self.set_current_index(index) self.scroll_to_item(index) def scroll_to_item(self, index): """Scroll to the selected item of ThumbnailScrollBar.""" spacing_between_items = self.scene.verticalSpacing() height_view = self.scrollarea.viewport().height() height_item = self.scene.itemAt(index).sizeHint().height() height_view_excluding_item = max(0, height_view - height_item) height_of_top_items = spacing_between_items for i in range(index): item = self.scene.itemAt(i) height_of_top_items += item.sizeHint().height() height_of_top_items += spacing_between_items pos_scroll = height_of_top_items - height_view_excluding_item // 2 vsb = self.scrollarea.verticalScrollBar() vsb.setValue(pos_scroll) # ---- ScrollBar Handlers def go_up(self): """Scroll the scrollbar of the scrollarea up by a single step.""" vsb = self.scrollarea.verticalScrollBar() vsb.setValue(int(vsb.value() - vsb.singleStep())) def go_down(self): """Scroll the scrollbar of the scrollarea down by a single step.""" vsb = self.scrollarea.verticalScrollBar() vsb.setValue(int(vsb.value() + vsb.singleStep()))
def _setupControlsWidget(self): ld_growenbl = QLabel('Grow/Damp Enable', self) cb_growenbl = PyDMEnumComboBox(self, self.dev_pref + ':GDEN') ld_down = QLabel('Rec. Downsample ', self) sb_down = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_REC_DS') sb_down.showStepExponent = False ld_rawdata = QLabel('Raw Data', self) cb_rawdata = PyDMStateButton(self, self.dev_pref + ':' + self.TYPE + '_DUMP') ld_acqtime = QLabel('Acquisition Time', self) sb_acqtime = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_ACQTIME') sb_acqtime.showStepExponent = False sb_acqtime.showUnits = True ld_holdoff = QLabel('Hold-Off Time', self) sb_holdoff = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_HOLDTIME') sb_holdoff.showStepExponent = False sb_holdoff.showUnits = True ld_posttrg = QLabel('Post Trigger', self) sb_posttrg = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_POSTTIME') sb_posttrg.showStepExponent = False sb_posttrg.showUnits = True fr_posttrg = SiriusFrame( self, self.dev_pref + ':' + self.TYPE + '_POSTREG_SUBWR') fr_posttrg.add_widget(sb_posttrg) ld_growtime = QLabel('Growth Time', self) sb_growtime = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_GDTIME') sb_growtime.showStepExponent = False sb_growtime.showUnits = True fr_growtime = SiriusFrame( self, self.dev_pref + ':' + self.TYPE + '_GDREG_SUBWR') fr_growtime.add_widget(sb_growtime) ld_acqlen = QLabel('Acquisition Length', self) lb_acqlen = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_ACQ_TURNS') lb_acqlen.showUnits = True ld_psttrglen = QLabel('Post Trigger Length', self) lb_psttrglen = PyDMLabel( self, self.dev_pref + ':' + self.TYPE + '_POST_TURNS') lb_psttrglen.showUnits = True bt_modal = QPushButton('Modal Analysis', self) window = create_window_from_widget(_BbBModalAnalysis, title='SRAM Modal Analysis', icon=get_bbb_icon(), is_main=True) connect_window(bt_modal, window, self, prefix=self._prefix, device=self._device, acq_type=self.TYPE) gbox_dtacq = QGroupBox('Data Acquisition', self) lay_dtacq = QGridLayout(gbox_dtacq) lay_dtacq.addWidget(ld_growenbl, 0, 0) lay_dtacq.addWidget(cb_growenbl, 0, 1) lay_dtacq.addWidget(ld_down, 1, 0) lay_dtacq.addWidget(sb_down, 1, 1) lay_dtacq.addWidget(ld_rawdata, 2, 0) lay_dtacq.addWidget(cb_rawdata, 2, 1) lay_dtacq.addWidget(ld_acqtime, 3, 0) lay_dtacq.addWidget(sb_acqtime, 3, 1) lay_dtacq.addWidget(ld_holdoff, 4, 0) lay_dtacq.addWidget(sb_holdoff, 4, 1) lay_dtacq.addWidget(ld_posttrg, 5, 0) lay_dtacq.addWidget(fr_posttrg, 5, 1) lay_dtacq.addWidget(ld_growtime, 6, 0) lay_dtacq.addWidget(fr_growtime, 6, 1) lay_dtacq.addWidget(ld_acqlen, 7, 0) lay_dtacq.addWidget(lb_acqlen, 7, 1) lay_dtacq.addWidget(ld_psttrglen, 8, 0) lay_dtacq.addWidget(lb_psttrglen, 8, 1) lay_dtacq.addWidget(bt_modal, 9, 0, 1, 2) ld_acqtyp = QLabel('<h4>Acq Type</h4>', self, alignment=Qt.AlignCenter) cb_acqtyp = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_POSTSEL') gbox_acqtyp = QGroupBox(self) lay_acqtyp = QVBoxLayout(gbox_acqtyp) lay_acqtyp.addWidget(ld_acqtyp) lay_acqtyp.addWidget(cb_acqtyp) ld_trgexten = QLabel('Internal/External', self) cb_trgexten = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_HWTEN') ld_trginsel = QLabel('Selection', self) cb_trginsel = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_TRIG_IN_SEL') ld_trgarm = QLabel('Arm', self) cb_trgarm = PyDMStateButton(self, self.dev_pref + ':' + self.TYPE + '_ARM') lb_armmon = SiriusLedState( self, self.dev_pref + ':' + self.TYPE + '_ARM_MON') ld_trgbrarm = QLabel('Auto re-arm', self) cb_trgbrarm = PyDMStateButton( self, self.dev_pref + ':' + self.TYPE + '_BR_ARM') ld_rst = QLabel('Trigger 1/2 Cap.:', self) lb_rst1 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_CAP_TRIG1') lb_rst2 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_CAP_TRIG2') gbox_trig = QGroupBox('Trigger', self) lay_trig = QGridLayout(gbox_trig) lay_trig.setAlignment(Qt.AlignTop) lay_trig.addWidget(ld_trgexten, 0, 0) lay_trig.addWidget(cb_trgexten, 0, 1, 1, 2) lay_trig.addWidget(ld_trginsel, 1, 0) lay_trig.addWidget(cb_trginsel, 1, 1, 1, 2) lay_trig.addWidget(ld_trgarm, 2, 0) lay_trig.addWidget(cb_trgarm, 2, 1) lay_trig.addWidget(lb_armmon, 2, 2) lay_trig.addWidget(ld_trgbrarm, 3, 0) lay_trig.addWidget(cb_trgbrarm, 3, 1) lay_trig.addWidget(ld_rst, 4, 0) lay_trig.addWidget(lb_rst1, 4, 1) lay_trig.addWidget(lb_rst2, 4, 2) lay_trig.setRowStretch(5, 2) pixmap = QPixmap( _os.path.join(_os.path.abspath(_os.path.dirname(__file__)), 'grow_damp.png')) img_wid = QLabel(self) img_wid.setPixmap(pixmap) img_wid.setScaledContents(True) wid = QWidget() lay = QGridLayout(wid) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(gbox_acqtyp, 0, 0) lay.addWidget(gbox_dtacq, 1, 0) lay.addWidget(gbox_trig, 2, 0) lay.addWidget(img_wid, 4, 0) lay.setRowStretch(3, 5) lay.setRowStretch(5, 5) wid.setStyleSheet("SiriusFrame{max-height: 1.8em;}") return wid
class UiLinelistsWindow(object): # this code was taken as-is from the Designer. # Cleaning it up sounds like a lower priority # task for now. def setupUi(self, MainWindow, title): MainWindow.setWindowTitle(title) MainWindow.setObjectName("MainWindow") MainWindow.resize(600, 850) MainWindow.setMinimumSize(QSize(300, 350)) self.centralWidget = QWidget(MainWindow) self.centralWidget.setObjectName("centralWidget") self.gridLayout = QGridLayout(self.centralWidget) self.gridLayout.setContentsMargins(11, 11, 11, 11) self.gridLayout.setSpacing(6) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout_5 = QHBoxLayout() self.horizontalLayout_5.setContentsMargins(11, 11, 11, 11) self.horizontalLayout_5.setSpacing(6) self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.lines_selected_label = QLabel(self.centralWidget) self.lines_selected_label.setObjectName("lines_selected_label") self.horizontalLayout_5.addWidget(self.lines_selected_label) self.label = QLabel(self.centralWidget) self.label.setObjectName("label") self.horizontalLayout_5.addWidget(self.label) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout_5.addItem(spacerItem) self.draw_button = QPushButton(self.centralWidget) self.draw_button.setObjectName("draw_button") self.horizontalLayout_5.addWidget(self.draw_button) self.erase_button = QPushButton(self.centralWidget) self.erase_button.setObjectName("erase_button") self.horizontalLayout_5.addWidget(self.erase_button) self.dismiss_button = QPushButton(self.centralWidget) self.dismiss_button.setObjectName("dismiss_button") self.horizontalLayout_5.addWidget(self.dismiss_button) self.gridLayout.addLayout(self.horizontalLayout_5, 4, 0, 1, 1) self.verticalLayout_11 = QVBoxLayout() self.verticalLayout_11.setContentsMargins(11, 11, 11, 11) self.verticalLayout_11.setSpacing(6) self.verticalLayout_11.setObjectName("verticalLayout_11") self.tabWidget = QTabWidget(self.centralWidget) self.tabWidget.setObjectName("tabWidget") self.tabWidget.setTabsClosable(True) self.verticalLayout_11.addWidget(self.tabWidget) self.gridLayout.addLayout(self.verticalLayout_11, 0, 0, 1, 1) self.horizontalLayout_7 = QHBoxLayout() self.horizontalLayout_7.setContentsMargins(11, 11, 11, 11) self.horizontalLayout_7.setSpacing(6) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout_7.addItem(spacerItem) self.horizontalLayout_7.setObjectName("horizontalLayout_7") self.gridLayout.addLayout(self.horizontalLayout_7, 2, 0, 2, 1) MainWindow.setCentralWidget(self.centralWidget) # self.menuBar = QMenuBar(MainWindow) # self.menuBar.setGeometry(QRect(0, 0, 767, 22)) # self.menuBar.setObjectName("menuBar") # # self.menuFile = QMenu(self.menuBar) # self.menuFile.setObjectName("menuFile") # # MainWindow.setMenuBar(self.menuBar) self.mainToolBar = QToolBar(MainWindow) self.mainToolBar.setMovable(False) self.mainToolBar.setFloatable(False) self.mainToolBar.setObjectName("mainToolBar") MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar) # self.statusBar = QStatusBar(MainWindow) # self.statusBar.setObjectName("statusBar") # MainWindow.setStatusBar(self.statusBar) self.actionOpen = QAction(MainWindow) icon = QIcon(os.path.join(ICON_PATH, "Open Folder-48.png")) self.actionOpen.setIcon(icon) self.actionOpen.setObjectName("actionOpen") self.actionExport = QAction(MainWindow) icon = QIcon(os.path.join(ICON_PATH, "Export-48.png")) self.actionExport.setIcon(icon) self.actionExport.setObjectName("actionExport") self.line_list_selector = QComboBox() self.line_list_selector.setToolTip( "Select line list from internal library") self.actionExit = QAction(MainWindow) self.actionExit.setObjectName("actionExit") self.actionRemove = QAction(MainWindow) self.actionRemove.setObjectName("actionRemove") self.actionChange_Color = QAction(MainWindow) self.actionChange_Color.setObjectName("actionChange_Color") # self.menuFile.addAction(self.actionOpen) # self.menuFile.addSeparator() # self.menuFile.addAction(self.actionExit) # self.menuBar.addAction(self.menuFile.menuAction()) self.mainToolBar.addAction(self.actionOpen) self.mainToolBar.addAction(self.actionExport) self.mainToolBar.addSeparator() self.mainToolBar.addWidget(self.line_list_selector) self.retranslateUi(MainWindow) QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QCoreApplication.translate self.lines_selected_label.setText(_translate("MainWindow", "0")) self.lines_selected_label.setToolTip( "Total number of lines selected in all sets.") self.label.setText(_translate("MainWindow", "lines selected")) self.label.setToolTip("Total number of lines selected in all sets.") self.draw_button.setText(_translate("MainWindow", "Draw")) self.draw_button.setToolTip( "Plot markers for all selected lines in all sets.") self.erase_button.setText(_translate("MainWindow", "Erase")) self.erase_button.setToolTip("Erase all markers") self.dismiss_button.setText(_translate("MainWindow", "Dismiss")) self.dismiss_button.setToolTip("Dismiss this window") # self.menuFile.setTitle(_translate("MainWindow", "File")) self.actionOpen.setText(_translate("MainWindow", "Open")) self.actionExport.setText( _translate("MainWindow", "Export plotted lines")) self.actionExit.setText(_translate("MainWindow", "Exit")) self.actionRemove.setText(_translate("MainWindow", "Remove")) self.actionRemove.setToolTip( _translate("MainWindow", "Removes the selected layer")) self.actionChange_Color.setText( _translate("MainWindow", "Change Color")) self.actionChange_Color.setToolTip( _translate("MainWindow", "Change the line color selected layer"))
def _setupWaveformsWidget(self): gp_mean = WfmGraph(self) gp_mean.setPlotTitle('Mean') gp_mean.getAxis('bottom').setLabel('Bunch number') gp_mean.getAxis('left').setLabel('CNT') gp_mean.add_scatter_curve( ychannel=self.dev_pref + ':' + self.TYPE + '_MEAN', xchannel=self.dev_pref + ':' + self.TYPE + '_XSC', color=QColor('red'), lineStyle=Qt.SolidLine) gp_maxrms = WfmGraph(self) gp_maxrms.setPlotTitle('Max RMS Channel (filtered)') gp_maxrms.getAxis('bottom').setLabel('Time (ms)') gp_maxrms.getAxis('left').setLabel('CNT') gp_maxrms.add_scatter_curve( ychannel=self.dev_pref + ':' + self.TYPE + '_MAXRMS', xchannel=self.dev_pref + ':' + self.TYPE + '_TSC', color=QColor('blue'), lineStyle=Qt.SolidLine) gp_rms = WfmGraph(self) gp_rms.setPlotTitle('RMS') gp_rms.getAxis('bottom').setLabel('Bunch number') gp_rms.getAxis('left').setLabel('CNT') gp_rms.add_scatter_curve( ychannel=self.dev_pref + ':' + self.TYPE + '_RMS', xchannel=self.dev_pref + ':' + self.TYPE + '_XSC', color=QColor('green'), lineStyle=Qt.SolidLine) gp_avgspe = WfmGraph(self) gp_avgspe.setPlotTitle('Average spectrum') gp_avgspe.getAxis('bottom').setLabel('Frequency (kHz)') gp_avgspe.getAxis('left').setLabel('dB') gp_avgspe.add_scatter_curve( ychannel=self.dev_pref + ':' + self.TYPE + '_SPEC', xchannel=self.dev_pref + ':' + self.TYPE + '_FREQ', color=QColor('blue'), lineStyle=Qt.SolidLine) gp_avgspe.add_marker( name='Marker 1', xchannel=self.dev_pref + ':' + self.TYPE + '_PEAKFREQ1', ychannel=self.dev_pref + ':' + self.TYPE + '_PEAK1', color=QColor('red'), symbol='o') gp_avgspe.add_marker( name='Marker 2', xchannel=self.dev_pref + ':' + self.TYPE + '_PEAKFREQ2', ychannel=self.dev_pref + ':' + self.TYPE + '_PEAK2', color=QColor('magenta'), symbol='s') lay_graph = QGridLayout() lay_graph.setContentsMargins(9, 9, 9, 9) lay_graph.addWidget(gp_mean, 0, 0) lay_graph.addWidget(gp_maxrms, 0, 1) lay_graph.addWidget(gp_rms, 1, 0) lay_graph.addWidget(gp_avgspe, 1, 1) ld_acqenbl = QLabel('Acq. Enable', self) cb_acqenbl = PyDMStateButton( self, self.dev_pref + ':' + self.TYPE + '_ACQ_EN') ld_acqsing = QLabel('Acq. Mode', self) cb_acqsing = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_ACQ_SINGLE') ld_mean = QLabel('Mean', self, alignment=Qt.AlignCenter) lb_mean = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_MEANVAL') ld_rms = QLabel('RMS', self, alignment=Qt.AlignCenter) lb_rms = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_RMSVAL') ld_ampp2p = QLabel('Amp P-P', self, alignment=Qt.AlignCenter) lb_ampp2p = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_AMP_PP') ld_maxrms = QLabel('Max RMS', self, alignment=Qt.AlignCenter) lb_maxrms = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_MAXRMSVAL') ld_bunpatt = QLabel('Bunch\npattern', self) le_bunpatt = PyDMLineEdit( self, self.dev_pref + ':' + self.TYPE + '_ACQ_PATTERN') ld_avg = QLabel('Sample Avg', self) sb_avg = PyDMSpinbox(self, self.dev_pref + ':' + self.TYPE + '_SP_AVG') sb_avg.showStepExponent = False gbox_acqctrl = QGroupBox('Acquisition control', self) lay_acqctrl = QGridLayout(gbox_acqctrl) lay_acqctrl.addWidget(ld_acqenbl, 0, 0) lay_acqctrl.addWidget(cb_acqenbl, 0, 1) lay_acqctrl.addWidget(ld_acqsing, 1, 0) lay_acqctrl.addWidget(cb_acqsing, 1, 1) lay_acqctrl.addWidget(ld_avg, 2, 0) lay_acqctrl.addWidget(sb_avg, 2, 1) lay_acqctrl.addItem(QSpacerItem(15, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 2, 3, 1) lay_acqctrl.addWidget(ld_mean, 0, 3) lay_acqctrl.addWidget(lb_mean, 0, 4) lay_acqctrl.addWidget(ld_ampp2p, 0, 5) lay_acqctrl.addWidget(lb_ampp2p, 0, 6) lay_acqctrl.addWidget(ld_rms, 1, 3) lay_acqctrl.addWidget(lb_rms, 1, 4) lay_acqctrl.addWidget(ld_maxrms, 1, 5) lay_acqctrl.addWidget(lb_maxrms, 1, 6) lay_acqctrl.addWidget(ld_bunpatt, 2, 3) lay_acqctrl.addWidget(le_bunpatt, 2, 4, 1, 3) # Markers ld_mk1 = QLabel('1', self, alignment=Qt.AlignCenter) ld_mk2 = QLabel('2', self, alignment=Qt.AlignCenter) ld_span = QLabel('Span (kHz)', self, alignment=Qt.AlignCenter) ld_mode = QLabel('Mode', self, alignment=Qt.AlignCenter) ld_val = QLabel('Value', self, alignment=Qt.AlignCenter) ld_pfrq = QLabel('Freq', self, alignment=Qt.AlignCenter) ld_tune = QLabel('Tune', self, alignment=Qt.AlignCenter) le_low1 = PyDMLineEdit(self, self.dev_pref + ':' + self.TYPE + '_SP_LOW1') le_high1 = PyDMLineEdit(self, self.dev_pref + ':' + self.TYPE + '_SP_HIGH1') cb_mode1 = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_SP_SEARCH1') lb_peak1 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAK1') lb_peak1.showUnits = True lb_pfrq1 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAKFREQ1') lb_pfrq1.showUnits = True lb_tune1 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAKTUNE1') le_low2 = PyDMLineEdit(self, self.dev_pref + ':' + self.TYPE + '_SP_LOW2') le_high2 = PyDMLineEdit(self, self.dev_pref + ':' + self.TYPE + '_SP_HIGH2') cb_mode2 = PyDMEnumComboBox( self, self.dev_pref + ':' + self.TYPE + '_SP_SEARCH2') lb_peak2 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAK2') lb_peak2.showUnits = True lb_pfrq2 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAKFREQ2') lb_pfrq2.showUnits = True lb_tune2 = PyDMLabel(self, self.dev_pref + ':' + self.TYPE + '_PEAKTUNE2') gbox_mark = QGroupBox('Markers', self) lay_mark = QGridLayout(gbox_mark) lay_mark.addWidget(ld_span, 0, 1, 1, 2) lay_mark.addWidget(ld_mode, 0, 3) lay_mark.addWidget(ld_val, 0, 4) lay_mark.addWidget(ld_pfrq, 0, 5) lay_mark.addWidget(ld_tune, 0, 6) lay_mark.addWidget(ld_mk1, 1, 0) lay_mark.addWidget(le_low1, 1, 1) lay_mark.addWidget(le_high1, 1, 2) lay_mark.addWidget(cb_mode1, 1, 3) lay_mark.addWidget(lb_peak1, 1, 4) lay_mark.addWidget(lb_pfrq1, 1, 5) lay_mark.addWidget(lb_tune1, 1, 6) lay_mark.addWidget(ld_mk2, 2, 0) lay_mark.addWidget(le_low2, 2, 1) lay_mark.addWidget(le_high2, 2, 2) lay_mark.addWidget(cb_mode2, 2, 3) lay_mark.addWidget(lb_peak2, 2, 4) lay_mark.addWidget(lb_pfrq2, 2, 5) lay_mark.addWidget(lb_tune2, 2, 6) wid = QWidget() lay = QGridLayout(wid) lay.setContentsMargins(0, 0, 0, 0) lay.addLayout(lay_graph, 0, 0, 1, 2) lay.addWidget(gbox_acqctrl, 1, 0) lay.addWidget(gbox_mark, 1, 1) lay.setRowStretch(0, 5) lay.setRowStretch(1, 1) lay.setColumnStretch(0, 1) lay.setColumnStretch(1, 1) return wid
class PyDMScaleIndicator(QFrame, TextFormatter, PyDMWidget): """ A bar-shaped indicator for scalar value with support for Channels and more from PyDM. Configurable features include indicator type (bar/pointer), scale tick marks and orientation (horizontal/vertical). Parameters ---------- parent : QWidget The parent widget for the Scale init_channel : str, optional The channel to be used by the widget. """ def __init__(self, parent=None, init_channel=None): QFrame.__init__(self, parent) PyDMWidget.__init__(self, init_channel=init_channel) self._show_value = True self._show_limits = True self.scale_indicator = QScale() self.value_label = QLabel() self.lower_label = QLabel() self.upper_label = QLabel() self.value_label.setText('<val>') self.lower_label.setText('<min>') self.upper_label.setText('<max>') self._value_position = Qt.TopEdge self._limits_from_channel = True self._user_lower_limit = 0 self._user_upper_limit = 0 self.value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setup_widgets_for_orientation(Qt.Horizontal, False, False, self._value_position) def update_labels(self): """ Update the limits and value labels with the correct values. """ self.lower_label.setText(str(self.scale_indicator._lower_limit)) self.upper_label.setText(str(self.scale_indicator._upper_limit)) self.value_label.setText( self.format_string.format(self.scale_indicator._value)) def value_changed(self, new_value): """ Callback invoked when the Channel value is changed. Parameters ---------- new_val : int or float The new value from the channel. """ super(PyDMScaleIndicator, self).value_changed(new_value) self.scale_indicator.set_value(new_value) self.update_labels() def upperCtrlLimitChanged(self, new_limit): """ PyQT Slot for changes on the upper control limit value of the Channel This slot sends the new limit value to the ```ctrl_limit_changed``` callback. Parameters ---------- new_limit : float """ super(PyDMScaleIndicator, self).upperCtrlLimitChanged(new_limit) if self.limitsFromChannel: self.scale_indicator.set_upper_limit(new_limit) self.update_labels() def lowerCtrlLimitChanged(self, new_limit): """ PyQT Slot for changes on the lower control limit value of the Channel This slot sends the new limit value to the ```ctrl_limit_changed``` callback. Parameters ---------- new_limit : float """ super(PyDMScaleIndicator, self).lowerCtrlLimitChanged(new_limit) if self.limitsFromChannel: self.scale_indicator.set_lower_limit(new_limit) self.update_labels() def setup_widgets_for_orientation(self, new_orientation, flipped, inverted, value_position): """ Reconstruct the widget given the orientation. Parameters ---------- new_orientation : int Qt.Horizontal or Qt.Vertical flipped : bool Indicates if scale tick marks are flipped to the other side inverted : bool Indicates if scale appearance is inverted """ self.limits_layout = None self.widget_layout = None if new_orientation == Qt.Horizontal: self.limits_layout = QHBoxLayout() if not inverted: self.limits_layout.addWidget(self.lower_label) self.limits_layout.addWidget(self.upper_label) else: self.limits_layout.addWidget(self.upper_label) self.limits_layout.addWidget(self.lower_label) self.widget_layout = QGridLayout() if not flipped: if value_position == Qt.LeftEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addItem(self.limits_layout, 1, 1) elif value_position == Qt.RightEdge: self.widget_layout.addWidget(self.value_label, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addItem(self.limits_layout, 2, 0) elif value_position == Qt.BottomEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.value_label, 2, 0) if not inverted: self.lower_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.upper_label.setAlignment(Qt.AlignTop | Qt.AlignRight) elif inverted: self.lower_label.setAlignment(Qt.AlignTop | Qt.AlignRight) self.upper_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) else: if value_position == Qt.LeftEdge: self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 1, 1) self.widget_layout.addWidget(self.value_label, 1, 0) elif value_position == Qt.RightEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addWidget(self.value_label, 1, 1) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.scale_indicator, 2, 0) elif value_position == Qt.BottomEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addWidget(self.value_label, 2, 0) if not inverted: self.lower_label.setAlignment(Qt.AlignBottom | Qt.AlignLeft) self.upper_label.setAlignment(Qt.AlignBottom | Qt.AlignRight) elif inverted: self.lower_label.setAlignment(Qt.AlignBottom | Qt.AlignRight) self.upper_label.setAlignment(Qt.AlignBottom | Qt.AlignLeft) elif new_orientation == Qt.Vertical: self.limits_layout = QVBoxLayout() if (value_position == Qt.RightEdge and flipped == False) or \ (value_position == Qt.LeftEdge and flipped == True): add_value_between_limits = True else: add_value_between_limits = False if not inverted: self.limits_layout.addWidget(self.upper_label) if add_value_between_limits: self.limits_layout.addWidget(self.value_label) self.limits_layout.addWidget(self.lower_label) self.lower_label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) else: self.limits_layout.addWidget(self.lower_label) if add_value_between_limits: self.limits_layout.addWidget(self.value_label) self.limits_layout.addWidget(self.upper_label) self.lower_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) self.widget_layout = QGridLayout() if not flipped: if value_position == Qt.LeftEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addItem(self.limits_layout, 0, 2) elif value_position == Qt.RightEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 0, 1) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0, 1, 2) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addItem(self.limits_layout, 1, 1) elif value_position == Qt.BottomEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.value_label, 1, 0, 1, 2) if not inverted: self.lower_label.setAlignment(Qt.AlignLeft | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) elif inverted: self.lower_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignLeft | Qt.AlignBottom) else: if value_position == Qt.LeftEdge: self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 0, 2) elif value_position == Qt.RightEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addWidget(self.value_label, 0, 2) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0, 1, 2) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 1) elif value_position == Qt.BottomEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addWidget(self.value_label, 1, 0, 1, 2) if not inverted: self.lower_label.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignRight | Qt.AlignTop) elif inverted: self.lower_label.setAlignment(Qt.AlignRight | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.value_label.setAlignment(Qt.AlignCenter) if self.layout() is not None: # Trick to remove the existing layout by re-parenting it in an empty widget. QWidget().setLayout(self.layout()) self.widget_layout.setContentsMargins(1, 1, 1, 1) self.setLayout(self.widget_layout) @Property(bool) def showValue(self): """ Whether or not the current value should be displayed on the scale. Returns ------- bool """ return self._show_value @showValue.setter def showValue(self, checked): """ Whether or not the current value should be displayed on the scale. Parameters ---------- checked : bool """ if self._show_value != bool(checked): self._show_value = checked if checked: self.value_label.show() else: self.value_label.hide() @Property(bool) def showLimits(self): """ Whether or not the high and low limits should be displayed on the scale. Returns ------- bool """ return self._show_limits @showLimits.setter def showLimits(self, checked): """ Whether or not the high and low limits should be displayed on the scale. Parameters ---------- checked : bool """ if self._show_limits != bool(checked): self._show_limits = checked if checked: self.lower_label.show() self.upper_label.show() else: self.lower_label.hide() self.upper_label.hide() @Property(bool) def showTicks(self): """ Whether or not the tick marks should be displayed on the scale. Returns ------- bool """ return self.scale_indicator.get_show_ticks() @showTicks.setter def showTicks(self, checked): """ Whether or not the tick marks should be displayed on the scale. Parameters ---------- checked : bool """ self.scale_indicator.set_show_ticks(checked) @Property(Qt.Orientation) def orientation(self): """ The scale orientation (Horizontal or Vertical) Returns ------- int Qt.Horizontal or Qt.Vertical """ return self.scale_indicator.get_orientation() @orientation.setter def orientation(self, orientation): """ The scale orientation (Horizontal or Vertical) Parameters ---------- new_orientation : int Qt.Horizontal or Qt.Vertical """ self.scale_indicator.set_orientation(orientation) self.setup_widgets_for_orientation(orientation, self.flipScale, self.invertedAppearance, self._value_position) @Property(bool) def flipScale(self): """ Whether or not the scale should be flipped. Returns ------- bool """ return self.scale_indicator.get_flip_scale() @flipScale.setter def flipScale(self, checked): """ Whether or not the scale should be flipped. Parameters ---------- checked : bool """ self.scale_indicator.set_flip_scale(checked) self.setup_widgets_for_orientation(self.orientation, checked, self.invertedAppearance, self._value_position) @Property(bool) def invertedAppearance(self): """ Whether or not the scale appearence should be inverted. Returns ------- bool """ return self.scale_indicator.get_inverted_appearance() @invertedAppearance.setter def invertedAppearance(self, inverted): """ Whether or not the scale appearence should be inverted. Parameters ---------- inverted : bool """ self.scale_indicator.set_inverted_appearance(inverted) self.setup_widgets_for_orientation(self.orientation, self.flipScale, inverted, self._value_position) @Property(bool) def barIndicator(self): """ Whether or not the scale indicator should be a bar instead of a pointer. Returns ------- bool """ return self.scale_indicator.get_bar_indicator() @barIndicator.setter def barIndicator(self, checked): """ Whether or not the scale indicator should be a bar instead of a pointer. Parameters ---------- checked : bool """ self.scale_indicator.set_bar_indicator(checked) @Property(QColor) def backgroundColor(self): """ The color of the scale background. Returns ------- QColor """ return self.scale_indicator.get_background_color() @backgroundColor.setter def backgroundColor(self, color): """ The color of the scale background. Parameters ------- color : QColor """ self.scale_indicator.set_background_color(color) @Property(QColor) def indicatorColor(self): """ The color of the scale indicator. Returns ------- QColor """ return self.scale_indicator.get_indicator_color() @indicatorColor.setter def indicatorColor(self, color): """ The color of the scale indicator. Parameters ------- color : QColor """ self.scale_indicator.set_indicator_color(color) @Property(QColor) def tickColor(self): """ The color of the scale tick marks. Returns ------- QColor """ return self.scale_indicator.get_tick_color() @tickColor.setter def tickColor(self, color): """ The color of the scale tick marks. Parameters ------- color : QColor """ self.scale_indicator.set_tick_color(color) @Property(float) def backgroundSizeRate(self): """ The rate of background height size (from top to bottom). Returns ------- float """ return self.scale_indicator.get_background_size_rate() @backgroundSizeRate.setter def backgroundSizeRate(self, rate): """ The rate of background height size (from top to bottom). Parameters ------- rate : float Between 0 and 1. """ self.scale_indicator.set_background_size_rate(rate) @Property(float) def tickSizeRate(self): """ The rate of tick marks height size (from bottom to top). Returns ------- float """ return self.scale_indicator.get_tick_size_rate() @tickSizeRate.setter def tickSizeRate(self, rate): """ The rate of tick marks height size (from bottom to top). Parameters ------- rate : float Between 0 and 1. """ self.scale_indicator.set_tick_size_rate(rate) @Property(int) def numDivisions(self): """ The number in which the scale is divided. Returns ------- int """ return self.scale_indicator.get_num_divisions() @numDivisions.setter def numDivisions(self, divisions): """ The number in which the scale is divided. Parameters ------- divisions : int The number of scale divisions. """ self.scale_indicator.set_num_divisions(divisions) @Property(int) def scaleHeight(self): """ The scale height, fixed so it do not wiggle when value label resizes. Returns ------- int """ return self.scale_indicator.get_scale_height() @scaleHeight.setter def scaleHeight(self, value): """ The scale height, fixed so it do not wiggle when value label resizes. Parameters ------- divisions : int The scale height. """ self.scale_indicator.set_scale_height(value) @Property(Qt.Edge) def valuePosition(self): """ The position of the value label (Top, Bottom, Left or Right). Returns ------- int Qt.TopEdge, Qt.BottomEdge, Qt.LeftEdge or Qt.RightEdge """ return self._value_position @valuePosition.setter def valuePosition(self, position): """ The position of the value label (Top, Bottom, Left or Right). Parameters ---------- position : int Qt.TopEdge, Qt.BottomEdge, Qt.LeftEdge or Qt.RightEdge """ self._value_position = position self.setup_widgets_for_orientation(self.orientation, self.flipScale, self.invertedAppearance, position) @Property(bool) def originAtZero(self): """ Whether or not the scale indicator should start at zero value. Applies only for bar indicator. Returns ------- bool """ return self.scale_indicator.get_origin_at_zero() @originAtZero.setter def originAtZero(self, checked): """ Whether or not the scale indicator should start at zero value. Applies only for bar indicator. Parameters ---------- checked : bool """ self.scale_indicator.set_origin_at_zero(checked) @Property(bool) def limitsFromChannel(self): """ Whether or not the scale indicator should use the limits information from the channel. Returns ------- bool """ return self._limits_from_channel @limitsFromChannel.setter def limitsFromChannel(self, checked): """ Whether or not the scale indicator should use the limits information from the channel. Parameters ---------- checked : bool True to use the limits from the Channel, False to use the user-defined values. """ if self._limits_from_channel != checked: self._limits_from_channel = checked if checked: if self._lower_ctrl_limit: self.scale_indicator.set_lower_limit( self._lower_ctrl_limit) if self._upper_ctrl_limit: self.scale_indicator.set_upper_limit( self._upper_ctrl_limit) else: self.scale_indicator.set_lower_limit(self._user_lower_limit) self.scale_indicator.set_upper_limit(self._user_upper_limit) self.update_labels() @Property(float) def userLowerLimit(self): """ The user-defined lower limit for the scale. Returns ------- float """ return self._user_lower_limit @userLowerLimit.setter def userLowerLimit(self, value): """ The user-defined lower limit for the scale. Parameters ---------- value : float The new lower limit value. """ if self._limits_from_channel: return self._user_lower_limit = value self.scale_indicator.set_lower_limit(self._user_lower_limit) self.update_labels() @Property(float) def userUpperLimit(self): """ The user-defined upper limit for the scale. Returns ------- float """ return self._user_upper_limit @userUpperLimit.setter def userUpperLimit(self, value): """ The user-defined upper limit for the scale. Parameters ---------- value : float The new upper limit value. """ if self._limits_from_channel: return self._user_upper_limit = value self.scale_indicator.set_upper_limit(self._user_upper_limit) self.update_labels()
class SegmentationWidget(QWidget): def __init__( self, viewer, boundaries_string=BOUNDARIES_STRING, ): super(SegmentationWidget, self).__init__() # general variables self.viewer = viewer # Disable / overwrite napari viewer functions # that either do not make sense or should be avoided by the user disable_napari_btns(self.viewer) disable_napari_key_bindings() # overwrite_napari_roll(self.viewer) # Main layers self.base_layer = [] # Contains registered brain / reference brain self.atlas_layer = [] # Contains annotations / region information # Track variables self.track_layers = [] # Region variables self.label_layers = [] # Atlas variables self.current_atlas_name = "" self.atlas = None self.boundaries_string = boundaries_string self.directory = "" # Set up segmentation methods self.region_seg = RegionSeg(self) self.track_seg = TrackSeg(self) # Generate main layout self.setup_main_layout() if DISPLAY_REGION_INFO: @self.viewer.mouse_move_callbacks.append def display_region_info(v, event): """ Show brain region info on mouse over in status bar on the right """ assert self.viewer == v if len(v.layers) and self.atlas_layer and self.atlas: _, _, _, region_info = structure_from_viewer( self.viewer.status, self.atlas_layer, self.atlas) self.viewer.help = region_info def setup_main_layout(self): """ Construct main layout of widget """ self.layout = QGridLayout() self.layout.setContentsMargins(10, 10, 10, 10) self.layout.setAlignment(QtCore.Qt.AlignTop) self.layout.setSpacing(4) # 3 Steps: # - Loading panel # - Segmentation methods panel # -> Individual segmentation methods (which are invisible at first) # - Saving panel self.add_loading_panel(1) self.add_segmentation_methods_panel(1) self.track_seg.add_track_panel(2) # Track segmentation subpanel self.region_seg.add_region_panel(3) # Region segmentation subpanel self.add_saving_panel(4) # Take care of status label self.status_label = QLabel() self.status_label.setText("Ready") self.layout.addWidget(self.status_label, 5, 0) self.setLayout(self.layout) # PANELS ############################################################### def add_segmentation_methods_panel(self, row, column=1): """ Segmentation methods chooser panel: Toggle visibility of segmentation methods """ self.toggle_methods_panel = QGroupBox("Segmentation") self.toggle_methods_layout = QGridLayout() self.toggle_methods_layout.setContentsMargins(10, 10, 10, 10) self.toggle_methods_layout.setSpacing(5) self.toggle_methods_layout.setAlignment(QtCore.Qt.AlignBottom) self.show_trackseg_button = add_button( "Track tracing", self.toggle_methods_layout, self.track_seg.toggle_track_panel, 0, 1, minimum_width=COLUMN_WIDTH, alignment=SEGM_METHODS_PANEL_ALIGN, ) self.show_trackseg_button.setEnabled(False) self.show_regionseg_button = add_button( "Region segmentation", self.toggle_methods_layout, self.region_seg.toggle_region_panel, 1, 1, minimum_width=COLUMN_WIDTH, alignment=SEGM_METHODS_PANEL_ALIGN, ) self.show_regionseg_button.setEnabled(False) self.toggle_methods_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.toggle_methods_panel.setLayout(self.toggle_methods_layout) self.toggle_methods_panel.setVisible(True) self.layout.addWidget(self.toggle_methods_panel, row, column, 1, 1) def add_loading_panel(self, row, column=0): """ Loading panel: - Load project (sample space) - Load project (atlas space) - Atlas chooser """ self.load_data_panel = QGroupBox("Load data") self.load_data_layout = QGridLayout() self.load_data_layout.setSpacing(15) self.load_data_layout.setContentsMargins(10, 10, 10, 10) self.load_data_layout.setAlignment(QtCore.Qt.AlignBottom) self.load_button = add_button( "Load project (sample space)", self.load_data_layout, self.load_brainreg_directory_sample, 0, 0, minimum_width=COLUMN_WIDTH, alignment=LOADING_PANEL_ALIGN, ) self.load_button_standard = add_button( "Load project (atlas space)", self.load_data_layout, self.load_brainreg_directory_standard, 1, 0, minimum_width=COLUMN_WIDTH, alignment=LOADING_PANEL_ALIGN, ) self.add_atlas_menu(self.load_data_layout) self.load_data_layout.setColumnMinimumWidth(0, COLUMN_WIDTH) self.load_data_panel.setLayout(self.load_data_layout) self.load_data_panel.setVisible(True) self.layout.addWidget(self.load_data_panel, row, column, 1, 1) def add_saving_panel(self, row): """ Saving/Export panel """ self.save_data_panel = QGroupBox() self.save_data_layout = QGridLayout() self.export_button = add_button( "To brainrender", self.save_data_layout, self.export_to_brainrender, 0, 0, visibility=False, ) self.save_button = add_button("Save", self.save_data_layout, self.save, 0, 1, visibility=False) self.save_data_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.save_data_panel.setLayout(self.save_data_layout) self.layout.addWidget(self.save_data_panel, row, 0, 1, 2) self.save_data_panel.setVisible(False) # ATLAS INTERACTION #################################################### def add_atlas_menu(self, layout): list_of_atlasses = ["Load atlas"] available_atlases = get_available_atlases() for atlas in available_atlases.keys(): atlas_desc = f"{atlas} v{available_atlases[atlas]}" list_of_atlasses.append(atlas_desc) atlas_menu, _ = add_combobox( layout, None, list_of_atlasses, 2, 0, label_stack=True, callback=self.initialise_atlas, width=COLUMN_WIDTH, ) self.atlas_menu = atlas_menu def initialise_atlas(self): atlas_string = self.atlas_menu.currentText() atlas_name = atlas_string.split(" ")[0].strip() if atlas_name != self.current_atlas_name: status = self.remove_layers() if not status: # Something prevented deletion self.reset_atlas_menu() return else: print(f"{atlas_string} already selected for segmentation.") self.reset_atlas_menu() return # Get / set output directory self.set_output_directory() if not self.directory: self.reset_atlas_menu() return self.current_atlas_name = atlas_name # Instantiate atlas layers self.load_atlas() self.directory = self.directory / atlas_name self.paths = Paths(self.directory, atlas_space=True) self.status_label.setText("Ready") # Set window title self.viewer.title = f"Atlas: {self.current_atlas_name}" self.initialise_segmentation_interface() # Check / load previous regions and tracks self.region_seg.check_saved_region() self.track_seg.check_saved_track() self.reset_atlas_menu() def set_output_directory(self): self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.directory = QFileDialog.getExistingDirectory( self, "Select output directory", options=options, ) if self.directory != "": self.directory = Path(self.directory) def load_atlas(self): atlas = BrainGlobeAtlas(self.current_atlas_name) self.atlas = atlas self.base_layer = self.viewer.add_image( self.atlas.reference, name="Reference", ) self.atlas_layer = self.viewer.add_labels( self.atlas.annotation, name=self.atlas.atlas_name, blending="additive", opacity=0.3, visible=False, ) self.standard_space = True def reset_atlas_menu(self): # Reset menu for atlas - show initial description self.atlas_menu.blockSignals(True) self.atlas_menu.setCurrentIndex(0) self.atlas_menu.blockSignals(False) # BRAINREG INTERACTION ################################################# def load_brainreg_directory_sample(self): self.get_brainreg_directory(standard_space=False) def load_brainreg_directory_standard(self): self.get_brainreg_directory(standard_space=True) def get_brainreg_directory(self, standard_space): """ Shows file dialog to choose output directory and sets global directory info """ if standard_space: self.plugin = "brainreg_standard" self.standard_space = True else: self.plugin = "brainreg" self.standard_space = False self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog brainreg_directory = QFileDialog.getExistingDirectory( self, "Select brainreg directory", options=options, ) if not brainreg_directory: return if self.directory != brainreg_directory: status = self.remove_layers() if not status: return # Something prevented deletion self.directory = Path(brainreg_directory) else: print(f"{str(brainreg_directory)} already loaded.") return # Otherwise, proceed loading brainreg dir self.load_brainreg_directory() def load_brainreg_directory(self): """ Opens brainreg folder in napari. Calls initialise_loaded_data to set up layers / info. Then checks for previously loaded data. """ try: self.viewer.open(str(self.directory), plugin=self.plugin) self.paths = Paths( self.directory, standard_space=self.standard_space, ) self.initialise_loaded_data() except ValueError: print(f"The directory ({self.directory}) does not appear to be " f"a brainreg directory, please try again.") return # Check / load previous regions and tracks self.region_seg.check_saved_region() self.track_seg.check_saved_track() def initialise_loaded_data(self): """ Set up brainreg layers in napari / fill with new data and info """ try: self.viewer.layers.remove(self.boundaries_string) except KeyError: pass self.base_layer = self.viewer.layers["Registered image"] self.metadata = self.base_layer.metadata self.atlas = self.metadata["atlas_class"] self.atlas_layer = self.viewer.layers[self.metadata["atlas"]] self.initialise_segmentation_interface() # Set window title self.viewer.title = ( f"Brainreg: {self.metadata['atlas']} ({self.plugin})") self.status_label.setText("Ready") # MORE LAYOUT COMPONENTS ########################################### def initialise_segmentation_interface(self): self.reset_variables() self.initialise_image_view() self.save_data_panel.setVisible(True) self.save_button.setVisible(True) self.export_button.setVisible(self.standard_space) self.show_regionseg_button.setEnabled(True) self.show_trackseg_button.setEnabled(True) self.status_label.setText("Ready") def initialise_image_view(self): self.set_z_position() def set_z_position(self): midpoint = int(round(len(self.base_layer.data) / 2)) self.viewer.dims.set_point(0, midpoint) def reset_variables(self): """ Reset atlas scale dependent variables - point_size (Track segmentation) - spline_size (Track segmentation) - brush_size (Region segmentation) """ self.mean_voxel_size = int( np.sum(self.atlas.resolution) / len(self.atlas.resolution)) self.track_seg.point_size = (self.track_seg.point_size_default / self.mean_voxel_size) self.track_seg.spline_size = (self.track_seg.spline_size_default / self.mean_voxel_size) self.region_seg.brush_size = (self.region_seg.brush_size_default / self.mean_voxel_size) return def display_delete_warning(self): """ Display a warning in a pop up that informs about deletion of all annotation layers """ message_reply = QMessageBox.question( self, "About to remove layers", "All layers are about to be deleted. Proceed?", QMessageBox.Yes | QMessageBox.Cancel, ) if message_reply == QMessageBox.Yes: return True else: return False def remove_layers(self): """ TODO: This needs work. Runs into an error currently when switching from a annotated project to another one """ if len(self.viewer.layers) != 0: # Check with user if that is really what is wanted if self.track_layers or self.label_layers: choice = self.display_delete_warning() if not choice: print('Preventing deletion because user chose "Cancel"') return False # Remove old layers for layer in list(self.viewer.layers): try: self.viewer.layers.remove(layer) except IndexError: # no idea why this happens pass # There seems to be a napari bug trying to access previously used slider # values. Trying to circument for now self.viewer.window.qt_viewer.dims._last_used = None self.track_layers = [] self.label_layers = [] return True def save(self): if self.label_layers or self.track_layers: print("Saving") worker = save_all( self.paths.regions_directory, self.paths.tracks_directory, self.label_layers, self.track_layers, track_file_extension=TRACK_FILE_EXT, ) worker.start() def export_to_brainrender(self): print("Exporting") max_axis_2 = self.base_layer.shape[2] worker = export_all( self.paths.regions_directory, self.paths.tracks_directory, self.label_layers, self.track_seg.splines, self.track_seg.spline_names, self.atlas.resolution[0], max_axis_2, ) worker.start()
def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.edge_width.connect(self._on_edge_width_change) self.layer.events.current_edge_color.connect( self._on_current_edge_color_change ) self.layer.events.current_face_color.connect( self._on_current_face_color_change ) self.layer.events.editable.connect(self._on_editable_change) self.layer.text.events.visible.connect(self._on_text_visibility_change) sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(40) sld.setSingleStep(1) value = self.layer.current_edge_width if isinstance(value, Iterable): if isinstance(value, list): value = np.asarray(value) value = value.mean() sld.setValue(int(value)) sld.valueChanged.connect(self.changeWidth) self.widthSlider = sld def _radio_button( parent, btn_name, mode, action_name, extra_tooltip_text='', **kwargs, ): """ Convenience local function to create a RadioButton and bind it to an action at the same time. Parameters ---------- parent : Any Parent of the generated QtModeRadioButton btn_name : str name fo the button mode : Enum Value Associated to current button action_name : str Action triggered when button pressed extra_tooltip_text : str Text you want added after the automatic tooltip set by the action manager **kwargs: Passed to QtModeRadioButton Returns ------- button: QtModeRadioButton button bound (or that will be bound to) to action `action_name` Notes ----- When shortcuts are modifed/added/removed via the action manager, the tooltip will be updated to reflect the new shortcut. """ action_name = 'napari:' + action_name btn = QtModeRadioButton(parent, btn_name, mode, **kwargs) action_manager.bind_button( action_name, btn, extra_tooltip_text='', ) return btn self.select_button = _radio_button( layer, 'select', Mode.SELECT, "activate_select_mode" ) self.direct_button = _radio_button( layer, 'direct', Mode.DIRECT, "activate_direct_mode" ) self.panzoom_button = _radio_button( layer, 'zoom', Mode.PAN_ZOOM, "napari:activate_shape_pan_zoom_mode", extra_tooltip_text=trans._('(or hold Space)'), checked=True, ) self.rectangle_button = _radio_button( layer, 'rectangle', Mode.ADD_RECTANGLE, "activate_add_rectangle_mode", ) self.ellipse_button = _radio_button( layer, 'ellipse', Mode.ADD_ELLIPSE, "activate_add_ellipse_mode", ) self.line_button = _radio_button( layer, 'line', Mode.ADD_LINE, "activate_add_line_mode" ) self.path_button = _radio_button( layer, 'path', Mode.ADD_PATH, "activate_add_path_mode" ) self.polygon_button = _radio_button( layer, 'polygon', Mode.ADD_POLYGON, "activate_add_polygon_mode", ) self.vertex_insert_button = _radio_button( layer, 'vertex_insert', Mode.VERTEX_INSERT, "activate_vertex_insert_mode", ) self.vertex_remove_button = _radio_button( layer, 'vertex_remove', Mode.VERTEX_REMOVE, "activate_vertex_remove_mode", ) self.move_front_button = QtModePushButton( layer, 'move_front', slot=self.layer.move_to_front, tooltip=trans._('Move to front'), ) action_manager.bind_button( 'napari:move_shapes_selection_to_front', self.move_front_button ) self.move_back_button = QtModePushButton( layer, 'move_back', slot=self.layer.move_to_back, tooltip=trans._('Move to back'), ) action_manager.bind_button( 'napari:move_shapes_selection_to_back', self.move_back_button ) self.delete_button = QtModePushButton( layer, 'delete_shape', slot=self.layer.remove_selected, tooltip=trans._( "Delete selected shapes ({shortcut})", shortcut=Shortcut('Backspace').platform, ), ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.select_button) self.button_group.addButton(self.direct_button) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.rectangle_button) self.button_group.addButton(self.ellipse_button) self.button_group.addButton(self.line_button) self.button_group.addButton(self.path_button) self.button_group.addButton(self.polygon_button) self.button_group.addButton(self.vertex_insert_button) self.button_group.addButton(self.vertex_remove_button) button_grid = QGridLayout() button_grid.addWidget(self.vertex_remove_button, 0, 2) button_grid.addWidget(self.vertex_insert_button, 0, 3) button_grid.addWidget(self.delete_button, 0, 4) button_grid.addWidget(self.direct_button, 0, 5) button_grid.addWidget(self.select_button, 0, 6) button_grid.addWidget(self.panzoom_button, 0, 7) button_grid.addWidget(self.move_back_button, 1, 1) button_grid.addWidget(self.move_front_button, 1, 2) button_grid.addWidget(self.ellipse_button, 1, 3) button_grid.addWidget(self.rectangle_button, 1, 4) button_grid.addWidget(self.polygon_button, 1, 5) button_grid.addWidget(self.line_button, 1, 6) button_grid.addWidget(self.path_button, 1, 7) button_grid.setContentsMargins(5, 0, 0, 5) button_grid.setColumnStretch(0, 1) button_grid.setSpacing(4) self.faceColorEdit = QColorSwatchEdit( initial_color=self.layer.current_face_color, tooltip=trans._('click to set current face color'), ) self._on_current_face_color_change() self.edgeColorEdit = QColorSwatchEdit( initial_color=self.layer.current_edge_color, tooltip=trans._('click to set current edge color'), ) self._on_current_edge_color_change() self.faceColorEdit.color_changed.connect(self.changeFaceColor) self.edgeColorEdit.color_changed.connect(self.changeEdgeColor) text_disp_cb = QCheckBox() text_disp_cb.setToolTip(trans._('toggle text visibility')) text_disp_cb.setChecked(self.layer.text.visible) text_disp_cb.stateChanged.connect(self.change_text_visibility) self.textDispCheckBox = text_disp_cb # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_grid, 0, 0, 1, 2) self.grid_layout.addWidget(QLabel(trans._('opacity:')), 1, 0) self.grid_layout.addWidget(self.opacitySlider, 1, 1) self.grid_layout.addWidget(QLabel(trans._('edge width:')), 2, 0) self.grid_layout.addWidget(self.widthSlider, 2, 1) self.grid_layout.addWidget(QLabel(trans._('blending:')), 3, 0) self.grid_layout.addWidget(self.blendComboBox, 3, 1) self.grid_layout.addWidget(QLabel(trans._('face color:')), 4, 0) self.grid_layout.addWidget(self.faceColorEdit, 4, 1) self.grid_layout.addWidget(QLabel(trans._('edge color:')), 5, 0) self.grid_layout.addWidget(self.edgeColorEdit, 5, 1) self.grid_layout.addWidget(QLabel(trans._('display text:')), 6, 0) self.grid_layout.addWidget(self.textDispCheckBox, 6, 1) self.grid_layout.setRowStretch(7, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4)
class PyDMScaleIndicator(QFrame, TextFormatter, PyDMWidget): """ A bar-shaped indicator for scalar value with support for Channels and more from PyDM. Configurable features include indicator type (bar/pointer), scale tick marks and orientation (horizontal/vertical). Parameters ---------- parent : QWidget The parent widget for the Scale init_channel : str, optional The channel to be used by the widget. """ def __init__(self, parent=None, init_channel=None): QFrame.__init__(self, parent) PyDMWidget.__init__(self, init_channel=init_channel) self._show_value = True self._show_limits = True self.scale_indicator = QScale() self.value_label = QLabel() self.lower_label = QLabel() self.upper_label = QLabel() self.value_label.setText('<val>') self.lower_label.setText('<min>') self.upper_label.setText('<max>') self._value_position = Qt.TopEdge self._limits_from_channel = True self._user_lower_limit = 0 self._user_upper_limit = 0 self.value_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setup_widgets_for_orientation(Qt.Horizontal, False, False, self._value_position) def update_labels(self): """ Update the limits and value labels with the correct values. """ self.lower_label.setText(str(self.scale_indicator._lower_limit)) self.upper_label.setText(str(self.scale_indicator._upper_limit)) self.value_label.setText(self.format_string.format(self.scale_indicator._value)) def value_changed(self, new_value): """ Callback invoked when the Channel value is changed. Parameters ---------- new_val : int or float The new value from the channel. """ super(PyDMScaleIndicator, self).value_changed(new_value) self.scale_indicator.set_value(new_value) self.update_labels() def upperCtrlLimitChanged(self, new_limit): """ PyQT Slot for changes on the upper control limit value of the Channel This slot sends the new limit value to the ```ctrl_limit_changed``` callback. Parameters ---------- new_limit : float """ super(PyDMScaleIndicator, self).upperCtrlLimitChanged(new_limit) if self.limitsFromChannel: self.scale_indicator.set_upper_limit(new_limit) self.update_labels() def lowerCtrlLimitChanged(self, new_limit): """ PyQT Slot for changes on the lower control limit value of the Channel This slot sends the new limit value to the ```ctrl_limit_changed``` callback. Parameters ---------- new_limit : float """ super(PyDMScaleIndicator, self).lowerCtrlLimitChanged(new_limit) if self.limitsFromChannel: self.scale_indicator.set_lower_limit(new_limit) self.update_labels() def setup_widgets_for_orientation(self, new_orientation, flipped, inverted, value_position): """ Reconstruct the widget given the orientation. Parameters ---------- new_orientation : int Qt.Horizontal or Qt.Vertical flipped : bool Indicates if scale tick marks are flipped to the other side inverted : bool Indicates if scale appearance is inverted """ self.limits_layout = None self.widget_layout = None if new_orientation == Qt.Horizontal: self.limits_layout = QHBoxLayout() if not inverted: self.limits_layout.addWidget(self.lower_label) self.limits_layout.addWidget(self.upper_label) else: self.limits_layout.addWidget(self.upper_label) self.limits_layout.addWidget(self.lower_label) self.widget_layout = QGridLayout() if not flipped: if value_position == Qt.LeftEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addItem(self.limits_layout, 1, 1) elif value_position == Qt.RightEdge: self.widget_layout.addWidget(self.value_label, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addItem(self.limits_layout, 2, 0) elif value_position == Qt.BottomEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.value_label, 2, 0) if not inverted: self.lower_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.upper_label.setAlignment(Qt.AlignTop | Qt.AlignRight) elif inverted: self.lower_label.setAlignment(Qt.AlignTop | Qt.AlignRight) self.upper_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) else: if value_position == Qt.LeftEdge: self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 1, 1) self.widget_layout.addWidget(self.value_label, 1, 0) elif value_position == Qt.RightEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addWidget(self.value_label, 1, 1) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.scale_indicator, 2, 0) elif value_position == Qt.BottomEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addWidget(self.value_label, 2, 0) if not inverted: self.lower_label.setAlignment(Qt.AlignBottom | Qt.AlignLeft) self.upper_label.setAlignment(Qt.AlignBottom | Qt.AlignRight) elif inverted: self.lower_label.setAlignment(Qt.AlignBottom | Qt.AlignRight) self.upper_label.setAlignment(Qt.AlignBottom | Qt.AlignLeft) elif new_orientation == Qt.Vertical: self.limits_layout = QVBoxLayout() if (value_position == Qt.RightEdge and flipped == False) or \ (value_position == Qt.LeftEdge and flipped == True): add_value_between_limits = True else: add_value_between_limits = False if not inverted: self.limits_layout.addWidget(self.upper_label) if add_value_between_limits: self.limits_layout.addWidget(self.value_label) self.limits_layout.addWidget(self.lower_label) self.lower_label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) else: self.limits_layout.addWidget(self.lower_label) if add_value_between_limits: self.limits_layout.addWidget(self.value_label) self.limits_layout.addWidget(self.upper_label) self.lower_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) self.widget_layout = QGridLayout() if not flipped: if value_position == Qt.LeftEdge: self.widget_layout.addWidget(self.value_label, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addItem(self.limits_layout, 0, 2) elif value_position == Qt.RightEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 0, 1) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0, 1, 2) self.widget_layout.addWidget(self.scale_indicator, 1, 0) self.widget_layout.addItem(self.limits_layout, 1, 1) elif value_position == Qt.BottomEdge: self.widget_layout.addWidget(self.scale_indicator, 0, 0) self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.value_label, 1, 0, 1, 2) if not inverted: self.lower_label.setAlignment(Qt.AlignLeft | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) elif inverted: self.lower_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignLeft | Qt.AlignBottom) else: if value_position == Qt.LeftEdge: self.widget_layout.addItem(self.limits_layout, 0, 1) self.widget_layout.addWidget(self.scale_indicator, 0, 2) elif value_position == Qt.RightEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addWidget(self.value_label, 0, 2) elif value_position == Qt.TopEdge: self.widget_layout.addWidget(self.value_label, 0, 0, 1, 2) self.widget_layout.addItem(self.limits_layout, 1, 0) self.widget_layout.addWidget(self.scale_indicator, 1, 1) elif value_position == Qt.BottomEdge: self.widget_layout.addItem(self.limits_layout, 0, 0) self.widget_layout.addWidget(self.scale_indicator, 0, 1) self.widget_layout.addWidget(self.value_label, 1, 0, 1, 2) if not inverted: self.lower_label.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.upper_label.setAlignment(Qt.AlignRight | Qt.AlignTop) elif inverted: self.lower_label.setAlignment(Qt.AlignRight | Qt.AlignTop) self.upper_label.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.value_label.setAlignment(Qt.AlignCenter) if self.layout() is not None: # Trick to remove the existing layout by re-parenting it in an empty widget. QWidget().setLayout(self.layout()) self.widget_layout.setContentsMargins(1, 1, 1, 1) self.setLayout(self.widget_layout) @Property(bool) def showValue(self): """ Whether or not the current value should be displayed on the scale. Returns ------- bool """ return self._show_value @showValue.setter def showValue(self, checked): """ Whether or not the current value should be displayed on the scale. Parameters ---------- checked : bool """ if self._show_value != bool(checked): self._show_value = checked if checked: self.value_label.show() else: self.value_label.hide() @Property(bool) def showLimits(self): """ Whether or not the high and low limits should be displayed on the scale. Returns ------- bool """ return self._show_limits @showLimits.setter def showLimits(self, checked): """ Whether or not the high and low limits should be displayed on the scale. Parameters ---------- checked : bool """ if self._show_limits != bool(checked): self._show_limits = checked if checked: self.lower_label.show() self.upper_label.show() else: self.lower_label.hide() self.upper_label.hide() @Property(bool) def showTicks(self): """ Whether or not the tick marks should be displayed on the scale. Returns ------- bool """ return self.scale_indicator.get_show_ticks() @showTicks.setter def showTicks(self, checked): """ Whether or not the tick marks should be displayed on the scale. Parameters ---------- checked : bool """ self.scale_indicator.set_show_ticks(checked) @Property(Qt.Orientation) def orientation(self): """ The scale orientation (Horizontal or Vertical) Returns ------- int Qt.Horizontal or Qt.Vertical """ return self.scale_indicator.get_orientation() @orientation.setter def orientation(self, orientation): """ The scale orientation (Horizontal or Vertical) Parameters ---------- new_orientation : int Qt.Horizontal or Qt.Vertical """ self.scale_indicator.set_orientation(orientation) self.setup_widgets_for_orientation(orientation, self.flipScale, self.invertedAppearance, self._value_position) @Property(bool) def flipScale(self): """ Whether or not the scale should be flipped. Returns ------- bool """ return self.scale_indicator.get_flip_scale() @flipScale.setter def flipScale(self, checked): """ Whether or not the scale should be flipped. Parameters ---------- checked : bool """ self.scale_indicator.set_flip_scale(checked) self.setup_widgets_for_orientation(self.orientation, checked, self.invertedAppearance, self._value_position) @Property(bool) def invertedAppearance(self): """ Whether or not the scale appearence should be inverted. Returns ------- bool """ return self.scale_indicator.get_inverted_appearance() @invertedAppearance.setter def invertedAppearance(self, inverted): """ Whether or not the scale appearence should be inverted. Parameters ---------- inverted : bool """ self.scale_indicator.set_inverted_appearance(inverted) self.setup_widgets_for_orientation(self.orientation, self.flipScale, inverted, self._value_position) @Property(bool) def barIndicator(self): """ Whether or not the scale indicator should be a bar instead of a pointer. Returns ------- bool """ return self.scale_indicator.get_bar_indicator() @barIndicator.setter def barIndicator(self, checked): """ Whether or not the scale indicator should be a bar instead of a pointer. Parameters ---------- checked : bool """ self.scale_indicator.set_bar_indicator(checked) @Property(QColor) def backgroundColor(self): """ The color of the scale background. Returns ------- QColor """ return self.scale_indicator.get_background_color() @backgroundColor.setter def backgroundColor(self, color): """ The color of the scale background. Parameters ------- color : QColor """ self.scale_indicator.set_background_color(color) @Property(QColor) def indicatorColor(self): """ The color of the scale indicator. Returns ------- QColor """ return self.scale_indicator.get_indicator_color() @indicatorColor.setter def indicatorColor(self, color): """ The color of the scale indicator. Parameters ------- color : QColor """ self.scale_indicator.set_indicator_color(color) @Property(QColor) def tickColor(self): """ The color of the scale tick marks. Returns ------- QColor """ return self.scale_indicator.get_tick_color() @tickColor.setter def tickColor(self, color): """ The color of the scale tick marks. Parameters ------- color : QColor """ self.scale_indicator.set_tick_color(color) @Property(float) def backgroundSizeRate(self): """ The rate of background height size (from top to bottom). Returns ------- float """ return self.scale_indicator.get_background_size_rate() @backgroundSizeRate.setter def backgroundSizeRate(self, rate): """ The rate of background height size (from top to bottom). Parameters ------- rate : float Between 0 and 1. """ self.scale_indicator.set_background_size_rate(rate) @Property(float) def tickSizeRate(self): """ The rate of tick marks height size (from bottom to top). Returns ------- float """ return self.scale_indicator.get_tick_size_rate() @tickSizeRate.setter def tickSizeRate(self, rate): """ The rate of tick marks height size (from bottom to top). Parameters ------- rate : float Between 0 and 1. """ self.scale_indicator.set_tick_size_rate(rate) @Property(int) def numDivisions(self): """ The number in which the scale is divided. Returns ------- int """ return self.scale_indicator.get_num_divisions() @numDivisions.setter def numDivisions(self, divisions): """ The number in which the scale is divided. Parameters ------- divisions : int The number of scale divisions. """ self.scale_indicator.set_num_divisions(divisions) @Property(int) def scaleHeight(self): """ The scale height, fixed so it do not wiggle when value label resizes. Returns ------- int """ return self.scale_indicator.get_scale_height() @scaleHeight.setter def scaleHeight(self, value): """ The scale height, fixed so it do not wiggle when value label resizes. Parameters ------- divisions : int The scale height. """ self.scale_indicator.set_scale_height(value) @Property(Qt.Edge) def valuePosition(self): """ The position of the value label (Top, Bottom, Left or Right). Returns ------- int Qt.TopEdge, Qt.BottomEdge, Qt.LeftEdge or Qt.RightEdge """ return self._value_position @valuePosition.setter def valuePosition(self, position): """ The position of the value label (Top, Bottom, Left or Right). Parameters ---------- position : int Qt.TopEdge, Qt.BottomEdge, Qt.LeftEdge or Qt.RightEdge """ self._value_position = position self.setup_widgets_for_orientation(self.orientation, self.flipScale, self.invertedAppearance, position) @Property(bool) def originAtZero(self): """ Whether or not the scale indicator should start at zero value. Applies only for bar indicator. Returns ------- bool """ return self.scale_indicator.get_origin_at_zero() @originAtZero.setter def originAtZero(self, checked): """ Whether or not the scale indicator should start at zero value. Applies only for bar indicator. Parameters ---------- checked : bool """ self.scale_indicator.set_origin_at_zero(checked) @Property(bool) def limitsFromChannel(self): """ Whether or not the scale indicator should use the limits information from the channel. Returns ------- bool """ return self._limits_from_channel @limitsFromChannel.setter def limitsFromChannel(self, checked): """ Whether or not the scale indicator should use the limits information from the channel. Parameters ---------- checked : bool True to use the limits from the Channel, False to use the user-defined values. """ if self._limits_from_channel != checked: self._limits_from_channel = checked if checked: if self._lower_ctrl_limit: self.scale_indicator.set_lower_limit(self._lower_ctrl_limit) if self._upper_ctrl_limit: self.scale_indicator.set_upper_limit(self._upper_ctrl_limit) else: self.scale_indicator.set_lower_limit(self._user_lower_limit) self.scale_indicator.set_upper_limit(self._user_upper_limit) self.update_labels() @Property(float) def userLowerLimit(self): """ The user-defined lower limit for the scale. Returns ------- float """ return self._user_lower_limit @userLowerLimit.setter def userLowerLimit(self, value): """ The user-defined lower limit for the scale. Parameters ---------- value : float The new lower limit value. """ if self._limits_from_channel: return self._user_lower_limit = value self.scale_indicator.set_lower_limit(self._user_lower_limit) self.update_labels() @Property(float) def userUpperLimit(self): """ The user-defined upper limit for the scale. Returns ------- float """ return self._user_upper_limit @userUpperLimit.setter def userUpperLimit(self, value): """ The user-defined upper limit for the scale. Parameters ---------- value : float The new upper limit value. """ if self._limits_from_channel: return self._user_upper_limit = value self.scale_indicator.set_upper_limit(self._user_upper_limit) self.update_labels()