def __init__(self, parent): QFileDialog.__init__(self, parent) self.setFileMode(QFileDialog.AnyFile) self.setAcceptMode(QFileDialog.AcceptSave) # Widgets self._chk_tight = QCheckBox('Tight layout') self._txt_dpi = QSpinBox() self._txt_dpi.setRange(1, 10000) self._txt_dpi.setSingleStep(50) self._txt_dpi.setSuffix('dpi') self._txt_dpi.setValue(100) # Layouts layout = self.layout() lyt_extras = QHBoxLayout() lyt_extras.addWidget(QLabel('Extra options')) lyt_extras.addWidget(self._chk_tight) lyt_extras.addWidget(QLabel('Resolution')) lyt_extras.addWidget(self._txt_dpi) layout.addLayout(lyt_extras, layout.rowCount(), 0, 1, layout.columnCount()) self.setLayout(layout)
def __init__(self, dim_info, number=0, state=State.NONE, parent=None): # hack in a number_of_bins for MDEventWorkspace dim_info['number_of_bins'] = 1000 dim_info['width'] = (dim_info['maximum']-dim_info['minimum'])/1000 self.spinBins = QSpinBox() self.spinBins.setRange(2,9999) self.spinBins.setValue(100) self.spinBins.hide() self.spinBins.setMinimumWidth(110) self.spinThick = QDoubleSpinBox() self.spinThick.setRange(0.001,999) self.spinThick.setValue(0.1) self.spinThick.setSingleStep(0.1) self.spinThick.setDecimals(3) self.spinThick.setMinimumWidth(110) self.rebinLabel = QLabel("thick") self.rebinLabel.setMinimumWidth(44) super(DimensionMDE, self).__init__(dim_info, number, state, parent) self.spinBins.valueChanged.connect(self.binningChanged) self.spinThick.valueChanged.connect(self.valueChanged) self.layout.addWidget(self.spinBins) self.layout.addWidget(self.spinThick) self.layout.addWidget(self.rebinLabel)
def create_controls(self): """ Create UI controls. """ vbox = QVBoxLayout() form = QFormLayout() self.num_angle = QDoubleSpinBox() self.num_angle.setValue(0.0) self.num_angle.setMinimum(-360) self.num_angle.setMaximum(360) form.addRow(tr("Angle:"), self.num_angle) vbox.addLayout(form) self.gbo_preview = QGroupBox(tr("Preview")) self.gbo_preview.setCheckable(True) self.gbo_preview.setChecked(False) gbo_vbox = QVBoxLayout() self.chk_grid = QCheckBox(tr("Grid")) self.chk_grid.setChecked(False) self.num_grid = QSpinBox() self.num_grid.setValue(4) self.num_grid.setMinimum(1) self.num_grid.setEnabled(False) self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled) gbo_vbox.addWidget(self.chk_grid) gbo_vbox.addWidget(self.num_grid) self.gbo_preview.setLayout(gbo_vbox) vbox.addWidget(self.gbo_preview) self.gbo_preview.toggled[bool].connect(self.set_preview) self.gbo_output = QGroupBox(tr("Output")) self.opt_new = QRadioButton(tr("New signal")) self.opt_replace = QRadioButton(tr("In place")) self.opt_new.setChecked(True) gbo_vbox2 = QVBoxLayout() gbo_vbox2.addWidget(self.opt_new) gbo_vbox2.addWidget(self.opt_replace) self.gbo_output.setLayout(gbo_vbox2) vbox.addWidget(self.gbo_output) self.chk_reshape = QCheckBox(tr("Resize to fit")) self.chk_reshape.setChecked(False) vbox.addWidget(self.chk_reshape) self.btn_ok = QPushButton(tr("&OK")) self.btn_ok.setDefault(True) self.btn_ok.clicked.connect(self.accept) self.btn_cancel = QPushButton(tr("&Cancel")) self.btn_cancel.clicked.connect(self.reject) hbox = QHBoxLayout() hbox.addWidget(self.btn_ok) hbox.addWidget(self.btn_cancel) vbox.addLayout(hbox) vbox.addStretch(1) self.setLayout(vbox)
def create_fontgroup(self, option=None, text=None, title=None, tip=None, fontfilters=None, without_group=False): """Option=None -> setting plugin font""" if title: fontlabel = QLabel(title) else: fontlabel = QLabel(_("Font")) fontbox = QFontComboBox() if fontfilters is not None: fontbox.setFontFilters(fontfilters) sizelabel = QLabel(" "+_("Size")) sizebox = QSpinBox() sizebox.setRange(7, 100) self.fontboxes[(fontbox, sizebox)] = option layout = QHBoxLayout() for subwidget in (fontlabel, fontbox, sizelabel, sizebox): layout.addWidget(subwidget) layout.addStretch(1) widget = QWidget(self) widget.fontlabel = fontlabel widget.sizelabel = sizelabel widget.fontbox = fontbox widget.sizebox = sizebox widget.setLayout(layout) if not without_group: if text is None: text = _("Font style") group = QGroupBox(text) group.setLayout(layout) if tip is not None: group.setToolTip(tip) return group else: return widget
class ShearMomentTorqueWindow(PyDialog): """ +-------------------------+ | ShearMomentTorqueWindow | +-------------------------+ | Origin cid x y z | | P2 cid x y z | | z-axis cid x y z | | tol cid x y z | | | | Apply OK Cancel | +-------------------------+ """ def __init__(self, data, win_parent=None): """ Saves the data members from data and performs type checks """ PyDialog.__init__(self, data, win_parent) self._updated_preference = False self._default_font_size = data['font_size'] #self.dim_max = data['dim_max'] self.model_name = data['model_name'] self.cids = data['cids'] self.gpforce = data['gpforce'] #self._origin = data['origin'] #self._p1 = data['origin'] #self._p2 = data['origin'] #self.out_data = data self.plane_color_float, self.plane_color_int = _check_color( data['plane_color']) self.plane_opacity = data['plane_opacity'] self.methods = ['Z-Axis Projection', 'CORD2R'] self.zaxis_methods = ['Global Z', 'Camera Normal', 'Manual'] self._zaxis_method = 0 # Global Z self.setWindowTitle('Shear, Moment, Torque') self.create_widgets() self.create_layout() self.set_connections() self.on_font(self._default_font_size) #self.on_gradient_scale() #self.show() def on_font(self, value=None): """update the font for the current window""" if value is None: value = self.font_size_edit.value() font = QtGui.QFont() font.setPointSize(value) self.setFont(font) def set_font_size(self, font_size): """ Updates the font size of all objects in the PyDialog Parameters ---------- font_size : int the font size """ if self.font_size == font_size: return self.font_size = font_size font = make_font(font_size, is_bold=False) self.setFont(font) self.set_bold_font(font_size) def set_bold_font(self, font_size): """ Updates the font size of all bolded objects in the dialog Parameters ---------- font_size : int the font size """ bold_font = make_font(font_size, is_bold=True) self.additional_params_label.setFont(bold_font) self.case_info_label.setFont(bold_font) self.plane_label.setFont(bold_font) self.location_label.setFont(bold_font) self.cid_label.setFont(bold_font) self.x_label.setFont(bold_font) self.y_label.setFont(bold_font) self.z_label.setFont(bold_font) def create_widgets(self): """creates the display window""" # CORD2R #self.origin_label = QLabel("Origin:") #self.zaxis_label = QLabel("Z Axis:") #self.xz_plane_label = QLabel("XZ Plane:") # Z-Axis Projection self.p1_label = QLabel("Origin:") self.p3_label = QLabel("End:") self.p2_label = QLabel("XZ Plane:") self.zaxis_label = QLabel("Z Axis:") self.method_pulldown = QComboBox() for method in self.methods: self.method_pulldown.addItem(method) self.zaxis_method_pulldown = QComboBox() for method in self.zaxis_methods: self.zaxis_method_pulldown.addItem(method) self.cid_label = QLabel("Coordinate System:") self.p1_cid_pulldown = QComboBox() self.p2_cid_pulldown = QComboBox() self.p3_cid_pulldown = QComboBox() self.zaxis_cid_pulldown = QComboBox() cid_global_str = '0/Global' for cid in sorted(self.cids): if cid == 0: cid_str = cid_global_str else: cid_str = str(cid) #print('cid_str = %r' % cid_str) self.p1_cid_pulldown.addItem(cid_str) self.p2_cid_pulldown.addItem(cid_str) self.p3_cid_pulldown.addItem(cid_str) self.zaxis_cid_pulldown.addItem(cid_str) self.p1_cid_pulldown.setCurrentIndex(0) self.p2_cid_pulldown.setCurrentIndex(0) self.p3_cid_pulldown.setCurrentIndex(0) self.zaxis_cid_pulldown.setCurrentIndex(0) if len(self.cids) == 1: self.p1_cid_pulldown.setEnabled(False) self.p2_cid_pulldown.setEnabled(False) self.p3_cid_pulldown.setEnabled(False) self.zaxis_cid_pulldown.setEnabled(False) #self.p1_cid_pulldown.setItemText(0, cid_str) #self.p2_cid_pulldown.setItemText(0, cid_str) #self.zaxis_cid_pulldown.setItemText(0, cid_str) self.p1_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P1') self.p2_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P2') self.p3_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P3') self.zaxis_cid_pulldown.setToolTip( 'Defines the coordinate system for the Z Axis') self.p1_x_edit = QLineEdit('') self.p1_y_edit = QLineEdit('') self.p1_z_edit = QLineEdit('') self.p2_x_edit = QLineEdit('') self.p2_y_edit = QLineEdit('') self.p2_z_edit = QLineEdit('') self.p3_x_edit = QLineEdit('') self.p3_y_edit = QLineEdit('') self.p3_z_edit = QLineEdit('') self.zaxis_x_edit = QLineEdit('') self.zaxis_y_edit = QLineEdit('') self.zaxis_z_edit = QLineEdit('') self.additional_params_label = QLabel('Plane Parameters:') self.case_info_label = QLabel('Case Info:') self.p2_label = QLabel("XZ Plane:") # Plane Color self.plane_color_label = QLabel("Plane Color:") self.plane_color_edit = QPushButtonColor(self.plane_color_int) self.plane_opacity_label = QLabel("Plane Opacity:") self.plane_opacity_edit = QDoubleSpinBox() self.plane_opacity_edit.setRange(0.1, 1.0) self.plane_opacity_edit.setDecimals(1) self.plane_opacity_edit.setSingleStep(0.1) self.plane_opacity_edit.setValue(self.plane_opacity) self.flip_coord_label = QLabel("Flip Coordinate System:") self.flip_coord_checkbox = QCheckBox() #----------------------------------------------------------------------- self.time_label = QLabel('Time:') if self.gpforce is None: times = ['0.', '0.5', '1.', '1.5', '2.'] time = '0.' else: times = [str(time) for time in self.gpforce._times] time = times[0] self.times_pulldown = make_combo_box(times, time) self.time_label.setEnabled(False) self.times_pulldown.setEnabled(False) #self.node_label = QLabel('Nodes:') #self.node_edit = QNodeEdit(self.win_parent, self.model_name, parent=self.gui, #pick_style='area', tab_to_next=False) #self.element_label = QLabel('Elements:') #self.element_edit = QElementEdit(self.win_parent, self.model_name, parent=self.gui, #pick_style='area', tab_to_next=False) #self.node_element_label = QLabel('Nodes/Elements:') #self.node_element_edit = QLineEdit() #self.node_element_edit.setReadOnly(True) self.nplanes_label = QLabel('Num Planes:') self.nplanes_spinner = QSpinBox() self.nplanes_spinner.setMinimum(2) self.nplanes_spinner.setMaximum(500) self.nplanes_spinner.setValue(20) #----------------------------------------------------------------------- self.method_label = QLabel('Method:') self.plane_label = QLabel('Plane:') self.location_label = QLabel('Location:') self.zaxis_method_label = QLabel('Z-Axis Method:') self.cid_label = QLabel('Coordinate System:') self.x_label = QLabel('X') self.y_label = QLabel('Y') self.z_label = QLabel('Z') #self.location_label.setAlignment(Qt.AlignCenter) self.cid_label.setAlignment(Qt.AlignCenter) self.x_label.setAlignment(Qt.AlignCenter) self.y_label.setAlignment(Qt.AlignCenter) self.z_label.setAlignment(Qt.AlignCenter) self.export_checkbox = QCheckBox() self.csv_label = QLabel('CSV Filename:') self.csv_edit = QLineEdit() self.csv_button = QPushButton('Browse...') self.csv_label.setEnabled(False) self.csv_edit.setEnabled(False) self.csv_button.setEnabled(False) #----------------------------------------------------------------------- # nodes self.add_button = QPushButton('Add') self.remove_button = QPushButton('Remove') # elements self.add2_button = QPushButton('Add') self.remove2_button = QPushButton('Remove') #----------------------------------------------------------------------- # closing self.apply_button = QPushButton('Apply') self.cancel_button = QPushButton('Cancel') self.set_bold_font(self._default_font_size) @property def gui(self): if self.win_parent is None: return None return self.win_parent.parent.gui def create_layout(self): """sets up the window""" grid = self._make_grid_layout() #hbox_csv = QHBoxLayout() grid2 = QGridLayout() irow = 0 #grid2.addWidget(self.node_label, irow, 0) #grid2.addWidget(self.node_edit, irow, 1) #grid2.addWidget(self.add_button, irow, 2) #grid2.addWidget(self.remove_button, irow, 3) #irow += 1 #grid2.addWidget(self.element_label, irow, 0) #grid2.addWidget(self.element_edit, irow, 1) #grid2.addWidget(self.add2_button, irow, 2) #grid2.addWidget(self.remove2_button, irow, 3) #irow += 1 #grid2.addWidget(self.node_element_label, irow, 0) #grid2.addWidget(self.node_element_edit, irow, 1) #irow += 1 hbox_csv = QHBoxLayout() hbox_csv.addWidget(self.export_checkbox) hbox_csv.addWidget(self.csv_label) hbox_csv.addWidget(self.csv_edit) hbox_csv.addWidget(self.csv_button) #---------------------------------------------- ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.apply_button) ok_cancel_box.addWidget(self.cancel_button) vbox = QVBoxLayout() vbox.addLayout(grid) vbox.addLayout(grid2) #vbox.addStretch() vbox.addLayout(hbox_csv) vbox.addStretch() #----------------------- #vbox.addLayout(add_remove_box) vbox.addLayout(ok_cancel_box) self.on_method(0) self.on_zaxis_method(0) self.setLayout(vbox) def on_export_checkbox(self): """this is called when the checkbox is clicked""" is_checked = self.export_checkbox.isChecked() self.csv_label.setEnabled(is_checked) self.csv_edit.setEnabled(is_checked) self.csv_button.setEnabled(is_checked) def on_browse_csv(self): """opens a file dialog""" default_dirname = os.getcwd() csv_filename, wildcard = save_file_dialog( self, 'Select the file name for export', default_dirname, wildcard_csv) if not csv_filename: return self.csv_edit.setText(csv_filename) def _make_grid_layout(self): """builds the QGridLayout""" grid = QGridLayout() irow = 0 #------------------------- grid.addWidget(self.location_label, irow, 0) grid.addWidget(self.cid_label, irow, 1) grid.addWidget(self.x_label, irow, 2) grid.addWidget(self.y_label, irow, 3) grid.addWidget(self.z_label, irow, 4) irow += 1 add_row(irow, grid, self.p1_label, self.p1_cid_pulldown, self.p1_x_edit, self.p1_y_edit, self.p1_z_edit) irow += 1 add_row(irow, grid, self.p3_label, self.p3_cid_pulldown, self.p3_x_edit, self.p3_y_edit, self.p3_z_edit) irow += 1 grid.addWidget(self.plane_label, irow, 0) irow += 1 grid.addWidget(self.method_label, irow, 0) grid.addWidget(self.method_pulldown, irow, 1) irow += 1 grid.addWidget(self.zaxis_method_label, irow, 0) grid.addWidget(self.zaxis_method_pulldown, irow, 1) irow += 1 add_row(irow, grid, self.zaxis_label, self.zaxis_cid_pulldown, self.zaxis_x_edit, self.zaxis_y_edit, self.zaxis_z_edit) irow += 1 add_row(irow, grid, self.p2_label, self.p2_cid_pulldown, self.p2_x_edit, self.p2_y_edit, self.p2_z_edit) irow += 1 #----------------------------------------- grid.addWidget(self.case_info_label, irow, 0) irow += 1 grid.addWidget(self.time_label, irow, 0) grid.addWidget(self.times_pulldown, irow, 1) irow += 1 grid.addWidget(self.nplanes_label, irow, 0) grid.addWidget(self.nplanes_spinner, irow, 1) irow += 1 #----------------------------------------- grid.addWidget(self.additional_params_label, irow, 0) irow += 1 grid.addWidget(self.plane_color_label, irow, 0) grid.addWidget(self.plane_color_edit, irow, 1) irow += 1 grid.addWidget(self.plane_opacity_label, irow, 0) grid.addWidget(self.plane_opacity_edit, irow, 1) irow += 1 #---------------------------------------------- return grid def set_connections(self): """creates the actions for the menu""" self.method_pulldown.currentIndexChanged.connect(self.on_method) self.zaxis_method_pulldown.currentIndexChanged.connect( self.on_zaxis_method) self.plane_color_edit.clicked.connect(self.on_plane_color) self.export_checkbox.clicked.connect(self.on_export_checkbox) self.csv_button.clicked.connect(self.on_browse_csv) self.apply_button.clicked.connect(self.on_apply) self.cancel_button.clicked.connect(self.on_cancel) def on_method(self, method_int=None): method = get_pulldown_text(method_int, self.methods, self.method_pulldown) if method == 'Z-Axis Projection': is_cord2r = False elif method == 'CORD2R': is_cord2r = True else: raise NotImplementedError(method) if is_cord2r: self._zaxis_method = self.zaxis_method_pulldown.currentIndex() # set to manual #self.on_zaxis_method(method_int=2) # manual self.zaxis_method_pulldown.setCurrentIndex(2) self.on_zaxis_method() # update else: self.zaxis_method_pulldown.setCurrentIndex(self._zaxis_method) self.on_zaxis_method() # update # works self.zaxis_method_pulldown.setEnabled(not is_cord2r) self.zaxis_method_pulldown.setVisible(not is_cord2r) self.zaxis_method_label.setEnabled(not is_cord2r) def on_zaxis_method(self, method_int=None): method = get_pulldown_text(method_int, self.zaxis_methods, self.zaxis_method_pulldown) if method == 'Global Z': is_visible = False elif method == 'Camera Normal': is_visible = False elif method == 'Manual': is_visible = True else: raise NotImplementedError(method) self.zaxis_cid_pulldown.setVisible(is_visible) self.zaxis_x_edit.setVisible(is_visible) self.zaxis_y_edit.setVisible(is_visible) self.zaxis_z_edit.setVisible(is_visible) def on_plane_color(self): """ Choose a plane color""" title = "Choose a cutting plane color" rgb_color_ints = self.plane_color_int color_edit = self.plane_color_edit func_name = 'set_plane_color' passed, rgb_color_ints, rgb_color_floats = self._background_color( title, color_edit, rgb_color_ints, func_name) if passed: self.plane_color_int = rgb_color_ints self.plane_color_float = rgb_color_floats def _background_color(self, title, color_edit, rgb_color_ints, func_name): """helper method for ``on_background_color`` and ``on_background_color2``""" passed, rgb_color_ints, rgb_color_floats = self.on_color( color_edit, rgb_color_ints, title) if passed and 0: if self.win_parent is not None: settings = self.win_parent.settings func_background_color = getattr(settings, func_name) func_background_color(rgb_color_floats) return passed, rgb_color_ints, rgb_color_floats def on_color(self, color_edit, rgb_color_ints, title): """pops a color dialog""" col = QColorDialog.getColor(QtGui.QColor(*rgb_color_ints), self, title) if not col.isValid(): return False, rgb_color_ints, None color_float = col.getRgbF()[:3] # floats color_int = [int(colori * 255) for colori in color_float] assert isinstance(color_float[0], float), color_float assert isinstance(color_int[0], int), color_int color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(color_int) + #"border:1px solid rgb(255, 170, 255); " "}") return True, color_int, color_float #--------------------------------------------------------------------------- def on_validate(self): p1_cidi = self.p1_cid_pulldown.currentText() p2_cidi = self.p2_cid_pulldown.currentText() p3_cidi = self.p3_cid_pulldown.currentText() zaxis_cidi = self.zaxis_cid_pulldown.currentText() p1_cid = int(p1_cidi) if 'Global' not in p1_cidi else 0 p2_cid = int(p2_cidi) if 'Global' not in p2_cidi else 0 p3_cid = int(p3_cidi) if 'Global' not in p3_cidi else 0 zaxis_cid = int(zaxis_cidi) if 'Global' not in zaxis_cidi else 0 #print('p1_cidi=%r p2_cidi=%r p3_cidi=%r' % (p1_cidi, p2_cidi, zaxis_cidi)) #print('p2_cid=%r p2_cid=%r p3_cidi=%r' % (p2_cid, p2_cid, zaxis_cid)) p1_x, flag1 = check_float(self.p1_x_edit) p1_y, flag2 = check_float(self.p1_y_edit) p1_z, flag3 = check_float(self.p1_z_edit) p2_x, flag4 = check_float(self.p2_x_edit) p2_y, flag5 = check_float(self.p2_y_edit) p2_z, flag6 = check_float(self.p2_z_edit) p3_x, flag7 = check_float(self.p3_x_edit) p3_y, flag8 = check_float(self.p3_y_edit) p3_z, flag9 = check_float(self.p3_z_edit) p1 = [p1_x, p1_y, p1_z] p2 = [p2_x, p2_y, p2_z] p3 = [p3_x, p3_y, p3_z] flag10, flag11, flag12, zaxis_cid, zaxis = get_zaxis( self.win_parent, # for camera self.zaxis_method_pulldown, self.zaxis_x_edit, self.zaxis_y_edit, self.zaxis_z_edit) method = self.method_pulldown.currentText() assert method in self.methods, 'method=%r' % method flag13 = True plane_opacity = self.plane_opacity_edit.value() nplanes = self.nplanes_spinner.value() csv_filename = None flag14 = True if self.export_checkbox.isChecked(): csv_filename, flag14 = check_save_path(self.csv_edit) flags = [ flag1, flag2, flag3, flag4, flag5, flag6, flag7, flag8, flag9, flag10, flag11, flag12, flag13, flag14 ] if all(flags): self.out_data['method'] = method self.out_data['p1'] = [p1_cid, p1] self.out_data['p2'] = [p2_cid, p2] self.out_data['p3'] = [p2_cid, p3] self.out_data['zaxis'] = [zaxis_cid, zaxis] self.out_data['plane_color'] = self.plane_color_float self.out_data['plane_opacity'] = plane_opacity self.out_data['nplanes'] = nplanes self.out_data['csv_filename'] = csv_filename self.out_data['clicked_ok'] = True return True return False def on_apply(self): passed = self.on_validate() if passed and self.win_parent is not None: self.win_parent.shear_moment_torque_obj.make_smt_from_data( self.out_data, show=True) #self.win_parent.make_smt_from_data(self.out_data) return passed def on_cancel(self): self.out_data['close'] = True self.close()
def setup_and_check(self, data: _Quantity, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data self.data.flags.writeable = True is_record_array = data.m.dtype.names is not None is_masked_array = isinstance(data.m, np.ma.MaskedArray) if data.m.ndim > 3: self.error("Arrays with more than 3 dimensions are not supported") return False if xlabels is not None and len(xlabels) != self.data.m.shape[1]: self.error("The 'xlabels' argument length do no match array column number") return False if ylabels is not None and len(ylabels) != self.data.m.shape[0]: self.error("The 'ylabels' argument length do no match array row number") return False if not is_record_array: dtn = data.m.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') and not dtn.startswith('unicode'): arr = "%s arrays" % data.m.dtype.name self.error("%s are currently not supported" % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) # self.setWindowIcon(ima.icon('arredit')) if title: title = str(title) + " - " + "NumPy array" + str(data.u) else: title = "Array editor" if readonly: title += ' (' + 'read only' + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(QuantityArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(QuantityArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(QuantityArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(QuantityArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(QuantityArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) self.unitLabel = QLabel() self.unitLabel.setText(f'Unit: {data.u}') self.dimensionLabel = QLabel() self.dimensionLabel.setText(f'Dimension: {data.dimensionality}') self.layout.addWidget(self.unitLabel) self.layout.addWidget(self.dimensionLabel) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.m.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel("Record array fields:")) names = [] for name in data.m.dtype.names: field = data.m.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = ['Masked data', 'Data', 'Mask'] if data.m.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel("Axis:") btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel("Index:") btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel("<u>Warning</u>: changes are applied separately") label.setToolTip("For performance reasons, changes applied " \ "to masked array won't be reflected in " \ "array's data (and vice-versa).") btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True
class CurveSettingsDisplay(Display): def __init__(self, main_display, pv_name, parent=None): """ Create all the widgets for the curve appearance settings. Parameters ---------- main_display : TimeChartDisplay The main display window pv_name: str The name of the PV the current curve is being plotted for parent : QWidget The parent widget, if applicable """ super(CurveSettingsDisplay, self).__init__(parent=parent) self.main_layout = QFormLayout() self.main_display = main_display self.chart = self.main_display.chart self.pv_name = pv_name self.channel_map = self.main_display.channel_map self.app = self.main_display.app self.curve_original_color = None self.curve_color_lbl = QLabel("Curve Color ") self.curve_color_btn = QPushButton() self.curve_color_btn.setMaximumWidth(20) self.curve_color_btn.clicked.connect( self.handle_curve_color_button_clicked) self.symbol_lbl = QLabel("Symbol") self.symbol_cmb = QComboBox() self.symbol_size_lbl = QLabel("Symbol Size") self.symbol_size_spin = QSpinBox() self.line_style_lbl = QLabel("Line Style") self.line_style_cmb = QComboBox() self.line_width_lbl = QLabel("Line Width") self.line_width_spin = QSpinBox() self.set_defaults_btn = QPushButton("Reset") self.set_defaults_btn.clicked.connect(self.handle_reset_button_clicked) self.close_dialog_btn = QPushButton("Close") self.close_dialog_btn.clicked.connect(self.handle_close_button_clicked) self.setWindowTitle(self.pv_name.split("://")[1]) self.setFixedSize(QSize(300, 200)) self.setWindowModality(Qt.ApplicationModal) self.setup_ui() def ui_filepath(self): """ The path to the UI file created by Qt Designer, if applicable. """ # No UI file is being used return None def ui_filename(self): """ The name of the UI file created by Qt Designer, if applicable. """ # No UI file is being used return None def setup_ui(self): """ Initialize the widgets and layouts. """ # Populate the values for k, _ in BasePlotCurveItem.symbols.items(): self.symbol_cmb.addItem(k) self.symbol_size_spin.setRange(1, 10) for k, _ in BasePlotCurveItem.lines.items(): self.line_style_cmb.addItem(k) self.line_width_spin.setRange(1, 5) # Set the widget values to the current settings of the current curve self.set_widgets_to_current_curve_settings() # Connect to slots to handle signal events self.symbol_cmb.currentIndexChanged.connect( self.handle_symbol_index_changed) self.symbol_size_spin.valueChanged.connect( self.handle_symbol_size_changed) self.line_style_cmb.currentIndexChanged.connect( self.handle_line_style_index_changed) self.line_width_spin.valueChanged.connect( self.handle_line_width_changed) # Add widgets to the form layout self.main_layout.setSpacing(10) self.main_layout.addRow(self.curve_color_lbl, self.curve_color_btn) self.main_layout.addRow(self.symbol_lbl, self.symbol_cmb) self.main_layout.addRow(self.symbol_size_lbl, self.symbol_size_spin) self.main_layout.addRow(self.line_style_lbl, self.line_style_cmb) self.main_layout.addRow(self.line_width_lbl, self.line_width_spin) self.main_layout.addRow(self.set_defaults_btn, self.close_dialog_btn) # Add the form layout to the main layout of this dialog self.setLayout(self.main_layout) def set_widgets_to_current_curve_settings(self): """ Make the dialog widgets display the current appearance settings of the current curve. """ curve = self.chart.findCurve(self.pv_name) if curve: self.curve_color_btn.setStyleSheet("background-color: " + curve.color.name()) self.set_combo_box(self.symbol_cmb, BasePlotCurveItem.symbols, curve.symbol) self.set_combo_box(self.line_style_cmb, BasePlotCurveItem.lines, curve.lineStyle) self.symbol_size_spin.setValue(curve.symbolSize) self.line_width_spin.setValue(curve.lineWidth) def set_combo_box(self, combo_box, reference_dict, curve_setting_value): """ Reverse look up for a dictionary key using a dictionary value, and then set that value to a QComboBox widget. Parameters ---------- combo_box : QComboBox The combo box to set the current value for reference_dict : dict The reference dict to find the key from a value to set the combo box curve_setting_value : str The setting value to look for from the reference_dict """ for k, v in reference_dict.items(): if curve_setting_value == v: combo_box.setCurrentText(k) break def handle_curve_color_button_clicked(self): selected_color = QColorDialog.getColor() curve = self.chart.findCurve(self.pv_name) if curve: self.curve_original_color = curve.color curve.color = selected_color self.curve_color_btn.setStyleSheet("background-color: " + curve.color.name()) self.chart.refreshCurve(curve) self.channel_map[self.pv_name] = curve def handle_symbol_index_changed(self, selected_index): """ Handle the change in the curve's symbol from a combo box. Parameters ---------- selected_index : int The currently selected index from the symbol combo box """ curve = self.chart.findCurve(self.pv_name) if curve: curve.symbol = BasePlotCurveItem.symbols[self.symbol_cmb.itemText( selected_index)] self.chart.refreshCurve(curve) self.channel_map[self.pv_name] = curve def handle_symbol_size_changed(self, new_size): """ Handle the symbol size value change from the symbol size spinner. Parameters ---------- new_size : int The new symbol size set by the user. """ curve = self.chart.findCurve(self.pv_name) if curve: curve.symbolSize = new_size self.chart.refreshCurve(curve) self.channel_map[self.pv_name] = curve def handle_line_width_changed(self, new_width): """ Handle the symbol size value change from the line width spinner. Parameters ---------- new_width: int The new line width set by the user. """ curve = self.chart.findCurve(self.pv_name) if curve: curve.lineWidth = new_width self.chart.refreshCurve(curve) self.channel_map[self.pv_name] = curve def handle_line_style_index_changed(self, selected_index): """ Handle the change in the curve's line style from a combo box. Parameters ---------- selected_index : int The currently selected index from the line style combo box """ curve = self.chart.findCurve(self.pv_name) if curve: curve.lineStyle = BasePlotCurveItem.lines[ self.line_style_cmb.itemText(selected_index)] self.chart.refreshCurve(curve) self.channel_map[self.pv_name] = curve def handle_reset_button_clicked(self): """ Handle the click of the Reset button. This will set all the dialog widgets to the default curve appearance settings. """ curve = self.chart.findCurve(self.pv_name) if curve: if self.curve_original_color: curve.color = self.curve_original_color self.curve_color_btn.setStyleSheet("background-color: " + curve.color.name()) self.symbol_cmb.setCurrentIndex(0) self.symbol_size_spin.setValue(10) self.line_style_cmb.setCurrentIndex(1) self.line_width_spin.setValue(1) def closeEvent(self, event): self.handle_close_button_clicked() def handle_close_button_clicked(self): """ Close the dialog when the Close button is clicked. """ self.close() curve = self.chart.findCurve(self.pv_name) if curve: # Update the widget checkbox text to the current curve color widget = self.main_display.findChild(QGroupBox, self.pv_name + "_grb") chb = widget.findChild(QCheckBox, self.pv_name + "_chb") lbl = widget.findChild(QLabel, self.pv_name + "_lbl") widget = [chb, lbl] for w in widget: palette = w.palette() palette.setColor(QPalette.Active, QPalette.WindowText, curve.color) w.setPalette(palette)
def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.selected_label.connect(self._on_selection_change) self.layer.events.brush_size.connect(self._on_brush_size_change) self.layer.events.contiguous.connect(self._on_contig_change) self.layer.events.n_dimensional.connect(self._on_n_dim_change) self.layer.events.editable.connect(self._on_editable_change) self.layer.events.preserve_labels.connect( self._on_preserve_labels_change ) self.layer.events.color_mode.connect(self._on_color_mode_change) # selection spinbox self.selectionSpinBox = QSpinBox() self.selectionSpinBox.setKeyboardTracking(False) self.selectionSpinBox.setSingleStep(1) self.selectionSpinBox.setMinimum(0) self.selectionSpinBox.setMaximum(2147483647) self.selectionSpinBox.valueChanged.connect(self.changeSelection) self.selectionSpinBox.setAlignment(Qt.AlignCenter) self._on_selection_change() sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(1) sld.setMaximum(40) sld.setSingleStep(1) sld.valueChanged.connect(self.changeSize) self.brushSizeSlider = sld self._on_brush_size_change() contig_cb = QCheckBox() contig_cb.setToolTip('contiguous editing') contig_cb.stateChanged.connect(self.change_contig) self.contigCheckBox = contig_cb self._on_contig_change() ndim_cb = QCheckBox() ndim_cb.setToolTip('n-dimensional editing') ndim_cb.stateChanged.connect(self.change_ndim) self.ndimCheckBox = ndim_cb self._on_n_dim_change() preserve_labels_cb = QCheckBox() preserve_labels_cb.setToolTip( 'preserve existing labels while painting' ) preserve_labels_cb.stateChanged.connect(self.change_preserve_labels) self.preserveLabelsCheckBox = preserve_labels_cb self._on_preserve_labels_change() # shuffle colormap button self.colormapUpdate = QtModePushButton( None, 'shuffle', slot=self.changeColor, tooltip='shuffle colors', ) self.panzoom_button = QtModeRadioButton( layer, 'zoom', Mode.PAN_ZOOM, tooltip='Pan/zoom mode (Space)', checked=True, ) self.pick_button = QtModeRadioButton( layer, 'picker', Mode.PICK, tooltip='Pick mode' ) self.paint_button = QtModeRadioButton( layer, 'paint', Mode.PAINT, tooltip='Paint mode' ) btn = 'Cmd' if sys.platform == 'darwin' else 'Ctrl' self.fill_button = QtModeRadioButton( layer, 'fill', Mode.FILL, tooltip=f'Fill mode ({btn})' ) self.erase_button = QtModeRadioButton( layer, 'erase', Mode.ERASE, tooltip='Erase mode (Alt)' ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.paint_button) self.button_group.addButton(self.pick_button) self.button_group.addButton(self.fill_button) self.button_group.addButton(self.erase_button) self._on_editable_change() button_row = QHBoxLayout() button_row.addStretch(1) button_row.addWidget(self.colormapUpdate) button_row.addWidget(self.erase_button) button_row.addWidget(self.fill_button) button_row.addWidget(self.paint_button) button_row.addWidget(self.pick_button) button_row.addWidget(self.panzoom_button) button_row.setSpacing(4) button_row.setContentsMargins(0, 0, 0, 5) brush_shape_comboBox = QComboBox(self) brush_shape_comboBox.addItems(LabelBrushShape.keys()) index = brush_shape_comboBox.findText( self.layer.brush_shape, Qt.MatchFixedString ) brush_shape_comboBox.setCurrentIndex(index) brush_shape_comboBox.activated[str].connect(self.change_brush_shape) self.brushShapeComboBox = brush_shape_comboBox self._on_brush_shape_change() color_mode_comboBox = QComboBox(self) color_mode_comboBox.addItems(LabelColorMode.keys()) index = color_mode_comboBox.findText( self.layer.color_mode, Qt.MatchFixedString ) color_mode_comboBox.setCurrentIndex(index) color_mode_comboBox.activated[str].connect(self.change_color_mode) self.colorModeComboBox = color_mode_comboBox self._on_color_mode_change() color_layout = QHBoxLayout() color_layout.addWidget(QtColorBox(layer)) color_layout.addWidget(self.selectionSpinBox) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_row, 0, 0, 1, 4) self.grid_layout.addWidget(QLabel('label:'), 1, 0, 1, 1) self.grid_layout.addLayout(color_layout, 1, 1, 1, 3) self.grid_layout.addWidget(QLabel('opacity:'), 2, 0, 1, 1) self.grid_layout.addWidget(self.opacitySlider, 2, 1, 1, 3) self.grid_layout.addWidget(QLabel('brush size:'), 3, 0, 1, 1) self.grid_layout.addWidget(self.brushSizeSlider, 3, 1, 1, 3) self.grid_layout.addWidget(QLabel('brush shape:'), 4, 0, 1, 1) self.grid_layout.addWidget(self.brushShapeComboBox, 4, 1, 1, 3) self.grid_layout.addWidget(QLabel('blending:'), 5, 0, 1, 1) self.grid_layout.addWidget(self.blendComboBox, 5, 1, 1, 3) self.grid_layout.addWidget(QLabel('color mode:'), 6, 0, 1, 1) self.grid_layout.addWidget(self.colorModeComboBox, 6, 1, 1, 3) self.grid_layout.addWidget(QLabel('contiguous:'), 7, 0, 1, 1) self.grid_layout.addWidget(self.contigCheckBox, 7, 1, 1, 1) self.grid_layout.addWidget(QLabel('n-dim:'), 7, 2, 1, 1) self.grid_layout.addWidget(self.ndimCheckBox, 7, 3, 1, 1) self.grid_layout.addWidget(QLabel('preserve labels:'), 8, 0, 1, 2) self.grid_layout.addWidget(self.preserveLabelsCheckBox, 8, 1, 1, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4)
def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton( self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton( self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton( self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton( self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut('plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton( self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut('plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(zoom_out_btn) layout.addWidget(zoom_in_btn) layout.addWidget(self.zoom_disp) return [savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan]
def __init__(self, data, win_parent=None): """ +-----------------+ | Edit Node Props | +-----------------+------+ | LEwingTip | | Node2 | | Node3 | | Node4 | | | | All Nodes: | | Color red | | PointSize 3 | | Opacity 0.3 | | Show/Hide | | | | Name LEwingTip | | Location X Y Z | | Coord 0 | | CoordType R, C, S | | | | Previous Next | | | | Close | +------------------------+ """ QDialog.__init__(self, win_parent) self.setWindowTitle('Edit Node Properties') #default self.win_parent = win_parent self.out_data = data point_properties = data['point_properties'] print(point_properties) #name = point_properties.name point_size = point_properties.point_size opacity = point_properties.opacity color = point_properties.color show = point_properties.is_visible self.points = data['points'] self.keys = sorted(self.points.keys()) keys = self.keys #nrows = len(keys) active_point = data['active_point'] #self.active_key = keys[0] self.active_key = active_point name = self.active_key description = self.points[self.active_key][0] self._use_old_table = False items = ['Node %i' % val for val in keys] header_labels = ['Nodes'] table_model = Model(items, header_labels, self) view = SingleChoiceQTableView(self) #Call your custom QTableView here view.setModel(table_model) view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table = view #self.representation = actor_obj.representation #print('rep =', self.representation) table = self.table #headers = [QtCore.QString('Groups')] header = table.horizontalHeader() header.setStretchLastSection(True) #---------------------------------------------- #self._default_is_apply = False self.color = QLabel("Color:") self.color_edit = QPushButton() #self.color_edit.setFlat(True) color = self.out_data['point_properties'].color opacity = self.out_data['point_properties'].opacity show = self.out_data['point_properties'].is_visible #color = self.out_data[self.active_key].color qcolor = QColor() qcolor.setRgb(*color) #print('color =%s' % str(color)) palette = QPalette( self.color_edit.palette()) # make a copy of the palette #palette.setColor(QPalette.Active, QPalette.Base, \ #qcolor) palette.setColor(QPalette.Background, QColor('blue')) # ButtonText self.color_edit.setPalette(palette) self.color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(color) + #"border:1px solid rgb(255, 170, 255); " "}") self.all_nodes_header = QLabel("All Nodes:") self.point_size = QLabel("Point Size:") self.point_size_edit = QSpinBox(self) self.point_size_edit.setRange(1, 10) self.point_size_edit.setSingleStep(1) self.point_size_edit.setValue(point_size) self.opacity = QLabel("Opacity:") self.opacity_edit = QDoubleSpinBox(self) self.opacity_edit.setRange(0.1, 1.0) self.opacity_edit.setDecimals(1) self.opacity_edit.setSingleStep(0.1) self.opacity_edit.setValue(opacity) # show/hide self.checkbox_show = QCheckBox("Show") self.checkbox_hide = QCheckBox("Hide") self.checkbox_show.setChecked(show) self.checkbox_hide.setChecked(not show) #---------------------------------------------- self.nodes_header = QLabel("Single Node:") self.name = QLabel("ID:") self.name_edit = QLineEdit('Node %i' % name) self.name_edit.setDisabled(True) self.description = QLabel("Description:") self.description_edit = QLineEdit(str(description)) #self.description_edit.setDisabled(True) location_x = 0.1 location_y = 0.1 location_z = 0.1 self.location = QLabel("Location:") self.location_x_edit = QDoubleSpinBox(self) self.location_y_edit = QDoubleSpinBox(self) self.location_z_edit = QDoubleSpinBox(self) #self.location_x_edit.setDecimals(1) delta_x = abs(location_x) / 100. if location_x != 0.0 else 0.1 delta_y = abs(location_y) / 100. if location_y != 0.0 else 0.1 delta_z = abs(location_z) / 100. if location_z != 0.0 else 0.1 self.location_x_edit.setSingleStep(delta_x) self.location_y_edit.setSingleStep(delta_y) self.location_z_edit.setSingleStep(delta_z) self.location_x_edit.setValue(location_x) self.location_y_edit.setValue(location_y) self.location_z_edit.setValue(location_z) self.coord = QLabel("Coord:") self.coord_edit = QSpinBox(self) self.coord_edit.setRange(0, 99999999) #self.coord_edit.setSingleStep(1) self.coord_edit.setValue(0) self.coord_type = QLabel("Coord Type:") #---------------------------------------------- # closing #if self._default_is_apply: #self.apply_button.setDisabled(True) self.close_button = QPushButton("Close") self.create_layout() self.set_connections()
class ArrayEditor(BaseDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None self.btn_save_and_close = None self.btn_close = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data readonly = readonly or not self.data.flags.writeable is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.ndim > 3: self.error( _("Arrays with more than 3 dimensions are not " "supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error( _("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error( _("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn == 'object': # If the array doesn't have shape, we can't display it if data.shape == (): self.error( _("Object arrays without shape are not " "supported")) return False # We don't know what's inside these arrays, so we can't handle # edits self.readonly = readonly = True elif (dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') and not dtn.startswith('unicode')): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy object array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget( ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect( self.save_and_close_enable) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect( self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel( _("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, left_top, bottom_right): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':'] * 3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText( (r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)] * 3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index is None: stack_index = self.stack.count() try: self.stack.addWidget( ArrayEditorWidget(self, self.data[tuple(slice_index)])) except IndexError: # Handle arrays of size 0 in one axis self.stack.addWidget(ArrayEditorWidget(self, self.data)) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i'] * 3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText( ('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index] - 1) @Slot() def accept(self): """Reimplement Qt method.""" try: for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) except RuntimeError: # Sometimes under CI testing the object the following error appears # RuntimeError: wrapped C/C++ object has been deleted pass def get_value(self): """Return modified array -- this is *not* a copy""" # It is important to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occurred, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class SampleLogsView(QSplitter): """Sample Logs View This contains a table of the logs, a plot of the currently selected logs, and the statistics of the selected log. """ def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0): super(SampleLogsView, self).__init__(parent) self.presenter = presenter self.setWindowTitle("{} sample logs".format(name)) self.setWindowFlags(Qt.Window) # Create sample log table self.table = QTableView() self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.clicked.connect(self.presenter.clicked) self.table.doubleClicked.connect(self.presenter.doubleClicked) self.table.contextMenuEvent = self.tableMenu self.addWidget(self.table) frame_right = QFrame() layout_right = QVBoxLayout() #Add full_time and experimentinfo options layout_options = QHBoxLayout() if isMD: layout_options.addWidget(QLabel("Experiment Info #")) self.experimentInfo = QSpinBox() self.experimentInfo.setMaximum(noExp-1) self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) layout_options.addWidget(self.experimentInfo) self.full_time = QCheckBox("Relative Time") self.full_time.setChecked(True) self.full_time.stateChanged.connect(self.presenter.plot_logs) layout_options.addWidget(self.full_time) layout_right.addLayout(layout_options) # Sample log plot self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) self.canvas.mpl_connect('button_press_event', self.presenter.plot_clicked) self.ax = self.fig.add_subplot(111, projection='mantid') layout_right.addWidget(self.canvas) # Sample stats self.create_stats_widgets() layout_stats = QFormLayout() layout_stats.addRow('', QLabel("Log Statistics")) layout_stats.addRow('Min:', self.stats_widgets["minimum"]) layout_stats.addRow('Max:', self.stats_widgets["maximum"]) layout_stats.addRow('Mean:', self.stats_widgets["mean"]) layout_stats.addRow('Median:', self.stats_widgets["median"]) layout_stats.addRow('Std Dev:', self.stats_widgets["standard_deviation"]) layout_stats.addRow('Time Avg:', self.stats_widgets["time_mean"]) layout_stats.addRow('Time Std Dev:', self.stats_widgets["time_standard_deviation"]) layout_stats.addRow('Duration:', self.stats_widgets["duration"]) layout_right.addLayout(layout_stats) frame_right.setLayout(layout_right) self.addWidget(frame_right) self.setStretchFactor(0,1) self.resize(1200,800) self.show() def tableMenu(self, event): """Right click menu for table, can plot or print selected logs""" menu = QMenu(self) plotAction = menu.addAction("Plot selected") plotAction.triggered.connect(self.presenter.new_plot_logs) plotAction = menu.addAction("Print selected") plotAction.triggered.connect(self.presenter.print_selected_logs) menu.exec_(event.globalPos()) def set_model(self, model): """Set the model onto the table""" self.model = model self.table.setModel(self.model) self.table.resizeColumnsToContents() self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) def plot_selected_logs(self, ws, exp, rows): """Update the plot with the selected rows""" self.ax.clear() self.create_ax_by_rows(self.ax, ws, exp, rows) self.fig.canvas.draw() def new_plot_selected_logs(self, ws, exp, rows): """Create a new plot, in a separate window for selected rows""" fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'}) self.create_ax_by_rows(ax, ws, exp, rows) fig.show() def create_ax_by_rows(self, ax, ws, exp, rows): """Creates the plots for given rows onto axis ax""" for row in rows: log_text = self.get_row_log_name(row) ax.plot(ws, LogName=log_text, label=log_text, marker='.', FullTime=not self.full_time.isChecked(), ExperimentInfo=exp) ax.set_ylabel('') if ax.get_legend_handles_labels()[0]: ax.legend() def get_row_log_name(self, i): """Returns the log name of particular row""" return str(self.model.item(i, 0).text()) def get_exp(self): """Get set experiment info number""" return self.experimentInfo.value() def get_selected_row_indexes(self): """Return a list of selected row from table""" return [row.row() for row in self.table.selectionModel().selectedRows()] def set_selected_rows(self, rows): """Set seleceted rows in table""" mode = QItemSelectionModel.Select | QItemSelectionModel.Rows for row in rows: self.table.selectionModel().select(self.model.index(row, 0), mode) def create_stats_widgets(self): """Creates the statistics widgets""" self.stats_widgets = {"minimum": QLineEdit(), "maximum": QLineEdit(), "mean": QLineEdit(), "median": QLineEdit(), "standard_deviation": QLineEdit(), "time_mean": QLineEdit(), "time_standard_deviation": QLineEdit(), "duration": QLineEdit()} for widget in self.stats_widgets.values(): widget.setReadOnly(True) def set_statistics(self, stats): """Updates the statistics widgets from stats dictionary""" for param in self.stats_widgets.keys(): self.stats_widgets[param].setText('{:.6}'.format(getattr(stats, param))) def clear_statistics(self): """Clears the values in statistics widgets""" for widget in self.stats_widgets.values(): widget.clear()
def create_spinbox(self, prefix, suffix, option, default=NoDefault, min_=None, max_=None, step=None, tip=None): widget = QWidget(self) if prefix: plabel = QLabel(prefix) widget.plabel = plabel else: plabel = None if suffix: slabel = QLabel(suffix) widget.slabel = slabel else: slabel = None if step is not None: if type(step) is int: spinbox = QSpinBox() else: spinbox = QDoubleSpinBox() spinbox.setDecimals(1) spinbox.setSingleStep(step) else: spinbox = QSpinBox() if min_ is not None: spinbox.setMinimum(min_) if max_ is not None: spinbox.setMaximum(max_) if tip is not None: spinbox.setToolTip(tip) self.spinboxes[spinbox] = (option, default) layout = QHBoxLayout() for subwidget in (plabel, spinbox, slabel): if subwidget is not None: layout.addWidget(subwidget) layout.addStretch(1) layout.setContentsMargins(0, 0, 0, 0) widget.spinbox = spinbox widget.setLayout(layout) return widget
def __init__(self, jsonitem: JsonItem, parent=None): super().__init__(parent) assert jsonitem is not None self.item = jsonitem # name self.nameLabel = QLabel(self.tr("name:")) self.nameLineEdit = QLineEdit(self.item.name or "") self.nameLabel.setBuddy(self.nameLineEdit) # unit self.unitLabel = QLabel(self.tr("unit:")) self.unitLineEdit = QLineEdit(self.item.unit or "") self.unitLabel.setBuddy(self.unitLineEdit) # type self.typeLabel = QLabel(self.tr("type:")) self.typeComboBox = QComboBox() self.typeComboBox.addItems([k for k, t in VALUETYPES]) self.typeComboBox.setCurrentIndex( self.typeComboBox.findText(self.item.type)) self.typeComboBox.currentIndexChanged.connect(self.data_changed) self.typeLabel.setBuddy(self.typeComboBox) # decimals self.decimalsLabel = QLabel(self.tr("decimals:")) self.decimalsSpinBox = QSpinBox() self.decimalsSpinBox.setRange(0, 10) self.decimalsSpinBox.setValue(self.item.decimals or 0) self.decimalsLabel.setBuddy(self.decimalsSpinBox) self.decimalsSpinBox.valueChanged.connect(self.data_changed) self.last_decimals = self.decimalsSpinBox.value() # min self.minLabel = QLabel(self.tr("minimum:")) self.minSpinBox =QDoubleSpinBox() self.minSpinBox.setRange(-sys.maxsize, sys.maxsize) self.minLabel.setBuddy(self.minSpinBox) self.minSpinBox.setValue(self.item.min or 0.0) # max self.maxLabel = QLabel(self.tr("maximum:")) self.maxSpinBox = QDoubleSpinBox() self.maxSpinBox.setRange(-sys.maxsize, sys.maxsize) self.maxSpinBox.setValue(self.item.max or 100.0) # numerator self.scalefactorLabel = QLabel(self.tr("scalefactor:")) self.scalefactorSpinBox = NoZerosDoubleSpinBox() self.scalefactorSpinBox.setRange(-sys.maxsize, sys.maxsize) self.scalefactorSpinBox.setButtonSymbols(QSpinBox.NoButtons) self.scalefactorSpinBox.setDecimals(10) self.scalefactorSpinBox.setValue(self.item.scalefactor or 1) self.scalefactorLabel.setBuddy(self.scalefactorSpinBox) # readonly self.readonlyCheckBox = QCheckBox(self.tr("readonly")) self.readonlyCheckBox.setChecked(Qt.Checked if self.item.readonly else Qt.Unchecked) self.readonlyCheckBox.stateChanged.connect(self.data_changed) # buttons self.buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal ) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) # layout layout = QGridLayout() layout.addWidget(self.nameLabel, 0, 0) layout.addWidget(self.nameLineEdit, 0, 1) layout.addWidget(self.unitLabel, 1, 0) layout.addWidget(self.unitLineEdit, 1, 1) layout.addWidget(self.typeLabel, 2, 0) layout.addWidget(self.typeComboBox, 2, 1) layout.addWidget(self.decimalsLabel, 3, 0) layout.addWidget(self.decimalsSpinBox, 3, 1) layout.addWidget(self.minLabel, 4, 0) layout.addWidget(self.minSpinBox, 4, 1) layout.addWidget(self.maxLabel, 5, 0) layout.addWidget(self.maxSpinBox, 5, 1) layout.addWidget(self.scalefactorLabel, 6, 0) layout.addWidget(self.scalefactorSpinBox, 6, 1) layout.addWidget(self.readonlyCheckBox, 7, 0, 1, 2) layout.addWidget(self.buttons, 8, 0, 1, 2) self.setLayout(layout) # misc self.setWindowTitle("Edit JsonItem '%s'" % self.item.key) self.data_changed()
class DimensionMDE(Dimension): binningChanged = Signal() """ MDEventWorkspace has additional properties for either number_of_bins or thickness from mantidqt.widgets.sliceviewer.dimensionwidget import DimensionMDE from qtpy.QtWidgets import QApplication app = QApplication([]) window = DimensionMDE({'minimum':-1.1, 'number_of_bins':11, 'width':0.2, 'name':'Dim0', 'units':'A'}) window.show() app.exec_() """ def __init__(self, dim_info, number=0, state=State.NONE, parent=None): # hack in a number_of_bins for MDEventWorkspace dim_info['number_of_bins'] = 1000 dim_info['width'] = (dim_info['maximum']-dim_info['minimum'])/1000 self.spinBins = QSpinBox() self.spinBins.setRange(2,9999) self.spinBins.setValue(100) self.spinBins.hide() self.spinBins.setMinimumWidth(110) self.spinThick = QDoubleSpinBox() self.spinThick.setRange(0.001,999) self.spinThick.setValue(0.1) self.spinThick.setSingleStep(0.1) self.spinThick.setDecimals(3) self.spinThick.setMinimumWidth(110) self.rebinLabel = QLabel("thick") self.rebinLabel.setMinimumWidth(44) super(DimensionMDE, self).__init__(dim_info, number, state, parent) self.spinBins.valueChanged.connect(self.binningChanged) self.spinThick.valueChanged.connect(self.valueChanged) self.layout.addWidget(self.spinBins) self.layout.addWidget(self.spinThick) self.layout.addWidget(self.rebinLabel) def get_bins(self): return int(self.spinBins.value()) def get_thickness(self): return float(self.spinThick.value()) def set_state(self, state): super(DimensionMDE, self).set_state(state) if self.state == State.X: self.spinBins.show() self.spinThick.hide() self.rebinLabel.setText('bins') elif self.state == State.Y: self.spinBins.show() self.spinThick.hide() self.rebinLabel.setText('bins') elif self.state == State.NONE: self.spinBins.hide() self.spinThick.show() self.rebinLabel.setText('thick') else: self.spinBins.hide() self.spinThick.hide() self.rebinLabel.hide()
class FigureBrowser(QWidget): """ Widget to browse the figures that were sent by the kernel to the IPython console to be plotted inline. """ sig_option_changed = Signal(str, object) sig_collapse = Signal() def __init__(self, parent=None, options_button=None, plugin_actions=[], background_color=None): super(FigureBrowser, self).__init__(parent) self.shellwidget = None self.is_visible = True self.figviewer = None self.setup_in_progress = False self.background_color = background_color # Options : self.mute_inline_plotting = None self.show_plot_outline = None self.auto_fit_plotting = None # Option actions : self.mute_inline_action = None self.show_plot_outline_action = None self.auto_fit_action = None self.options_button = options_button self.plugin_actions = plugin_actions self.shortcuts = self.create_shortcuts() def setup(self, mute_inline_plotting=None, show_plot_outline=None, auto_fit_plotting=None): """Setup the figure browser with provided settings.""" assert self.shellwidget is not None self.mute_inline_plotting = mute_inline_plotting self.show_plot_outline = show_plot_outline self.auto_fit_plotting = auto_fit_plotting if self.figviewer is not None: self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action.setChecked(show_plot_outline) self.auto_fit_action.setChecked(auto_fit_plotting) return self.figviewer = FigureViewer(background_color=self.background_color) self.figviewer.setStyleSheet("FigureViewer{" "border: 1px solid lightgrey;" "border-top-width: 0px;" "border-bottom-width: 0px;" "border-left-width: 0px;" "}") self.thumbnails_sb = ThumbnailScrollBar( self.figviewer, background_color=self.background_color) toolbar = self.setup_toolbar() self.setup_option_actions(mute_inline_plotting, show_plot_outline, auto_fit_plotting) # Create the layout. main_widget = QSplitter() main_widget.addWidget(self.figviewer) main_widget.addWidget(self.thumbnails_sb) main_widget.setFrameStyle(QScrollArea().frameStyle()) self.tools_layout = QHBoxLayout() for widget in toolbar: self.tools_layout.addWidget(widget) self.tools_layout.addStretch() self.setup_options_button() layout = create_plugin_layout(self.tools_layout, main_widget) self.setLayout(layout) def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton(self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton(self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton(self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton(self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut( 'plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton(self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut( 'plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) self.zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) self.zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.zoom_out_btn) layout.addWidget(self.zoom_in_btn) layout.addWidget(self.zoom_disp) return [ savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan ] def setup_option_actions(self, mute_inline_plotting, show_plot_outline, auto_fit_plotting): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.mute_inline_action = create_action( self, _("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda state: self.option_changed('mute_inline_plotting', state)) self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action = create_action( self, _("Show plot outline"), tip=_("Show the plot outline."), toggled=self.show_fig_outline_in_viewer) self.show_plot_outline_action.setChecked(show_plot_outline) self.auto_fit_action = create_action( self, _("Fit plots to window"), tip=_("Automatically fit plots to Plot pane size."), toggled=self.change_auto_fit_plotting) self.auto_fit_action.setChecked(auto_fit_plotting) self.actions = [ self.mute_inline_action, self.show_plot_outline_action, self.auto_fit_action ] self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: # When the FigureBowser widget is instatiated outside of the # plugin (for testing purpose for instance), we need to create # the options_button and set its menu. self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions self.options_menu = QMenu(self) add_actions(self.options_menu, actions) self.options_button.setMenu(self.options_menu) if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None: self.tools_layout.insertWidget(self.tools_layout.count() - 1, self.options_button) else: self.tools_layout.addWidget(self.options_button) def create_shortcuts(self): """Create shortcuts for this widget.""" # Configurable copyfig = config_shortcut(self.copy_figure, context='plots', name='copy', parent=self) prevfig = config_shortcut(self.go_previous_thumbnail, context='plots', name='previous figure', parent=self) nextfig = config_shortcut(self.go_next_thumbnail, context='plots', name='next figure', parent=self) return [copyfig, prevfig, nextfig] def get_shortcut_data(self): """ Return shortcut data, a list of tuples (shortcut, text, default). shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def option_changed(self, option, value): """Handle when the value of an option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() if self.setup_in_progress is False: self.sig_option_changed.emit(option, value) def show_fig_outline_in_viewer(self, state): """Draw a frame around the figure viewer if state is True.""" if state is True: self.figviewer.figcanvas.setStyleSheet( "FigureCanvas{border: 1px solid lightgrey;}") else: self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}") self.option_changed('show_plot_outline', state) def change_auto_fit_plotting(self, state): """Change the auto_fit_plotting option and scale images.""" self.option_changed('auto_fit_plotting', state) self.figviewer.auto_fit_plotting = state self.zoom_out_btn.setEnabled(not state) self.zoom_in_btn.setEnabled(not state) def set_shellwidget(self, shellwidget): """Bind the shellwidget instance to the figure browser""" self.shellwidget = shellwidget shellwidget.set_figurebrowser(self) shellwidget.sig_new_inline_figure.connect(self._handle_new_figure) def get_actions(self): """Get the actions of the widget.""" return self.actions def _handle_new_figure(self, fig, fmt): """ Handle when a new figure is sent to the IPython console by the kernel. """ self.thumbnails_sb.add_thumbnail(fig, fmt) # ---- Toolbar Handlers def zoom_in(self): """Zoom the figure in by a single step in the figure viewer.""" self.figviewer.zoom_in() def zoom_out(self): """Zoom the figure out by a single step in the figure viewer.""" self.figviewer.zoom_out() def go_previous_thumbnail(self): """ Select the thumbnail previous to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_previous_thumbnail() def go_next_thumbnail(self): """ Select the thumbnail next to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_next_thumbnail() def save_figure(self): """Save the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.save_current_figure_as() def save_all_figures(self): """Save all the figures in a selected directory.""" return self.thumbnails_sb.save_all_figures_as() def close_figure(self): """Close the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.remove_current_thumbnail() def close_all_figures(self): """Close all the figures in the thumbnail scrollbar.""" self.thumbnails_sb.remove_all_thumbnails() def copy_figure(self): """Copy figure from figviewer to clipboard.""" if self.figviewer and self.figviewer.figcanvas.fig: self.figviewer.figcanvas.copy_figure()
class LSPServerEditor(QDialog): DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 2084 DEFAULT_CMD = '' DEFAULT_ARGS = '' DEFAULT_CONFIGURATION = '{}' DEFAULT_EXTERNAL = False DEFAULT_STDIO = False HOST_REGEX = re.compile(r'^\w+([.]\w+)*$') NON_EMPTY_REGEX = re.compile(r'^\S+$') JSON_VALID = _('Valid JSON') JSON_INVALID = _('Invalid JSON') MIN_SIZE = QSize(850, 600) INVALID_CSS = "QLineEdit {border: 1px solid red;}" VALID_CSS = "QLineEdit {border: 1px solid green;}" def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, stdio=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) description = _( "To create a new server configuration, you need to select a " "programming language, set the command to start its associated " "server and enter any arguments that should be passed to it on " "startup. Additionally, you can set the server's hostname and " "port if connecting to an external server, " "or to a local one using TCP instead of stdio pipes." "<br><br>" "<i>Note</i>: You can use the placeholders <tt>{host}</tt> and " "<tt>{port}</tt> in the server arguments field to automatically " "fill in the respective values.<br>" ) self.parent = parent self.external = external # Widgets self.server_settings_description = QLabel(description) self.lang_cb = QComboBox(self) self.external_cb = QCheckBox(_('External server'), self) self.host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.cmd_label = QLabel(_('Command:')) self.cmd_input = QLineEdit(self) self.args_label = QLabel(_('Arguments:')) self.args_input = QLineEdit(self) self.json_label = QLabel(self.JSON_VALID, self) self.conf_label = QLabel(_('<b>Server Configuration:</b>')) self.conf_input = CodeEditor(None) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) self.button_cancel = self.bbox.button(QDialogButtonBox.Cancel) # Widget setup self.setMinimumSize(self.MIN_SIZE) self.setWindowTitle(_('LSP server editor')) self.server_settings_description.setWordWrap(True) self.lang_cb.setToolTip( _('Programming language provided by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) self.button_ok.setEnabled(False) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) self.host_input.setPlaceholderText('127.0.0.1') self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) self.cmd_input.setText(cmd) self.cmd_input.setPlaceholderText('/absolute/path/to/command') self.args_input.setToolTip( _('Additional arguments required to start the server')) self.args_input.setText(args) self.args_input.setPlaceholderText(r'--host {host} --port {port}') self.conf_input.setup_editor( language='json', color_scheme=CONF.get('appearance', 'selected'), wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json', folding=False ) self.conf_input.set_language('json', 'config.json') self.conf_input.setToolTip(_('Additional LSP server configuration ' 'set at runtime. JSON required')) try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: conf_text = '{}' self.conf_input.set_text(conf_text) self.external_cb.setToolTip( _('Check if the server runs on a remote location')) self.external_cb.setChecked(external) self.stdio_cb = QCheckBox(_('Use stdio pipes for communication'), self) self.stdio_cb.setToolTip(_('Check if the server communicates ' 'using stdin/out pipes')) self.stdio_cb.setChecked(stdio) # Layout setup hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(self.server_settings_description) vlayout = QVBoxLayout() lang_group = QGroupBox(_('Language')) lang_layout = QVBoxLayout() lang_layout.addWidget(self.lang_cb) lang_group.setLayout(lang_layout) vlayout.addWidget(lang_group) server_group = QGroupBox(_('Language server')) server_layout = QGridLayout() server_layout.addWidget(self.cmd_label, 0, 0) server_layout.addWidget(self.cmd_input, 0, 1) server_layout.addWidget(self.args_label, 1, 0) server_layout.addWidget(self.args_input, 1, 1) server_group.setLayout(server_layout) vlayout.addWidget(server_group) address_group = QGroupBox(_('Server address')) host_layout = QVBoxLayout() host_layout.addWidget(self.host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(self.port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) address_group.setLayout(conn_info_layout) vlayout.addWidget(address_group) advanced_group = QGroupBox(_('Advanced')) advanced_layout = QVBoxLayout() advanced_layout.addWidget(self.external_cb) advanced_layout.addWidget(self.stdio_cb) advanced_group.setLayout(advanced_layout) vlayout.addWidget(advanced_group) conf_layout = QVBoxLayout() conf_layout.addWidget(self.conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) vlayout.addStretch() hlayout.addLayout(vlayout, 2) hlayout.addLayout(conf_layout, 3) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(self.bbox) self.setLayout(general_vlayout) self.form_status(False) # Signals if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) self.external_cb.stateChanged.connect(self.set_local_options) self.stdio_cb.stateChanged.connect(self.set_stdio_options) self.lang_cb.currentIndexChanged.connect(self.lang_selection_changed) self.conf_input.textChanged.connect(self.validate) self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.reject) # Final setup if language is not None: self.form_status(True) self.validate() if stdio: self.set_stdio_options(True) if external: self.set_local_options(True) @Slot() def validate(self): host_text = self.host_input.text() cmd_text = self.cmd_input.text() if not self.HOST_REGEX.match(host_text): self.button_ok.setEnabled(False) self.host_input.setStyleSheet(self.INVALID_CSS) if bool(host_text): self.host_input.setToolTip(_('Hostname must be valid')) else: self.host_input.setToolTip( _('Hostname or IP address of the host on which the server ' 'is running. Must be non empty.')) else: self.host_input.setStyleSheet(self.VALID_CSS) self.host_input.setToolTip(_('Hostname is valid')) self.button_ok.setEnabled(True) if not self.external: if not self.NON_EMPTY_REGEX.match(cmd_text): self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet(self.INVALID_CSS) self.cmd_input.setToolTip( _('Command used to start the LSP server locally. Must be ' 'non empty')) return if find_program(cmd_text) is None: self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet(self.INVALID_CSS) self.cmd_input.setToolTip(_('Program was not found ' 'on your system')) else: self.cmd_input.setStyleSheet(self.VALID_CSS) self.cmd_input.setToolTip(_('Program was found on your ' 'system')) self.button_ok.setEnabled(True) try: json.loads(self.conf_input.toPlainText()) try: self.json_label.setText(self.JSON_VALID) except Exception: pass except ValueError: try: self.json_label.setText(self.JSON_INVALID) self.button_ok.setEnabled(False) except Exception: pass def form_status(self, status): self.host_input.setEnabled(status) self.port_spinner.setEnabled(status) self.external_cb.setEnabled(status) self.stdio_cb.setEnabled(status) self.cmd_input.setEnabled(status) self.args_input.setEnabled(status) self.conf_input.setEnabled(status) self.json_label.setVisible(status) @Slot() def lang_selection_changed(self): idx = self.lang_cb.currentIndex() if idx == 0: self.set_defaults() self.form_status(False) self.button_ok.setEnabled(False) else: server = self.parent.get_server_by_lang(LSP_LANGUAGES[idx - 1]) self.form_status(True) if server is not None: self.host_input.setText(server.host) self.port_spinner.setValue(server.port) self.external_cb.setChecked(server.external) self.stdio_cb.setChecked(server.stdio) self.cmd_input.setText(server.cmd) self.args_input.setText(server.args) self.conf_input.set_text(json.dumps(server.configurations)) self.json_label.setText(self.JSON_VALID) self.button_ok.setEnabled(True) else: self.set_defaults() def set_defaults(self): self.cmd_input.setStyleSheet('') self.host_input.setStyleSheet('') self.host_input.setText(self.DEFAULT_HOST) self.port_spinner.setValue(self.DEFAULT_PORT) self.external_cb.setChecked(self.DEFAULT_EXTERNAL) self.stdio_cb.setChecked(self.DEFAULT_STDIO) self.cmd_input.setText(self.DEFAULT_CMD) self.args_input.setText(self.DEFAULT_ARGS) self.conf_input.set_text(self.DEFAULT_CONFIGURATION) self.json_label.setText(self.JSON_VALID) @Slot(bool) @Slot(int) def set_local_options(self, enabled): self.external = enabled self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) if enabled: self.cmd_input.setEnabled(False) self.cmd_input.setStyleSheet('') self.args_input.setEnabled(False) self.stdio_cb.stateChanged.disconnect() self.stdio_cb.setChecked(False) self.stdio_cb.setEnabled(False) else: self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) self.stdio_cb.setEnabled(True) self.stdio_cb.setChecked(False) self.stdio_cb.stateChanged.connect(self.set_stdio_options) try: self.validate() except Exception: pass @Slot(bool) @Slot(int) def set_stdio_options(self, enabled): self.stdio = enabled if enabled: self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) self.external_cb.stateChanged.disconnect() self.external_cb.setChecked(False) self.external_cb.setEnabled(False) self.host_input.setStyleSheet('') self.host_input.setEnabled(False) self.port_spinner.setEnabled(False) else: self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) self.external_cb.setChecked(False) self.external_cb.setEnabled(True) self.external_cb.stateChanged.connect(self.set_local_options) self.host_input.setEnabled(True) self.port_spinner.setEnabled(True) try: self.validate() except Exception: pass def get_options(self): language_idx = self.lang_cb.currentIndex() language = LSP_LANGUAGES[language_idx - 1] host = self.host_input.text() port = int(self.port_spinner.value()) external = self.external_cb.isChecked() stdio = self.stdio_cb.isChecked() args = self.args_input.text() cmd = self.cmd_input.text() configurations = json.loads(self.conf_input.toPlainText()) server = LSPServer(language=language.lower(), cmd=cmd, args=args, host=host, port=port, external=external, stdio=stdio, configurations=configurations) return server
def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0): super(SampleLogsView, self).__init__(parent) self.presenter = presenter self.setWindowTitle("{} sample logs".format(name)) self.setWindowFlags(Qt.Window) # Create sample log table self.table = QTableView() self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.clicked.connect(self.presenter.clicked) self.table.doubleClicked.connect(self.presenter.doubleClicked) self.table.contextMenuEvent = self.tableMenu self.addWidget(self.table) frame_right = QFrame() layout_right = QVBoxLayout() #Add full_time and experimentinfo options layout_options = QHBoxLayout() if isMD: layout_options.addWidget(QLabel("Experiment Info #")) self.experimentInfo = QSpinBox() self.experimentInfo.setMaximum(noExp-1) self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) layout_options.addWidget(self.experimentInfo) self.full_time = QCheckBox("Relative Time") self.full_time.setChecked(True) self.full_time.stateChanged.connect(self.presenter.plot_logs) layout_options.addWidget(self.full_time) layout_right.addLayout(layout_options) # Sample log plot self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) self.canvas.mpl_connect('button_press_event', self.presenter.plot_clicked) self.ax = self.fig.add_subplot(111, projection='mantid') layout_right.addWidget(self.canvas) # Sample stats self.create_stats_widgets() layout_stats = QFormLayout() layout_stats.addRow('', QLabel("Log Statistics")) layout_stats.addRow('Min:', self.stats_widgets["minimum"]) layout_stats.addRow('Max:', self.stats_widgets["maximum"]) layout_stats.addRow('Mean:', self.stats_widgets["mean"]) layout_stats.addRow('Median:', self.stats_widgets["median"]) layout_stats.addRow('Std Dev:', self.stats_widgets["standard_deviation"]) layout_stats.addRow('Time Avg:', self.stats_widgets["time_mean"]) layout_stats.addRow('Time Std Dev:', self.stats_widgets["time_standard_deviation"]) layout_stats.addRow('Duration:', self.stats_widgets["duration"]) layout_right.addLayout(layout_stats) frame_right.setLayout(layout_right) self.addWidget(frame_right) self.setStretchFactor(0,1) self.resize(1200,800) self.show()
class RunICADialog(QDialog): def __init__(self, parent, nchan, have_picard=True, have_sklearn=True): super().__init__(parent) self.setWindowTitle("Run ICA") vbox = QVBoxLayout(self) grid = QGridLayout() grid.addWidget(QLabel("Method:"), 0, 0) self.method = QComboBox() self.methods = {"Infomax": "infomax"} if have_sklearn: self.methods["FastICA"] = "fastica" if have_picard: self.methods["Picard"] = "picard" self.method.addItems(self.methods.keys()) if have_picard: self.method.setCurrentText("Picard") else: self.method.setCurrentText("Infomax") min_len = max(len(key) for key in self.methods.keys()) self.method.setMinimumContentsLength(min_len) grid.addWidget(self.method, 0, 1) self.extended_label = QLabel("Extended:") grid.addWidget(self.extended_label, 1, 0) self.extended = QCheckBox() self.extended.setChecked(True) grid.addWidget(self.extended, 1, 1) self.ortho_label = QLabel("Orthogonal:") grid.addWidget(self.ortho_label, 2, 0) self.ortho = QCheckBox() self.ortho.setChecked(False) grid.addWidget(self.ortho, 2, 1) self.toggle_options() self.method.currentIndexChanged.connect(self.toggle_options) grid.addWidget(QLabel("Number of components:"), 3, 0) self.n_components = QSpinBox() self.n_components.setMinimum(0) self.n_components.setMaximum(nchan) self.n_components.setValue(nchan) self.n_components.setAlignment(Qt.AlignRight) grid.addWidget(self.n_components, 3, 1) grid.addWidget(QLabel("Exclude bad segments:"), 4, 0) self.exclude_bad_segments = QCheckBox() self.exclude_bad_segments.setChecked(True) grid.addWidget(self.exclude_bad_segments) vbox.addLayout(grid) buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) vbox.addWidget(buttonbox) buttonbox.accepted.connect(self.accept) buttonbox.rejected.connect(self.reject) vbox.setSizeConstraint(QVBoxLayout.SetFixedSize) @Slot() def toggle_options(self): """Toggle extended options. """ if self.method.currentText() == "Picard": self.extended_label.show() self.extended.show() self.ortho_label.show() self.ortho.show() elif self.method.currentText() == "Infomax": self.extended_label.show() self.extended.show() self.ortho_label.hide() self.ortho.hide() else: self.extended_label.hide() self.extended.hide() self.ortho_label.hide() self.ortho.hide()
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.size == 0: self.error(_("Array is empty")) return False if data.ndim > 3: self.error(_("Arrays with more than 3 dimensions are not supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error(_("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error(_("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - '+title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel(_("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':']*3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)]*3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index == None: stack_index = self.stack.count() self.stack.addWidget(ArrayEditorWidget(self, self.data[slice_index])) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i']*3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText(('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index]-1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- 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 return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
def create_widgets(self): """creates the display window""" # Text Size self.font_size = QLabel("Text Size:") self.font_size_edit = QSpinBox(self) self.font_size_edit.setValue(self._default_font_size) self.font_size_edit.setRange(7, 20) self.font_size_button = QPushButton("Default") #----------------------------------------------------------------------- # Annotation Color self.annotation_color = QLabel("Annotation Color:") self.annotation_color_edit = QPushButtonColor( self.annotation_color_int) # Background Color self.background_color = QLabel("Background Color:") self.background_color_edit = QPushButtonColor( self.background_color_int) # Text Color self.text_color = QLabel("Text Color:") self.text_color_edit = QPushButtonColor(self.text_color_int) #----------------------------------------------------------------------- # Annotation Size self.annotation_size = QLabel("Annotation Size:") self.annotation_size_edit = QSpinBox(self) self.annotation_size_edit.setRange(1, 500) self.annotation_size_edit.setValue(self._default_annotation_size) self.annotation_size_button = QPushButton("Default") #----------------------------------------------------------------------- # Picker Size self.picker_size = QLabel("Picker Size (% of Screen):") self.picker_size_edit = QDoubleSpinBox(self) self.picker_size_edit.setRange(0., 10.) log_dim = log10(self.dim_max) decimals = int(ceil(abs(log_dim))) decimals = max(6, decimals) self.picker_size_edit.setDecimals(decimals) self.picker_size_edit.setSingleStep(10. / 5000.) self.picker_size_edit.setValue(self._picker_size) #----------------------------------------------------------------------- # Clipping Min self.clipping_min = QLabel("Clipping Min:") self.clipping_min_edit = QLineEdit(str(self._default_clipping_min)) self.clipping_min_button = QPushButton("Default") # Clipping Max self.clipping_max = QLabel("Clipping Max:") self.clipping_max_edit = QLineEdit(str(self._default_clipping_max)) self.clipping_max_button = QPushButton("Default") #----------------------------------------------------------------------- # closing self.apply_button = QPushButton("Apply") self.ok_button = QPushButton("OK") self.cancel_button = QPushButton("Cancel")
def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.size == 0: self.error(_("Array is empty")) return False if data.ndim > 3: self.error(_("Arrays with more than 3 dimensions are not supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error(_("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error(_("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - '+title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel(_("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True
class LSPServerEditor(QDialog): DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 2084 DEFAULT_CMD = '' DEFAULT_ARGS = '' DEFAULT_CONFIGURATION = '{}' DEFAULT_EXTERNAL = False HOST_REGEX = re.compile(r'^\w+([.]\w+)*$') NON_EMPTY_REGEX = re.compile(r'^\S+$') JSON_VALID = _('JSON valid') JSON_INVALID = _('JSON invalid') def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) self.parent = parent self.external = external bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = bbox.button(QDialogButtonBox.Ok) self.button_cancel = bbox.button(QDialogButtonBox.Cancel) self.button_ok.setEnabled(False) description = _('To create a new configuration, ' 'you need to select a programming ' 'language, along with a executable ' 'name for the server to execute ' '(If the instance is local), ' 'and the host and port. Finally, ' 'you need to provide the ' 'arguments that the server accepts. ' 'The placeholders <tt>%(host)s</tt> and ' '<tt>%(port)s</tt> refer to the host ' 'and the port, respectively.') server_settings_description = QLabel(description) server_settings_description.setWordWrap(True) lang_label = QLabel(_('Language:')) self.lang_cb = QComboBox(self) self.lang_cb.setToolTip(_('Programming language provided ' 'by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.host_input.setToolTip(_('Name of the host that will provide ' 'access to the server')) self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) cmd_label = QLabel(_('Command to execute:')) self.cmd_input = QLineEdit(self) self.cmd_input.setToolTip(_('Command used to start the ' 'LSP server locally')) self.cmd_input.setText(cmd) if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) args_label = QLabel(_('Server arguments:')) self.args_input = QLineEdit(self) self.args_input.setToolTip(_('Additional arguments required to ' 'start the server')) self.args_input.setText(args) conf_label = QLabel(_('LSP Server Configurations:')) self.conf_input = CodeEditor(None) self.conf_input.textChanged.connect(self.validate) color_scheme = CONF.get('appearance', 'selected') self.conf_input.setup_editor( language='JSON', color_scheme=color_scheme, wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json') self.conf_input.setToolTip(_('Additional LSP server configurations ' 'set at runtime. JSON required')) conf_text = '{}' try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: pass self.conf_input.set_text(conf_text) self.json_label = QLabel(self.JSON_VALID, self) self.external_cb = QCheckBox(_('External server'), self) self.external_cb.setToolTip(_('Check if the server runs ' 'on a remote location')) self.external_cb.setChecked(external) self.external_cb.stateChanged.connect(self.set_local_options) hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(server_settings_description) vlayout = QVBoxLayout() lang_layout = QVBoxLayout() lang_layout.addWidget(lang_label) lang_layout.addWidget(self.lang_cb) # layout2 = QHBoxLayout() # layout2.addLayout(lang_layout) lang_layout.addWidget(self.external_cb) vlayout.addLayout(lang_layout) host_layout = QVBoxLayout() host_layout.addWidget(host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) vlayout.addLayout(conn_info_layout) cmd_layout = QVBoxLayout() cmd_layout.addWidget(cmd_label) cmd_layout.addWidget(self.cmd_input) vlayout.addLayout(cmd_layout) args_layout = QVBoxLayout() args_layout.addWidget(args_label) args_layout.addWidget(self.args_input) vlayout.addLayout(args_layout) conf_layout = QVBoxLayout() conf_layout.addWidget(conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) hlayout.addLayout(vlayout) hlayout.addLayout(conf_layout) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(bbox) self.setLayout(general_vlayout) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.lang_cb.currentIndexChanged.connect( self.lang_selection_changed) self.form_status(False) if language is not None: self.form_status(True) self.validate() @Slot() def validate(self): host_text = self.host_input.text() cmd_text = self.cmd_input.text() if not self.HOST_REGEX.match(host_text): self.button_ok.setEnabled(False) self.host_input.setStyleSheet("QLineEdit{border: 1px solid red;}") self.host_input.setToolTip('Hostname must be valid') return else: self.host_input.setStyleSheet( "QLineEdit{border: 1px solid green;}") self.host_input.setToolTip('Hostname is valid') self.button_ok.setEnabled(True) if not self.external: if not self.NON_EMPTY_REGEX.match(cmd_text): self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid red;}") self.cmd_input.setToolTip('Command must be non empty') return if find_program(cmd_text) is None: self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid red;}") self.cmd_input.setToolTip('Program was not found ' 'on your system') return else: self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid green;}") self.cmd_input.setToolTip('Program was found on your system') self.button_ok.setEnabled(True) try: json.loads(self.conf_input.toPlainText()) try: self.json_label.setText(self.JSON_VALID) except: pass except (ValueError, json.decoder.JSONDecodeError): try: self.json_label.setText(self.JSON_INVALID) self.button_ok.setEnabled(False) except: pass def form_status(self, status): self.host_input.setEnabled(status) self.port_spinner.setEnabled(status) self.external_cb.setEnabled(status) self.cmd_input.setEnabled(status) self.args_input.setEnabled(status) self.conf_input.setEnabled(status) self.json_label.setVisible(status) @Slot() def lang_selection_changed(self): idx = self.lang_cb.currentIndex() if idx == 0: self.set_defaults() self.form_status(False) self.button_ok.setEnabled(False) else: server = self.parent.get_server_by_lang(LSP_LANGUAGES[idx - 1]) self.form_status(True) if server is not None: self.host_input.setText(server.host) self.port_spinner.setValue(server.port) self.external_cb.setChecked(server.external) self.cmd_input.setText(server.cmd) self.args_input.setText(server.args) self.conf_input.set_text(json.dumps(server.configurations)) self.json_label.setText(self.JSON_VALID) self.button_ok.setEnabled(True) else: self.set_defaults() def set_defaults(self): self.cmd_input.setStyleSheet('') self.host_input.setStyleSheet('') self.host_input.setText(self.DEFAULT_HOST) self.port_spinner.setValue(self.DEFAULT_PORT) self.external_cb.setChecked(self.DEFAULT_EXTERNAL) self.cmd_input.setText(self.DEFAULT_CMD) self.args_input.setText(self.DEFAULT_ARGS) self.conf_input.set_text(self.DEFAULT_CONFIGURATION) self.json_label.setText(self.JSON_VALID) @Slot(bool) @Slot(int) def set_local_options(self, enabled): self.external = enabled self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) if enabled: self.cmd_input.setEnabled(False) self.cmd_input.setStyleSheet('') self.args_input.setEnabled(False) try: self.validate() except: pass def get_options(self): language_idx = self.lang_cb.currentIndex() language = LSP_LANGUAGES[language_idx - 1] host = self.host_input.text() port = int(self.port_spinner.value()) external = self.external_cb.isChecked() args = self.args_input.text() cmd = self.cmd_input.text() configurations = json.loads(self.conf_input.toPlainText()) server = LSPServer(language=language.lower(), cmd=cmd, args=args, host=host, port=port, external=external, configurations=configurations) return server
def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) self.parent = parent self.external = external bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = bbox.button(QDialogButtonBox.Ok) self.button_cancel = bbox.button(QDialogButtonBox.Cancel) self.button_ok.setEnabled(False) description = _('To create a new configuration, ' 'you need to select a programming ' 'language, along with a executable ' 'name for the server to execute ' '(If the instance is local), ' 'and the host and port. Finally, ' 'you need to provide the ' 'arguments that the server accepts. ' 'The placeholders <tt>%(host)s</tt> and ' '<tt>%(port)s</tt> refer to the host ' 'and the port, respectively.') server_settings_description = QLabel(description) server_settings_description.setWordWrap(True) lang_label = QLabel(_('Language:')) self.lang_cb = QComboBox(self) self.lang_cb.setToolTip(_('Programming language provided ' 'by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.host_input.setToolTip(_('Name of the host that will provide ' 'access to the server')) self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) cmd_label = QLabel(_('Command to execute:')) self.cmd_input = QLineEdit(self) self.cmd_input.setToolTip(_('Command used to start the ' 'LSP server locally')) self.cmd_input.setText(cmd) if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) args_label = QLabel(_('Server arguments:')) self.args_input = QLineEdit(self) self.args_input.setToolTip(_('Additional arguments required to ' 'start the server')) self.args_input.setText(args) conf_label = QLabel(_('LSP Server Configurations:')) self.conf_input = CodeEditor(None) self.conf_input.textChanged.connect(self.validate) color_scheme = CONF.get('appearance', 'selected') self.conf_input.setup_editor( language='JSON', color_scheme=color_scheme, wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json') self.conf_input.setToolTip(_('Additional LSP server configurations ' 'set at runtime. JSON required')) conf_text = '{}' try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: pass self.conf_input.set_text(conf_text) self.json_label = QLabel(self.JSON_VALID, self) self.external_cb = QCheckBox(_('External server'), self) self.external_cb.setToolTip(_('Check if the server runs ' 'on a remote location')) self.external_cb.setChecked(external) self.external_cb.stateChanged.connect(self.set_local_options) hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(server_settings_description) vlayout = QVBoxLayout() lang_layout = QVBoxLayout() lang_layout.addWidget(lang_label) lang_layout.addWidget(self.lang_cb) # layout2 = QHBoxLayout() # layout2.addLayout(lang_layout) lang_layout.addWidget(self.external_cb) vlayout.addLayout(lang_layout) host_layout = QVBoxLayout() host_layout.addWidget(host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) vlayout.addLayout(conn_info_layout) cmd_layout = QVBoxLayout() cmd_layout.addWidget(cmd_label) cmd_layout.addWidget(self.cmd_input) vlayout.addLayout(cmd_layout) args_layout = QVBoxLayout() args_layout.addWidget(args_label) args_layout.addWidget(self.args_input) vlayout.addLayout(args_layout) conf_layout = QVBoxLayout() conf_layout.addWidget(conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) hlayout.addLayout(vlayout) hlayout.addLayout(conf_layout) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(bbox) self.setLayout(general_vlayout) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.lang_cb.currentIndexChanged.connect( self.lang_selection_changed) self.form_status(False) if language is not None: self.form_status(True) self.validate()
class ProgressView(QWidget): """ :type batch_manager: CalculationManager """ def __init__(self, parent, batch_manager): QWidget.__init__(self, parent) self.calculation_manager = batch_manager self.whole_progress = QProgressBar(self) self.whole_progress.setMinimum(0) self.whole_progress.setMaximum(1) self.whole_progress.setFormat("%v of %m") self.whole_progress.setTextVisible(True) self.part_progress = QProgressBar(self) self.part_progress.setMinimum(0) self.part_progress.setMaximum(1) self.part_progress.setFormat("%v of %m") self.whole_label = QLabel("All batch progress:", self) self.part_label = QLabel("Single batch progress:", self) self.logs = ExceptionList(self) self.logs.setToolTip("Logs") self.task_que = QListWidget() self.process_num_timer = QTimer() self.process_num_timer.setInterval(1000) self.process_num_timer.setSingleShot(True) self.process_num_timer.timeout.connect(self.change_number_of_workers) self.number_of_process = QSpinBox(self) self.number_of_process.setRange(1, multiprocessing.cpu_count()) self.number_of_process.setValue(1) self.number_of_process.setToolTip( "Number of process used in batch calculation") self.number_of_process.valueChanged.connect( self.process_num_timer_start) layout = QGridLayout() layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight) layout.addWidget(self.whole_progress, 0, 1, 1, 2) layout.addWidget(self.part_label, 1, 0, Qt.AlignRight) layout.addWidget(self.part_progress, 1, 1, 1, 2) lab = QLabel("Number of process:") lab.setToolTip("Number of process used in batch calculation") layout.addWidget(lab, 2, 0) layout.addWidget(self.number_of_process, 2, 1) layout.addWidget(self.logs, 3, 0, 1, 3) layout.addWidget(self.task_que, 0, 4, 0, 1) layout.setColumnMinimumWidth(2, 10) layout.setColumnStretch(2, 1) self.setLayout(layout) self.preview_timer = QTimer() self.preview_timer.setInterval(1000) self.preview_timer.timeout.connect(self.update_info) def new_task(self): self.whole_progress.setMaximum( self.calculation_manager.calculation_size) if not self.preview_timer.isActive(): self.update_info() self.preview_timer.start() def update_info(self): res = self.calculation_manager.get_results() for el in res.errors: if el[0]: QListWidgetItem(el[0], self.logs) ExceptionListItem(el[1], self.logs) if (state_store.report_errors and parsed_version.is_devrelease and not isinstance(el[1][0], SegmentationLimitException) and isinstance(el[1][1], tuple)): with sentry_sdk.push_scope() as scope: scope.set_tag("auto_report", "true") sentry_sdk.capture_event(el[1][1][0]) self.whole_progress.setValue(res.global_counter) working_search = True for i, (progress, total) in enumerate(res.jobs_status): if working_search and progress != total: self.part_progress.setMaximum(total) self.part_progress.setValue(progress) working_search = False if i < self.task_que.count(): item = self.task_que.item(i) item.setText("Task {} ({}/{})".format(i, progress, total)) else: self.task_que.addItem("Task {} ({}/{})".format( i, progress, total)) if not self.calculation_manager.has_work: print( "[ProgressView.update_info]", self.calculation_manager.has_work, self.calculation_manager.batch_manager.has_work, self.calculation_manager.writer.writing_finished(), ) self.part_progress.setValue(self.part_progress.maximum()) self.preview_timer.stop() logging.info("Progress stop") def process_num_timer_start(self): self.process_num_timer.start() def update_progress(self, total_progress, part_progress): self.whole_progress.setValue(total_progress) self.part_progress.setValue(part_progress) def set_total_size(self, size): self.whole_progress.setMaximum(size) def set_part_size(self, size): self.part_progress.setMaximum(size) def change_number_of_workers(self): self.calculation_manager.set_number_of_workers( self.number_of_process.value())
class LSPServerEditor(QDialog): DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 2084 DEFAULT_CMD = '' DEFAULT_ARGS = '' DEFAULT_CONFIGURATION = '{}' DEFAULT_EXTERNAL = False HOST_REGEX = re.compile(r'^\w+([.]\w+)*$') NON_EMPTY_REGEX = re.compile(r'^\S+$') JSON_VALID = _('JSON valid') JSON_INVALID = _('JSON invalid') def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) self.parent = parent self.external = external bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = bbox.button(QDialogButtonBox.Ok) self.button_cancel = bbox.button(QDialogButtonBox.Cancel) self.button_ok.setEnabled(False) description = _('To create a new configuration, ' 'you need to select a programming ' 'language, along with a executable ' 'name for the server to execute ' '(If the instance is local), ' 'and the host and port. Finally, ' 'you need to provide the ' 'arguments that the server accepts. ' 'The placeholders <tt>%(host)s</tt> and ' '<tt>%(port)s</tt> refer to the host ' 'and the port, respectively.') server_settings_description = QLabel(description) server_settings_description.setWordWrap(True) lang_label = QLabel(_('Language:')) self.lang_cb = QComboBox(self) self.lang_cb.setToolTip( _('Programming language provided ' 'by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.host_input.setToolTip( _('Name of the host that will provide ' 'access to the server')) self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) cmd_label = QLabel(_('Command to execute:')) self.cmd_input = QLineEdit(self) self.cmd_input.setToolTip( _('Command used to start the ' 'LSP server locally')) self.cmd_input.setText(cmd) if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) args_label = QLabel(_('Server arguments:')) self.args_input = QLineEdit(self) self.args_input.setToolTip( _('Additional arguments required to ' 'start the server')) self.args_input.setText(args) conf_label = QLabel(_('LSP Server Configurations:')) self.conf_input = CodeEditor(None) self.conf_input.textChanged.connect(self.validate) color_scheme = CONF.get('color_schemes', 'selected') self.conf_input.setup_editor(language='JSON', color_scheme=color_scheme, wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json') self.conf_input.setToolTip( _('Additional LSP server configurations ' 'set at runtime. JSON required')) conf_text = '{}' try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: pass self.conf_input.set_text(conf_text) self.json_label = QLabel(self.JSON_VALID, self) self.external_cb = QCheckBox(_('External server'), self) self.external_cb.setToolTip( _('Check if the server runs ' 'on a remote location')) self.external_cb.setChecked(external) self.external_cb.stateChanged.connect(self.set_local_options) hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(server_settings_description) vlayout = QVBoxLayout() lang_layout = QVBoxLayout() lang_layout.addWidget(lang_label) lang_layout.addWidget(self.lang_cb) # layout2 = QHBoxLayout() # layout2.addLayout(lang_layout) lang_layout.addWidget(self.external_cb) vlayout.addLayout(lang_layout) host_layout = QVBoxLayout() host_layout.addWidget(host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) vlayout.addLayout(conn_info_layout) cmd_layout = QVBoxLayout() cmd_layout.addWidget(cmd_label) cmd_layout.addWidget(self.cmd_input) vlayout.addLayout(cmd_layout) args_layout = QVBoxLayout() args_layout.addWidget(args_label) args_layout.addWidget(self.args_input) vlayout.addLayout(args_layout) conf_layout = QVBoxLayout() conf_layout.addWidget(conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) hlayout.addLayout(vlayout) hlayout.addLayout(conf_layout) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(bbox) self.setLayout(general_vlayout) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.lang_cb.currentIndexChanged.connect(self.lang_selection_changed) self.form_status(False) if language is not None: self.form_status(True) self.validate() @Slot() def validate(self): host_text = self.host_input.text() cmd_text = self.cmd_input.text() if not self.HOST_REGEX.match(host_text): self.button_ok.setEnabled(False) self.host_input.setStyleSheet("QLineEdit{border: 1px solid red;}") self.host_input.setToolTip('Hostname must be valid') return else: self.host_input.setStyleSheet( "QLineEdit{border: 1px solid green;}") self.host_input.setToolTip('Hostname is valid') self.button_ok.setEnabled(True) if not self.external: if not self.NON_EMPTY_REGEX.match(cmd_text): self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid red;}") self.cmd_input.setToolTip('Command must be non empty') return if find_program(cmd_text) is None: self.button_ok.setEnabled(False) self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid red;}") self.cmd_input.setToolTip('Program was not found ' 'on your system') return else: self.cmd_input.setStyleSheet( "QLineEdit{border: 1px solid green;}") self.cmd_input.setToolTip('Program was found on your system') self.button_ok.setEnabled(True) try: json.loads(self.conf_input.toPlainText()) try: self.json_label.setText(self.JSON_VALID) except: pass except (ValueError, json.decoder.JSONDecodeError): try: self.json_label.setText(self.JSON_INVALID) self.button_ok.setEnabled(False) except: pass def form_status(self, status): self.host_input.setEnabled(status) self.port_spinner.setEnabled(status) self.external_cb.setEnabled(status) self.cmd_input.setEnabled(status) self.args_input.setEnabled(status) self.conf_input.setEnabled(status) self.json_label.setVisible(status) @Slot() def lang_selection_changed(self): idx = self.lang_cb.currentIndex() if idx == 0: self.set_defaults() self.form_status(False) self.button_ok.setEnabled(False) else: server = self.parent.get_server_by_lang(LSP_LANGUAGES[idx - 1]) self.form_status(True) if server is not None: self.host_input.setText(server.host) self.port_spinner.setValue(server.port) self.external_cb.setChecked(server.external) self.cmd_input.setText(server.cmd) self.args_input.setText(server.args) self.conf_input.set_text(json.dumps(server.configurations)) self.json_label.setText(self.JSON_VALID) self.button_ok.setEnabled(True) else: self.set_defaults() def set_defaults(self): self.cmd_input.setStyleSheet('') self.host_input.setStyleSheet('') self.host_input.setText(self.DEFAULT_HOST) self.port_spinner.setValue(self.DEFAULT_PORT) self.external_cb.setChecked(self.DEFAULT_EXTERNAL) self.cmd_input.setText(self.DEFAULT_CMD) self.args_input.setText(self.DEFAULT_ARGS) self.conf_input.set_text(self.DEFAULT_CONFIGURATION) self.json_label.setText(self.JSON_VALID) @Slot(bool) @Slot(int) def set_local_options(self, enabled): self.external = enabled self.cmd_input.setEnabled(True) self.args_input.setEnabled(True) if enabled: self.cmd_input.setEnabled(False) self.cmd_input.setStyleSheet('') self.args_input.setEnabled(False) try: self.validate() except: pass def get_options(self): language_idx = self.lang_cb.currentIndex() language = LSP_LANGUAGES[language_idx - 1] host = self.host_input.text() port = int(self.port_spinner.value()) external = self.external_cb.isChecked() args = self.args_input.text() cmd = self.cmd_input.text() configurations = json.loads(self.conf_input.toPlainText()) server = LSPServer(language=language.lower(), cmd=cmd, args=args, host=host, port=port, external=external, configurations=configurations) return server
class QuantityArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension self.unit = None def setup_and_check(self, data: _Quantity, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data self.data.flags.writeable = True is_record_array = data.m.dtype.names is not None is_masked_array = isinstance(data.m, np.ma.MaskedArray) if data.m.ndim > 3: self.error("Arrays with more than 3 dimensions are not supported") return False if xlabels is not None and len(xlabels) != self.data.m.shape[1]: self.error("The 'xlabels' argument length do no match array column number") return False if ylabels is not None and len(ylabels) != self.data.m.shape[0]: self.error("The 'ylabels' argument length do no match array row number") return False if not is_record_array: dtn = data.m.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') and not dtn.startswith('unicode'): arr = "%s arrays" % data.m.dtype.name self.error("%s are currently not supported" % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) # self.setWindowIcon(ima.icon('arredit')) if title: title = str(title) + " - " + "NumPy array" + str(data.u) else: title = "Array editor" if readonly: title += ' (' + 'read only' + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(QuantityArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(QuantityArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(QuantityArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(QuantityArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(QuantityArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) self.unitLabel = QLabel() self.unitLabel.setText(f'Unit: {data.u}') self.dimensionLabel = QLabel() self.dimensionLabel.setText(f'Dimension: {data.dimensionality}') self.layout.addWidget(self.unitLabel) self.layout.addWidget(self.dimensionLabel) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.m.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel("Record array fields:")) names = [] for name in data.m.dtype.names: field = data.m.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = ['Masked data', 'Data', 'Mask'] if data.m.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel("Axis:") btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel("Index:") btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel("<u>Warning</u>: changes are applied separately") label.setToolTip("For performance reasons, changes applied " \ "to masked array won't be reflected in " \ "array's data (and vice-versa).") btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':'] * 3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.m.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)] * 3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index == None: stack_index = self.stack.count() self.stack.addWidget(QuantityArrayEditor(self, self.data.m[slice_index])) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i'] * 3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText(('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index] - 1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- 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 return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, "Array editor", message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) self.parent = parent self.external = external bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = bbox.button(QDialogButtonBox.Ok) self.button_cancel = bbox.button(QDialogButtonBox.Cancel) self.button_ok.setEnabled(False) description = _('To create a new configuration, ' 'you need to select a programming ' 'language, along with a executable ' 'name for the server to execute ' '(If the instance is local), ' 'and the host and port. Finally, ' 'you need to provide the ' 'arguments that the server accepts. ' 'The placeholders <tt>%(host)s</tt> and ' '<tt>%(port)s</tt> refer to the host ' 'and the port, respectively.') server_settings_description = QLabel(description) server_settings_description.setWordWrap(True) lang_label = QLabel(_('Language:')) self.lang_cb = QComboBox(self) self.lang_cb.setToolTip( _('Programming language provided ' 'by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.host_input.setToolTip( _('Name of the host that will provide ' 'access to the server')) self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) cmd_label = QLabel(_('Command to execute:')) self.cmd_input = QLineEdit(self) self.cmd_input.setToolTip( _('Command used to start the ' 'LSP server locally')) self.cmd_input.setText(cmd) if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) args_label = QLabel(_('Server arguments:')) self.args_input = QLineEdit(self) self.args_input.setToolTip( _('Additional arguments required to ' 'start the server')) self.args_input.setText(args) conf_label = QLabel(_('LSP Server Configurations:')) self.conf_input = CodeEditor(None) self.conf_input.textChanged.connect(self.validate) color_scheme = CONF.get('color_schemes', 'selected') self.conf_input.setup_editor(language='JSON', color_scheme=color_scheme, wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json') self.conf_input.setToolTip( _('Additional LSP server configurations ' 'set at runtime. JSON required')) conf_text = '{}' try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: pass self.conf_input.set_text(conf_text) self.json_label = QLabel(self.JSON_VALID, self) self.external_cb = QCheckBox(_('External server'), self) self.external_cb.setToolTip( _('Check if the server runs ' 'on a remote location')) self.external_cb.setChecked(external) self.external_cb.stateChanged.connect(self.set_local_options) hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(server_settings_description) vlayout = QVBoxLayout() lang_layout = QVBoxLayout() lang_layout.addWidget(lang_label) lang_layout.addWidget(self.lang_cb) # layout2 = QHBoxLayout() # layout2.addLayout(lang_layout) lang_layout.addWidget(self.external_cb) vlayout.addLayout(lang_layout) host_layout = QVBoxLayout() host_layout.addWidget(host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) vlayout.addLayout(conn_info_layout) cmd_layout = QVBoxLayout() cmd_layout.addWidget(cmd_label) cmd_layout.addWidget(self.cmd_input) vlayout.addLayout(cmd_layout) args_layout = QVBoxLayout() args_layout.addWidget(args_label) args_layout.addWidget(self.args_input) vlayout.addLayout(args_layout) conf_layout = QVBoxLayout() conf_layout.addWidget(conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) hlayout.addLayout(vlayout) hlayout.addLayout(conf_layout) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(bbox) self.setLayout(general_vlayout) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.lang_cb.currentIndexChanged.connect(self.lang_selection_changed) self.form_status(False) if language is not None: self.form_status(True) self.validate()
def create_widgets(self): """creates the display window""" # CORD2R #self.origin_label = QLabel("Origin:") #self.zaxis_label = QLabel("Z Axis:") #self.xz_plane_label = QLabel("XZ Plane:") # Z-Axis Projection self.p1_label = QLabel("Origin:") self.p3_label = QLabel("End:") self.p2_label = QLabel("XZ Plane:") self.zaxis_label = QLabel("Z Axis:") self.method_pulldown = QComboBox() for method in self.methods: self.method_pulldown.addItem(method) self.zaxis_method_pulldown = QComboBox() for method in self.zaxis_methods: self.zaxis_method_pulldown.addItem(method) self.cid_label = QLabel("Coordinate System:") self.p1_cid_pulldown = QComboBox() self.p2_cid_pulldown = QComboBox() self.p3_cid_pulldown = QComboBox() self.zaxis_cid_pulldown = QComboBox() cid_global_str = '0/Global' for cid in sorted(self.cids): if cid == 0: cid_str = cid_global_str else: cid_str = str(cid) #print('cid_str = %r' % cid_str) self.p1_cid_pulldown.addItem(cid_str) self.p2_cid_pulldown.addItem(cid_str) self.p3_cid_pulldown.addItem(cid_str) self.zaxis_cid_pulldown.addItem(cid_str) self.p1_cid_pulldown.setCurrentIndex(0) self.p2_cid_pulldown.setCurrentIndex(0) self.p3_cid_pulldown.setCurrentIndex(0) self.zaxis_cid_pulldown.setCurrentIndex(0) if len(self.cids) == 1: self.p1_cid_pulldown.setEnabled(False) self.p2_cid_pulldown.setEnabled(False) self.p3_cid_pulldown.setEnabled(False) self.zaxis_cid_pulldown.setEnabled(False) #self.p1_cid_pulldown.setItemText(0, cid_str) #self.p2_cid_pulldown.setItemText(0, cid_str) #self.zaxis_cid_pulldown.setItemText(0, cid_str) self.p1_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P1') self.p2_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P2') self.p3_cid_pulldown.setToolTip( 'Defines the coordinate system for Point P3') self.zaxis_cid_pulldown.setToolTip( 'Defines the coordinate system for the Z Axis') self.p1_x_edit = QLineEdit('') self.p1_y_edit = QLineEdit('') self.p1_z_edit = QLineEdit('') self.p2_x_edit = QLineEdit('') self.p2_y_edit = QLineEdit('') self.p2_z_edit = QLineEdit('') self.p3_x_edit = QLineEdit('') self.p3_y_edit = QLineEdit('') self.p3_z_edit = QLineEdit('') self.zaxis_x_edit = QLineEdit('') self.zaxis_y_edit = QLineEdit('') self.zaxis_z_edit = QLineEdit('') self.additional_params_label = QLabel('Plane Parameters:') self.case_info_label = QLabel('Case Info:') self.p2_label = QLabel("XZ Plane:") # Plane Color self.plane_color_label = QLabel("Plane Color:") self.plane_color_edit = QPushButtonColor(self.plane_color_int) self.plane_opacity_label = QLabel("Plane Opacity:") self.plane_opacity_edit = QDoubleSpinBox() self.plane_opacity_edit.setRange(0.1, 1.0) self.plane_opacity_edit.setDecimals(1) self.plane_opacity_edit.setSingleStep(0.1) self.plane_opacity_edit.setValue(self.plane_opacity) self.flip_coord_label = QLabel("Flip Coordinate System:") self.flip_coord_checkbox = QCheckBox() #----------------------------------------------------------------------- self.time_label = QLabel('Time:') if self.gpforce is None: times = ['0.', '0.5', '1.', '1.5', '2.'] time = '0.' else: times = [str(time) for time in self.gpforce._times] time = times[0] self.times_pulldown = make_combo_box(times, time) self.time_label.setEnabled(False) self.times_pulldown.setEnabled(False) #self.node_label = QLabel('Nodes:') #self.node_edit = QNodeEdit(self.win_parent, self.model_name, parent=self.gui, #pick_style='area', tab_to_next=False) #self.element_label = QLabel('Elements:') #self.element_edit = QElementEdit(self.win_parent, self.model_name, parent=self.gui, #pick_style='area', tab_to_next=False) #self.node_element_label = QLabel('Nodes/Elements:') #self.node_element_edit = QLineEdit() #self.node_element_edit.setReadOnly(True) self.nplanes_label = QLabel('Num Planes:') self.nplanes_spinner = QSpinBox() self.nplanes_spinner.setMinimum(2) self.nplanes_spinner.setMaximum(500) self.nplanes_spinner.setValue(20) #----------------------------------------------------------------------- self.method_label = QLabel('Method:') self.plane_label = QLabel('Plane:') self.location_label = QLabel('Location:') self.zaxis_method_label = QLabel('Z-Axis Method:') self.cid_label = QLabel('Coordinate System:') self.x_label = QLabel('X') self.y_label = QLabel('Y') self.z_label = QLabel('Z') #self.location_label.setAlignment(Qt.AlignCenter) self.cid_label.setAlignment(Qt.AlignCenter) self.x_label.setAlignment(Qt.AlignCenter) self.y_label.setAlignment(Qt.AlignCenter) self.z_label.setAlignment(Qt.AlignCenter) self.export_checkbox = QCheckBox() self.csv_label = QLabel('CSV Filename:') self.csv_edit = QLineEdit() self.csv_button = QPushButton('Browse...') self.csv_label.setEnabled(False) self.csv_edit.setEnabled(False) self.csv_button.setEnabled(False) #----------------------------------------------------------------------- # nodes self.add_button = QPushButton('Add') self.remove_button = QPushButton('Remove') # elements self.add2_button = QPushButton('Add') self.remove2_button = QPushButton('Remove') #----------------------------------------------------------------------- # closing self.apply_button = QPushButton('Apply') self.cancel_button = QPushButton('Cancel') self.set_bold_font(self._default_font_size)
class CreatePlan(QWidget): plan_created = Signal() plan_node_changed = Signal() def __init__(self, settings: PartSettings): super().__init__() self.settings = settings self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} self.plan = PlanPreview(self) self.save_plan_btn = QPushButton("Save") self.clean_plan_btn = QPushButton("Remove all") self.remove_btn = QPushButton("Remove") self.update_element_chk = QCheckBox("Update element") self.change_root = EnumComboBox(RootType) self.save_choose = QComboBox() self.save_choose.addItem("<none>") self.save_choose.addItems(list(self.save_translate_dict.keys())) self.save_btn = QPushButton("Save") self.segment_profile = SearchableListWidget() self.pipeline_profile = SearchableListWidget() self.segment_stack = QTabWidget() self.segment_stack.addTab(self.segment_profile, "Profile") self.segment_stack.addTab(self.pipeline_profile, "Pipeline") self.generate_mask_btn = QPushButton("Add mask") self.generate_mask_btn.setToolTip("Mask need to have unique name") self.mask_name = QLineEdit() self.mask_operation = EnumComboBox(MaskOperation) self.chanel_num = QSpinBox() self.choose_channel_for_measurements = QComboBox() self.choose_channel_for_measurements.addItems( ["Same as segmentation"] + [str(x + 1) for x in range(MAX_CHANNEL_NUM)] ) self.units_choose = EnumComboBox(Units) self.units_choose.set_value(self.settings.get("units_value", Units.nm)) self.chanel_num.setRange(0, 10) self.expected_node_type = None self.save_constructor = None self.chose_profile_btn = QPushButton("Add Profile") self.get_big_btn = QPushButton("Leave the biggest") self.get_big_btn.hide() self.get_big_btn.setDisabled(True) self.measurements_list = SearchableListWidget(self) self.measurement_name_prefix = QLineEdit(self) self.add_calculation_btn = QPushButton("Add measurement calculation") self.information = QTextEdit() self.information.setReadOnly(True) self.protect = False self.mask_set = set() self.calculation_plan = CalculationPlan() self.plan.set_plan(self.calculation_plan) self.segmentation_mask = MaskWidget(settings) self.file_mask = FileMask() self.change_root.currentIndexChanged.connect(self.change_root_type) self.save_choose.currentTextChanged.connect(self.save_changed) self.measurements_list.currentTextChanged.connect(self.show_measurement) self.segment_profile.currentTextChanged.connect(self.show_segment) self.measurements_list.currentTextChanged.connect(self.show_measurement_info) self.segment_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment) self.mask_name.textChanged.connect(self.mask_name_changed) self.generate_mask_btn.clicked.connect(self.create_mask) self.clean_plan_btn.clicked.connect(self.clean_plan) self.remove_btn.clicked.connect(self.remove_element) self.mask_name.textChanged.connect(self.mask_text_changed) self.chose_profile_btn.clicked.connect(self.add_segmentation) self.get_big_btn.clicked.connect(self.add_leave_biggest) self.add_calculation_btn.clicked.connect(self.add_measurement) self.save_plan_btn.clicked.connect(self.add_calculation_plan) # self.forgot_mask_btn.clicked.connect(self.forgot_mask) # self.cmap_save_btn.clicked.connect(self.save_to_cmap) self.save_btn.clicked.connect(self.add_save_to_project) self.update_element_chk.stateChanged.connect(self.mask_text_changed) self.update_element_chk.stateChanged.connect(self.show_measurement) self.update_element_chk.stateChanged.connect(self.show_segment) self.update_element_chk.stateChanged.connect(self.update_names) self.segment_stack.currentChanged.connect(self.change_segmentation_table) plan_box = QGroupBox("Prepare workflow:") lay = QVBoxLayout() lay.addWidget(self.plan) bt_lay = QGridLayout() bt_lay.setSpacing(1) bt_lay.addWidget(self.save_plan_btn, 0, 0) bt_lay.addWidget(self.clean_plan_btn, 0, 1) bt_lay.addWidget(self.remove_btn, 1, 0) bt_lay.addWidget(self.update_element_chk, 1, 1) lay.addLayout(bt_lay) plan_box.setLayout(lay) plan_box.setStyleSheet(group_sheet) other_box = QGroupBox("Other operations:") other_box.setContentsMargins(0, 0, 0, 0) bt_lay = QVBoxLayout() bt_lay.setSpacing(0) bt_lay.addWidget(QLabel("Root type:")) bt_lay.addWidget(self.change_root) bt_lay.addStretch(1) bt_lay.addWidget(QLabel("Saving:")) bt_lay.addWidget(self.save_choose) bt_lay.addWidget(self.save_btn) other_box.setLayout(bt_lay) other_box.setStyleSheet(group_sheet) mask_box = QGroupBox("Use mask from:") mask_box.setStyleSheet(group_sheet) self.mask_stack = QTabWidget() self.mask_stack.addTab(stretch_widget(self.file_mask), "File") self.mask_stack.addTab(stretch_widget(self.segmentation_mask), "Current ROI") self.mask_stack.addTab(stretch_widget(self.mask_operation), "Operations on masks") self.mask_stack.setTabToolTip(2, "Allows to create mask which is based on masks previously added to plan.") lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.mask_stack, 0, 0, 1, 2) label = QLabel("Mask name:") label.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") self.mask_name.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") lay.addWidget(label, 1, 0) lay.addWidget(self.mask_name, 1, 1) lay.addWidget(self.generate_mask_btn, 2, 0, 1, 2) mask_box.setLayout(lay) segment_box = QGroupBox("ROI extraction:") segment_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.setSpacing(0) lay.addWidget(self.segment_stack) lay.addWidget(self.chose_profile_btn) lay.addWidget(self.get_big_btn) segment_box.setLayout(lay) measurement_box = QGroupBox("Set of measurements:") measurement_box.setStyleSheet(group_sheet) lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.measurements_list, 0, 0, 1, 2) lab = QLabel("Name prefix:") lab.setToolTip("Prefix added before each column name") lay.addWidget(lab, 1, 0) lay.addWidget(self.measurement_name_prefix, 1, 1) lay.addWidget(QLabel("Channel:"), 2, 0) lay.addWidget(self.choose_channel_for_measurements, 2, 1) lay.addWidget(QLabel("Units:"), 3, 0) lay.addWidget(self.units_choose, 3, 1) lay.addWidget(self.add_calculation_btn, 4, 0, 1, 2) measurement_box.setLayout(lay) info_box = QGroupBox("Information") info_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.addWidget(self.information) info_box.setLayout(lay) layout = QGridLayout() fst_col = QVBoxLayout() fst_col.addWidget(plan_box, 1) fst_col.addWidget(mask_box) layout.addWidget(plan_box, 0, 0, 5, 1) # layout.addWidget(plan_box, 0, 0, 3, 1) # layout.addWidget(mask_box, 3, 0, 2, 1) # layout.addWidget(segmentation_mask_box, 1, 1) layout.addWidget(mask_box, 0, 2, 1, 2) layout.addWidget(other_box, 0, 1) layout.addWidget(segment_box, 1, 1, 1, 2) layout.addWidget(measurement_box, 1, 3) layout.addWidget(info_box, 3, 1, 1, 3) self.setLayout(layout) self.generate_mask_btn.setDisabled(True) self.chose_profile_btn.setDisabled(True) self.add_calculation_btn.setDisabled(True) self.mask_allow = False self.segment_allow = False self.file_mask_allow = False self.node_type = NodeType.root self.node_name = "" self.plan_node_changed.connect(self.mask_text_changed) self.plan.changed_node.connect(self.node_type_changed) self.plan_node_changed.connect(self.show_segment) self.plan_node_changed.connect(self.show_measurement) self.plan_node_changed.connect(self.mask_stack_change) self.mask_stack.currentChanged.connect(self.mask_stack_change) self.file_mask.value_changed.connect(self.mask_stack_change) self.mask_name.textChanged.connect(self.mask_stack_change) self.node_type_changed() def change_root_type(self): value: RootType = self.change_root.get_value() self.calculation_plan.set_root_type(value) self.plan.update_view() def change_segmentation_table(self): index = self.segment_stack.currentIndex() text = self.segment_stack.tabText(index) if self.update_element_chk.isChecked(): self.chose_profile_btn.setText("Replace " + text) else: self.chose_profile_btn.setText("Add " + text) self.segment_profile.setCurrentItem(None) self.pipeline_profile.setCurrentItem(None) def save_changed(self, text): text = str(text) if text == "<none>": self.save_btn.setText("Save") self.save_btn.setToolTip("Choose file type") self.expected_node_type = None self.save_constructor = None else: save_class = self.save_translate_dict.get(text, None) if save_class is None: self.save_choose.setCurrentText("<none>") return self.save_btn.setText(f"Save to {save_class.get_short_name()}") self.save_btn.setToolTip("Choose mask create in plan view") if save_class.need_mask(): self.expected_node_type = NodeType.mask elif save_class.need_segmentation(): self.expected_node_type = NodeType.segment else: self.expected_node_type = NodeType.root self.save_constructor = Save self.save_activate() def save_activate(self): self.save_btn.setDisabled(True) if self.node_type == self.expected_node_type: self.save_btn.setEnabled(True) return def segmentation_from_project(self): self.calculation_plan.add_step(Operations.reset_to_base) self.plan.update_view() def update_names(self): if self.update_element_chk.isChecked(): self.chose_profile_btn.setText("Replace Profile") self.add_calculation_btn.setText("Replace set of measurements") self.generate_mask_btn.setText("Replace mask") else: self.chose_profile_btn.setText("Add Profile") self.add_calculation_btn.setText("Add set of measurements") self.generate_mask_btn.setText("Generate mask") def node_type_changed(self): # self.cmap_save_btn.setDisabled(True) self.save_btn.setDisabled(True) self.node_name = "" if self.plan.currentItem() is None: self.mask_allow = False self.file_mask_allow = False self.segment_allow = False self.remove_btn.setDisabled(True) self.plan_node_changed.emit() logging.debug("[node_type_changed] return") return node_type = self.calculation_plan.get_node_type() self.node_type = node_type if node_type in [NodeType.file_mask, NodeType.mask, NodeType.segment, NodeType.measurement, NodeType.save]: self.remove_btn.setEnabled(True) else: self.remove_btn.setEnabled(False) if node_type in (NodeType.mask, NodeType.file_mask): self.mask_allow = False self.segment_allow = True self.file_mask_allow = False self.node_name = self.calculation_plan.get_node().operation.name elif node_type == NodeType.segment: self.mask_allow = True self.segment_allow = False self.file_mask_allow = False self.save_btn.setEnabled(True) # self.cmap_save_btn.setEnabled(True) elif node_type == NodeType.root: self.mask_allow = False self.segment_allow = True self.file_mask_allow = True elif node_type in (NodeType.none, NodeType.measurement, NodeType.save): self.mask_allow = False self.segment_allow = False self.file_mask_allow = False self.save_activate() self.plan_node_changed.emit() def add_save_to_project(self): save_class = self.save_translate_dict.get(self.save_choose.currentText(), None) if save_class is None: QMessageBox.warning(self, "Save problem", "Not found save class") dial = FormDialog( [AlgorithmProperty("suffix", "File suffix", ""), AlgorithmProperty("directory", "Sub directory", "")] + save_class.get_fields() ) if dial.exec(): values = dial.get_values() suffix = values["suffix"] directory = values["directory"] del values["suffix"] del values["directory"] save_elem = Save(suffix, directory, save_class.get_name(), save_class.get_short_name(), values) if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(save_elem) else: self.calculation_plan.add_step(save_elem) self.plan.update_view() def create_mask(self): text = str(self.mask_name.text()).strip() if text != "" and text in self.mask_set: QMessageBox.warning(self, "Already exists", "Mask with this name already exists", QMessageBox.Ok) return if _check_widget(self.mask_stack, EnumComboBox): # existing mask mask_dialog = TwoMaskDialog if self.mask_operation.get_value() == MaskOperation.mask_intersection: # Mask intersection MaskConstruct = MaskIntersection else: MaskConstruct = MaskSum dial = mask_dialog(self.mask_set) if not dial.exec(): return names = dial.get_result() mask_ob = MaskConstruct(text, *names) elif _check_widget(self.mask_stack, MaskWidget): mask_ob = MaskCreate(text, self.segmentation_mask.get_mask_property()) elif _check_widget(self.mask_stack, FileMask): mask_ob = self.file_mask.get_value(text) else: raise ValueError("Unknowsn widget") if self.update_element_chk.isChecked(): node = self.calculation_plan.get_node() name = node.operation.name if name in self.calculation_plan.get_reused_mask() and name != text: QMessageBox.warning( self, "Cannot remove", f"Cannot remove mask '{name}' from plan because it is used in other elements" ) return self.mask_set.remove(name) self.mask_set.add(mask_ob.name) self.calculation_plan.replace_step(mask_ob) else: self.mask_set.add(mask_ob.name) self.calculation_plan.add_step(mask_ob) self.plan.update_view() self.mask_text_changed() def mask_stack_change(self): node_type = self.calculation_plan.get_node_type() if self.update_element_chk.isChecked() and node_type not in [NodeType.mask, NodeType.file_mask]: self.generate_mask_btn.setDisabled(True) text = self.mask_name.text() update = self.update_element_chk.isChecked() if self.node_type == NodeType.none: self.generate_mask_btn.setDisabled(True) return operation = self.calculation_plan.get_node().operation if ( not update and isinstance(operation, (MaskMapper, MaskBase)) and self.calculation_plan.get_node().operation.name == text ): self.generate_mask_btn.setDisabled(True) return if _check_widget(self.mask_stack, EnumComboBox): # reuse mask if len(self.mask_set) > 1 and ( (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask) ): self.generate_mask_btn.setEnabled(True) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Need at least two named mask and root selected") elif _check_widget(self.mask_stack, MaskWidget): # mask from segmentation if (not update and node_type == NodeType.segment) or (update and node_type == NodeType.mask): self.generate_mask_btn.setEnabled(True) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Select segmentation") else: if (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask): self.generate_mask_btn.setEnabled(self.file_mask.is_valid()) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Need root selected") def mask_name_changed(self, text): if str(text) in self.mask_set: self.generate_mask_btn.setDisabled(True) else: self.generate_mask_btn.setDisabled(False) def add_leave_biggest(self): profile = self.calculation_plan.get_node().operation profile.leave_biggest_swap() self.calculation_plan.replace_step(profile) self.plan.update_view() def add_segmentation(self): if self.segment_stack.currentIndex() == 0: text = str(self.segment_profile.currentItem().text()) if text not in self.settings.segmentation_profiles: self.refresh_all_profiles() return profile = self.settings.segmentation_profiles[text] if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(profile) else: self.calculation_plan.add_step(profile) self.plan.update_view() else: # self.segment_stack.currentIndex() == 1 text = self.pipeline_profile.currentItem().text() segmentation_pipeline = self.settings.segmentation_pipelines[text] pos = self.calculation_plan.current_pos[:] old_pos = self.calculation_plan.current_pos[:] for el in segmentation_pipeline.mask_history: self.calculation_plan.add_step(el.segmentation) self.plan.update_view() node = self.calculation_plan.get_node(pos) pos.append(len(node.children) - 1) self.calculation_plan.set_position(pos) self.calculation_plan.add_step(MaskCreate("", el.mask_property)) self.plan.update_view() pos.append(0) self.calculation_plan.set_position(pos) self.calculation_plan.add_step(segmentation_pipeline.segmentation) self.calculation_plan.set_position(old_pos) self.plan.update_view() def add_measurement(self): text = str(self.measurements_list.currentItem().text()) measurement_copy = deepcopy(self.settings.measurement_profiles[text]) prefix = str(self.measurement_name_prefix.text()).strip() channel = self.choose_channel_for_measurements.currentIndex() - 1 measurement_copy.name_prefix = prefix # noinspection PyTypeChecker measurement_calculate = MeasurementCalculate( channel=channel, measurement_profile=measurement_copy, name_prefix=prefix, units=self.units_choose.get_value(), ) if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(measurement_calculate) else: self.calculation_plan.add_step(measurement_calculate) self.plan.update_view() def remove_element(self): conflict_mask, used_mask = self.calculation_plan.get_file_mask_names() if len(conflict_mask) > 0: logging.info("Mask in use") QMessageBox.warning(self, "In use", "Masks {} are used in other places".format(", ".join(conflict_mask))) return self.mask_set -= used_mask self.calculation_plan.remove_step() self.plan.update_view() def clean_plan(self): self.calculation_plan = CalculationPlan() self.plan.set_plan(self.calculation_plan) self.node_type_changed() self.mask_set = set() def mask_text_changed(self): name = str(self.mask_name.text()).strip() self.generate_mask_btn.setDisabled(True) # load mask from file if not self.update_element_chk.isChecked(): # generate mask from segmentation if self.mask_allow and (name == "" or name not in self.mask_set): self.generate_mask_btn.setEnabled(True) else: if self.node_type != NodeType.file_mask and self.node_type != NodeType.mask: return # generate mask from segmentation if self.node_type == NodeType.mask and (name == "" or name == self.node_name or name not in self.mask_set): self.generate_mask_btn.setEnabled(True) def add_calculation_plan(self, text=None): if text is None or isinstance(text, bool): text, ok = QInputDialog.getText(self, "Plan title", "Set plan title") else: text, ok = QInputDialog.getText( self, "Plan title", f"Set plan title. Previous ({text}) is already in use", text=text ) text = text.strip() if ok: if text == "": QMessageBox.information( self, "Name cannot be empty", "Name cannot be empty, Please set correct name", QMessageBox.Ok ) self.add_calculation_plan() return if text in self.settings.batch_plans: res = QMessageBox.information( self, "Name already in use", "Name already in use. Would like to overwrite?", QMessageBox.Yes | QMessageBox.No, ) if res == QMessageBox.No: self.add_calculation_plan(text) return plan = copy(self.calculation_plan) plan.set_name(text) self.settings.batch_plans[text] = plan self.settings.dump() self.plan_created.emit() @staticmethod def get_index(item: QListWidgetItem, new_values: typing.List[str]) -> int: if item is None: return -1 text = item.text() try: return new_values.index(text) except ValueError: return -1 @staticmethod def refresh_profiles(list_widget: QListWidget, new_values: typing.List[str], index: int): list_widget.clear() list_widget.addItems(new_values) if index != -1: list_widget.setCurrentRow(index) def showEvent(self, _event): self.refresh_all_profiles() def refresh_all_profiles(self): new_measurements = list(sorted(self.settings.measurement_profiles.keys())) new_segment = list(sorted(self.settings.segmentation_profiles.keys())) new_pipelines = list(sorted(self.settings.segmentation_pipelines.keys())) measurement_index = self.get_index(self.measurements_list.currentItem(), new_measurements) segment_index = self.get_index(self.segment_profile.currentItem(), new_segment) pipeline_index = self.get_index(self.pipeline_profile.currentItem(), new_pipelines) self.protect = True self.refresh_profiles(self.measurements_list, new_measurements, measurement_index) self.refresh_profiles(self.segment_profile, new_segment, segment_index) self.refresh_profiles(self.pipeline_profile, new_pipelines, pipeline_index) self.protect = False def show_measurement_info(self, text=None): if self.protect: return if text is None: if self.measurements_list.currentItem() is not None: text = str(self.measurements_list.currentItem().text()) else: return profile = self.settings.measurement_profiles[text] self.information.setText(str(profile)) def show_measurement(self): if self.update_element_chk.isChecked(): if self.node_type == NodeType.measurement: self.add_calculation_btn.setEnabled(True) else: self.add_calculation_btn.setDisabled(True) else: if self.measurements_list.currentItem() is not None: self.add_calculation_btn.setEnabled(self.mask_allow) else: self.add_calculation_btn.setDisabled(True) def show_segment_info(self, text=None): if self.protect: return if text == "": return if self.segment_stack.currentIndex() == 0: if text is None: if self.segment_profile.currentItem() is not None: text = str(self.segment_profile.currentItem().text()) else: return profile = self.settings.segmentation_profiles[text] else: if text is None: if self.pipeline_profile.currentItem() is not None: text = str(self.pipeline_profile.currentItem().text()) else: return profile = self.settings.segmentation_pipelines[text] self.information.setText(profile.pretty_print(analysis_algorithm_dict)) def show_segment(self): if self.update_element_chk.isChecked() and self.segment_stack.currentIndex() == 0: self.get_big_btn.setDisabled(True) if self.node_type == NodeType.segment: self.chose_profile_btn.setEnabled(True) else: self.chose_profile_btn.setDisabled(True) else: if self.node_type == NodeType.segment: self.get_big_btn.setEnabled(True) else: self.get_big_btn.setDisabled(True) if ( self.segment_stack.currentIndex() == 0 and self.segment_profile.currentItem() ) or self.pipeline_profile.currentItem() is not None: self.chose_profile_btn.setEnabled(self.segment_allow) else: self.chose_profile_btn.setDisabled(True) def edit_plan(self): plan = self.sender().plan_to_edit # type: CalculationPlan self.calculation_plan = copy(plan) self.plan.set_plan(self.calculation_plan) self.mask_set.clear() self.calculation_plan.set_position([]) self.mask_set.update(self.calculation_plan.get_mask_names())
def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton(self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton(self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton(self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton(self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut( 'plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton(self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut( 'plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) self.zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) self.zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.zoom_out_btn) layout.addWidget(self.zoom_in_btn) layout.addWidget(self.zoom_disp) return [ savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan ]
def __init__(self, settings: PartSettings): super().__init__() self.settings = settings self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} self.plan = PlanPreview(self) self.save_plan_btn = QPushButton("Save") self.clean_plan_btn = QPushButton("Remove all") self.remove_btn = QPushButton("Remove") self.update_element_chk = QCheckBox("Update element") self.change_root = EnumComboBox(RootType) self.save_choose = QComboBox() self.save_choose.addItem("<none>") self.save_choose.addItems(list(self.save_translate_dict.keys())) self.save_btn = QPushButton("Save") self.segment_profile = SearchableListWidget() self.pipeline_profile = SearchableListWidget() self.segment_stack = QTabWidget() self.segment_stack.addTab(self.segment_profile, "Profile") self.segment_stack.addTab(self.pipeline_profile, "Pipeline") self.generate_mask_btn = QPushButton("Add mask") self.generate_mask_btn.setToolTip("Mask need to have unique name") self.mask_name = QLineEdit() self.mask_operation = EnumComboBox(MaskOperation) self.chanel_num = QSpinBox() self.choose_channel_for_measurements = QComboBox() self.choose_channel_for_measurements.addItems( ["Same as segmentation"] + [str(x + 1) for x in range(MAX_CHANNEL_NUM)] ) self.units_choose = EnumComboBox(Units) self.units_choose.set_value(self.settings.get("units_value", Units.nm)) self.chanel_num.setRange(0, 10) self.expected_node_type = None self.save_constructor = None self.chose_profile_btn = QPushButton("Add Profile") self.get_big_btn = QPushButton("Leave the biggest") self.get_big_btn.hide() self.get_big_btn.setDisabled(True) self.measurements_list = SearchableListWidget(self) self.measurement_name_prefix = QLineEdit(self) self.add_calculation_btn = QPushButton("Add measurement calculation") self.information = QTextEdit() self.information.setReadOnly(True) self.protect = False self.mask_set = set() self.calculation_plan = CalculationPlan() self.plan.set_plan(self.calculation_plan) self.segmentation_mask = MaskWidget(settings) self.file_mask = FileMask() self.change_root.currentIndexChanged.connect(self.change_root_type) self.save_choose.currentTextChanged.connect(self.save_changed) self.measurements_list.currentTextChanged.connect(self.show_measurement) self.segment_profile.currentTextChanged.connect(self.show_segment) self.measurements_list.currentTextChanged.connect(self.show_measurement_info) self.segment_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment) self.mask_name.textChanged.connect(self.mask_name_changed) self.generate_mask_btn.clicked.connect(self.create_mask) self.clean_plan_btn.clicked.connect(self.clean_plan) self.remove_btn.clicked.connect(self.remove_element) self.mask_name.textChanged.connect(self.mask_text_changed) self.chose_profile_btn.clicked.connect(self.add_segmentation) self.get_big_btn.clicked.connect(self.add_leave_biggest) self.add_calculation_btn.clicked.connect(self.add_measurement) self.save_plan_btn.clicked.connect(self.add_calculation_plan) # self.forgot_mask_btn.clicked.connect(self.forgot_mask) # self.cmap_save_btn.clicked.connect(self.save_to_cmap) self.save_btn.clicked.connect(self.add_save_to_project) self.update_element_chk.stateChanged.connect(self.mask_text_changed) self.update_element_chk.stateChanged.connect(self.show_measurement) self.update_element_chk.stateChanged.connect(self.show_segment) self.update_element_chk.stateChanged.connect(self.update_names) self.segment_stack.currentChanged.connect(self.change_segmentation_table) plan_box = QGroupBox("Prepare workflow:") lay = QVBoxLayout() lay.addWidget(self.plan) bt_lay = QGridLayout() bt_lay.setSpacing(1) bt_lay.addWidget(self.save_plan_btn, 0, 0) bt_lay.addWidget(self.clean_plan_btn, 0, 1) bt_lay.addWidget(self.remove_btn, 1, 0) bt_lay.addWidget(self.update_element_chk, 1, 1) lay.addLayout(bt_lay) plan_box.setLayout(lay) plan_box.setStyleSheet(group_sheet) other_box = QGroupBox("Other operations:") other_box.setContentsMargins(0, 0, 0, 0) bt_lay = QVBoxLayout() bt_lay.setSpacing(0) bt_lay.addWidget(QLabel("Root type:")) bt_lay.addWidget(self.change_root) bt_lay.addStretch(1) bt_lay.addWidget(QLabel("Saving:")) bt_lay.addWidget(self.save_choose) bt_lay.addWidget(self.save_btn) other_box.setLayout(bt_lay) other_box.setStyleSheet(group_sheet) mask_box = QGroupBox("Use mask from:") mask_box.setStyleSheet(group_sheet) self.mask_stack = QTabWidget() self.mask_stack.addTab(stretch_widget(self.file_mask), "File") self.mask_stack.addTab(stretch_widget(self.segmentation_mask), "Current ROI") self.mask_stack.addTab(stretch_widget(self.mask_operation), "Operations on masks") self.mask_stack.setTabToolTip(2, "Allows to create mask which is based on masks previously added to plan.") lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.mask_stack, 0, 0, 1, 2) label = QLabel("Mask name:") label.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") self.mask_name.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") lay.addWidget(label, 1, 0) lay.addWidget(self.mask_name, 1, 1) lay.addWidget(self.generate_mask_btn, 2, 0, 1, 2) mask_box.setLayout(lay) segment_box = QGroupBox("ROI extraction:") segment_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.setSpacing(0) lay.addWidget(self.segment_stack) lay.addWidget(self.chose_profile_btn) lay.addWidget(self.get_big_btn) segment_box.setLayout(lay) measurement_box = QGroupBox("Set of measurements:") measurement_box.setStyleSheet(group_sheet) lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.measurements_list, 0, 0, 1, 2) lab = QLabel("Name prefix:") lab.setToolTip("Prefix added before each column name") lay.addWidget(lab, 1, 0) lay.addWidget(self.measurement_name_prefix, 1, 1) lay.addWidget(QLabel("Channel:"), 2, 0) lay.addWidget(self.choose_channel_for_measurements, 2, 1) lay.addWidget(QLabel("Units:"), 3, 0) lay.addWidget(self.units_choose, 3, 1) lay.addWidget(self.add_calculation_btn, 4, 0, 1, 2) measurement_box.setLayout(lay) info_box = QGroupBox("Information") info_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.addWidget(self.information) info_box.setLayout(lay) layout = QGridLayout() fst_col = QVBoxLayout() fst_col.addWidget(plan_box, 1) fst_col.addWidget(mask_box) layout.addWidget(plan_box, 0, 0, 5, 1) # layout.addWidget(plan_box, 0, 0, 3, 1) # layout.addWidget(mask_box, 3, 0, 2, 1) # layout.addWidget(segmentation_mask_box, 1, 1) layout.addWidget(mask_box, 0, 2, 1, 2) layout.addWidget(other_box, 0, 1) layout.addWidget(segment_box, 1, 1, 1, 2) layout.addWidget(measurement_box, 1, 3) layout.addWidget(info_box, 3, 1, 1, 3) self.setLayout(layout) self.generate_mask_btn.setDisabled(True) self.chose_profile_btn.setDisabled(True) self.add_calculation_btn.setDisabled(True) self.mask_allow = False self.segment_allow = False self.file_mask_allow = False self.node_type = NodeType.root self.node_name = "" self.plan_node_changed.connect(self.mask_text_changed) self.plan.changed_node.connect(self.node_type_changed) self.plan_node_changed.connect(self.show_segment) self.plan_node_changed.connect(self.show_measurement) self.plan_node_changed.connect(self.mask_stack_change) self.mask_stack.currentChanged.connect(self.mask_stack_change) self.file_mask.value_changed.connect(self.mask_stack_change) self.mask_name.textChanged.connect(self.mask_stack_change) self.node_type_changed()
def __init__(self, parent, language=None, cmd='', host='127.0.0.1', port=2084, args='', external=False, stdio=False, configurations={}, **kwargs): super(LSPServerEditor, self).__init__(parent) description = _( "To create a new server configuration, you need to select a " "programming language, set the command to start its associated " "server and enter any arguments that should be passed to it on " "startup. Additionally, you can set the server's hostname and " "port if connecting to an external server, " "or to a local one using TCP instead of stdio pipes." "<br><br>" "<i>Note</i>: You can use the placeholders <tt>{host}</tt> and " "<tt>{port}</tt> in the server arguments field to automatically " "fill in the respective values.<br>" ) self.parent = parent self.external = external # Widgets self.server_settings_description = QLabel(description) self.lang_cb = QComboBox(self) self.external_cb = QCheckBox(_('External server'), self) self.host_label = QLabel(_('Host:')) self.host_input = QLineEdit(self) self.port_label = QLabel(_('Port:')) self.port_spinner = QSpinBox(self) self.cmd_label = QLabel(_('Command:')) self.cmd_input = QLineEdit(self) self.args_label = QLabel(_('Arguments:')) self.args_input = QLineEdit(self) self.json_label = QLabel(self.JSON_VALID, self) self.conf_label = QLabel(_('<b>Server Configuration:</b>')) self.conf_input = CodeEditor(None) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) self.button_cancel = self.bbox.button(QDialogButtonBox.Cancel) # Widget setup self.setMinimumSize(self.MIN_SIZE) self.setWindowTitle(_('LSP server editor')) self.server_settings_description.setWordWrap(True) self.lang_cb.setToolTip( _('Programming language provided by the LSP server')) self.lang_cb.addItem(_('Select a language')) self.lang_cb.addItems(LSP_LANGUAGES) self.button_ok.setEnabled(False) if language is not None: idx = LSP_LANGUAGES.index(language) self.lang_cb.setCurrentIndex(idx + 1) self.button_ok.setEnabled(True) self.host_input.setPlaceholderText('127.0.0.1') self.host_input.setText(host) self.host_input.textChanged.connect(lambda x: self.validate()) self.port_spinner.setToolTip(_('TCP port number of the server')) self.port_spinner.setMinimum(1) self.port_spinner.setMaximum(60000) self.port_spinner.setValue(port) self.cmd_input.setText(cmd) self.cmd_input.setPlaceholderText('/absolute/path/to/command') self.args_input.setToolTip( _('Additional arguments required to start the server')) self.args_input.setText(args) self.args_input.setPlaceholderText(r'--host {host} --port {port}') self.conf_input.setup_editor( language='json', color_scheme=CONF.get('appearance', 'selected'), wrap=False, edge_line=True, highlight_current_line=True, highlight_current_cell=True, occurrence_highlighting=True, auto_unindent=True, font=get_font(), filename='config.json', folding=False ) self.conf_input.set_language('json', 'config.json') self.conf_input.setToolTip(_('Additional LSP server configuration ' 'set at runtime. JSON required')) try: conf_text = json.dumps(configurations, indent=4, sort_keys=True) except Exception: conf_text = '{}' self.conf_input.set_text(conf_text) self.external_cb.setToolTip( _('Check if the server runs on a remote location')) self.external_cb.setChecked(external) self.stdio_cb = QCheckBox(_('Use stdio pipes for communication'), self) self.stdio_cb.setToolTip(_('Check if the server communicates ' 'using stdin/out pipes')) self.stdio_cb.setChecked(stdio) # Layout setup hlayout = QHBoxLayout() general_vlayout = QVBoxLayout() general_vlayout.addWidget(self.server_settings_description) vlayout = QVBoxLayout() lang_group = QGroupBox(_('Language')) lang_layout = QVBoxLayout() lang_layout.addWidget(self.lang_cb) lang_group.setLayout(lang_layout) vlayout.addWidget(lang_group) server_group = QGroupBox(_('Language server')) server_layout = QGridLayout() server_layout.addWidget(self.cmd_label, 0, 0) server_layout.addWidget(self.cmd_input, 0, 1) server_layout.addWidget(self.args_label, 1, 0) server_layout.addWidget(self.args_input, 1, 1) server_group.setLayout(server_layout) vlayout.addWidget(server_group) address_group = QGroupBox(_('Server address')) host_layout = QVBoxLayout() host_layout.addWidget(self.host_label) host_layout.addWidget(self.host_input) port_layout = QVBoxLayout() port_layout.addWidget(self.port_label) port_layout.addWidget(self.port_spinner) conn_info_layout = QHBoxLayout() conn_info_layout.addLayout(host_layout) conn_info_layout.addLayout(port_layout) address_group.setLayout(conn_info_layout) vlayout.addWidget(address_group) advanced_group = QGroupBox(_('Advanced')) advanced_layout = QVBoxLayout() advanced_layout.addWidget(self.external_cb) advanced_layout.addWidget(self.stdio_cb) advanced_group.setLayout(advanced_layout) vlayout.addWidget(advanced_group) conf_layout = QVBoxLayout() conf_layout.addWidget(self.conf_label) conf_layout.addWidget(self.conf_input) conf_layout.addWidget(self.json_label) vlayout.addStretch() hlayout.addLayout(vlayout, 2) hlayout.addLayout(conf_layout, 3) general_vlayout.addLayout(hlayout) general_vlayout.addWidget(self.bbox) self.setLayout(general_vlayout) self.form_status(False) # Signals if not external: self.cmd_input.textChanged.connect(lambda x: self.validate()) self.external_cb.stateChanged.connect(self.set_local_options) self.stdio_cb.stateChanged.connect(self.set_stdio_options) self.lang_cb.currentIndexChanged.connect(self.lang_selection_changed) self.conf_input.textChanged.connect(self.validate) self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.reject) # Final setup if language is not None: self.form_status(True) self.validate() if stdio: self.set_stdio_options(True) if external: self.set_local_options(True)
class RotateLabelDlg(QDialog): rotateSelectedPolygon = QtCore.Signal(bool, int) def __init__(self, parent=None, rotation_connection=None, clockwise=True, angle=0): super(RotateLabelDlg, self).__init__(parent) self.setWindowTitle(self.tr("Rotate the selected polygon")) if rotation_connection is not None: self.rotateSelectedPolygon.connect(rotation_connection) self.clockwise = clockwise self.angle = angle self.setLayout(self.createLayout()) self.setFixedSize(400, 80) def resetRotateDlg(self, parent_topright): self.clockwise = True self.angle = 0 self.radio_clockwise.setChecked(self.clockwise) self.angle_editor.setValue(self.angle) self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) size = self.size() self.move(parent_topright.x() - size.width(), parent_topright.y()) def createLayout(self): hbox = QHBoxLayout() self.radio_clockwise = QRadioButton(self.tr("clockwise"), self) self.radio_clockwise.clockwise = True self.radio_clockwise.setChecked(self.clockwise) self.radio_clockwise.toggled.connect(self.rotationDirectionChanged) self.radio_anticlockwise = QRadioButton(self.tr("anticlockwise"), self) self.radio_anticlockwise.clockwise = False self.radio_anticlockwise.setChecked(not self.clockwise) self.radio_anticlockwise.toggled.connect(self.rotationDirectionChanged) self.angle_editor = QSpinBox(self) self.angle_editor.setButtonSymbols( QtWidgets.QAbstractSpinBox.NoButtons) self.angle_editor.setRange(0, 360) self.angle_editor.setSuffix(" °") self.angle_editor.setValue(self.angle) self.angle_editor.setToolTip("rotation angle") self.angle_editor.setStatusTip(self.toolTip()) self.angle_editor.setAlignment(QtCore.Qt.AlignCenter) self.angle_editor.valueChanged.connect(self.rotationAngleChanged) hbox.addWidget(self.radio_anticlockwise) hbox.addWidget(self.radio_clockwise) hbox.addWidget(self.angle_editor) self.rotate_bar = AngleBar(self, self.clockwise, self.angle, self.angleBarChanged) vbox = QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.rotate_bar) return vbox def rotationDirectionChanged(self, value): # radio button rbtn = self.sender() if rbtn.isChecked(): self.clockwise = rbtn.clockwise self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) self.appleRotateInfo() def rotationAngleChanged(self, value): # spinbox if value != self.angle: self.angle = value self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) self.appleRotateInfo() def angleBarChanged(self, clockwise, angle): if self.clockwise == clockwise and self.angle == angle: return self.clockwise = clockwise self.angle = angle self.angle_editor.setValue(self.angle) if self.clockwise and not self.radio_clockwise.isChecked(): self.radio_clockwise.setChecked(True) elif not self.clockwise and not self.radio_anticlockwise.isChecked(): self.radio_anticlockwise.setChecked(True) else: self.appleRotateInfo() def appleRotateInfo(self): self.rotateSelectedPolygon.emit(self.clockwise, self.angle)
class PlotsWidget(PluginMainWidget): DEFAULT_OPTIONS = { 'auto_fit_plotting': True, 'mute_inline_plotting': True, 'show_plot_outline': True, 'save_dir': getcwd_or_home() } # Signals sig_option_changed = Signal(str, object) sig_figure_loaded = Signal() """This signal is emitted when a figure is loaded succesfully""" sig_redirect_stdio_requested = Signal(bool) """ This signal is emitted to request the main application to redirect standard output/error when using Open/Save/Browse dialogs within widgets. Parameters ---------- redirect: bool Start redirect (True) or stop redirect (False). """ def __init__(self, name=None, plugin=None, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent, options) # Widgets self._stack = PlotsStackedWidget(parent=self) self._shellwidgets = {} self.zoom_disp = QSpinBox(self) self._right_clicked_thumbnail = None # Widget setup self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) # Layout layout = QHBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._stack) self.setLayout(layout) # Signals self._stack.sig_figure_loaded.connect(self.sig_figure_loaded) self._stack.sig_figure_menu_requested.connect(self.show_figure_menu) self._stack.sig_thumbnail_menu_requested.connect( self.show_thumbnail_menu) self._stack.sig_zoom_changed.connect(self.zoom_disp.setValue) self._stack.sig_figure_loaded.connect(self.update_actions) self._stack.sig_save_dir_changed.connect( lambda val: self.set_option('save_dir', val)) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Plots') def get_focus_widget(self): widget = self.current_widget() if widget and widget.thumbnails_sb.current_thumbnail is not None: if widget.figviewer.figcanvas.fig: widget = widget.thumbnails_sb.scrollarea return widget def setup(self, options): # Menu actions self.mute_action = self.create_action( name=PlotsWidgetActions.ToggleMuteInlinePlotting, text=_("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda val: self.set_option('mute_inline_plotting', val), initial=options['mute_inline_plotting'], ) self.outline_action = self.create_action( name=PlotsWidgetActions.ToggleShowPlotOutline, text=_("Show plot outline"), tip=_("Show the plot outline."), toggled=lambda val: self.set_option('show_plot_outline', val), initial=options['show_plot_outline'], ) self.fit_action = self.create_action( name=PlotsWidgetActions.ToggleAutoFitPlotting, text=_("Fit plots to window"), tip=_("Automatically fit plots to Plot pane size."), toggled=lambda val: self.set_option('auto_fit_plotting', val), initial=options['auto_fit_plotting'], ) # Toolbar actions save_action = self.create_action( name=PlotsWidgetActions.Save, text=_("Save plot as..."), icon=self.create_icon('filesave'), triggered=self.save_plot, register_shortcut=True, ) save_all_action = self.create_action( name=PlotsWidgetActions.SaveAll, text=_("Save all plots..."), icon=self.create_icon('save_all'), triggered=self.save_all_plots, register_shortcut=True, ) copy_action = self.create_action( name=PlotsWidgetActions.Copy, text=_("Copy Image"), icon=self.create_icon('editcopy'), triggered=self.copy_image, register_shortcut=True, ) remove_action = self.create_action( name=PlotsWidgetActions.Close, text=_("Remove plot"), icon=self.create_icon('editclear'), triggered=self.remove_plot, ) remove_all_action = self.create_action( name=PlotsWidgetActions.CloseAll, text=_("Remove all plots"), tip=_("Remove all plots"), icon=self.create_icon('filecloseall'), triggered=self.remove_all_plots, register_shortcut=True, ) previous_action = self.create_action( name=PlotsWidgetActions.MoveToPreviousFigure, text=_("Previous plot"), tip=_("Previous plot"), icon=self.create_icon('ArrowBack'), triggered=self.previous_plot, register_shortcut=True, ) next_action = self.create_action( name=PlotsWidgetActions.MoveToNextFigure, text=_("Next plot"), tip=_("Next plot"), icon=self.create_icon('ArrowForward'), triggered=self.next_plot, register_shortcut=True, ) zoom_in_action = self.create_action( name=PlotsWidgetActions.ZoomIn, text=_("Zoom in"), tip=_("Zoom in"), icon=self.create_icon('zoom_in'), triggered=self.zoom_in, register_shortcut=True, ) zoom_out_action = self.create_action( name=PlotsWidgetActions.ZoomOut, text=_("Zoom out"), tip=_("Zoom out"), icon=self.create_icon('zoom_out'), triggered=self.zoom_out, register_shortcut=True, ) # Options menu options_menu = self.get_options_menu() self.add_item_to_menu(self.mute_action, menu=options_menu) self.add_item_to_menu(self.outline_action, menu=options_menu) self.add_item_to_menu(self.fit_action, menu=options_menu) # Main toolbar main_toolbar = self.get_main_toolbar() for item in [ save_action, save_all_action, copy_action, remove_action, remove_all_action, previous_action, next_action, zoom_in_action, zoom_out_action, self.zoom_disp ]: self.add_item_to_toolbar( item, toolbar=main_toolbar, section=PlotsWidgetMainToolBarSections.Edit, ) # Context menu context_menu = self.create_menu(PluginMainWidgetMenus.Context) for item in [save_action, copy_action, remove_action]: self.add_item_to_menu(item, menu=context_menu) def update_actions(self): value = False widget = self.current_widget() figviewer = None if widget: figviewer = widget.figviewer thumbnails_sb = widget.thumbnails_sb value = figviewer.figcanvas.fig is not None for __, action in self.get_actions().items(): if action and action not in [ self.mute_action, self.outline_action, self.fit_action ]: action.setEnabled(value) # IMPORTANT: Since we are defining the main actions in here # and the context is WidgetWithChildrenShortcut we need to # assign the same actions to the children widgets in order # for shortcuts to work if figviewer: figviewer_actions = figviewer.actions() thumbnails_sb_actions = thumbnails_sb.actions() if action not in figviewer_actions: figviewer.addAction(action) if action not in thumbnails_sb_actions: thumbnails_sb.addAction(action) self.zoom_disp.setEnabled(value) # Disable zoom buttons if autofit if value: value = not self.get_option('auto_fit_plotting') self.get_action(PlotsWidgetActions.ZoomIn).setEnabled(value) self.get_action(PlotsWidgetActions.ZoomOut).setEnabled(value) self.zoom_disp.setEnabled(value) def on_option_update(self, option, value): for index in range(self.count()): widget = self._stack.widget(index) if widget: widget.setup({option: value}) self.update_actions() # --- Public API: # ------------------------------------------------------------------------ def set_current_widget(self, fig_browser): """ Set the current figure browser widget in the stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The widget to set. """ self._stack.setCurrentWidget(fig_browser) def current_widget(self): """ Return the current figure browser widget in the stack. Returns ------- spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The current widget. """ return self._stack.currentWidget() def count(self): """ Return the number of widgets in the stack. Returns ------- int The number of widgets in the stack. """ return self._stack.count() def remove_widget(self, fig_browser): """ Remove widget from stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The figure browser widget to remove. """ self._stack.removeWidget(fig_browser) def add_widget(self, fig_browser): """ Add widget to stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The figure browser widget to add. """ self._stack.addWidget(fig_browser) def add_shellwidget(self, shellwidget): """ Add a new shellwidget registered with the plots plugin. This function registers a new FigureBrowser for browsing the figures in the shell. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id not in self._shellwidgets: fig_browser = FigureBrowser(parent=self._stack, background_color=MAIN_BG_COLOR) fig_browser.set_shellwidget(shellwidget) fig_browser.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.add_widget(fig_browser) self._shellwidgets[shellwidget_id] = fig_browser self.set_shellwidget(shellwidget) return fig_browser def remove_shellwidget(self, shellwidget): """ Remove the shellwidget registered with the plots plugin. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id in self._shellwidgets: fig_browser = self._shellwidgets.pop(shellwidget_id) self.remove_widget(fig_browser) fig_browser.close() def set_shellwidget(self, shellwidget): """ Update the current shellwidget displayed with the plots plugin. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id in self._shellwidgets: fig_browser = self._shellwidgets[shellwidget_id] fig_browser.setup(self._options) self.set_current_widget(fig_browser) def show_figure_menu(self, qpoint): """ Show main figure menu and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = None widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def show_thumbnail_menu(self, qpoint, thumbnail): """ Show menu on a given `thumbnail` and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = thumbnail widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def save_plot(self): """ Save currently active plot or plot selected to be saved with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.save_current_figure_as() else: widget.thumbnails_sb.save_thumbnail_figure_as( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def save_all_plots(self): """Save all available plots.""" widget = self.current_widget() if widget: widget.thumbnails_sb.save_all_figures_as() def copy_image(self): """ Copy currently active plot or plot selected to be copied with context menu in the thumbnails scrollbar into the clipboard. """ widget = self.current_widget() if widget and widget.figviewer and widget.figviewer.figcanvas.fig: if self._right_clicked_thumbnail is None: widget.figviewer.figcanvas.copy_figure() else: self._right_clicked_thumbnail.canvas.copy_figure() # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def remove_plot(self): """ Remove currently active plot or plot selected to be removed with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.remove_current_thumbnail() else: widget.thumbnails_sb.remove_thumbnail( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None self.update_actions() def remove_all_plots(self): """Remove all available plots..""" widget = self.current_widget() if widget: widget.thumbnails_sb.remove_all_thumbnails() self.update_actions() def previous_plot(self): """Select the previous plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_previous_thumbnail() def next_plot(self): """Select the next plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_next_thumbnail() def zoom_in(self): """Perform a zoom in on the main figure.""" widget = self.current_widget() if widget: widget.zoom_in() def zoom_out(self): """Perform a zoom out on the main figure.""" widget = self.current_widget() if widget: widget.zoom_out()
class QtLabelsControls(QtLayerControls): """Qt view and controls for the napari Labels layer. Parameters ---------- layer : napari.layers.Labels An instance of a napari Labels layer. Attributes ---------- button_group : qtpy.QtWidgets.QButtonGroup Button group of labels layer modes: PAN_ZOOM, PICKER, PAINT, ERASE, or FILL. colormapUpdate : qtpy.QtWidgets.QPushButton Button to update colormap of label layer. contigCheckBox : qtpy.QtWidgets.QCheckBox Checkbox to control if label layer is contiguous. fill_button : qtpy.QtWidgets.QtModeRadioButton Button to select FILL mode on Labels layer. grid_layout : qtpy.QtWidgets.QGridLayout Layout of Qt widget controls for the layer. layer : napari.layers.Labels An instance of a napari Labels layer. ndimCheckBox : qtpy.QtWidgets.QCheckBox Checkbox to control if label layer is n-dimensional. paint_button : qtpy.QtWidgets.QtModeRadioButton Button to select PAINT mode on Labels layer. panzoom_button : qtpy.QtWidgets.QtModeRadioButton Button to select PAN_ZOOM mode on Labels layer. pick_button : qtpy.QtWidgets.QtModeRadioButton Button to select PICKER mode on Labels layer. erase_button : qtpy.QtWidgets.QtModeRadioButton Button to select ERASE mode on Labels layer. selectionSpinBox : qtpy.QtWidgets.QSpinBox Widget to select a specific label by its index. Raises ------ ValueError Raise error if label mode is not PAN_ZOOM, PICKER, PAINT, ERASE, or FILL. """ def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.selected_label.connect( self._on_selected_label_change) self.layer.events.brush_size.connect(self._on_brush_size_change) self.layer.events.contiguous.connect(self._on_contiguous_change) self.layer.events.n_dimensional.connect(self._on_n_dimensional_change) self.layer.events.contour.connect(self._on_contour_change) self.layer.events.editable.connect(self._on_editable_change) self.layer.events.preserve_labels.connect( self._on_preserve_labels_change) self.layer.events.color_mode.connect(self._on_color_mode_change) # selection spinbox self.selectionSpinBox = QSpinBox() self.selectionSpinBox.setKeyboardTracking(False) self.selectionSpinBox.setSingleStep(1) self.selectionSpinBox.setMinimum(0) self.selectionSpinBox.setMaximum(1024) self.selectionSpinBox.valueChanged.connect(self.changeSelection) self.selectionSpinBox.setAlignment(Qt.AlignCenter) self._on_selected_label_change() sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(1) sld.setMaximum(40) sld.setSingleStep(1) sld.valueChanged.connect(self.changeSize) self.brushSizeSlider = sld self._on_brush_size_change() contig_cb = QCheckBox() contig_cb.setToolTip(trans._('contiguous editing')) contig_cb.stateChanged.connect(self.change_contig) self.contigCheckBox = contig_cb self._on_contiguous_change() ndim_cb = QCheckBox() ndim_cb.setToolTip(trans._('edit all dimensions')) ndim_cb.stateChanged.connect(self.change_ndim) self.ndimCheckBox = ndim_cb self._on_n_dimensional_change() contour_sb = QSpinBox() contour_sb.setToolTip(trans._('display contours of labels')) contour_sb.valueChanged.connect(self.change_contour) self.contourSpinBox = contour_sb self.contourSpinBox.setKeyboardTracking(False) self.contourSpinBox.setSingleStep(1) self.contourSpinBox.setMinimum(0) self.contourSpinBox.setMaximum(2147483647) self.contourSpinBox.setAlignment(Qt.AlignCenter) self._on_contour_change() preserve_labels_cb = QCheckBox() preserve_labels_cb.setToolTip( trans._('preserve existing labels while painting')) preserve_labels_cb.stateChanged.connect(self.change_preserve_labels) self.preserveLabelsCheckBox = preserve_labels_cb self._on_preserve_labels_change() selectedColorCheckbox = QCheckBox() selectedColorCheckbox.setToolTip( trans._("Display only selected label")) selectedColorCheckbox.stateChanged.connect(self.toggle_selected_mode) self.selectedColorCheckbox = selectedColorCheckbox # shuffle colormap button self.colormapUpdate = QtModePushButton( None, 'shuffle', slot=self.changeColor, tooltip=trans._('shuffle colors'), ) self.panzoom_button = QtModeRadioButton( layer, 'zoom', Mode.PAN_ZOOM, tooltip=trans._('Pan/zoom mode (Space)'), checked=True, ) self.pick_button = QtModeRadioButton(layer, 'picker', Mode.PICK, tooltip=trans._('Pick mode (L)')) self.paint_button = QtModeRadioButton( layer, 'paint', Mode.PAINT, tooltip=trans._('Paint mode (P)')) self.fill_button = QtModeRadioButton( layer, 'fill', Mode.FILL, tooltip=trans._("Fill mode (F) \nToggle with {key}".format( key=KEY_SYMBOLS['Control'])), ) self.erase_button = QtModeRadioButton( layer, 'erase', Mode.ERASE, tooltip=trans._("Erase mode (E) \nToggle with {key}".format( key=KEY_SYMBOLS['Alt'])), ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.paint_button) self.button_group.addButton(self.pick_button) self.button_group.addButton(self.fill_button) self.button_group.addButton(self.erase_button) self._on_editable_change() button_row = QHBoxLayout() button_row.addStretch(1) button_row.addWidget(self.colormapUpdate) button_row.addWidget(self.erase_button) button_row.addWidget(self.fill_button) button_row.addWidget(self.paint_button) button_row.addWidget(self.pick_button) button_row.addWidget(self.panzoom_button) button_row.setSpacing(4) button_row.setContentsMargins(0, 0, 0, 5) brush_shape_comboBox = QComboBox(self) for index, (data, text) in enumerate(LABEL_BRUSH_SHAPE_TRANSLATIONS.items()): data = data.value brush_shape_comboBox.addItem(text, data) if self.layer.brush_shape == data: brush_shape_comboBox.setCurrentIndex(index) brush_shape_comboBox.activated[str].connect(self.change_brush_shape) self.brushShapeComboBox = brush_shape_comboBox self._on_brush_shape_change() color_mode_comboBox = QComboBox(self) for index, (data, text) in enumerate(LABEL_COLOR_MODE_TRANSLATIONS.items()): data = data.value color_mode_comboBox.addItem(text, data) if self.layer.color_mode == data: color_mode_comboBox.setCurrentIndex(index) color_mode_comboBox.activated[str].connect(self.change_color_mode) self.colorModeComboBox = color_mode_comboBox self._on_color_mode_change() color_layout = QHBoxLayout() self.colorBox = QtColorBox(layer) color_layout.addWidget(self.colorBox) color_layout.addWidget(self.selectionSpinBox) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_row, 0, 0, 1, 4) self.grid_layout.addWidget(QLabel(trans._('label:')), 1, 0, 1, 1) self.grid_layout.addLayout(color_layout, 1, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('opacity:')), 2, 0, 1, 1) self.grid_layout.addWidget(self.opacitySlider, 2, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('brush size:')), 3, 0, 1, 1) self.grid_layout.addWidget(self.brushSizeSlider, 3, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('brush shape:')), 4, 0, 1, 1) self.grid_layout.addWidget(self.brushShapeComboBox, 4, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('blending:')), 5, 0, 1, 1) self.grid_layout.addWidget(self.blendComboBox, 5, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('color mode:')), 6, 0, 1, 1) self.grid_layout.addWidget(self.colorModeComboBox, 6, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('contour:')), 7, 0, 1, 1) self.grid_layout.addWidget(self.contourSpinBox, 7, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('contiguous:')), 8, 0, 1, 1) self.grid_layout.addWidget(self.contigCheckBox, 8, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('n-dim:')), 8, 2, 1, 1) self.grid_layout.addWidget(self.ndimCheckBox, 8, 3, 1, 1) self.grid_layout.addWidget(QLabel(trans._('preserve labels:')), 9, 0, 1, 2) self.grid_layout.addWidget(self.preserveLabelsCheckBox, 9, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('show selected:')), 9, 2, 1, 1) self.grid_layout.addWidget(self.selectedColorCheckbox, 9, 3, 1, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4) def _on_mode_change(self, event): """Receive layer model mode change event and update checkbox ticks. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. Raises ------ ValueError Raise error if event.mode is not PAN_ZOOM, PICK, PAINT, ERASE, or FILL """ mode = event.mode if mode == Mode.PAN_ZOOM: self.panzoom_button.setChecked(True) elif mode == Mode.PICK: self.pick_button.setChecked(True) elif mode == Mode.PAINT: self.paint_button.setChecked(True) elif mode == Mode.FILL: self.fill_button.setChecked(True) elif mode == Mode.ERASE: self.erase_button.setChecked(True) else: raise ValueError(trans._("Mode not recognized")) def changeColor(self): """Change colormap of the label layer.""" self.layer.new_colormap() def changeSelection(self, value): """Change currently selected label. Parameters ---------- value : int Index of label to select. """ self.layer.selected_label = value self.selectionSpinBox.clearFocus() self.setFocus() def toggle_selected_mode(self, state): if state == Qt.Checked: self.layer.show_selected_label = True else: self.layer.show_selected_label = False def changeSize(self, value): """Change paint brush size. Parameters ---------- value : float Size of the paint brush. """ self.layer.brush_size = value def change_contig(self, state): """Toggle contiguous state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if labels are contiguous. """ if state == Qt.Checked: self.layer.contiguous = True else: self.layer.contiguous = False def change_ndim(self, state): """Toggle n-dimensional state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if label layer is n-dimensional. """ if state == Qt.Checked: self.layer.n_dimensional = True else: self.layer.n_dimensional = False def change_contour(self, value): """Change contour thickness. Parameters ---------- value : int Thickness of contour. """ self.layer.contour = value self.contourSpinBox.clearFocus() self.setFocus() def change_preserve_labels(self, state): """Toggle preserve_labels state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if overwriting label is enabled. """ if state == Qt.Checked: self.layer.preserve_labels = True else: self.layer.preserve_labels = False def change_color_mode(self, new_mode): """Change color mode of label layer. Parameters ---------- new_mode : str AUTO (default) allows color to be set via a hash function with a seed. DIRECT allows color of each label to be set directly by a color dictionary. """ self.layer.color_mode = self.colorModeComboBox.currentData() def change_brush_shape(self, brush_shape): """Change paintbrush shape of label layer. Parameters ---------- brush_shape : str CIRCLE (default) uses circle paintbrush (case insensitive). SQUARE uses square paintbrush (case insensitive). """ self.layer.brush_shape = self.brushShapeComboBox.currentData() def _on_contour_change(self, event=None): """Receive layer model contour value change event and update spinbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.contour.blocker(): value = self.layer.contour self.contourSpinBox.setValue(int(value)) def _on_selected_label_change(self, event=None): """Receive layer model label selection change event and update spinbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.selected_label.blocker(): value = self.layer.selected_label self.selectionSpinBox.setValue(int(value)) def _on_brush_size_change(self, event=None): """Receive layer model brush size change event and update the slider. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.brush_size.blocker(): value = self.layer.brush_size value = np.clip(int(value), 1, 40) self.brushSizeSlider.setValue(value) def _on_n_dimensional_change(self, event=None): """Receive layer model n-dim mode change event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.n_dimensional.blocker(): self.ndimCheckBox.setChecked(self.layer.n_dimensional) def _on_contiguous_change(self, event=None): """Receive layer model contiguous change event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.contiguous.blocker(): self.contigCheckBox.setChecked(self.layer.contiguous) def _on_preserve_labels_change(self, event=None): """Receive layer model preserve_labels event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.preserve_labels.blocker(): self.preserveLabelsCheckBox.setChecked(self.layer.preserve_labels) def _on_color_mode_change(self, event=None): """Receive layer model color. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.color_mode.blocker(): # `self.colorModeComboBox.findData` is not returning the correct index. for index in range(self.colorModeComboBox.count()): if self.layer.color_mode == self.colorModeComboBox.itemData( index): self.colorModeComboBox.setCurrentIndex(index) break def _on_brush_shape_change(self, event=None): """Receive brush shape change event and update dropdown menu. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.brush_shape.blocker(): # `self.brushShapeComboBox.findData` is not returning the correct index. for index in range(self.brushShapeComboBox.count()): if self.layer.brush_shape == self.brushShapeComboBox.itemData( index): self.brushShapeComboBox.setCurrentIndex(index) break def _on_editable_change(self, event=None): """Receive layer model editable change event & enable/disable buttons. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ disable_with_opacity( self, ['pick_button', 'paint_button', 'fill_button'], self.layer.editable, )
def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data readonly = readonly or not self.data.flags.writeable is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.ndim > 3: self.error( _("Arrays with more than 3 dimensions are not " "supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error( _("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error( _("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn == 'object': # If the array doesn't have shape, we can't display it if data.shape == (): self.error( _("Object arrays without shape are not " "supported")) return False # We don't know what's inside these arrays, so we can't handle # edits self.readonly = readonly = True elif (dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') and not dtn.startswith('unicode')): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy object array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget( ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect( self.save_and_close_enable) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect( self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel( _("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True
def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.selected_label.connect( self._on_selected_label_change) self.layer.events.brush_size.connect(self._on_brush_size_change) self.layer.events.contiguous.connect(self._on_contiguous_change) self.layer.events.n_dimensional.connect(self._on_n_dimensional_change) self.layer.events.contour.connect(self._on_contour_change) self.layer.events.editable.connect(self._on_editable_change) self.layer.events.preserve_labels.connect( self._on_preserve_labels_change) self.layer.events.color_mode.connect(self._on_color_mode_change) # selection spinbox self.selectionSpinBox = QSpinBox() self.selectionSpinBox.setKeyboardTracking(False) self.selectionSpinBox.setSingleStep(1) self.selectionSpinBox.setMinimum(0) self.selectionSpinBox.setMaximum(1024) self.selectionSpinBox.valueChanged.connect(self.changeSelection) self.selectionSpinBox.setAlignment(Qt.AlignCenter) self._on_selected_label_change() sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(1) sld.setMaximum(40) sld.setSingleStep(1) sld.valueChanged.connect(self.changeSize) self.brushSizeSlider = sld self._on_brush_size_change() contig_cb = QCheckBox() contig_cb.setToolTip(trans._('contiguous editing')) contig_cb.stateChanged.connect(self.change_contig) self.contigCheckBox = contig_cb self._on_contiguous_change() ndim_cb = QCheckBox() ndim_cb.setToolTip(trans._('edit all dimensions')) ndim_cb.stateChanged.connect(self.change_ndim) self.ndimCheckBox = ndim_cb self._on_n_dimensional_change() contour_sb = QSpinBox() contour_sb.setToolTip(trans._('display contours of labels')) contour_sb.valueChanged.connect(self.change_contour) self.contourSpinBox = contour_sb self.contourSpinBox.setKeyboardTracking(False) self.contourSpinBox.setSingleStep(1) self.contourSpinBox.setMinimum(0) self.contourSpinBox.setMaximum(2147483647) self.contourSpinBox.setAlignment(Qt.AlignCenter) self._on_contour_change() preserve_labels_cb = QCheckBox() preserve_labels_cb.setToolTip( trans._('preserve existing labels while painting')) preserve_labels_cb.stateChanged.connect(self.change_preserve_labels) self.preserveLabelsCheckBox = preserve_labels_cb self._on_preserve_labels_change() selectedColorCheckbox = QCheckBox() selectedColorCheckbox.setToolTip( trans._("Display only selected label")) selectedColorCheckbox.stateChanged.connect(self.toggle_selected_mode) self.selectedColorCheckbox = selectedColorCheckbox # shuffle colormap button self.colormapUpdate = QtModePushButton( None, 'shuffle', slot=self.changeColor, tooltip=trans._('shuffle colors'), ) self.panzoom_button = QtModeRadioButton( layer, 'zoom', Mode.PAN_ZOOM, tooltip=trans._('Pan/zoom mode (Space)'), checked=True, ) self.pick_button = QtModeRadioButton(layer, 'picker', Mode.PICK, tooltip=trans._('Pick mode (L)')) self.paint_button = QtModeRadioButton( layer, 'paint', Mode.PAINT, tooltip=trans._('Paint mode (P)')) self.fill_button = QtModeRadioButton( layer, 'fill', Mode.FILL, tooltip=trans._("Fill mode (F) \nToggle with {key}".format( key=KEY_SYMBOLS['Control'])), ) self.erase_button = QtModeRadioButton( layer, 'erase', Mode.ERASE, tooltip=trans._("Erase mode (E) \nToggle with {key}".format( key=KEY_SYMBOLS['Alt'])), ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.paint_button) self.button_group.addButton(self.pick_button) self.button_group.addButton(self.fill_button) self.button_group.addButton(self.erase_button) self._on_editable_change() button_row = QHBoxLayout() button_row.addStretch(1) button_row.addWidget(self.colormapUpdate) button_row.addWidget(self.erase_button) button_row.addWidget(self.fill_button) button_row.addWidget(self.paint_button) button_row.addWidget(self.pick_button) button_row.addWidget(self.panzoom_button) button_row.setSpacing(4) button_row.setContentsMargins(0, 0, 0, 5) brush_shape_comboBox = QComboBox(self) for index, (data, text) in enumerate(LABEL_BRUSH_SHAPE_TRANSLATIONS.items()): data = data.value brush_shape_comboBox.addItem(text, data) if self.layer.brush_shape == data: brush_shape_comboBox.setCurrentIndex(index) brush_shape_comboBox.activated[str].connect(self.change_brush_shape) self.brushShapeComboBox = brush_shape_comboBox self._on_brush_shape_change() color_mode_comboBox = QComboBox(self) for index, (data, text) in enumerate(LABEL_COLOR_MODE_TRANSLATIONS.items()): data = data.value color_mode_comboBox.addItem(text, data) if self.layer.color_mode == data: color_mode_comboBox.setCurrentIndex(index) color_mode_comboBox.activated[str].connect(self.change_color_mode) self.colorModeComboBox = color_mode_comboBox self._on_color_mode_change() color_layout = QHBoxLayout() self.colorBox = QtColorBox(layer) color_layout.addWidget(self.colorBox) color_layout.addWidget(self.selectionSpinBox) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_row, 0, 0, 1, 4) self.grid_layout.addWidget(QLabel(trans._('label:')), 1, 0, 1, 1) self.grid_layout.addLayout(color_layout, 1, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('opacity:')), 2, 0, 1, 1) self.grid_layout.addWidget(self.opacitySlider, 2, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('brush size:')), 3, 0, 1, 1) self.grid_layout.addWidget(self.brushSizeSlider, 3, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('brush shape:')), 4, 0, 1, 1) self.grid_layout.addWidget(self.brushShapeComboBox, 4, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('blending:')), 5, 0, 1, 1) self.grid_layout.addWidget(self.blendComboBox, 5, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('color mode:')), 6, 0, 1, 1) self.grid_layout.addWidget(self.colorModeComboBox, 6, 1, 1, 3) self.grid_layout.addWidget(QLabel(trans._('contour:')), 7, 0, 1, 1) self.grid_layout.addWidget(self.contourSpinBox, 7, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('contiguous:')), 8, 0, 1, 1) self.grid_layout.addWidget(self.contigCheckBox, 8, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('n-dim:')), 8, 2, 1, 1) self.grid_layout.addWidget(self.ndimCheckBox, 8, 3, 1, 1) self.grid_layout.addWidget(QLabel(trans._('preserve labels:')), 9, 0, 1, 2) self.grid_layout.addWidget(self.preserveLabelsCheckBox, 9, 1, 1, 1) self.grid_layout.addWidget(QLabel(trans._('show selected:')), 9, 2, 1, 1) self.grid_layout.addWidget(self.selectedColorCheckbox, 9, 3, 1, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4)
class EditNodeProperties(QDialog): def __init__(self, data, win_parent=None): """ +-----------------+ | Edit Node Props | +-----------------+------+ | LEwingTip | | Node2 | | Node3 | | Node4 | | | | All Nodes: | | Color red | | PointSize 3 | | Opacity 0.3 | | Show/Hide | | | | Name LEwingTip | | Location X Y Z | | Coord 0 | | CoordType R, C, S | | | | Previous Next | | | | Close | +------------------------+ """ QDialog.__init__(self, win_parent) self.setWindowTitle('Edit Node Properties') #default self.win_parent = win_parent self.out_data = data point_properties = data['point_properties'] print(point_properties) #name = point_properties.name point_size = point_properties.point_size opacity = point_properties.opacity color = point_properties.color show = point_properties.is_visible self.points = data['points'] self.keys = sorted(self.points.keys()) keys = self.keys #nrows = len(keys) active_point = data['active_point'] #self.active_key = keys[0] self.active_key = active_point name = self.active_key description = self.points[self.active_key][0] self._use_old_table = False items = ['Node %i' % val for val in keys] header_labels = ['Nodes'] table_model = Model(items, header_labels, self) view = SingleChoiceQTableView(self) #Call your custom QTableView here view.setModel(table_model) view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table = view #self.representation = actor_obj.representation #print('rep =', self.representation) table = self.table #headers = [QtCore.QString('Groups')] header = table.horizontalHeader() header.setStretchLastSection(True) #---------------------------------------------- #self._default_is_apply = False self.color = QLabel("Color:") self.color_edit = QPushButton() #self.color_edit.setFlat(True) color = self.out_data['point_properties'].color opacity = self.out_data['point_properties'].opacity show = self.out_data['point_properties'].is_visible #color = self.out_data[self.active_key].color qcolor = QColor() qcolor.setRgb(*color) #print('color =%s' % str(color)) palette = QPalette( self.color_edit.palette()) # make a copy of the palette #palette.setColor(QPalette.Active, QPalette.Base, \ #qcolor) palette.setColor(QPalette.Background, QColor('blue')) # ButtonText self.color_edit.setPalette(palette) self.color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(color) + #"border:1px solid rgb(255, 170, 255); " "}") self.all_nodes_header = QLabel("All Nodes:") self.point_size = QLabel("Point Size:") self.point_size_edit = QSpinBox(self) self.point_size_edit.setRange(1, 10) self.point_size_edit.setSingleStep(1) self.point_size_edit.setValue(point_size) self.opacity = QLabel("Opacity:") self.opacity_edit = QDoubleSpinBox(self) self.opacity_edit.setRange(0.1, 1.0) self.opacity_edit.setDecimals(1) self.opacity_edit.setSingleStep(0.1) self.opacity_edit.setValue(opacity) # show/hide self.checkbox_show = QCheckBox("Show") self.checkbox_hide = QCheckBox("Hide") self.checkbox_show.setChecked(show) self.checkbox_hide.setChecked(not show) #---------------------------------------------- self.nodes_header = QLabel("Single Node:") self.name = QLabel("ID:") self.name_edit = QLineEdit('Node %i' % name) self.name_edit.setDisabled(True) self.description = QLabel("Description:") self.description_edit = QLineEdit(str(description)) #self.description_edit.setDisabled(True) location_x = 0.1 location_y = 0.1 location_z = 0.1 self.location = QLabel("Location:") self.location_x_edit = QDoubleSpinBox(self) self.location_y_edit = QDoubleSpinBox(self) self.location_z_edit = QDoubleSpinBox(self) #self.location_x_edit.setDecimals(1) delta_x = abs(location_x) / 100. if location_x != 0.0 else 0.1 delta_y = abs(location_y) / 100. if location_y != 0.0 else 0.1 delta_z = abs(location_z) / 100. if location_z != 0.0 else 0.1 self.location_x_edit.setSingleStep(delta_x) self.location_y_edit.setSingleStep(delta_y) self.location_z_edit.setSingleStep(delta_z) self.location_x_edit.setValue(location_x) self.location_y_edit.setValue(location_y) self.location_z_edit.setValue(location_z) self.coord = QLabel("Coord:") self.coord_edit = QSpinBox(self) self.coord_edit.setRange(0, 99999999) #self.coord_edit.setSingleStep(1) self.coord_edit.setValue(0) self.coord_type = QLabel("Coord Type:") #---------------------------------------------- # closing #if self._default_is_apply: #self.apply_button.setDisabled(True) self.close_button = QPushButton("Close") self.create_layout() self.set_connections() def update_active_key(self, index): name = self.active_key old_obj = self.out_data['points'][name] #self.active_key #self.points[self.active_key] old_obj[0] = str(self.description_edit.text()) #old_obj.coord = self.description_edit.value() #old_obj.description = self.description_edit.value() #old_obj.description = self.description_edit.value() str_name = str(index.data().toString()) name = int(str_name[5:]) #i = self.keys.index(self.active_key) self.active_key = name point = self.points[self.active_key] #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], self.name_edit.setText(str(self.active_key)) self.description_edit.setText(point[0]) self.coord_edit.setValue(point[1]) if point[2] == 'R': self.radio_rectangular.setChecked(True) elif point[2] == 'C': self.radio_cylindrical.setChecked(True) elif point[2] == 'S': self.radio_spherical.setChecked(True) self.location_x_edit.setValue(point[3]) self.location_y_edit.setValue(point[4]) self.location_z_edit.setValue(point[5]) #obj = self.out_data[name] #point_size = obj.point_size #opacity = obj.opacity #representation = obj.representation #is_visible = obj.is_visible #self.opacity_edit.setValue(opacity) #self.checkbox_show.setChecked(is_visible) #self.checkbox_hide.setChecked(not is_visible) #def on_name_select(self): #print('on_name_select') #return def create_layout(self): cancel_box = QHBoxLayout() cancel_box.addWidget(self.close_button) grid1 = QGridLayout() grid2 = QGridLayout() #----------------------------------------- # setup self.radio_rectangular = QRadioButton('Rectangular') self.radio_cylindrical = QRadioButton('Cylindrical') self.radio_spherical = QRadioButton('Spherical') coord_type_layout = QHBoxLayout() coord_type_layout.addWidget(self.radio_rectangular) coord_type_layout.addWidget(self.radio_cylindrical) coord_type_layout.addWidget(self.radio_spherical) location_layout = QHBoxLayout() location_layout.addWidget(self.location_x_edit) location_layout.addWidget(self.location_y_edit) location_layout.addWidget(self.location_z_edit) checkboxs = QButtonGroup(self) checkboxs.addButton(self.checkbox_show) checkboxs.addButton(self.checkbox_hide) vbox1 = QVBoxLayout() vbox1.addWidget(self.checkbox_show) vbox1.addWidget(self.checkbox_hide) #vbox1.addLayout(checkboxs) #----------------------------------------- irow = 0 grid1.addWidget(self.all_nodes_header, irow, 0) irow += 1 grid1.addWidget(self.color, irow, 0) grid1.addWidget(self.color_edit, irow, 1) irow += 1 grid1.addWidget(self.opacity, irow, 0) grid1.addWidget(self.opacity_edit, irow, 1) irow += 1 grid1.addWidget(self.point_size, irow, 0) grid1.addWidget(self.point_size_edit, irow, 1) irow += 1 #----------------------------------------- irow = 0 grid2.addWidget(self.nodes_header, irow, 0) irow += 1 grid2.addWidget(self.name, irow, 0) grid2.addWidget(self.name_edit, irow, 1) irow += 1 grid2.addWidget(self.description, irow, 0) grid2.addWidget(self.description_edit, irow, 1) irow += 1 #| All Nodes: | #| Color red | #| PointSize 3 | #| Opacity 0.3 | #| Show/Hide | #| | #| Name LEwingTip | #| Location X Y Z | #| Coord 0 | #| CoordType R, C, S | #| | #| Previous Next | grid2.addWidget(self.coord, irow, 0) grid2.addWidget(self.coord_edit, irow, 1) irow += 1 grid2.addWidget(self.coord_type, irow, 0) grid2.addLayout(coord_type_layout, irow, 1) irow += 1 grid2.addWidget(self.location, irow, 0) grid2.addLayout(location_layout, irow, 1) irow += 1 #------------------------------------ vbox = QVBoxLayout() vbox.addLayout(grid1) vbox.addLayout(vbox1) vbox.addStretch() vbox.addWidget(self.table) vbox.addLayout(grid2) vbox.addStretch() #vbox.addWidget(self.check_apply) vbox.addLayout(cancel_box) self.setLayout(vbox) def set_connections(self): """creates the actions for the menu""" self.opacity_edit.valueChanged.connect(self.on_opacity) #self.connect(self.point_size, QtCore.SIGNAL('clicked()'), self.on_point_size) #self.point_size_edit.clicked.connect(self.on_point_size) # need to update name?? self.color_edit.clicked.connect(self.on_color) self.checkbox_show.clicked.connect(self.on_show) self.checkbox_hide.clicked.connect(self.on_hide) self.description_edit.textEdited.connect( self.on_description) # valueChanged???? self.coord_edit.valueChanged.connect(self.on_coord) self.radio_rectangular.clicked.connect(self.on_coord_type) self.radio_cylindrical.clicked.connect(self.on_coord_type) self.radio_spherical.clicked.connect(self.on_coord_type) self.location_x_edit.valueChanged.connect(self.on_location_x) self.location_y_edit.valueChanged.connect(self.on_location_y) self.location_z_edit.valueChanged.connect(self.on_location_z) #self.connect(self.check_apply, QtCore.SIGNAL('clicked()'), self.on_check_apply) #self.connect(self.apply_button, QtCore.SIGNAL('clicked()'), self.on_apply) #self.connect(self.ok_button, QtCore.SIGNAL('clicked()'), self.on_ok) self.close_button.clicked.connect(self.on_close) def on_color(self): obj = self.out_data['point_properties'] rgb_color_ints = obj.color msg = 'Points' col = QColorDialog.getColor(QColor(*rgb_color_ints), self, "Choose a %s color" % msg) if col.isValid(): color = col.getRgbF()[:3] obj.color = color #print('new_color =', color) self.color_edit.setStyleSheet( "QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(obj.color) + #"border:1px solid rgb(255, 170, 255); " "}") def on_show(self): is_checked = self.checkbox_show.isChecked() self.out_data['point_properties'].is_visible = is_checked def on_hide(self): is_checked = self.checkbox_hide.isChecked() self.out_data['point_properties'].is_visible = not is_checked def on_point_size(self): point_size = self.point_size_edit.value() self.out_data['point_properties'].point_size = point_size def on_opacity(self): opacity = self.opacity_edit.value() self.out_data['point_properties'].opacity = opacity def on_description(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key description = self.description_edit.value() self.out_data['points'][name][0] = description def on_coord(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key coord_id = self.coord_edit.value() self.out_data['points'][name][1] = coord_id def on_coord_type(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key if self.radio_rectangular.isChecked(): coord_type = 'R' elif self.radio_cylindrical.isChecked(): coord_type = 'C' elif self.radio_spherical.isChecked(): coord_type = 'S' else: raise NotImplementedError() self.out_data['points'][name][2] = coord_type def on_location_x(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key value = self.coord_edit.value() self.out_data['points'][name][3] = value def on_location_y(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key value = self.coord_edit.value() self.out_data['points'][name][4] = value def on_location_z(self): #1 : ['LERoot', 0, 'R', 1.0, 2.0, 3.0], name = self.active_key value = self.coord_edit.value() self.out_data['points'][name][5] = value def closeEvent(self, event): event.accept() #def on_default_name(self): #self.name_edit.setText(str(self._default_name)) #self.name_edit.setStyleSheet("QLineEdit{background: white;}") #def check_name(self, cell): #text = str(cell.text()).strip() #if len(text): #cell.setStyleSheet("QLineEdit{background: white;}") #return text, True #else: #cell.setStyleSheet("QLineEdit{background: red;}") #return None, False def on_validate(self): self.out_data['clicked_ok'] = True self.out_data['clicked_cancel'] = False old_obj = self.out_data[self.active_key] old_obj.point_size = self.point_size_edit.value() old_obj.opacity = self.opacity_edit.value() old_obj.is_visible = self.checkbox_show.isChecked() return True #name_value, flag0 = self.check_name(self.name_edit) #ox_value, flag1 = check_float(self.transparency_edit) #if flag0 and flag1: #self.out_data['clicked_ok'] = True #return True #return False def on_apply(self): passed = self.on_validate() if passed: self.win_parent.on_update_gui_nodes(self.out_data) return passed def on_ok(self): passed = self.on_apply() if passed: self.close() #self.destroy() def on_close(self): self.out_data['clicked_close'] = True self.close()
class PyDMChartingDisplay(Display): def __init__(self, parent=None, args=[], macros=None): """ Create all the widgets, including any child dialogs. Parameters ---------- parent : QWidget The parent widget of the charting display args : list The command parameters macros : str Macros to modify the UI parameters at runtime """ super(PyDMChartingDisplay, self).__init__(parent=parent, args=args, macros=macros) self.channel_map = dict() self.setWindowTitle("PyDM Charting Tool") self.main_layout = QVBoxLayout() self.body_layout = QVBoxLayout() self.pv_layout = QHBoxLayout() self.pv_name_line_edt = QLineEdit() self.pv_name_line_edt.setAcceptDrops(True) self.pv_name_line_edt.installEventFilter(self) self.pv_protocol_cmb = QComboBox() self.pv_protocol_cmb.addItems(["ca://", "archive://"]) self.pv_connect_push_btn = QPushButton("Connect") self.pv_connect_push_btn.clicked.connect(self.add_curve) self.tab_panel = QTabWidget() self.tab_panel.setMaximumWidth(450) self.curve_settings_tab = QWidget() self.chart_settings_tab = QWidget() self.charting_layout = QHBoxLayout() self.chart = PyDMTimePlot(plot_by_timestamps=False, plot_display=self) self.chart.setPlotTitle("Time Plot") self.splitter = QSplitter() self.curve_settings_layout = QVBoxLayout() self.curve_settings_layout.setAlignment(Qt.AlignTop) self.curve_settings_layout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.curve_settings_layout.setSpacing(5) self.crosshair_settings_layout = QVBoxLayout() self.crosshair_settings_layout.setAlignment(Qt.AlignTop) self.crosshair_settings_layout.setSpacing(5) self.enable_crosshair_chk = QCheckBox("Enable Crosshair") self.cross_hair_coord_lbl = QLabel() self.curve_settings_inner_frame = QFrame() self.curve_settings_inner_frame.setLayout(self.curve_settings_layout) self.curve_settings_scroll = QScrollArea() self.curve_settings_scroll.setVerticalScrollBarPolicy( Qt.ScrollBarAsNeeded) self.curve_settings_scroll.setWidget(self.curve_settings_inner_frame) self.curves_tab_layout = QHBoxLayout() self.curves_tab_layout.addWidget(self.curve_settings_scroll) self.enable_crosshair_chk.setChecked(False) self.enable_crosshair_chk.clicked.connect( self.handle_enable_crosshair_checkbox_clicked) self.enable_crosshair_chk.clicked.emit(False) self.chart_settings_layout = QVBoxLayout() self.chart_settings_layout.setAlignment(Qt.AlignTop) self.chart_layout = QVBoxLayout() self.chart_panel = QWidget() self.chart_control_layout = QHBoxLayout() self.chart_control_layout.setAlignment(Qt.AlignHCenter) self.chart_control_layout.setSpacing(10) self.view_all_btn = QPushButton("View All") self.view_all_btn.clicked.connect(self.handle_view_all_button_clicked) self.view_all_btn.setEnabled(False) self.auto_scale_btn = QPushButton("Auto Scale") self.auto_scale_btn.clicked.connect(self.handle_auto_scale_btn_clicked) self.auto_scale_btn.setEnabled(False) self.reset_chart_btn = QPushButton("Reset") self.reset_chart_btn.clicked.connect( self.handle_reset_chart_btn_clicked) self.reset_chart_btn.setEnabled(False) self.resume_chart_text = "Resume" self.pause_chart_text = "Pause" self.pause_chart_btn = QPushButton(self.pause_chart_text) self.pause_chart_btn.clicked.connect( self.handle_pause_chart_btn_clicked) self.title_settings_layout = QVBoxLayout() self.title_settings_layout.setSpacing(10) self.title_settings_grpbx = QGroupBox() self.title_settings_grpbx.setFixedHeight(150) self.import_data_btn = QPushButton("Import Data...") self.import_data_btn.clicked.connect( self.handle_import_data_btn_clicked) self.export_data_btn = QPushButton("Export Data...") self.export_data_btn.clicked.connect( self.handle_export_data_btn_clicked) self.chart_title_lbl = QLabel(text="Chart Title") self.chart_title_line_edt = QLineEdit() self.chart_title_line_edt.setText(self.chart.getPlotTitle()) self.chart_title_line_edt.textChanged.connect( self.handle_title_text_changed) self.chart_change_axis_settings_btn = QPushButton( text="Change Axis Settings...") self.chart_change_axis_settings_btn.clicked.connect( self.handle_change_axis_settings_clicked) self.update_datetime_timer = QTimer(self) self.update_datetime_timer.timeout.connect( self.handle_update_datetime_timer_timeout) self.chart_sync_mode_layout = QVBoxLayout() self.chart_sync_mode_layout.setSpacing(5) self.chart_sync_mode_grpbx = QGroupBox("Data Sampling Mode") self.chart_sync_mode_grpbx.setFixedHeight(80) self.chart_sync_mode_sync_radio = QRadioButton("Synchronous") self.chart_sync_mode_async_radio = QRadioButton("Asynchronous") self.chart_sync_mode_async_radio.setChecked(True) self.graph_drawing_settings_layout = QVBoxLayout() self.chart_redraw_rate_lbl = QLabel("Redraw Rate (Hz)") self.chart_redraw_rate_spin = QSpinBox() self.chart_redraw_rate_spin.setRange(MIN_REDRAW_RATE_HZ, MAX_REDRAW_RATE_HZ) self.chart_redraw_rate_spin.setValue(DEFAULT_REDRAW_RATE_HZ) self.chart_redraw_rate_spin.valueChanged.connect( self.handle_redraw_rate_changed) self.chart_data_sampling_rate_lbl = QLabel( "Asynchronous Data Sampling Rate (Hz)") self.chart_data_async_sampling_rate_spin = QSpinBox() self.chart_data_async_sampling_rate_spin.setRange( MIN_DATA_SAMPLING_RATE_HZ, MAX_DATA_SAMPLING_RATE_HZ) self.chart_data_async_sampling_rate_spin.setValue( DEFAULT_DATA_SAMPLING_RATE_HZ) self.chart_data_async_sampling_rate_spin.valueChanged.connect( self.handle_data_sampling_rate_changed) self.chart_data_sampling_rate_lbl.hide() self.chart_data_async_sampling_rate_spin.hide() self.chart_limit_time_span_layout = QHBoxLayout() self.chart_limit_time_span_layout.setSpacing(5) self.limit_time_plan_text = "Limit Time Span" self.chart_limit_time_span_chk = QCheckBox(self.limit_time_plan_text) self.chart_limit_time_span_chk.hide() self.chart_limit_time_span_lbl = QLabel("Hours : Minutes : Seconds") self.chart_limit_time_span_hours_line_edt = QLineEdit() self.chart_limit_time_span_minutes_line_edt = QLineEdit() self.chart_limit_time_span_seconds_line_edt = QLineEdit() self.chart_limit_time_span_activate_btn = QPushButton("Apply") self.chart_limit_time_span_activate_btn.setDisabled(True) self.chart_ring_buffer_size_lbl = QLabel("Ring Buffer Size") self.chart_ring_buffer_size_edt = QLineEdit() self.chart_ring_buffer_size_edt.installEventFilter(self) self.chart_ring_buffer_size_edt.textChanged.connect( self.handle_buffer_size_changed) self.chart_ring_buffer_size_edt.setText(str(DEFAULT_BUFFER_SIZE)) self.show_legend_chk = QCheckBox("Show Legend") self.show_legend_chk.setChecked(self.chart.showLegend) self.show_legend_chk.clicked.connect( self.handle_show_legend_checkbox_clicked) self.graph_background_color_layout = QFormLayout() self.background_color_lbl = QLabel("Graph Background Color ") self.background_color_btn = QPushButton() self.background_color_btn.setStyleSheet( "background-color: " + self.chart.getBackgroundColor().name()) self.background_color_btn.setContentsMargins(10, 0, 5, 5) self.background_color_btn.setMaximumWidth(20) self.background_color_btn.clicked.connect( self.handle_background_color_button_clicked) self.axis_settings_layout = QVBoxLayout() self.axis_settings_layout.setSpacing(5) self.show_x_grid_chk = QCheckBox("Show x Grid") self.show_x_grid_chk.setChecked(self.chart.showXGrid) self.show_x_grid_chk.clicked.connect( self.handle_show_x_grid_checkbox_clicked) self.show_y_grid_chk = QCheckBox("Show y Grid") self.show_y_grid_chk.setChecked(self.chart.showYGrid) self.show_y_grid_chk.clicked.connect( self.handle_show_y_grid_checkbox_clicked) self.axis_color_lbl = QLabel("Axis and Grid Color") self.axis_color_lbl.setEnabled(False) self.axis_color_btn = QPushButton() self.axis_color_btn.setStyleSheet("background-color: " + DEFAULT_CHART_AXIS_COLOR.name()) self.axis_color_btn.setContentsMargins(10, 0, 5, 5) self.axis_color_btn.setMaximumWidth(20) self.axis_color_btn.clicked.connect( self.handle_axis_color_button_clicked) self.axis_color_btn.setEnabled(False) self.grid_opacity_lbl = QLabel("Grid Opacity") self.grid_opacity_lbl.setEnabled(False) self.grid_opacity_slr = QSlider(Qt.Horizontal) self.grid_opacity_slr.setFocusPolicy(Qt.StrongFocus) self.grid_opacity_slr.setRange(0, 10) self.grid_opacity_slr.setValue(5) self.grid_opacity_slr.setTickInterval(1) self.grid_opacity_slr.setSingleStep(1) self.grid_opacity_slr.setTickPosition(QSlider.TicksBelow) self.grid_opacity_slr.valueChanged.connect( self.handle_grid_opacity_slider_mouse_release) self.grid_opacity_slr.setEnabled(False) self.reset_chart_settings_btn = QPushButton("Reset Chart Settings") self.reset_chart_settings_btn.clicked.connect( self.handle_reset_chart_settings_btn_clicked) self.curve_checkbox_panel = QWidget() self.graph_drawing_settings_grpbx = QGroupBox() self.graph_drawing_settings_grpbx.setFixedHeight(270) self.axis_settings_grpbx = QGroupBox() self.axis_settings_grpbx.setFixedHeight(180) self.app = QApplication.instance() self.setup_ui() self.curve_settings_disp = None self.axis_settings_disp = None self.chart_data_export_disp = None self.chart_data_import_disp = None self.grid_alpha = 5 self.time_span_limit_hours = None self.time_span_limit_minutes = None self.time_span_limit_seconds = None self.data_sampling_mode = ASYNC_DATA_SAMPLING def minimumSizeHint(self): """ The minimum recommended size of the main window. """ return QSize(1490, 800) def ui_filepath(self): """ The path to the UI file created by Qt Designer, if applicable. """ # No UI file is being used return None def ui_filename(self): """ The name the UI file created by Qt Designer, if applicable. """ # No UI file is being used return None def setup_ui(self): """ Initialize the widgets and layouts. """ self.setLayout(self.main_layout) self.pv_layout.addWidget(self.pv_protocol_cmb) self.pv_layout.addWidget(self.pv_name_line_edt) self.pv_layout.addWidget(self.pv_connect_push_btn) QTimer.singleShot(0, self.pv_name_line_edt.setFocus) self.curve_settings_tab.setLayout(self.curves_tab_layout) self.chart_settings_tab.setLayout(self.chart_settings_layout) self.setup_chart_settings_layout() self.tab_panel.addTab(self.curve_settings_tab, "Curves") self.tab_panel.addTab(self.chart_settings_tab, "Chart") self.tab_panel.hide() self.crosshair_settings_layout.addWidget(self.enable_crosshair_chk) self.crosshair_settings_layout.addWidget(self.cross_hair_coord_lbl) self.chart_control_layout.addWidget(self.auto_scale_btn) self.chart_control_layout.addWidget(self.view_all_btn) self.chart_control_layout.addWidget(self.reset_chart_btn) self.chart_control_layout.addWidget(self.pause_chart_btn) self.chart_control_layout.addLayout(self.crosshair_settings_layout) self.chart_control_layout.addWidget(self.import_data_btn) self.chart_control_layout.addWidget(self.export_data_btn) self.chart_control_layout.setStretch(4, 15) self.chart_control_layout.insertSpacing(5, 350) self.chart_layout.addWidget(self.chart) self.chart_layout.addLayout(self.chart_control_layout) self.chart_panel.setLayout(self.chart_layout) self.splitter.addWidget(self.chart_panel) self.splitter.addWidget(self.tab_panel) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.charting_layout.addWidget(self.splitter) self.body_layout.addLayout(self.pv_layout) self.body_layout.addLayout(self.charting_layout) self.body_layout.addLayout(self.chart_control_layout) self.main_layout.addLayout(self.body_layout) self.enable_chart_control_buttons(False) def setup_chart_settings_layout(self): self.chart_sync_mode_sync_radio.toggled.connect( partial(self.handle_sync_mode_radio_toggle, self.chart_sync_mode_sync_radio)) self.chart_sync_mode_async_radio.toggled.connect( partial(self.handle_sync_mode_radio_toggle, self.chart_sync_mode_async_radio)) self.title_settings_layout.addWidget(self.chart_title_lbl) self.title_settings_layout.addWidget(self.chart_title_line_edt) self.title_settings_layout.addWidget(self.show_legend_chk) self.title_settings_layout.addWidget( self.chart_change_axis_settings_btn) self.title_settings_grpbx.setLayout(self.title_settings_layout) self.chart_settings_layout.addWidget(self.title_settings_grpbx) self.chart_sync_mode_layout.addWidget(self.chart_sync_mode_sync_radio) self.chart_sync_mode_layout.addWidget(self.chart_sync_mode_async_radio) self.chart_sync_mode_grpbx.setLayout(self.chart_sync_mode_layout) self.chart_settings_layout.addWidget(self.chart_sync_mode_grpbx) self.chart_settings_layout.addWidget(self.chart_sync_mode_grpbx) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_lbl) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_hours_line_edt) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_minutes_line_edt) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_seconds_line_edt) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_activate_btn) self.chart_limit_time_span_lbl.hide() self.chart_limit_time_span_hours_line_edt.hide() self.chart_limit_time_span_minutes_line_edt.hide() self.chart_limit_time_span_seconds_line_edt.hide() self.chart_limit_time_span_activate_btn.hide() self.chart_limit_time_span_hours_line_edt.textChanged.connect( self.handle_time_span_edt_text_changed) self.chart_limit_time_span_minutes_line_edt.textChanged.connect( self.handle_time_span_edt_text_changed) self.chart_limit_time_span_seconds_line_edt.textChanged.connect( self.handle_time_span_edt_text_changed) self.chart_limit_time_span_chk.clicked.connect( self.handle_limit_time_span_checkbox_clicked) self.chart_limit_time_span_activate_btn.clicked.connect( self.handle_chart_limit_time_span_activate_btn_clicked) self.chart_limit_time_span_activate_btn.installEventFilter(self) self.graph_background_color_layout.addRow(self.background_color_lbl, self.background_color_btn) self.graph_drawing_settings_layout.addLayout( self.graph_background_color_layout) self.graph_drawing_settings_layout.addWidget( self.chart_redraw_rate_lbl) self.graph_drawing_settings_layout.addWidget( self.chart_redraw_rate_spin) self.graph_drawing_settings_layout.addWidget( self.chart_data_sampling_rate_lbl) self.graph_drawing_settings_layout.addWidget( self.chart_data_async_sampling_rate_spin) self.graph_drawing_settings_layout.addWidget( self.chart_limit_time_span_chk) self.graph_drawing_settings_layout.addLayout( self.chart_limit_time_span_layout) self.graph_drawing_settings_layout.addWidget( self.chart_ring_buffer_size_lbl) self.graph_drawing_settings_layout.addWidget( self.chart_ring_buffer_size_edt) self.graph_drawing_settings_grpbx.setLayout( self.graph_drawing_settings_layout) self.axis_settings_layout.addWidget(self.show_x_grid_chk) self.axis_settings_layout.addWidget(self.show_y_grid_chk) self.axis_settings_layout.addWidget(self.axis_color_lbl) self.axis_settings_layout.addWidget(self.axis_color_btn) self.axis_settings_layout.addWidget(self.grid_opacity_lbl) self.axis_settings_layout.addWidget(self.grid_opacity_slr) self.axis_settings_grpbx.setLayout(self.axis_settings_layout) self.chart_settings_layout.addWidget(self.graph_drawing_settings_grpbx) self.chart_settings_layout.addWidget(self.axis_settings_grpbx) self.chart_settings_layout.addWidget(self.reset_chart_settings_btn) self.chart_sync_mode_async_radio.toggled.emit(True) self.update_datetime_timer.start(1000) def eventFilter(self, obj, event): """ Handle key and mouse events for any applicable widget. Parameters ---------- obj : QWidget The current widget that accepts the event event : QEvent The key or mouse event to handle Returns ------- True if the event was handled successfully; False otherwise """ if obj == self.pv_name_line_edt and event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return: self.add_curve() return True elif obj == self.chart_limit_time_span_activate_btn and event.type( ) == QEvent.KeyPress: if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return: self.handle_chart_limit_time_span_activate_btn_clicked() return True elif obj == self.chart_ring_buffer_size_edt: if event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return) or \ event.type() == QEvent.FocusOut: try: buffer_size = int(self.chart_ring_buffer_size_edt.text()) if buffer_size < MINIMUM_BUFFER_SIZE: self.chart_ring_buffer_size_edt.setText( str(MINIMUM_BUFFER_SIZE)) except ValueError: display_message_box(QMessageBox.Critical, "Invalid Values", "Only integer values are accepted.") return True return super(PyDMChartingDisplay, self).eventFilter(obj, event) def add_curve(self): """ Add a new curve to the chart. """ pv_name = self._get_full_pv_name(self.pv_name_line_edt.text()) color = random_color() for k, v in self.channel_map.items(): if color == v.color: color = random_color() self.add_y_channel(pv_name=pv_name, curve_name=pv_name, color=color) def handle_enable_crosshair_checkbox_clicked(self, is_checked): self.chart.enableCrosshair(is_checked) self.cross_hair_coord_lbl.setVisible(is_checked) def add_y_channel(self, pv_name, curve_name, color, line_style=Qt.SolidLine, line_width=2, symbol=None, symbol_size=None): if pv_name in self.channel_map: logger.error("'{0}' has already been added.".format(pv_name)) return curve = self.chart.addYChannel(y_channel=pv_name, name=curve_name, color=color, lineStyle=line_style, lineWidth=line_width, symbol=symbol, symbolSize=symbol_size) self.channel_map[pv_name] = curve self.generate_pv_controls(pv_name, color) self.enable_chart_control_buttons() self.app.establish_widget_connections(self) def generate_pv_controls(self, pv_name, curve_color): """ Generate a set of widgets to manage the appearance of a curve. The set of widgets includes: 1. A checkbox which shows the curve on the chart if checked, and hide the curve if not checked 2. Two buttons -- Modify... and Remove. Modify... will bring up the Curve Settings dialog. Remove will delete the curve from the chart This set of widgets will be hidden initially, until the first curve is plotted. Parameters ---------- pv_name: str The name of the PV the current curve is being plotted for curve_color : QColor The color of the curve to paint for the checkbox label to help the user track the curve to the checkbox """ checkbox = QCheckBox() checkbox.setObjectName(pv_name) palette = checkbox.palette() palette.setColor(QPalette.Active, QPalette.WindowText, curve_color) checkbox.setPalette(palette) display_name = pv_name.split("://")[1] if len(display_name) > MAX_DISPLAY_PV_NAME_LENGTH: # Only display max allowed number of characters of the PV Name display_name = display_name[:int(MAX_DISPLAY_PV_NAME_LENGTH / 2) - 1] + "..." + \ display_name[-int(MAX_DISPLAY_PV_NAME_LENGTH / 2) + 2:] checkbox.setText(display_name) data_text = QLabel() data_text.setObjectName(pv_name) data_text.setPalette(palette) checkbox.setChecked(True) checkbox.clicked.connect( partial(self.handle_curve_chkbox_toggled, checkbox)) curve_btn_layout = QHBoxLayout() modify_curve_btn = QPushButton("Modify...") modify_curve_btn.setObjectName(pv_name) modify_curve_btn.setMaximumWidth(100) modify_curve_btn.clicked.connect( partial(self.display_curve_settings_dialog, pv_name)) focus_curve_btn = QPushButton("Focus") focus_curve_btn.setObjectName(pv_name) focus_curve_btn.setMaximumWidth(100) focus_curve_btn.clicked.connect(partial(self.focus_curve, pv_name)) annotate_curve_btn = QPushButton("Annotate...") annotate_curve_btn.setObjectName(pv_name) annotate_curve_btn.setMaximumWidth(100) annotate_curve_btn.clicked.connect( partial(self.annotate_curve, pv_name)) remove_curve_btn = QPushButton("Remove") remove_curve_btn.setObjectName(pv_name) remove_curve_btn.setMaximumWidth(100) remove_curve_btn.clicked.connect(partial(self.remove_curve, pv_name)) curve_btn_layout.addWidget(modify_curve_btn) curve_btn_layout.addWidget(focus_curve_btn) curve_btn_layout.addWidget(annotate_curve_btn) curve_btn_layout.addWidget(remove_curve_btn) individual_curve_layout = QVBoxLayout() individual_curve_layout.addWidget(checkbox) individual_curve_layout.addWidget(data_text) individual_curve_layout.addLayout(curve_btn_layout) size_policy = QSizePolicy() size_policy.setVerticalPolicy(QSizePolicy.Fixed) individual_curve_grpbx = QGroupBox() individual_curve_grpbx.setSizePolicy(size_policy) individual_curve_grpbx.setObjectName(pv_name) individual_curve_grpbx.setLayout(individual_curve_layout) self.curve_settings_layout.addWidget(individual_curve_grpbx) self.tab_panel.show() def handle_curve_chkbox_toggled(self, checkbox): """ Handle a checkbox's checked and unchecked events. If a checkbox is checked, find the curve from the channel map. If found, re-draw the curve with its previous appearance settings. If a checkbox is unchecked, remove the curve from the chart, but keep the cached data in the channel map. Parameters ---------- checkbox : QCheckBox The current checkbox being toggled """ pv_name = self._get_full_pv_name(checkbox.text()) if checkbox.isChecked(): curve = self.channel_map.get(pv_name, None) if curve: self.chart.addLegendItem(curve, pv_name, self.show_legend_chk.isChecked()) curve.show() else: curve = self.chart.findCurve(pv_name) if curve: curve.hide() self.chart.removeLegendItem(pv_name) def display_curve_settings_dialog(self, pv_name): """ Bring up the Curve Settings dialog to modify the appearance of a curve. Parameters ---------- pv_name : str The name of the PV the curve is being plotted for """ self.curve_settings_disp = CurveSettingsDisplay(self, pv_name) self.curve_settings_disp.show() def focus_curve(self, pv_name): curve = self.chart.findCurve(pv_name) if curve: self.chart.plotItem.setYRange(curve.minY, curve.maxY, padding=0) def annotate_curve(self, pv_name): curve = self.chart.findCurve(pv_name) if curve: annot = TextItem( html= '<div style="text-align: center"><span style="color: #FFF;">This is the' '</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3, 0.5), border='w', fill=(0, 0, 255, 100)) annot = TextItem("test", anchor=(-0.3, 0.5)) self.chart.annotateCurve(curve, annot) def remove_curve(self, pv_name): """ Remove a curve from the chart permanently. This will also clear the channel map cache from retaining the removed curve's appearance settings. Parameters ---------- pv_name : str The name of the PV the curve is being plotted for """ curve = self.chart.findCurve(pv_name) if curve: self.chart.removeYChannel(curve) del self.channel_map[pv_name] self.chart.removeLegendItem(pv_name) widgets = self.findChildren( (QCheckBox, QLabel, QPushButton, QGroupBox), pv_name) for w in widgets: w.deleteLater() if len(self.chart.getCurves()) < 1: self.enable_chart_control_buttons(False) self.show_legend_chk.setChecked(False) def handle_title_text_changed(self, new_text): self.chart.setPlotTitle(new_text) def handle_change_axis_settings_clicked(self): self.axis_settings_disp = AxisSettingsDisplay(self) self.axis_settings_disp.show() def handle_limit_time_span_checkbox_clicked(self, is_checked): self.chart_limit_time_span_lbl.setVisible(is_checked) self.chart_limit_time_span_hours_line_edt.setVisible(is_checked) self.chart_limit_time_span_minutes_line_edt.setVisible(is_checked) self.chart_limit_time_span_seconds_line_edt.setVisible(is_checked) self.chart_limit_time_span_activate_btn.setVisible(is_checked) self.chart_ring_buffer_size_lbl.setDisabled(is_checked) self.chart_ring_buffer_size_edt.setDisabled(is_checked) if not is_checked: self.chart_limit_time_span_chk.setText(self.limit_time_plan_text) def handle_time_span_edt_text_changed(self, new_text): try: self.time_span_limit_hours = int( self.chart_limit_time_span_hours_line_edt.text()) self.time_span_limit_minutes = int( self.chart_limit_time_span_minutes_line_edt.text()) self.time_span_limit_seconds = int( self.chart_limit_time_span_seconds_line_edt.text()) except ValueError as e: self.time_span_limit_hours = None self.time_span_limit_minutes = None self.time_span_limit_seconds = None if self.time_span_limit_hours is not None and self.time_span_limit_minutes is not None and \ self.time_span_limit_seconds is not None: self.chart_limit_time_span_activate_btn.setEnabled(True) else: self.chart_limit_time_span_activate_btn.setEnabled(False) def handle_chart_limit_time_span_activate_btn_clicked(self): if self.time_span_limit_hours is None or self.time_span_limit_minutes is None or \ self.time_span_limit_seconds is None: display_message_box( QMessageBox.Critical, "Invalid Values", "Hours, minutes, and seconds expect only integer values.") else: timeout_milliseconds = (self.time_span_limit_hours * 3600 + self.time_span_limit_minutes * 60 + self.time_span_limit_seconds) * 1000 self.chart.setTimeSpan(timeout_milliseconds / 1000.0) self.chart_ring_buffer_size_edt.setText( str(self.chart.getBufferSize())) def handle_buffer_size_changed(self, new_buffer_size): try: if new_buffer_size and int(new_buffer_size) > MINIMUM_BUFFER_SIZE: self.chart.setBufferSize(new_buffer_size) except ValueError: display_message_box(QMessageBox.Critical, "Invalid Values", "Only integer values are accepted.") def handle_redraw_rate_changed(self, new_redraw_rate): self.chart.maxRedrawRate = new_redraw_rate def handle_data_sampling_rate_changed(self, new_data_sampling_rate): # The chart expects the value in milliseconds sampling_rate_seconds = 1 / new_data_sampling_rate self.chart.setUpdateInterval(sampling_rate_seconds) def handle_background_color_button_clicked(self): selected_color = QColorDialog.getColor() self.chart.setBackgroundColor(selected_color) self.background_color_btn.setStyleSheet("background-color: " + selected_color.name()) def handle_axis_color_button_clicked(self): selected_color = QColorDialog.getColor() self.chart.setAxisColor(selected_color) self.axis_color_btn.setStyleSheet("background-color: " + selected_color.name()) def handle_grid_opacity_slider_mouse_release(self): self.grid_alpha = float(self.grid_opacity_slr.value()) / 10.0 self.chart.setShowXGrid(self.show_x_grid_chk.isChecked(), self.grid_alpha) self.chart.setShowYGrid(self.show_y_grid_chk.isChecked(), self.grid_alpha) def handle_show_x_grid_checkbox_clicked(self, is_checked): self.chart.setShowXGrid(is_checked, self.grid_alpha) self.axis_color_lbl.setEnabled(is_checked or self.show_y_grid_chk.isChecked()) self.axis_color_btn.setEnabled(is_checked or self.show_y_grid_chk.isChecked()) self.grid_opacity_lbl.setEnabled(is_checked or self.show_y_grid_chk.isChecked()) self.grid_opacity_slr.setEnabled(is_checked or self.show_y_grid_chk.isChecked()) def handle_show_y_grid_checkbox_clicked(self, is_checked): self.chart.setShowYGrid(is_checked, self.grid_alpha) self.axis_color_lbl.setEnabled(is_checked or self.show_x_grid_chk.isChecked()) self.axis_color_btn.setEnabled(is_checked or self.show_x_grid_chk.isChecked()) self.grid_opacity_lbl.setEnabled(is_checked or self.show_x_grid_chk.isChecked()) self.grid_opacity_slr.setEnabled(is_checked or self.show_x_grid_chk.isChecked()) def handle_show_legend_checkbox_clicked(self, is_checked): self.chart.setShowLegend(is_checked) def handle_export_data_btn_clicked(self): self.chart_data_export_disp = ChartDataExportDisplay(self) self.chart_data_export_disp.show() def handle_import_data_btn_clicked(self): open_file_info = QFileDialog.getOpenFileName(self, caption="Save File", filter="*." + IMPORT_FILE_FORMAT) open_file_name = open_file_info[0] if open_file_name: importer = SettingsImporter(self) importer.import_settings(open_file_name) def handle_sync_mode_radio_toggle(self, radio_btn): if radio_btn.isChecked(): if radio_btn.text() == "Synchronous": self.data_sampling_mode = SYNC_DATA_SAMPLING self.chart_data_sampling_rate_lbl.hide() self.chart_data_async_sampling_rate_spin.hide() self.chart.resetTimeSpan() self.chart_limit_time_span_chk.setChecked(False) self.chart_limit_time_span_chk.clicked.emit(False) self.chart_limit_time_span_chk.hide() self.graph_drawing_settings_grpbx.setFixedHeight(180) self.chart.setUpdatesAsynchronously(False) elif radio_btn.text() == "Asynchronous": self.data_sampling_mode = ASYNC_DATA_SAMPLING self.chart_data_sampling_rate_lbl.show() self.chart_data_async_sampling_rate_spin.show() self.chart_limit_time_span_chk.show() self.graph_drawing_settings_grpbx.setFixedHeight(270) self.chart.setUpdatesAsynchronously(True) self.app.establish_widget_connections(self) def handle_auto_scale_btn_clicked(self): self.chart.resetAutoRangeX() self.chart.resetAutoRangeY() def handle_view_all_button_clicked(self): self.chart.getViewBox().autoRange() def handle_pause_chart_btn_clicked(self): if self.chart.pausePlotting(): self.pause_chart_btn.setText(self.pause_chart_text) else: self.pause_chart_btn.setText(self.resume_chart_text) def handle_reset_chart_btn_clicked(self): self.chart.getViewBox().setXRange(DEFAULT_X_MIN, 0) self.chart.resetAutoRangeY() @Slot() def handle_reset_chart_settings_btn_clicked(self): self.chart_ring_buffer_size_edt.setText(str(DEFAULT_BUFFER_SIZE)) self.chart_redraw_rate_spin.setValue(DEFAULT_REDRAW_RATE_HZ) self.chart_data_async_sampling_rate_spin.setValue( DEFAULT_DATA_SAMPLING_RATE_HZ) self.chart_data_sampling_rate_lbl.hide() self.chart_data_async_sampling_rate_spin.hide() self.chart_sync_mode_async_radio.setChecked(True) self.chart_sync_mode_async_radio.toggled.emit(True) self.chart_limit_time_span_chk.setChecked(False) self.chart_limit_time_span_chk.setText(self.limit_time_plan_text) self.chart_limit_time_span_chk.clicked.emit(False) self.chart.setUpdatesAsynchronously(True) self.chart.resetTimeSpan() self.chart.resetUpdateInterval() self.chart.setBufferSize(DEFAULT_BUFFER_SIZE) self.chart.setBackgroundColor(DEFAULT_CHART_BACKGROUND_COLOR) self.background_color_btn.setStyleSheet( "background-color: " + DEFAULT_CHART_BACKGROUND_COLOR.name()) self.chart.setAxisColor(DEFAULT_CHART_AXIS_COLOR) self.axis_color_btn.setStyleSheet("background-color: " + DEFAULT_CHART_AXIS_COLOR.name()) self.grid_opacity_slr.setValue(5) self.show_x_grid_chk.setChecked(False) self.show_x_grid_chk.clicked.emit(False) self.show_y_grid_chk.setChecked(False) self.show_y_grid_chk.clicked.emit(False) self.show_legend_chk.setChecked(False) self.chart.setShowXGrid(False) self.chart.setShowYGrid(False) self.chart.setShowLegend(False) def enable_chart_control_buttons(self, enabled=True): self.auto_scale_btn.setEnabled(enabled) self.view_all_btn.setEnabled(enabled) self.reset_chart_btn.setEnabled(enabled) self.pause_chart_btn.setText(self.pause_chart_text) self.pause_chart_btn.setEnabled(enabled) self.export_data_btn.setEnabled(enabled) def _get_full_pv_name(self, pv_name): """ Append the protocol to the PV Name. Parameters ---------- pv_name : str The name of the PV the curve is being plotted for """ if pv_name and "://" not in pv_name: pv_name = ''.join([self.pv_protocol_cmb.currentText(), pv_name]) return pv_name def handle_update_datetime_timer_timeout(self): current_label = self.chart.getBottomAxisLabel() new_label = "Current Time: " + PyDMChartingDisplay.get_current_datetime( ) if X_AXIS_LABEL_SEPARATOR in current_label: current_label = current_label[current_label. find(X_AXIS_LABEL_SEPARATOR) + len(X_AXIS_LABEL_SEPARATOR):] new_label += X_AXIS_LABEL_SEPARATOR + current_label self.chart.setLabel("bottom", text=new_label) def update_curve_data(self, curve): """ Determine if the PV is active. If not, disable the related PV controls. If the PV is active, update the PV controls' states. Parameters ---------- curve : PlotItem A PlotItem, i.e. a plot, to draw on the chart. """ pv_name = curve.name() max_x = self.chart.getViewBox().viewRange()[1][0] max_y = self.chart.getViewBox().viewRange()[1][1] current_y = curve.data_buffer[1, -1] widgets = self.findChildren((QCheckBox, QLabel, QPushButton), pv_name) for w in widgets: if np.isnan(current_y): if isinstance(w, QCheckBox): w.setChecked(False) else: if isinstance(w, QCheckBox) and not w.isEnabled(): w.setChecked(True) if isinstance(w, QLabel): w.clear() w.setText( "(yMin = {0:.3f}, yMax = {1:.3f}) y = {2:.3f}".format( max_x, max_y, current_y)) w.show() w.setEnabled(not np.isnan(current_y)) if isinstance(w, QPushButton) and w.text() == "Remove": # Enable the Remove button to make removing inactive PVs possible anytime w.setEnabled(True) def show_mouse_coordinates(self, x, y): self.cross_hair_coord_lbl.clear() self.cross_hair_coord_lbl.setText("x = {0:.3f}, y = {1:.3f}".format( x, y)) @staticmethod def get_current_datetime(): current_date = datetime.datetime.now().strftime("%b %d, %Y") current_time = datetime.datetime.now().strftime("%H:%M:%S") current_datetime = current_time + ' (' + current_date + ')' return current_datetime @property def gridAlpha(self): return self.grid_alpha
class PreferencesWindow(PyDialog): """ +-------------+ | Preferences | +---------------------------------+ | Text Size ______ Default | | Annotation Color ______ | | Annotation Size ______ | | Picker Size ______ | | Back Color ______ | | Text Color ______ | | | | Apply OK Cancel | +---------------------------------+ """ def __init__(self, data, win_parent=None): """ Saves the data members from data and performs type checks """ PyDialog.__init__(self, data, win_parent) self._updated_preference = False self._default_font_size = data['font_size'] self._default_annotation_size = 18 self._default_clipping_min = data['clipping_min'] self._default_clipping_max = data['clipping_max'] self.dim_max = data['dim_max'] self._default_annotation_size = data['annotation_size'] # int #self.out_data = data self._picker_size = data['picker_size'] * 100. self.annotation_color_float, self.annotation_color_int = _check_color( data['annotation_color']) self.background_color_float, self.background_color_int = _check_color( data['background_color']) self.text_color_float, self.text_color_int = _check_color( data['text_color']) #self.setupUi(self) self.setWindowTitle('Preferences') self.create_widgets() self.create_layout() self.set_connections() self.on_set_font(self._default_font_size) #self.show() def create_widgets(self): """creates the display window""" # Text Size self.font_size = QLabel("Text Size:") self.font_size_edit = QSpinBox(self) self.font_size_edit.setValue(self._default_font_size) self.font_size_edit.setRange(7, 20) self.font_size_button = QPushButton("Default") #----------------------------------------------------------------------- # Annotation Color self.annotation_color = QLabel("Annotation Color:") self.annotation_color_edit = QPushButtonColor( self.annotation_color_int) # Background Color self.background_color = QLabel("Background Color:") self.background_color_edit = QPushButtonColor( self.background_color_int) # Text Color self.text_color = QLabel("Text Color:") self.text_color_edit = QPushButtonColor(self.text_color_int) #----------------------------------------------------------------------- # Annotation Size self.annotation_size = QLabel("Annotation Size:") self.annotation_size_edit = QSpinBox(self) self.annotation_size_edit.setRange(1, 500) self.annotation_size_edit.setValue(self._default_annotation_size) self.annotation_size_button = QPushButton("Default") #----------------------------------------------------------------------- # Picker Size self.picker_size = QLabel("Picker Size (% of Screen):") self.picker_size_edit = QDoubleSpinBox(self) self.picker_size_edit.setRange(0., 10.) log_dim = log10(self.dim_max) decimals = int(ceil(abs(log_dim))) decimals = max(6, decimals) self.picker_size_edit.setDecimals(decimals) self.picker_size_edit.setSingleStep(10. / 5000.) self.picker_size_edit.setValue(self._picker_size) #----------------------------------------------------------------------- # Clipping Min self.clipping_min = QLabel("Clipping Min:") self.clipping_min_edit = QLineEdit(str(self._default_clipping_min)) self.clipping_min_button = QPushButton("Default") # Clipping Max self.clipping_max = QLabel("Clipping Max:") self.clipping_max_edit = QLineEdit(str(self._default_clipping_max)) self.clipping_max_button = QPushButton("Default") #----------------------------------------------------------------------- # closing self.apply_button = QPushButton("Apply") self.ok_button = QPushButton("OK") self.cancel_button = QPushButton("Cancel") def create_legend_widgets(self): """ Creates the widgets for the legend control Name Itailic Bold Font ==== ======= ===== ======== Title check check pulldown Label check check pulldown """ self.name_label = QLabel("Name:") self.italic_label = QLabel("Italic:") self.bold_label = QLabel("Bold:") self.font_label = QLabel("Font:") self.legend_label = QLabel("Legend:") self.legend_title_name = QLabel("Title") self.legend_title_italic_check = QCheckBox() self.legend_title_bold_check = QCheckBox() self.legend_title_font_edit = QComboBox() self.legend_title_font_edit.addItems(['cat', 'dog', 'frog']) self.legend_label_italic_name = QLabel("Label") self.legend_label_italic_check = QCheckBox() self.legend_label_bold_check = QCheckBox() self.legend_label_font_edit = QComboBox() self.legend_label_font_edit.addItems(['cat2', 'dog2', 'frog2']) def create_legend_layout(self): """ Creates the layout for the legend control Name Itailic Bold Font ==== ======= ===== ======== Title check check pulldown Label check check pulldown """ grid2 = QGridLayout() grid2.addWidget(self.legend_label, 0, 0) grid2.addWidget(self.name_label, 1, 0) grid2.addWidget(self.italic_label, 1, 1) grid2.addWidget(self.bold_label, 1, 2) grid2.addWidget(self.font_label, 1, 3) grid2.addWidget(self.legend_title_name, 2, 0) grid2.addWidget(self.legend_title_italic_check, 2, 1) grid2.addWidget(self.legend_title_bold_check, 2, 2) grid2.addWidget(self.legend_title_font_edit, 2, 3) grid2.addWidget(self.legend_label_italic_name, 3, 0) grid2.addWidget(self.legend_label_italic_check, 3, 1) grid2.addWidget(self.legend_label_bold_check, 3, 2) grid2.addWidget(self.legend_label_font_edit, 3, 3) return grid2 def create_layout(self): grid = QGridLayout() grid.addWidget(self.font_size, 0, 0) grid.addWidget(self.font_size_edit, 0, 1) grid.addWidget(self.font_size_button, 0, 2) grid.addWidget(self.background_color, 1, 0) grid.addWidget(self.background_color_edit, 1, 1) grid.addWidget(self.text_color, 2, 0) grid.addWidget(self.text_color_edit, 2, 1) grid.addWidget(self.annotation_color, 3, 0) grid.addWidget(self.annotation_color_edit, 3, 1) grid.addWidget(self.annotation_size, 4, 0) grid.addWidget(self.annotation_size_edit, 4, 1) grid.addWidget(self.annotation_size_button, 4, 2) grid.addWidget(self.picker_size, 5, 0) grid.addWidget(self.picker_size_edit, 5, 1) #grid.addWidget(self.clipping_min, 6, 0) #grid.addWidget(self.clipping_min_edit, 6, 1) #grid.addWidget(self.clipping_min_button, 6, 2) #grid.addWidget(self.clipping_max, 7, 0) #grid.addWidget(self.clipping_max_edit, 7, 1) #grid.addWidget(self.clipping_max_button, 7, 2) #self.create_legend_widgets() #grid2 = self.create_legend_layout() ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.apply_button) ok_cancel_box.addWidget(self.ok_button) ok_cancel_box.addWidget(self.cancel_button) vbox = QVBoxLayout() vbox.addLayout(grid) #vbox.addStretch() #vbox.addLayout(grid2) vbox.addStretch() vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def set_connections(self): self.font_size_button.clicked.connect(self.on_default_font_size) self.font_size_edit.valueChanged.connect(self.on_set_font) self.annotation_size_button.clicked.connect( self.on_default_annotation_size) self.annotation_size_edit.editingFinished.connect( self.on_set_annotation_size) self.annotation_size_edit.valueChanged.connect( self.on_set_annotation_size) self.annotation_color_edit.clicked.connect(self.on_annotation_color) self.background_color_edit.clicked.connect(self.on_background_color) self.text_color_edit.clicked.connect(self.on_text_color) self.picker_size_edit.valueChanged.connect(self.on_picker_size) self.picker_size_edit.editingFinished.connect(self.on_picker_size) self.picker_size_edit.valueChanged.connect(self.on_picker_size) self.clipping_min_button.clicked.connect(self.on_default_clipping_min) self.clipping_max_button.clicked.connect(self.on_default_clipping_max) self.apply_button.clicked.connect(self.on_apply) self.ok_button.clicked.connect(self.on_ok) self.cancel_button.clicked.connect(self.on_cancel) # closeEvent def on_set_font(self, value=None): """update the font for the current window""" if value is None: value = self.font_size_edit.value() font = QtGui.QFont() font.setPointSize(value) self.setFont(font) def on_set_annotation_size(self, value=None): """update the annotation size""" if value is None: value = int(self.annotation_size_edit.text()) self._annotation_size = value #self.on_apply(force=True) #self.min_edit.setText(str(self._default_min)) #self.min_edit.setStyleSheet("QLineEdit{background: white;}") self.update_annotation_size_color() def update_annotation_size_color(self): if self.win_parent is not None: self.win_parent.settings.set_annotation_size_color( size=self._annotation_size) def on_annotation_color(self): rgb_color_ints = self.annotation_color_int title = "Choose an annotation color" passed, rgb_color_ints, rgb_color_floats = self.on_color( self.annotation_color_edit, rgb_color_ints, title) if passed: self.annotation_color_int = rgb_color_ints self.annotation_color_float = rgb_color_floats self.update_annotation_size_color() def on_background_color(self): """ Choose a background color """ rgb_color_ints = self.background_color_int title = "Choose a background color" passed, rgb_color_ints, rgb_color_floats = self.on_color( self.background_color_edit, rgb_color_ints, title) if passed: self.background_color_int = rgb_color_ints self.background_color_float = rgb_color_floats if self.win_parent is not None: self.win_parent.settings.set_background_color(rgb_color_floats) def on_text_color(self): """ Choose a text color """ rgb_color_ints = self.text_color_int title = "Choose a text color" passed, rgb_color_ints, rgb_color_floats = self.on_color( self.text_color_edit, rgb_color_ints, title) if passed: self.text_color_int = rgb_color_ints self.text_color_float = rgb_color_floats if self.win_parent is not None: self.win_parent.settings.set_text_color(rgb_color_floats) def on_color(self, color_edit, rgb_color_ints, title): """pops a color dialog""" col = QColorDialog.getColor(QtGui.QColor(*rgb_color_ints), self, title) if not col.isValid(): return False, rgb_color_ints, None color_float = col.getRgbF()[:3] # floats color_int = [int(colori * 255) for colori in color_float] assert isinstance(color_float[0], float), color_float assert isinstance(color_int[0], int), color_int color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(color_int) + #"border:1px solid rgb(255, 170, 255); " "}") return True, color_int, color_float def on_picker_size(self): self._picker_size = float(self.picker_size_edit.text()) if self.win_parent is not None: self.win_parent.element_picker_size = self._picker_size / 100. #self.on_apply(force=True) def on_default_font_size(self): self.font_size_edit.setValue(self._default_font_size) self.on_set_font(self._default_font_size) def on_default_annotation_size(self): self.annotation_size_edit.setValue(self._default_annotation_size) self.on_set_annotation_size(self._default_annotation_size) def on_default_clipping_min(self): self.clipping_min_edit.setText(str(self._default_clipping_min)) self.clipping_min_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_clipping_max(self): self.clipping_max_edit.setText(str(self._default_clipping_max)) self.clipping_max_edit.setStyleSheet("QLineEdit{background: white;}") @staticmethod def check_float(cell): text = cell.text() value = float(text) return value, True @staticmethod def check_label_float(cell): text = cell.text() try: value = eval_float_from_string(text) cell.setStyleSheet("QLineEdit{background: white;}") return value, True except ValueError: cell.setStyleSheet("QLineEdit{background: red;}") return None, False def on_validate(self): font_size_value, flag0 = self.check_float(self.font_size_edit) annotation_size_value, flag1 = self.check_float( self.annotation_size_edit) assert isinstance(self.annotation_color_float[0], float), self.annotation_color_float assert isinstance(self.annotation_color_int[0], int), self.annotation_color_int picker_size_value, flag2 = self.check_float(self.picker_size_edit) clipping_min_value, flag3 = self.check_label_float( self.clipping_min_edit) clipping_max_value, flag4 = self.check_label_float( self.clipping_max_edit) if all([flag0, flag1, flag2, flag3, flag4]): self._annotation_size = annotation_size_value self._picker_size = picker_size_value self.out_data['font_size'] = int(font_size_value) self.out_data['clipping_min'] = min(clipping_min_value, clipping_max_value) self.out_data['clipping_max'] = max(clipping_min_value, clipping_max_value) self.out_data['clicked_ok'] = True return True return False def on_apply(self, force=False): passed = self.on_validate() if (passed or force) and self.win_parent is not None: self.win_parent.settings.on_set_font_size( self.out_data['font_size']) #self.win_parent.settings.set_annotation_size_color(self._annotation_size) #self.win_parent.element_picker_size = self._picker_size / 100. if passed and self.win_parent is not None: self.win_parent._apply_clipping(self.out_data) return passed def on_ok(self): passed = self.on_apply() if passed: self.close() #self.destroy() def on_cancel(self): self.out_data['close'] = True self.close()
def __init__(self, parent=None, args=[], macros=None): """ Create all the widgets, including any child dialogs. Parameters ---------- parent : QWidget The parent widget of the charting display args : list The command parameters macros : str Macros to modify the UI parameters at runtime """ super(PyDMChartingDisplay, self).__init__(parent=parent, args=args, macros=macros) self.channel_map = dict() self.setWindowTitle("PyDM Charting Tool") self.main_layout = QVBoxLayout() self.body_layout = QVBoxLayout() self.pv_layout = QHBoxLayout() self.pv_name_line_edt = QLineEdit() self.pv_name_line_edt.setAcceptDrops(True) self.pv_name_line_edt.installEventFilter(self) self.pv_protocol_cmb = QComboBox() self.pv_protocol_cmb.addItems(["ca://", "archive://"]) self.pv_connect_push_btn = QPushButton("Connect") self.pv_connect_push_btn.clicked.connect(self.add_curve) self.tab_panel = QTabWidget() self.tab_panel.setMaximumWidth(450) self.curve_settings_tab = QWidget() self.chart_settings_tab = QWidget() self.charting_layout = QHBoxLayout() self.chart = PyDMTimePlot(plot_by_timestamps=False, plot_display=self) self.chart.setPlotTitle("Time Plot") self.splitter = QSplitter() self.curve_settings_layout = QVBoxLayout() self.curve_settings_layout.setAlignment(Qt.AlignTop) self.curve_settings_layout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.curve_settings_layout.setSpacing(5) self.crosshair_settings_layout = QVBoxLayout() self.crosshair_settings_layout.setAlignment(Qt.AlignTop) self.crosshair_settings_layout.setSpacing(5) self.enable_crosshair_chk = QCheckBox("Enable Crosshair") self.cross_hair_coord_lbl = QLabel() self.curve_settings_inner_frame = QFrame() self.curve_settings_inner_frame.setLayout(self.curve_settings_layout) self.curve_settings_scroll = QScrollArea() self.curve_settings_scroll.setVerticalScrollBarPolicy( Qt.ScrollBarAsNeeded) self.curve_settings_scroll.setWidget(self.curve_settings_inner_frame) self.curves_tab_layout = QHBoxLayout() self.curves_tab_layout.addWidget(self.curve_settings_scroll) self.enable_crosshair_chk.setChecked(False) self.enable_crosshair_chk.clicked.connect( self.handle_enable_crosshair_checkbox_clicked) self.enable_crosshair_chk.clicked.emit(False) self.chart_settings_layout = QVBoxLayout() self.chart_settings_layout.setAlignment(Qt.AlignTop) self.chart_layout = QVBoxLayout() self.chart_panel = QWidget() self.chart_control_layout = QHBoxLayout() self.chart_control_layout.setAlignment(Qt.AlignHCenter) self.chart_control_layout.setSpacing(10) self.view_all_btn = QPushButton("View All") self.view_all_btn.clicked.connect(self.handle_view_all_button_clicked) self.view_all_btn.setEnabled(False) self.auto_scale_btn = QPushButton("Auto Scale") self.auto_scale_btn.clicked.connect(self.handle_auto_scale_btn_clicked) self.auto_scale_btn.setEnabled(False) self.reset_chart_btn = QPushButton("Reset") self.reset_chart_btn.clicked.connect( self.handle_reset_chart_btn_clicked) self.reset_chart_btn.setEnabled(False) self.resume_chart_text = "Resume" self.pause_chart_text = "Pause" self.pause_chart_btn = QPushButton(self.pause_chart_text) self.pause_chart_btn.clicked.connect( self.handle_pause_chart_btn_clicked) self.title_settings_layout = QVBoxLayout() self.title_settings_layout.setSpacing(10) self.title_settings_grpbx = QGroupBox() self.title_settings_grpbx.setFixedHeight(150) self.import_data_btn = QPushButton("Import Data...") self.import_data_btn.clicked.connect( self.handle_import_data_btn_clicked) self.export_data_btn = QPushButton("Export Data...") self.export_data_btn.clicked.connect( self.handle_export_data_btn_clicked) self.chart_title_lbl = QLabel(text="Chart Title") self.chart_title_line_edt = QLineEdit() self.chart_title_line_edt.setText(self.chart.getPlotTitle()) self.chart_title_line_edt.textChanged.connect( self.handle_title_text_changed) self.chart_change_axis_settings_btn = QPushButton( text="Change Axis Settings...") self.chart_change_axis_settings_btn.clicked.connect( self.handle_change_axis_settings_clicked) self.update_datetime_timer = QTimer(self) self.update_datetime_timer.timeout.connect( self.handle_update_datetime_timer_timeout) self.chart_sync_mode_layout = QVBoxLayout() self.chart_sync_mode_layout.setSpacing(5) self.chart_sync_mode_grpbx = QGroupBox("Data Sampling Mode") self.chart_sync_mode_grpbx.setFixedHeight(80) self.chart_sync_mode_sync_radio = QRadioButton("Synchronous") self.chart_sync_mode_async_radio = QRadioButton("Asynchronous") self.chart_sync_mode_async_radio.setChecked(True) self.graph_drawing_settings_layout = QVBoxLayout() self.chart_redraw_rate_lbl = QLabel("Redraw Rate (Hz)") self.chart_redraw_rate_spin = QSpinBox() self.chart_redraw_rate_spin.setRange(MIN_REDRAW_RATE_HZ, MAX_REDRAW_RATE_HZ) self.chart_redraw_rate_spin.setValue(DEFAULT_REDRAW_RATE_HZ) self.chart_redraw_rate_spin.valueChanged.connect( self.handle_redraw_rate_changed) self.chart_data_sampling_rate_lbl = QLabel( "Asynchronous Data Sampling Rate (Hz)") self.chart_data_async_sampling_rate_spin = QSpinBox() self.chart_data_async_sampling_rate_spin.setRange( MIN_DATA_SAMPLING_RATE_HZ, MAX_DATA_SAMPLING_RATE_HZ) self.chart_data_async_sampling_rate_spin.setValue( DEFAULT_DATA_SAMPLING_RATE_HZ) self.chart_data_async_sampling_rate_spin.valueChanged.connect( self.handle_data_sampling_rate_changed) self.chart_data_sampling_rate_lbl.hide() self.chart_data_async_sampling_rate_spin.hide() self.chart_limit_time_span_layout = QHBoxLayout() self.chart_limit_time_span_layout.setSpacing(5) self.limit_time_plan_text = "Limit Time Span" self.chart_limit_time_span_chk = QCheckBox(self.limit_time_plan_text) self.chart_limit_time_span_chk.hide() self.chart_limit_time_span_lbl = QLabel("Hours : Minutes : Seconds") self.chart_limit_time_span_hours_line_edt = QLineEdit() self.chart_limit_time_span_minutes_line_edt = QLineEdit() self.chart_limit_time_span_seconds_line_edt = QLineEdit() self.chart_limit_time_span_activate_btn = QPushButton("Apply") self.chart_limit_time_span_activate_btn.setDisabled(True) self.chart_ring_buffer_size_lbl = QLabel("Ring Buffer Size") self.chart_ring_buffer_size_edt = QLineEdit() self.chart_ring_buffer_size_edt.installEventFilter(self) self.chart_ring_buffer_size_edt.textChanged.connect( self.handle_buffer_size_changed) self.chart_ring_buffer_size_edt.setText(str(DEFAULT_BUFFER_SIZE)) self.show_legend_chk = QCheckBox("Show Legend") self.show_legend_chk.setChecked(self.chart.showLegend) self.show_legend_chk.clicked.connect( self.handle_show_legend_checkbox_clicked) self.graph_background_color_layout = QFormLayout() self.background_color_lbl = QLabel("Graph Background Color ") self.background_color_btn = QPushButton() self.background_color_btn.setStyleSheet( "background-color: " + self.chart.getBackgroundColor().name()) self.background_color_btn.setContentsMargins(10, 0, 5, 5) self.background_color_btn.setMaximumWidth(20) self.background_color_btn.clicked.connect( self.handle_background_color_button_clicked) self.axis_settings_layout = QVBoxLayout() self.axis_settings_layout.setSpacing(5) self.show_x_grid_chk = QCheckBox("Show x Grid") self.show_x_grid_chk.setChecked(self.chart.showXGrid) self.show_x_grid_chk.clicked.connect( self.handle_show_x_grid_checkbox_clicked) self.show_y_grid_chk = QCheckBox("Show y Grid") self.show_y_grid_chk.setChecked(self.chart.showYGrid) self.show_y_grid_chk.clicked.connect( self.handle_show_y_grid_checkbox_clicked) self.axis_color_lbl = QLabel("Axis and Grid Color") self.axis_color_lbl.setEnabled(False) self.axis_color_btn = QPushButton() self.axis_color_btn.setStyleSheet("background-color: " + DEFAULT_CHART_AXIS_COLOR.name()) self.axis_color_btn.setContentsMargins(10, 0, 5, 5) self.axis_color_btn.setMaximumWidth(20) self.axis_color_btn.clicked.connect( self.handle_axis_color_button_clicked) self.axis_color_btn.setEnabled(False) self.grid_opacity_lbl = QLabel("Grid Opacity") self.grid_opacity_lbl.setEnabled(False) self.grid_opacity_slr = QSlider(Qt.Horizontal) self.grid_opacity_slr.setFocusPolicy(Qt.StrongFocus) self.grid_opacity_slr.setRange(0, 10) self.grid_opacity_slr.setValue(5) self.grid_opacity_slr.setTickInterval(1) self.grid_opacity_slr.setSingleStep(1) self.grid_opacity_slr.setTickPosition(QSlider.TicksBelow) self.grid_opacity_slr.valueChanged.connect( self.handle_grid_opacity_slider_mouse_release) self.grid_opacity_slr.setEnabled(False) self.reset_chart_settings_btn = QPushButton("Reset Chart Settings") self.reset_chart_settings_btn.clicked.connect( self.handle_reset_chart_settings_btn_clicked) self.curve_checkbox_panel = QWidget() self.graph_drawing_settings_grpbx = QGroupBox() self.graph_drawing_settings_grpbx.setFixedHeight(270) self.axis_settings_grpbx = QGroupBox() self.axis_settings_grpbx.setFixedHeight(180) self.app = QApplication.instance() self.setup_ui() self.curve_settings_disp = None self.axis_settings_disp = None self.chart_data_export_disp = None self.chart_data_import_disp = None self.grid_alpha = 5 self.time_span_limit_hours = None self.time_span_limit_minutes = None self.time_span_limit_seconds = None self.data_sampling_mode = ASYNC_DATA_SAMPLING
def create_spinbox(self, prefix, suffix, option, default=NoDefault, min_=None, max_=None, step=None, tip=None, section=None): widget = QWidget(self) if prefix: plabel = QLabel(prefix) widget.plabel = plabel else: plabel = None if suffix: slabel = QLabel(suffix) widget.slabel = slabel else: slabel = None if step is not None: if type(step) is int: spinbox = QSpinBox() else: spinbox = QDoubleSpinBox() spinbox.setDecimals(1) spinbox.setSingleStep(step) else: spinbox = QSpinBox() if min_ is not None: spinbox.setMinimum(min_) if max_ is not None: spinbox.setMaximum(max_) if tip is not None: spinbox.setToolTip(tip) self.spinboxes[spinbox] = (section, option, default) layout = QHBoxLayout() for subwidget in (plabel, spinbox, slabel): if subwidget is not None: layout.addWidget(subwidget) layout.addStretch(1) layout.setContentsMargins(0, 0, 0, 0) widget.spinbox = spinbox widget.setLayout(layout) return widget
class QtLabelsControls(QtLayerControls): """Qt view and controls for the napari Labels layer. Parameters ---------- layer : napari.layers.Labels An instance of a napari Labels layer. Attributes ---------- button_group : qtpy.QtWidgets.QButtonGroup Button group of labels layer modes: PAN_ZOOM, PICKER, PAINT, ERASE, or FILL. colormapUpdate : qtpy.QtWidgets.QPushButton Button to update colormap of label layer. contigCheckBox : qtpy.QtWidgets.QCheckBox Checkbox to control if label layer is contiguous. fill_button : qtpy.QtWidgets.QtModeRadioButton Button to select FILL mode on Labels layer. grid_layout : qtpy.QtWidgets.QGridLayout Layout of Qt widget controls for the layer. layer : napari.layers.Labels An instance of a napari Labels layer. ndimCheckBox : qtpy.QtWidgets.QCheckBox Checkbox to control if label layer is n-dimensional. paint_button : qtpy.QtWidgets.QtModeRadioButton Button to select PAINT mode on Labels layer. panzoom_button : qtpy.QtWidgets.QtModeRadioButton Button to select PAN_ZOOM mode on Labels layer. pick_button : qtpy.QtWidgets.QtModeRadioButton Button to select PICKER mode on Labels layer. erase_button : qtpy.QtWidgets.QtModeRadioButton Button to select ERASE mode on Labels layer. selectionSpinBox : qtpy.QtWidgets.QSpinBox Widget to select a specific label by its index. Raises ------ ValueError Raise error if label mode is not PAN_ZOOM, PICKER, PAINT, ERASE, or FILL. """ def __init__(self, layer): super().__init__(layer) self.layer.events.mode.connect(self._on_mode_change) self.layer.events.selected_label.connect(self._on_selection_change) self.layer.events.brush_size.connect(self._on_brush_size_change) self.layer.events.contiguous.connect(self._on_contig_change) self.layer.events.n_dimensional.connect(self._on_n_dim_change) self.layer.events.editable.connect(self._on_editable_change) self.layer.events.preserve_labels.connect( self._on_preserve_labels_change ) self.layer.events.color_mode.connect(self._on_color_mode_change) # selection spinbox self.selectionSpinBox = QSpinBox() self.selectionSpinBox.setKeyboardTracking(False) self.selectionSpinBox.setSingleStep(1) self.selectionSpinBox.setMinimum(0) self.selectionSpinBox.setMaximum(2147483647) self.selectionSpinBox.valueChanged.connect(self.changeSelection) self.selectionSpinBox.setAlignment(Qt.AlignCenter) self._on_selection_change() sld = QSlider(Qt.Horizontal) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(1) sld.setMaximum(40) sld.setSingleStep(1) sld.valueChanged.connect(self.changeSize) self.brushSizeSlider = sld self._on_brush_size_change() contig_cb = QCheckBox() contig_cb.setToolTip('contiguous editing') contig_cb.stateChanged.connect(self.change_contig) self.contigCheckBox = contig_cb self._on_contig_change() ndim_cb = QCheckBox() ndim_cb.setToolTip('n-dimensional editing') ndim_cb.stateChanged.connect(self.change_ndim) self.ndimCheckBox = ndim_cb self._on_n_dim_change() preserve_labels_cb = QCheckBox() preserve_labels_cb.setToolTip( 'preserve existing labels while painting' ) preserve_labels_cb.stateChanged.connect(self.change_preserve_labels) self.preserveLabelsCheckBox = preserve_labels_cb self._on_preserve_labels_change() # shuffle colormap button self.colormapUpdate = QtModePushButton( None, 'shuffle', slot=self.changeColor, tooltip='shuffle colors', ) self.panzoom_button = QtModeRadioButton( layer, 'zoom', Mode.PAN_ZOOM, tooltip='Pan/zoom mode (Space)', checked=True, ) self.pick_button = QtModeRadioButton( layer, 'picker', Mode.PICK, tooltip='Pick mode' ) self.paint_button = QtModeRadioButton( layer, 'paint', Mode.PAINT, tooltip='Paint mode' ) btn = 'Cmd' if sys.platform == 'darwin' else 'Ctrl' self.fill_button = QtModeRadioButton( layer, 'fill', Mode.FILL, tooltip=f'Fill mode ({btn})' ) self.erase_button = QtModeRadioButton( layer, 'erase', Mode.ERASE, tooltip='Erase mode (Alt)' ) self.button_group = QButtonGroup(self) self.button_group.addButton(self.panzoom_button) self.button_group.addButton(self.paint_button) self.button_group.addButton(self.pick_button) self.button_group.addButton(self.fill_button) self.button_group.addButton(self.erase_button) self._on_editable_change() button_row = QHBoxLayout() button_row.addStretch(1) button_row.addWidget(self.colormapUpdate) button_row.addWidget(self.erase_button) button_row.addWidget(self.fill_button) button_row.addWidget(self.paint_button) button_row.addWidget(self.pick_button) button_row.addWidget(self.panzoom_button) button_row.setSpacing(4) button_row.setContentsMargins(0, 0, 0, 5) brush_shape_comboBox = QComboBox(self) brush_shape_comboBox.addItems(LabelBrushShape.keys()) index = brush_shape_comboBox.findText( self.layer.brush_shape, Qt.MatchFixedString ) brush_shape_comboBox.setCurrentIndex(index) brush_shape_comboBox.activated[str].connect(self.change_brush_shape) self.brushShapeComboBox = brush_shape_comboBox self._on_brush_shape_change() color_mode_comboBox = QComboBox(self) color_mode_comboBox.addItems(LabelColorMode.keys()) index = color_mode_comboBox.findText( self.layer.color_mode, Qt.MatchFixedString ) color_mode_comboBox.setCurrentIndex(index) color_mode_comboBox.activated[str].connect(self.change_color_mode) self.colorModeComboBox = color_mode_comboBox self._on_color_mode_change() color_layout = QHBoxLayout() color_layout.addWidget(QtColorBox(layer)) color_layout.addWidget(self.selectionSpinBox) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addLayout(button_row, 0, 0, 1, 4) self.grid_layout.addWidget(QLabel('label:'), 1, 0, 1, 1) self.grid_layout.addLayout(color_layout, 1, 1, 1, 3) self.grid_layout.addWidget(QLabel('opacity:'), 2, 0, 1, 1) self.grid_layout.addWidget(self.opacitySlider, 2, 1, 1, 3) self.grid_layout.addWidget(QLabel('brush size:'), 3, 0, 1, 1) self.grid_layout.addWidget(self.brushSizeSlider, 3, 1, 1, 3) self.grid_layout.addWidget(QLabel('brush shape:'), 4, 0, 1, 1) self.grid_layout.addWidget(self.brushShapeComboBox, 4, 1, 1, 3) self.grid_layout.addWidget(QLabel('blending:'), 5, 0, 1, 1) self.grid_layout.addWidget(self.blendComboBox, 5, 1, 1, 3) self.grid_layout.addWidget(QLabel('color mode:'), 6, 0, 1, 1) self.grid_layout.addWidget(self.colorModeComboBox, 6, 1, 1, 3) self.grid_layout.addWidget(QLabel('contiguous:'), 7, 0, 1, 1) self.grid_layout.addWidget(self.contigCheckBox, 7, 1, 1, 1) self.grid_layout.addWidget(QLabel('n-dim:'), 7, 2, 1, 1) self.grid_layout.addWidget(self.ndimCheckBox, 7, 3, 1, 1) self.grid_layout.addWidget(QLabel('preserve labels:'), 8, 0, 1, 2) self.grid_layout.addWidget(self.preserveLabelsCheckBox, 8, 1, 1, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4) def mouseMoveEvent(self, event): """On mouse move, set layer status equal to the current selected mode. Available mode options are: PAN_ZOOM, PICKER, PAINT, ERASE or FILL Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self.layer.status = str(self.layer.mode) def _on_mode_change(self, event): """Receive layer model mode change event and update checkbox ticks. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. Raises ------ ValueError Raise error if event.mode is not PAN_ZOOM, PICK, PAINT, ERASE, or FILL """ mode = event.mode if mode == Mode.PAN_ZOOM: self.panzoom_button.setChecked(True) elif mode == Mode.PICK: self.pick_button.setChecked(True) elif mode == Mode.PAINT: self.paint_button.setChecked(True) elif mode == Mode.FILL: self.fill_button.setChecked(True) elif mode == Mode.ERASE: self.erase_button.setChecked(True) else: raise ValueError("Mode not recognized") def changeColor(self): """Change colormap of the label layer.""" self.layer.new_colormap() def changeSelection(self, value): """Change currently selected label. Parameters ---------- value : int Index of label to select. """ self.layer.selected_label = value self.selectionSpinBox.clearFocus() self.setFocus() def changeSize(self, value): """Change paint brush size. Parameters ---------- value : float Size of the paint brush. """ self.layer.brush_size = value def change_contig(self, state): """Toggle contiguous state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if labels are contiguous. """ if state == Qt.Checked: self.layer.contiguous = True else: self.layer.contiguous = False def change_ndim(self, state): """Toggle n-dimensional state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if label layer is n-dimensional. """ if state == Qt.Checked: self.layer.n_dimensional = True else: self.layer.n_dimensional = False def change_preserve_labels(self, state): """Toggle preserve_labels state of label layer. Parameters ---------- state : QCheckBox Checkbox indicating if overwriting label is enabled. """ if state == Qt.Checked: self.layer.preserve_labels = True else: self.layer.preserve_labels = False def change_color_mode(self, new_mode): """Change color mode of label layer. Parameters ---------- new_mode : str AUTO (default) allows color to be set via a hash function with a seed. DIRECT allows color of each label to be set directly by a color dictionary. """ self.layer.color_mode = new_mode def change_brush_shape(self, brush_shape): """Change paintbrush shape of label layer. Parameters ---------- brush_shape : str CIRCLE (default) uses circle paintbrush (case insensitive). SQUARE uses square paintbrush (case insensitive). """ self.layer.brush_shape = brush_shape def _on_selection_change(self, event=None): """Receive layer model label selection change event and update spinbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.selected_label.blocker(): value = self.layer.selected_label self.selectionSpinBox.setValue(int(value)) def _on_brush_size_change(self, event=None): """Receive layer model brush size change event and update the slider. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.brush_size.blocker(): value = self.layer.brush_size value = np.clip(int(value), 1, 40) self.brushSizeSlider.setValue(value) def _on_n_dim_change(self, event=None): """Receive layer model n-dim mode change event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.n_dimensional.blocker(): self.ndimCheckBox.setChecked(self.layer.n_dimensional) def _on_contig_change(self, event=None): """Receive layer model contiguous change event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.contiguous.blocker(): self.contigCheckBox.setChecked(self.layer.contiguous) def _on_preserve_labels_change(self, event=None): """Receive layer model preserve_labels event and update the checkbox. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.preserve_labels.blocker(): self.preserveLabelsCheckBox.setChecked(self.layer.preserve_labels) def _on_color_mode_change(self, event=None): """Receive layer model color. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ with self.layer.events.color_mode.blocker(): index = self.colorModeComboBox.findText( self.layer.color_mode, Qt.MatchFixedString ) self.blendComboBox.setCurrentIndex(index) def _on_brush_shape_change(self, event=None): """Receive brush shape change event and update dropdown menu. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.brush_shape.blocker(): index = self.brushShapeComboBox.findText( self.layer.brush_shape, Qt.MatchFixedString ) self.brushShapeComboBox.setCurrentIndex(index) def _on_editable_change(self, event=None): """Receive layer model editable change event & enable/disable buttons. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method. """ disable_with_opacity( self, ['pick_button', 'paint_button', 'fill_button'], self.layer.editable, )
class FigureBrowser(QWidget): """ Widget to browse the figures that were sent by the kernel to the IPython console to be plotted inline. """ sig_option_changed = Signal(str, object) sig_collapse = Signal() def __init__(self, parent=None, options_button=None, plugin_actions=[], background_color=None): super(FigureBrowser, self).__init__(parent) self.shellwidget = None self.is_visible = True self.figviewer = None self.setup_in_progress = False self.background_color = background_color # Options : self.mute_inline_plotting = None self.show_plot_outline = None # Option actions : self.mute_inline_action = None self.show_plot_outline_action = None self.options_button = options_button self.plugin_actions = plugin_actions self.shortcuts = self.create_shortcuts() def setup(self, mute_inline_plotting=None, show_plot_outline=None): """Setup the figure browser with provided settings.""" assert self.shellwidget is not None self.mute_inline_plotting = mute_inline_plotting self.show_plot_outline = show_plot_outline if self.figviewer is not None: self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action.setChecked(show_plot_outline) return self.figviewer = FigureViewer(background_color=self.background_color) self.figviewer.setStyleSheet("FigureViewer{" "border: 1px solid lightgrey;" "border-top-width: 0px;" "border-bottom-width: 0px;" "border-left-width: 0px;" "}") self.thumbnails_sb = ThumbnailScrollBar( self.figviewer, background_color=self.background_color) # Option actions : self.setup_option_actions(mute_inline_plotting, show_plot_outline) # Create the layout : main_widget = QSplitter() main_widget.addWidget(self.figviewer) main_widget.addWidget(self.thumbnails_sb) main_widget.setFrameStyle(QScrollArea().frameStyle()) self.tools_layout = QHBoxLayout() toolbar = self.setup_toolbar() for widget in toolbar: self.tools_layout.addWidget(widget) self.tools_layout.addStretch() self.setup_options_button() layout = create_plugin_layout(self.tools_layout, main_widget) self.setLayout(layout) def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton( self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton( self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton( self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton( self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut('plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton( self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut('plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(zoom_out_btn) layout.addWidget(zoom_in_btn) layout.addWidget(self.zoom_disp) return [savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan] def setup_option_actions(self, mute_inline_plotting, show_plot_outline): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.mute_inline_action = create_action( self, _("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda state: self.option_changed('mute_inline_plotting', state) ) self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action = create_action( self, _("Show plot outline"), tip=_("Show the plot outline."), toggled=self.show_fig_outline_in_viewer ) self.show_plot_outline_action.setChecked(show_plot_outline) self.actions = [self.mute_inline_action, self.show_plot_outline_action] self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: # When the FigureBowser widget is instatiated outside of the # plugin (for testing purpose for instance), we need to create # the options_button and set its menu. self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions self.options_menu = QMenu(self) add_actions(self.options_menu, actions) self.options_button.setMenu(self.options_menu) if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None: self.tools_layout.insertWidget( self.tools_layout.count() - 1, self.options_button) else: self.tools_layout.addWidget(self.options_button) def create_shortcuts(self): """Create shortcuts for this widget.""" # Configurable copyfig = config_shortcut(self.copy_figure, context='plots', name='copy', parent=self) prevfig = config_shortcut(self.go_previous_thumbnail, context='plots', name='previous figure', parent=self) nextfig = config_shortcut(self.go_next_thumbnail, context='plots', name='next figure', parent=self) return [copyfig, prevfig, nextfig] def get_shortcut_data(self): """ Return shortcut data, a list of tuples (shortcut, text, default). shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def option_changed(self, option, value): """Handle when the value of an option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() if self.setup_in_progress is False: self.sig_option_changed.emit(option, value) def show_fig_outline_in_viewer(self, state): """Draw a frame around the figure viewer if state is True.""" if state is True: self.figviewer.figcanvas.setStyleSheet( "FigureCanvas{border: 1px solid lightgrey;}") else: self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}") self.option_changed('show_plot_outline', state) def set_shellwidget(self, shellwidget): """Bind the shellwidget instance to the figure browser""" self.shellwidget = shellwidget shellwidget.set_figurebrowser(self) shellwidget.sig_new_inline_figure.connect(self._handle_new_figure) def get_actions(self): """Get the actions of the widget.""" return self.actions def _handle_new_figure(self, fig, fmt): """ Handle when a new figure is sent to the IPython console by the kernel. """ self.thumbnails_sb.add_thumbnail(fig, fmt) # ---- Toolbar Handlers def zoom_in(self): """Zoom the figure in by a single step in the figure viewer.""" self.figviewer.zoom_in() def zoom_out(self): """Zoom the figure out by a single step in the figure viewer.""" self.figviewer.zoom_out() def go_previous_thumbnail(self): """ Select the thumbnail previous to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_previous_thumbnail() def go_next_thumbnail(self): """ Select the thumbnail next to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_next_thumbnail() def save_figure(self): """Save the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.save_current_figure_as() def save_all_figures(self): """Save all the figures in a selected directory.""" return self.thumbnails_sb.save_all_figures_as() def close_figure(self): """Close the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.remove_current_thumbnail() def close_all_figures(self): """Close all the figures in the thumbnail scrollbar.""" self.thumbnails_sb.remove_all_thumbnails() def copy_figure(self): """Copy figure from figviewer to clipboard.""" if self.figviewer and self.figviewer.figcanvas.fig: self.figviewer.figcanvas.copy_figure()
class ImageRotationDialog(ExToolWindow): def __init__(self, signal, axes, parent, plugin): super(ImageRotationDialog, self).__init__(parent) self.ui = parent self.create_controls() self.accepted.connect(self.ok) self.rejected.connect(self.close_new) self.signal = signal self.plugin = plugin self.new_out = None self._connected_updates = False if isinstance(axes, str): axm = signal.signal.axes_manager if axes.startswith("nav"): axes = (axm._axes.index(axm.navigation_axes[0]), axm._axes.index(axm.navigation_axes[1])) elif axes.startswith("sig"): axes = (axm._axes.index(axm.signal_axes[0]), axm._axes.index(axm.signal_axes[1])) self.axes = axes self.setWindowTitle(tr("Rotate")) # TODO: TAG: Functionality check if not hasattr(signal.signal, 'events'): self.gbo_preview.setVisible(False) # TODO: Add dynamic rotation, e.g. one that rotates when source # signal's data_changed event triggers def connect(self): # TODO: Don't have to con/dis those in gbo self.opt_new.toggled.connect(self.close_new) self.num_angle.valueChanged.connect(self.update) self.chk_grid.toggled.connect(self.update) self.num_grid.valueChanged.connect(self.update) self.chk_reshape.toggled.connect(self.update) self.opt_new.toggled.connect(self.update) self.opt_replace.toggled.connect(self.update) def disconnect(self): self.num_angle.valueChanged.disconnect(self.update) self.chk_grid.toggled.disconnect(self.update) self.num_grid.valueChanged.disconnect(self.update) self.chk_reshape.toggled.disconnect(self.update) self.opt_new.toggled.disconnect(self.update) self.opt_replace.toggled.disconnect(self.update) def ok(self): # Draw figure if not already done # TODO: TAG: Functionality check if not hasattr(self.signal.signal, 'events') or \ not self.gbo_preview.isChecked(): self.update() angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() self.plugin.record_code( r"<p>.rotate_signal({0}, reshape={1}, axes={2})".format( angle, reshape, self.axes)) # Clean up event connections if self.new_out is not None: self.connect_update_plot(self.new_out.signal, disconnect=True) def close_new(self, value=False): if self.new_out is not None and not value: self.new_out.close() self.new_out = None self._connected_updates = False def set_preview(self, value): if not hasattr(self.signal.signal, 'events'): return if value: self.connect() self.update() else: self.disconnect() self.close_new() def _axes_in_nav(self): axm = self.signal.signal.axes_manager navidx = [axm._axes.index(ax) for ax in axm.navigation_axes] if self.axes[0] in navidx: return True return False def connect_update_plot(self, signal, disconnect=False): if self._connected_updates != disconnect: return # Nothing to do, prevent double connections if self._axes_in_nav(): f = signal._plot.navigator_plot.update else: f = signal._plot.signal_plot.update # TODO: TAG: Functionality check if hasattr(signal, 'events') and hasattr( signal.events, 'data_changed'): if disconnect: signal.events.data_changed.disconnect(f) else: signal.events.data_changed.connect(f, []) self._connected_updates = not disconnect def update(self): angle = self.num_angle.value() reshape = self.chk_reshape.isChecked() if self.opt_new.isChecked(): if self.new_out is None: out = None else: out = self.new_out.signal elif self.opt_replace.isChecked(): out = self.signal.signal else: return # Indeterminate state, do nothing s = self.plugin.rotate_signal(angle, self.signal.signal, record=False, reshape=reshape, out=out, axes=self.axes) if out is None: s.metadata.General.title = self.signal.name + "[Rotated]" s.plot() self.connect_update_plot(s) if (self.gbo_preview.isChecked() and self.opt_new.isChecked() and self.new_out is None): self.new_out = self.ui.lut_signalwrapper[s] else: s = out if self.chk_grid.isChecked() is True: pass # TODO: Draw grid def create_controls(self): """ Create UI controls. """ vbox = QVBoxLayout() form = QFormLayout() self.num_angle = QDoubleSpinBox() self.num_angle.setValue(0.0) self.num_angle.setMinimum(-360) self.num_angle.setMaximum(360) form.addRow(tr("Angle:"), self.num_angle) vbox.addLayout(form) self.gbo_preview = QGroupBox(tr("Preview")) self.gbo_preview.setCheckable(True) self.gbo_preview.setChecked(False) gbo_vbox = QVBoxLayout() self.chk_grid = QCheckBox(tr("Grid")) self.chk_grid.setChecked(False) self.num_grid = QSpinBox() self.num_grid.setValue(4) self.num_grid.setMinimum(1) self.num_grid.setEnabled(False) self.chk_grid.toggled[bool].connect(self.num_grid.setEnabled) gbo_vbox.addWidget(self.chk_grid) gbo_vbox.addWidget(self.num_grid) self.gbo_preview.setLayout(gbo_vbox) vbox.addWidget(self.gbo_preview) self.gbo_preview.toggled[bool].connect(self.set_preview) self.gbo_output = QGroupBox(tr("Output")) self.opt_new = QRadioButton(tr("New signal")) self.opt_replace = QRadioButton(tr("In place")) self.opt_new.setChecked(True) gbo_vbox2 = QVBoxLayout() gbo_vbox2.addWidget(self.opt_new) gbo_vbox2.addWidget(self.opt_replace) self.gbo_output.setLayout(gbo_vbox2) vbox.addWidget(self.gbo_output) self.chk_reshape = QCheckBox(tr("Resize to fit")) self.chk_reshape.setChecked(False) vbox.addWidget(self.chk_reshape) self.btn_ok = QPushButton(tr("&OK")) self.btn_ok.setDefault(True) self.btn_ok.clicked.connect(self.accept) self.btn_cancel = QPushButton(tr("&Cancel")) self.btn_cancel.clicked.connect(self.reject) hbox = QHBoxLayout() hbox.addWidget(self.btn_ok) hbox.addWidget(self.btn_cancel) vbox.addLayout(hbox) vbox.addStretch(1) self.setLayout(vbox)