class LabeledSlider(QWidget): def __init__(self, text, *args, minimum=1, maximum=10, parent=None, **kwargs): super(LabeledSlider, self).__init__(parent) self.label = QLabel(text) self.slider = QSlider(*args, **kwargs) self.slider.setMinimum(minimum) self.slider.setMaximum(maximum) self.slider.setTickInterval(5) self.valuebox = QSpinBox() self.valuebox.setRange(minimum, maximum) self.valuebox.setValue(self.slider.value()) self.slider.valueChanged.connect(self.valuebox.setValue) self.valuebox.valueChanged.connect(self.slider.setValue) layout = QHBoxLayout() layout.addWidget(self.label) layout.addWidget(self.slider) layout.addWidget(self.valuebox) self.setLayout(layout)
class BinningSpinbox(QWidget): def __init__(self, plot, *args, **kwargs): super().__init__(*args, **kwargs) self.plot = plot ly = QGridLayout() # remove annoying padding ly.setContentsMargins(0, 0, 0, 0) self.setLayout(ly) self.label = QLabel('Binning:') ly.addWidget(self.label, 0, 0) self.auto = QCheckBox('auto', checked=False) ly.addWidget(self.auto, 0, 1) self.spinbox = QSpinBox() ly.addWidget(self.spinbox, 1, 0, 1, 2) self.spinbox.setRange(1, 100000) self.spinbox.setValue(10) self.spinbox.valueChanged.connect(self._on_binning_change) self.auto.clicked.connect(self._on_auto_change) self.auto.click() def _on_binning_change(self): self.plot.set_binning(self.spinbox.value()) def _on_auto_change(self, checked): self.spinbox.setVisible(not checked) if checked: self.plot.set_binning('auto') else: self._on_binning_change()
class FrameWidget(QWidget): """Widget for interatviely making animations using the napari viewer.""" def __init__(self, parent=None): super().__init__(parent=parent) self.animation = self.parentWidget().animation self._layout = QFormLayout(parent=self) self._init_steps() self._init_ease() self._add_callbacks() def _init_steps(self): self.stepsSpinBox = QSpinBox() self.stepsSpinBox.setRange(1, 100000) self.stepsSpinBox.setValue(15) self._layout.addRow("Steps", self.stepsSpinBox) def _init_ease(self): self.easeComboBox = QComboBox() self.easeComboBox.addItems([e.name.lower() for e in Easing]) index = self.easeComboBox.findText("linear", Qt.MatchFixedString) self.easeComboBox.setCurrentIndex(index) self._layout.addRow("Ease", self.easeComboBox) def get_easing_func(self): easing_name = str(self.easeComboBox.currentText()) return Easing[easing_name.upper()] def update_from_animation(self): """update state of self to reflect animation state at current key frame""" self._update_steps_spin_box() self._update_ease_combo_box() def _update_steps_spin_box(self): """update state of steps spin box to reflect animation state at current key frame""" self.stepsSpinBox.setValue(self.animation.current_key_frame.steps) def _update_animation_steps(self, event): """update state of 'steps' at current key-frame to reflect GUI state""" self.animation.current_key_frame.steps = self.stepsSpinBox.value() def _update_ease_combo_box(self): """update state of ease combo box to reflect animation state at current key frame""" ease = self.animation.current_key_frame.ease name = _easing_func_to_name(ease) index = self.easeComboBox.findText(name, Qt.MatchFixedString) self.easeComboBox.setCurrentIndex(index) def _update_animation_ease(self, event): """update state of 'ease' at current key-frame to reflect GUI state""" self.animation.current_key_frame.ease = self.get_easing_func() def _add_callbacks(self): """add callbacks to steps and ease widgets""" self.stepsSpinBox.valueChanged.connect(self._update_animation_steps) self.easeComboBox.currentIndexChanged.connect( self._update_animation_ease)
def create_input_int(obj, ob2=None): if ob2 is not None: val = obj obj = ob2 else: val = 0 res = QSpinBox(obj) res.setRange(-1000000, 1000000) res.setValue(val) return res
def set_kwargs(self, task): """Creates widgets for each task keyword argument.""" if not task or task is self.current_task: return else: for widget_dict in self.widgets: key_widget = widget_dict.get("key") value_widget = widget_dict.get("value") if key_widget: key_widget.setParent(None) self.widget_layout.removeWidget(key_widget) value_widget.setParent(None) self.widget_layout.removeWidget(value_widget) self.widgets.clear() self.current_task = task kwargs_dict = task.get("kwargs") for key, value in kwargs_dict.items(): key_label = QLabel(str(key).replace("_", " ").capitalize(), self) val_type = value.get("type") default = value.get("default") if val_type is int: value_widget = QSpinBox(self) value_widget.setRange(-1, 100000) value_widget.setValue(int(default)) elif val_type is list: value_widget = QListWidget(self) value_widget.addItems([str(item) for item in default]) for i in range(value_widget.count()): value_widget.item(i).setFlags( value_widget.item(i).flags() | Qt.ItemIsEditable) elif val_type is str: value_widget = QLineEdit(self) value_widget.setText(str(default)) elif val_type is bool: key_label = None value_widget = QCheckBox( str(key).replace("_", " ").capitalize(), self) value_widget.setChecked(bool(default)) elif val_type == "text": value_widget = QTextEdit(self) value_widget.setText(str(default)) else: continue value_widget.setObjectName(key) value_widget.setToolTip(value.get("description")) self.widgets.append({"key": key_label, "value": value_widget}) if type(value_widget) is QCheckBox: self.widget_layout.addWidget(value_widget) else: self.widget_layout.addRow(key_label, value_widget)
class QTiffStackView(QWidget): #the view which the user of the videoviewer sees. #This class contains relevant 'client side' attributes e.g. buttons to get a frame, a slide bar and a timer. These attributes submit requests to the QTiffStackController to give the next frame etc. The controller returns either the requested frame or an error message def __init__(self): super(QTiffStackView, self).__init__() #add the image display. This is a subclass of QLabel, where paintEvent is overriden. self.frame_view = FrameView() #self.frame_view.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) #add the slide bar which allows the user to manual flick through self.slideBar = QSlider(Qt.Horizontal) self.slideBar.setTickPosition(QSlider.TicksAbove) self.slideBar.setTracking(True) self.slideBar.setTickInterval(100) #add a counter which displays the frame which is currently displayed self.counter = QSpinBox() self.counter.setSingleStep(1) self.counter.setRange(self.slideBar.minimum(), self.slideBar.maximum()) #self explanatory self.play = QPushButton('Play') #when play button is pressed the timer takes control of the displaying of frames self.frametimer = QTimer() frame_rate = 30 self.frametimer.setInterval(frame_rate) #Add a sublayout to align the slidebar and frame counter next to eachother slidelyt = QHBoxLayout() slidelyt.addWidget(self.slideBar) slidelyt.addWidget(self.counter) #Add the main layout for the widget lyt = QVBoxLayout() lyt.addWidget(self.frame_view) lyt.addLayout(slidelyt) lyt.addWidget(self.play) self.setLayout(lyt) def updateRanges(self, maximum): assert type(maximum) == int self.slideBar.setMaximum(maximum) self.counter.setRange(self.slideBar.minimum(), self.slideBar.maximum())
class FrameWidget(QWidget): """Widget for interatviely making animations using the napari viewer.""" def __init__(self, parent=None): super().__init__(parent=parent) self.animation: Animation = self.parentWidget().animation self.animation.key_frames.selection.events.active.connect( self._on_active_keyframe_changed) # init steps self.stepsSpinBox = QSpinBox() self.stepsSpinBox.setRange(1, 100000) self.stepsSpinBox.setValue(15) # init ease self.easeComboBox = QComboBox() self.easeComboBox.addItems([e.name.lower() for e in Easing]) self.easeComboBox.setCurrentText("linear") # add callbacks self.stepsSpinBox.valueChanged.connect(self._update_animation_steps) self.easeComboBox.currentIndexChanged.connect( self._update_animation_ease) # layout self.setLayout(QFormLayout()) self.layout().addRow("Steps", self.stepsSpinBox) self.layout().addRow("Ease", self.easeComboBox) def _on_active_keyframe_changed(self, event): """update state of self to reflect animation state at active key frame""" key_frame = event.value if key_frame: self.stepsSpinBox.setValue(key_frame.steps) active_keyframe = self.animation.key_frames.selection.active ease = active_keyframe.ease self.easeComboBox.setCurrentText(ease.name.lower()) def _update_animation_steps(self, event): """update state of 'steps' at current key-frame to reflect GUI state""" active_keyframe = self.animation.key_frames.selection.active active_keyframe.steps = self.stepsSpinBox.value() # TODO: if this changes programatically the slider will be out of sync. # but we don't currently have events on the keyframe.steps attribute. self.animation._frames._rebuild_frame_index() def _update_animation_ease(self, event): """update state of 'ease' at current key-frame to reflect GUI state""" active_keyframe = self.animation.key_frames.selection.active active_keyframe.ease = self.get_easing_func() def get_easing_func(self): return Easing[self.easeComboBox.currentText().upper()]
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 ServerAddressWidget(BaseFrame): connect_clicked = Signal(str, int, bytes) def __init__(self, parent): super(ServerAddressWidget, self).__init__(parent) self.widget_layout = QVBoxLayout(self) self.setLayout(self.widget_layout) self.pass_widget = QWidget(self) self.pass_widget_layout = QFormLayout(self.pass_widget) self.pass_widget_layout.setContentsMargins(0, 0, 0, 0) self.ip_label = QLabel(self.pass_widget) self.ip_label.setText("IP Address") self.ip_line_edit = QLineEdit(self.pass_widget) self.pass_widget_layout.addRow(self.ip_label, self.ip_line_edit) self.port_label = QLabel(self.pass_widget) self.port_label.setText("Port") self.port_edit = QSpinBox(self.pass_widget) self.port_edit.setRange(1024, 65535) self.pass_widget_layout.addRow(self.port_label, self.port_edit) self.key_label = QLabel(self.pass_widget) self.key_label.setText("Key") self.key_line_edit = QLineEdit(self.pass_widget) self.pass_widget_layout.addRow(self.key_label, self.key_line_edit) self.widget_layout.addWidget(self.pass_widget) self.connect_button = QPushButton(self) self.connect_button.clicked.connect(self.on_connect_clicked) self.connect_button.setMinimumSize(QSize(0, 35)) self.connect_button.setText("Connect") self.widget_layout.addWidget(self.connect_button) @Slot() def on_connect_clicked(self): self.connect_clicked.emit(self.ip_line_edit.text(), self.port_edit.value(), self.key_line_edit.text().encode())
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 ParamSpinBox(WidgetForParam): def __init__(self): super().__init__() self.keys_to_check.extend(['range']) self.conditions.extend([lambda param_x: param_x['type'] == int, lambda param_x: param_x["range"][1] - param_x["range"][0] > 20]) def create_widget(self): self.check_param(self.param, assert_error=True) self.default = self.param['range'][0] if 'default' in self.param.keys(): if self.param['default']: self.default = self.param['default'] self.widget = QSpinBox() self.widget.setValue(self.default) self.widget.setRange(self.param['range'][0], self.param['range'][1]) def connect_widget(self): self.widget.valueChanged.connect(lambda value: self.change_param(value))
class _SaveDialog(QFileDialog): 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 tightLayout(self): return self._chk_tight.isChecked() def setTightLayout(self, tight): self._chk_tight.setChecked(tight) def dpi(self): return self._txt_dpi.value() def setDpi(self, dpi): self._txt_dpi.setValue(dpi)
class ParamSlider(WidgetForParam): def __init__(self): super().__init__() self.keys_to_check.extend(['range']) self.conditions.extend([ lambda param_x: param_x['type'] == int, lambda param_x: param_x["range"][1] - param_x["range"][0] <= 20 ]) def create_widget(self): self.check_param(self.param, assert_error=True) self.default = self.param['range'][0] if self.param['default']: self.default = self.param['default'] self.widget_group = QWidget() slider = QSlider(Qt.Horizontal) slider.setValue(self.default) slider.setRange(self.param['range'][0], self.param['range'][1]) self.widget = QSpinBox() self.widget.setValue(self.default) self.widget.setRange(self.param['range'][0], self.param['range'][1]) self.widget.valueChanged.connect(slider.setValue) slider.valueChanged.connect(self.widget.setValue) h_box = QHBoxLayout() h_box.addWidget(slider) h_box.addWidget(self.widget) h_box.addStretch(1) self.widget_group.setLayout(h_box) def connect_widget(self): self.widget.valueChanged.connect( lambda value: self.change_param(value))
class MainWindow(QMainWindow): def selectFileToOpen(self): def getPreProcessingChoice(self, filename, filestructure): items = ("Choose the longest", "Merge all") item, okPressed = QInputDialog.getItem( self, "Multiple tracks/segments", "File '" + filename + "' contains more than one track/segment\n\n" + infos + "\nWhat to do?", items, 0, False) if okPressed and item: return items.index(item) else: return 0 # Try to recover the last used directory old_directory = self.settings.value("lastdirectory", str) # Check if the setting exists if old_directory is not None: # Check if it's not empty if old_directory: old_directory = old_directory else: old_directory = bombo.TRACKS_FOLDER else: old_directory = bombo.TRACKS_FOLDER # Open the dialog box fullfilename_list = QFileDialog.getOpenFileNames( self, 'Open .gpx', old_directory, "GPX files (*.gpx)") if os.environ['QT_API'] == 'pyqt': pass elif os.environ['QT_API'] == 'pyqt5': fullfilename_list = fullfilename_list[0] # Process every selected file for i, fullfilename in enumerate(fullfilename_list): # Process filename directory, filename = os.path.split(str(fullfilename)) filename, fileextension = os.path.splitext(filename) # Save the new directory in the application settings (it only # needs to be done once) if i == 0: # print "New directory to be saved: {}\n".format(directory) if os.environ['QT_API'] == 'pyqt': self.settings.setValue("lastdirectory", str(directory)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("lastdirectory", QtCore.QVariant(str(directory))) # Open file and inspect what's inside gpxraw, longest_traseg, Ntracks, Nsegments, infos = bombo.LoadGPX( fullfilename) # If there's more than one track or segment, ask how to proceed if (Ntracks > 1) or (Nsegments > 1): preprocessingchoice = getPreProcessingChoice( self, filename, infos) if preprocessingchoice == 0: preprocessedgpx = bombo.SelectOneTrackAndSegmentFromGPX( gpxraw, longest_traseg[0], longest_traseg[1]) listname = filename + " (longest)" elif preprocessingchoice == 1: preprocessedgpx = bombo.MergeAllTracksAndSegmentsFromGPX( gpxraw) listname = filename + " (merged)" else: preprocessedgpx = gpxraw listname = filename # Append the list of open GPX files using the next available color (that's the size of the list -1) self.gpxlist.append(preprocessedgpx) self.gpxnamelist.append(listname) newitem = QListWidgetItem(listname) newitem.setBackground( QtGui.QColor(self.palette[len(self.gpxlist) - 1])) self.tracklist.addItem(newitem) return def Go(self): if len(self.gpxselectedlist) > 0: # Temporarily change cursor QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) # Clear up global variables self.proc_coords = [] self.proc_measurements = [] self.proc_state_means = [] self.proc_state_vars = [] self.proc_new_coords = [] self.proc_new_gpx = [] self.proc_coords_to_plot = [] self.proc_coords_to_plot2 = [] self.proc_balloondata = [] # For every GPX file that is selected self.textWarningConsole.clear() for i, currentgpx in enumerate(self.gpxselectedlist): # Parse the GPX file gpx, coords, dinfos_before, warnings = bombo.ParseGPX( currentgpx, track_nr=0, segment_nr=0, use_srtm_elevation=bool(self.checkUseSRTM.isChecked())) self.textWarningConsole.append(warnings) # Kalman processing coords, measurements, state_means, state_vars, dinfos_during = bombo.ApplyKalmanFilter( coords, gpx, method=self.comboBoxProcessingMethod.currentIndex(), use_acceleration=self.checkUseAcceleration.isChecked(), extra_smooth=self.checkExtraSmooth.isChecked(), debug_plot=False) # Save data in GPX structure to compute speed and elevations new_coords, new_gpx, dinfos_after = bombo.SaveDataToCoordsAndGPX( coords, state_means) # Update GUI with the computed stats parent = QtGui.QStandardItem(self.gpxselectednamelist[i]) parent_beforeprocessing = QtGui.QStandardItem("Raw GPX stats") parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_before['total_distance']) ]) parent_beforeprocessing_moving = QtGui.QStandardItem("Moving") parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['moving_time']) ]) parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['moving_distance']) ]) parent_beforeprocessing.appendRow( parent_beforeprocessing_moving) parent_beforeprocessing_idle = QtGui.QStandardItem("Idle") parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['idle_time']) ]) parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['idle_distance']) ]) parent_beforeprocessing.appendRow(parent_beforeprocessing_idle) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_before['elevation']) ]) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_before['climb']) ]) parent.appendRow(parent_beforeprocessing) parent.appendRow([ QtGui.QStandardItem("Samples"), QtGui.QStandardItem(dinfos_during['nsamples']) ]) parent.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_after['total_distance']) ]) parent_moving = QtGui.QStandardItem("Moving") parent_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['moving_time']) ]) parent_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['moving_distance']) ]) parent.appendRow(parent_moving) parent_idle = QtGui.QStandardItem("Idle") parent_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['idle_time']) ]) parent_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['idle_distance']) ]) parent.appendRow(parent_idle) parent.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_after['elevation']) ]) parent.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_after['climb']) ]) self.treemodel.appendRow(parent) # Create balloondata for the html plot balloondata = { 'distance': np.cumsum( bombo.HaversineDistance(np.asarray(new_coords['lat']), np.asarray( new_coords['lon']))), 'elevation': np.asarray(new_coords['ele']), 'speed': None } # Create extra data for the html plot (fully implemented in bombo, not here) """ data = np.ones((len(lat_cleaned),2)) data[:,0] = h_filtered / np.max(h_filtered) * 0.0004 data[:,1] = np.hstack((np.asarray([0]), speed_h)) / np.max(np.hstack((np.asarray([0]), speed_h))) * 0.0004 tangentdata = {'data': data, 'sides': (0, 1), 'palette': ('blue','red')} """ # Save relevant output in global variables self.proc_coords.append(coords) self.proc_measurements.append(measurements) self.proc_state_means.append(state_means) self.proc_state_vars.append(state_vars) self.proc_new_coords.append(new_coords) self.proc_new_gpx.append(new_gpx) self.proc_coords_to_plot.append( np.vstack((new_coords['lat'], new_coords['lon'])).T) self.proc_coords_to_plot2.append( np.vstack((coords['lat'], coords['lon'])).T) self.proc_balloondata.append(balloondata) # Restore original cursor QApplication.restoreOverrideCursor() # Generate embedded plots if len(self.gpxselectedlist) == 1: self.plotEmbeddedElevationAndSpeed.update_figure( measurements, state_means, new_gpx.tracks[0].segments[0]) self.plotEmbeddedDetails.update_figure( measurements, state_means, state_vars, new_gpx.tracks[0].segments[0]) else: # Commentato per adesso # self.plotEmbeddedElevationAndSpeed.update_figure_multiple_tracks(self.proc_measurements, self.proc_state_means, self.proc_new_gpx) self.plotEmbeddedElevationAndSpeed.clear_figure() self.plotEmbeddedDetails.clear_figure() # Generate html plot, if only one track is selected, proceed with the complete output, otherwise just plot the traces if len(self.gpxselectedlist) is 1: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=self.proc_coords_to_plot2, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) else: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=None, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) self.map2d.load(QtCore.QUrl(bombo.MAP_2D_FILENAME)) self.map2d.show() # Generate 3D plot, only with one track for the moment if len(self.gpxselectedlist) == 1: if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( new_coords['lat'], new_coords['lon'], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) else: self.textWarningConsole.setText( "You need to open a .gpx file before!") return def PlotSpecificAreaDialog(self): def PlotSpecificArea(): # Save coordinates for the next time if os.environ['QT_API'] == 'pyqt': self.settings.setValue("last_point_coord_lat", self.spinboxLatDec.value()) self.settings.setValue("last_point_coord_lon", self.spinboxLonDec.value()) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue( "last_point_coord_lat", QtCore.QVariant(self.spinboxLatDec.value())) self.settings.setValue( "last_point_coord_lon", QtCore.QVariant(self.spinboxLonDec.value())) # Select the 3D Map tab self.tab.setCurrentIndex(2) # Plot if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( [self.spinboxLatDec.value()], [self.spinboxLonDec.value()], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) d.done(0) def Convert(): try: dd = bombo.parse_dms(self.textLatLonGMS.text()) self.spinboxLatDec.setValue(dd[0]) self.spinboxLonDec.setValue(dd[1]) except: pass d = QDialog() grid = QGridLayout() hBox_coordsGMS = QHBoxLayout() hBox_coordsGMS.setSpacing(5) label = QLabel('Coordinates (gms)') grid.addWidget(label, 0, 0) self.textLatLonGMS = QLineEdit() self.textLatLonGMS.setText("") grid.addWidget(self.textLatLonGMS, 0, 1, 1, 2) button1 = QPushButton("Convert to decimal") button1.clicked.connect(Convert) grid.addWidget(button1, 0, 3) label = QLabel('Coordinates (decimal)') grid.addWidget(label, 1, 0) self.spinboxLatDec = QDoubleSpinBox() self.spinboxLatDec.setRange(-90, +90) self.spinboxLatDec.setSingleStep(0.0000001) self.spinboxLatDec.setDecimals(7) grid.addWidget(self.spinboxLatDec, 1, 1) self.spinboxLonDec = QDoubleSpinBox() self.spinboxLonDec.setRange(-180, +180) self.spinboxLonDec.setSingleStep(0.0000001) self.spinboxLonDec.setDecimals(7) grid.addWidget(self.spinboxLonDec, 1, 2) # Try to recover the last used points try: old_lat = self.settings.value("last_point_coord_lat", type=float) old_lon = self.settings.value("last_point_coord_lon", type=float) self.spinboxLatDec.setValue(old_lat) self.spinboxLonDec.setValue(old_lon) except: # Coordinates of Mt. Rinjani in Indonesia self.spinboxLatDec.setValue(-8.4166000) self.spinboxLonDec.setValue(116.4666000) button2 = QPushButton("Show 3D map") button2.clicked.connect(PlotSpecificArea) grid.addWidget(button2, 1, 3) d.setWindowTitle("Show point on 3D map") d.setLayout(grid) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def ProxyDialog(self): def SetProxy(): self.use_proxy = bool(self.checkUseProxy.isChecked()) self.proxy_config = self.textProxyConfig.text() if os.environ['QT_API'] == 'pyqt': self.settings.setValue("use_proxy", self.use_proxy) self.settings.setValue("proxy_config", str(self.proxy_config)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("use_proxy", QtCore.QVariant(self.use_proxy)) self.settings.setValue("proxy_config", QtCore.QVariant(str(self.proxy_config))) d.done(0) d = QDialog() box = QVBoxLayout() hBox_proxy = QHBoxLayout() hBox_proxy.setSpacing(5) label = QLabel('Proxy') hBox_proxy.addWidget(label) self.textProxyConfig = QLineEdit() try: self.textProxyConfig.setText( self.settings.value('proxy_config', str)) except: self.textProxyConfig.setText(bombo.PROXY_DATA) self.textProxyConfig.setMinimumWidth(200) hBox_proxy.addWidget(self.textProxyConfig) box.addLayout(hBox_proxy) self.checkUseProxy = QCheckBox("Use proxy") try: self.checkUseProxy.setChecked( self.settings.value('use_proxy', bool)) except: self.checkUseProxy.setChecked(bool(bombo.USE_PROXY)) box.addWidget(self.checkUseProxy) button = QPushButton("Save configuration") button.clicked.connect(SetProxy) box.addWidget(button) d.setWindowTitle("Proxy configuration") d.setLayout(box) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def __init__(self, parent=None): super(MainWindow, self).__init__() self.initVariables() self.initUI() def initVariables(self): self.gpxlist = list() self.gpxnamelist = list() self.gpxselectedlist = list() self.gpxselectednamelist = list() self.palette = bombo.GeneratePalette(N=10) * 5 # replicated 5 times #self.palette = ["#0000FF", "#00FF00", "#00FFFF", "#FF0000", "#FF00FF", "#FFFF00", "#FFFFFF"] # test palette self.selectedpalette = list() self.proc_coords = list() self.proc_measurements = list() self.proc_state_means = list() self.proc_state_vars = list() self.proc_new_coords = list() self.proc_new_gpx = list() self.proc_coords_to_plot = list() self.proc_coords_to_plot2 = list() self.proc_balloondata = list() def initUI(self): def selection_changed(): # Retrieve selected items # selecteditems = self.tracklist.selectedItems() selectedindexes = self.tracklist.selectedIndexes() # Adding the selected items to the processing list self.gpxselectedlist[:] = [] self.gpxselectednamelist[:] = [] self.selectedpalette[:] = [] for i in selectedindexes: # print str(i.text()) self.gpxselectedlist.append(self.gpxlist[i.row()]) self.gpxselectednamelist.append(self.gpxnamelist[i.row()]) self.selectedpalette.append(self.palette[i.row()]) def ClearStats(): """ # Some other code that could be used in the future index = self.treemodel.indexFromItem(parent1) self.tree.expand(index) selmod = self.tree.selectionModel() index2 = self.treemodel.indexFromItem(child2) selmod.select(index2, QtCore.QItemSelectionModel.Select|QtCore.QItemSelectionModel.Rows) root = self.treemodel.invisibleRootItem() (item.parent() or root).removeChild(item) """ # Returns a list of indexes. In our case, for each row there are 2 indexes, cos there are 2 columns. for index in self.tree.selectedIndexes(): # Consider only the first columns if index.column() == 0: # Need to check if it's a top item (i.e. track), otherwise if a subitem (i.e. distance or time) is selected, the result might be buggy parent = index.parent() parent_item = self.treemodel.itemFromIndex(parent) if parent_item is None: self.treemodel.removeRow(index.row()) # Application Settings QtCore.QCoreApplication.setOrganizationName("Ste") QtCore.QCoreApplication.setOrganizationDomain( "https://github.com/stesalati/sport/") QtCore.QCoreApplication.setApplicationName("TrackAnalyser") # Config settings self.settings = QtCore.QSettings(self) # Proxy settings try: self.use_proxy = self.settings.value('use_proxy', bool) self.proxy_config = self.settings.value('proxy_config', str) except: self.use_proxy = bombo.USE_PROXY self.proxy_config = bombo.PROXY_DATA # Actions openfile = QAction(QtGui.QIcon("icons/openfile.png"), "Open .gpx", self) openfile.setShortcut("Ctrl+O") openfile.setStatusTip("Open file") openfile.triggered.connect(self.selectFileToOpen) go = QAction(QtGui.QIcon("icons/go.png"), "Go!", self) go.setShortcut("Ctrl+R") go.setStatusTip("Run analysis") go.triggered.connect(self.Go) clearstats = QAction(QtGui.QIcon("icons/clear.png"), "Clear stats", self) clearstats.setShortcut("Ctrl+C") clearstats.setStatusTip("Clear stats") clearstats.triggered.connect(ClearStats) sep1 = QAction(self) sep1.setSeparator(True) showpoint = QAction(QtGui.QIcon("icons/point.png"), "Show point", self) showpoint.setShortcut("Ctrl+P") showpoint.setStatusTip("Show point") showpoint.triggered.connect(self.PlotSpecificAreaDialog) sep2 = QAction(self) sep2.setSeparator(True) quitapp = QAction(QtGui.QIcon("icons/quit.png"), "Quit", self) quitapp.setShortcut("Ctrl+Q") quitapp.setStatusTip("Quit application") quitapp.triggered.connect(qApp.quit) configs = QAction(QtGui.QIcon("icons/configs.png"), "Configs", self) configs.setStatusTip("Configs") configs.triggered.connect(self.ProxyDialog) # Menubar mainMenu = self.menuBar() configMenu = mainMenu.addMenu('&Config') configMenu.addAction(configs) # Toolbar toolbar = self.addToolBar('My tools') toolbar.addAction(openfile) toolbar.addAction(go) toolbar.addAction(clearstats) toolbar.addAction(sep1) toolbar.addAction(showpoint) toolbar.addAction(sep2) toolbar.addAction(quitapp) toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QtCore.QSize(30, 30)) # Status bar self.statusBar().show() # Main widget (everything that's not toolbar, statusbar or menubar must be in this widget) self.scatola = QWidget() # Main horizontal impagination hBox = QHBoxLayout() hBox.setSpacing(5) # Vertical left column vBox_left = QVBoxLayout() vBox_left.setSpacing(5) # 1st vertical box, a list self.tracklist = QListWidget() vBox_left.addWidget(self.tracklist) self.tracklist.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tracklist.itemSelectionChanged.connect(selection_changed) self.tracklist.setMaximumHeight(120) # 2nd vertical box, containing several horizontal boxes, one for each setting vBox2 = QVBoxLayout() vBox2.setSpacing(5) # Just the group label labelSettings = QLabel('Settings') vBox2.addWidget(labelSettings) # Use/don't use corrected altitude self.checkUseSRTM = QCheckBox( "Use SRTM corrected elevation (needs Internet)") self.checkUseSRTM.setChecked(False) vBox2.addWidget(self.checkUseSRTM) # Choose processing method + use/don't use acceleration hBoxProcessingMethod = QHBoxLayout() labelProcessingMethod = QLabel('Processing method') hBoxProcessingMethod.addWidget(labelProcessingMethod) self.comboBoxProcessingMethod = QComboBox() self.comboBoxProcessingMethod.addItem("Just use available data") self.comboBoxProcessingMethod.addItem( "Fill all gaps at T=1s (resample)") self.comboBoxProcessingMethod.addItem("Fill only smaller gaps at T=1s") hBoxProcessingMethod.addWidget(self.comboBoxProcessingMethod) self.checkUseAcceleration = QCheckBox("Use acceleration") self.checkUseAcceleration.setChecked(False) hBoxProcessingMethod.addWidget(self.checkUseAcceleration) vBox2.addLayout(hBoxProcessingMethod) # Use/don't use variance smooth self.checkExtraSmooth = QCheckBox("Extra smooth") self.checkExtraSmooth.setChecked(False) vBox2.addWidget(self.checkExtraSmooth) # 2D interactive map settings hBox2DMap = QHBoxLayout() self.checkUseRDP = QCheckBox("Use RDP to reduce points") self.checkUseRDP.setChecked(False) hBox2DMap.addWidget(self.checkUseRDP) self.check2DMapInExternalBrowser = QCheckBox( "Show in external browser") self.check2DMapInExternalBrowser.setChecked(False) hBox2DMap.addWidget(self.check2DMapInExternalBrowser) vBox2.addLayout(hBox2DMap) # Settings for the 3D map line3DViewSettings = QFrame() #line3DViewSettings.setGeometry(QtCore.QRect(320, 150, 118, 3)) line3DViewSettings.setFrameShape(QFrame.HLine) line3DViewSettings.setFrameShadow(QFrame.Sunken) vBox2.addWidget(line3DViewSettings) label3DViewSettings = QLabel('3D view settings') vBox2.addWidget(label3DViewSettings) hBox3DMapSelection = QHBoxLayout() self.check3DMapSelection = QCheckBox( "Select elevation tiles automatically, otherwise") self.check3DMapSelection.setChecked(True) hBox3DMapSelection.addWidget(self.check3DMapSelection) self.text3DMapName = QLineEdit() self.text3DMapName.setText("Iceland.tif") hBox3DMapSelection.addWidget(self.text3DMapName) vBox2.addLayout(hBox3DMapSelection) hBox3D = QHBoxLayout() label3DMargin = QLabel('Margin') hBox3D.addWidget(label3DMargin) self.spinbox3DMargin = QSpinBox() self.spinbox3DMargin.setRange(50, 1000) self.spinbox3DMargin.setValue(100) self.spinbox3DMargin.setSingleStep(10) hBox3D.addWidget(self.spinbox3DMargin) labelSpace = QLabel(' ') hBox3D.addWidget(labelSpace) label3DElevationScale = QLabel('Elev. scale') hBox3D.addWidget(label3DElevationScale) self.spinbox3DElevationScale = QDoubleSpinBox() self.spinbox3DElevationScale.setRange(1, 50) self.spinbox3DElevationScale.setSingleStep(0.1) hBox3D.addWidget(self.spinbox3DElevationScale) hBox3D.addWidget(labelSpace) label3DOSMZoom = QLabel('Zoom') hBox3D.addWidget(label3DOSMZoom) self.spinbox3DOSMZoom = QSpinBox() self.spinbox3DOSMZoom.setRange(8, 15) self.spinbox3DOSMZoom.setValue(13) self.spinbox3DOSMZoom.setSingleStep(1) hBox3D.addWidget(self.spinbox3DOSMZoom) hBox3D.addWidget(labelSpace) self.check3DOSMInvert = QCheckBox("Invert") self.check3DOSMInvert.setChecked(False) hBox3D.addWidget(self.check3DOSMInvert) vBox2.addLayout(hBox3D) vBox_left.addLayout(vBox2) # 3rd stats tree lineTree = QFrame() lineTree.setFrameShape(QFrame.HLine) lineTree.setFrameShadow(QFrame.Sunken) vBox2.addWidget(lineTree) labelTree = QLabel('Track stats') vBox2.addWidget(labelTree) self.tree = QTreeView() self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.treemodel = QtGui.QStandardItemModel() self.treemodel.setHorizontalHeaderLabels(['Name', 'Value']) self.tree.setModel(self.treemodel) self.tree.setUniformRowHeights(True) self.tree.setColumnWidth(0, 200) vBox_left.addWidget(self.tree) # 4th text, containing text messages/errors self.textWarningConsole = QTextEdit() self.textWarningConsole.setReadOnly(True) self.textWarningConsole.setFont(QtGui.QFont("Courier New", FONTSIZE)) self.textWarningConsole.clear() self.textWarningConsole.setMaximumHeight(50) vBox_left.addWidget(self.textWarningConsole) # I put "vBox_left" inside a widget and then the widget inside "hBox" # instead of just doing "hBox.addLayout(vBox_left) so I can set its # maximum width. vBox_left_widget = QWidget() vBox_left_widget.setLayout(vBox_left) vBox_left_widget.setMinimumWidth(400) vBox_left_widget.setMaximumWidth(500) hBox.addWidget(vBox_left_widget) # Vertical right column self.tab = QTabWidget() # Tab 1: Summary: elevation and speed tab1 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedElevationAndSpeed = EmbeddedPlot_ElevationSpeed( width=5, height=4, dpi=100) self.plotEmbeddedElevationAndSpeed.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar1 = NavigationToolbar( self.plotEmbeddedElevationAndSpeed, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedElevationAndSpeed) vBox_tab.addWidget(self.mpl_toolbar1) # Associate the layout to the tab tab1.setLayout(vBox_tab) # Tab 2: html 2D map tab2 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map2d = QtWebEngineWidgets.QWebEngineView() # Add widgets to the layout vBox_tab.addWidget(self.map2d) # Associate the layout to the tab tab2.setLayout(vBox_tab) # Tab 3: 3D plot tab3 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map3d = MayaviQWidget() # Add widgets to the layout vBox_tab.addWidget(self.map3d) # Associate the layout to the tab tab3.setLayout(vBox_tab) # Tab 4: Details tab4 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedDetails = EmbeddedPlot_Details(width=5, height=4, dpi=100) self.plotEmbeddedDetails.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar2 = NavigationToolbar(self.plotEmbeddedDetails, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedDetails) vBox_tab.addWidget(self.mpl_toolbar2) # Associate the layout to the tab tab4.setLayout(vBox_tab) # Associate tabs self.tab.addTab(tab1, "Summary") self.tab.addTab(tab2, "2D Map") self.tab.addTab(tab3, "3D Map") self.tab.addTab(tab4, "Details") hBox.addWidget(self.tab) # Setting hBox as main box self.scatola.setLayout(hBox) self.setCentralWidget(self.scatola) # Application settings self.setWindowTitle('TrackAnalyser') self.setWindowIcon((QtGui.QIcon('icons/app.png'))) self.setGeometry(100, 100, 1200, 700) self.show()
class MaskWidget(QWidget): values_changed = Signal() def __init__(self, settings: ImageSettings, parent=None): super().__init__(parent) self.setContentsMargins(0, 0, 0, 0) self.settings = settings self.dilate_radius = QSpinBox() self.dilate_radius.setRange(-100, 100) # self.dilate_radius.setButtonSymbols(QAbstractSpinBox.NoButtons) self.dilate_radius.setSingleStep(1) self.dilate_radius.setDisabled(True) self.dilate_dim = QEnumComboBox(enum_class=RadiusType) self.dilate_dim.setToolTip("With minus radius mask will be eroded") # noinspection PyUnresolvedReferences self.dilate_dim.currentIndexChanged.connect( partial(off_widget, self.dilate_radius, self.dilate_dim)) self.radius_information = QLabel() # noinspection PyUnresolvedReferences self.dilate_radius.valueChanged.connect(self.dilate_change) # noinspection PyUnresolvedReferences self.dilate_dim.currentIndexChanged.connect(self.dilate_change) self.fill_holes = QEnumComboBox(enum_class=RadiusType) self.max_hole_size = QSpinBox() self.max_hole_size.setRange(-1, 10000) self.max_hole_size.setValue(-1) self.max_hole_size.setSingleStep(100) self.max_hole_size.setDisabled(True) self.max_hole_size.setToolTip( "Maximum size of holes to be closed. -1 means that all holes will be closed" ) # noinspection PyUnresolvedReferences self.fill_holes.currentIndexChanged.connect( partial(off_widget, self.max_hole_size, self.fill_holes)) self.save_components = QCheckBox() self.clip_to_mask = QCheckBox() self.reversed_check = QCheckBox() # noinspection PyUnresolvedReferences self.dilate_radius.valueChanged.connect(self.values_changed.emit) # noinspection PyUnresolvedReferences self.dilate_dim.currentIndexChanged.connect(self.values_changed.emit) # noinspection PyUnresolvedReferences self.fill_holes.currentIndexChanged.connect(self.values_changed.emit) # noinspection PyUnresolvedReferences self.max_hole_size.valueChanged.connect(self.values_changed.emit) self.save_components.stateChanged.connect(self.values_changed.emit) self.clip_to_mask.stateChanged.connect(self.values_changed.emit) self.reversed_check.stateChanged.connect(self.values_changed.emit) layout = QVBoxLayout() layout1 = QHBoxLayout() layout1.addWidget(QLabel("Dilate mask:")) layout1.addWidget(self.dilate_dim) layout1.addWidget(QLabel("radius (in pix):")) layout1.addWidget(self.dilate_radius) layout.addLayout(layout1) layout2 = QHBoxLayout() layout2.addWidget(QLabel("Fill holes:")) layout2.addWidget(self.fill_holes) layout2.addWidget(QLabel("Max size:")) layout2.addWidget(self.max_hole_size) layout.addLayout(layout2) layout3 = QHBoxLayout() comp_lab = QLabel("Save components:") comp_lab.setToolTip( "save components information in mask. Dilation, " "holes filing will be done separately for each component") self.save_components.setToolTip(comp_lab.toolTip()) layout3.addWidget(comp_lab) layout3.addWidget(self.save_components) layout3.addStretch() clip_mask = QLabel("Clip to previous mask:") clip_mask.setToolTip("Useful dilated new mask") layout3.addWidget(clip_mask) layout3.addWidget(self.clip_to_mask) layout.addLayout(layout3) layout4 = QHBoxLayout() layout4.addWidget(QLabel("Reversed mask:")) layout4.addWidget(self.reversed_check) layout4.addStretch(1) layout.addLayout(layout4) self.setLayout(layout) self.dilate_change() def get_dilate_radius(self): radius = calculate_operation_radius(self.dilate_radius.value(), self.settings.image_spacing, self.dilate_dim.currentEnum()) if isinstance(radius, (list, tuple)): return [int(x + 0.5) for x in radius] return int(radius) def dilate_change(self): if self.dilate_radius.value() == 0 or self.dilate_dim.currentEnum( ) == RadiusType.NO: self.radius_information.setText("Dilation radius: 0") else: dilate_radius = self.get_dilate_radius() if isinstance(dilate_radius, list): self.radius_information.setText( f"Dilation radius: {dilate_radius[::-1]}") else: self.radius_information.setText( f"Dilation radius: {dilate_radius}") def get_mask_property(self): return MaskProperty( dilate=self.dilate_dim.currentEnum() if self.dilate_radius.value() != 0 else RadiusType.NO, dilate_radius=self.dilate_radius.value() if self.dilate_dim.currentEnum() != RadiusType.NO else 0, fill_holes=self.fill_holes.currentEnum() if self.max_hole_size.value() != 0 else RadiusType.NO, max_holes_size=self.max_hole_size.value() if self.fill_holes.currentEnum() != RadiusType.NO else 0, save_components=self.save_components.isChecked(), clip_to_mask=self.clip_to_mask.isChecked(), reversed_mask=self.reversed_check.isChecked(), ) def set_mask_property(self, prop: MaskProperty): self.dilate_dim.setCurrentEnum(prop.dilate) self.dilate_radius.setValue(prop.dilate_radius) self.fill_holes.setCurrentEnum(prop.fill_holes) self.max_hole_size.setValue(prop.max_holes_size) self.save_components.setChecked(prop.save_components) self.clip_to_mask.setChecked(prop.clip_to_mask) self.reversed_check.setChecked(prop.reversed_mask)
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())
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) # Option actions : 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() 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, 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.figviewer.scale_image() 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 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().__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().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 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 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 self.data.flags.writeable = True 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 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() 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 == None: stack_index = self.stack.count() try: self.stack.addWidget( ArrayEditorWidget(self, self.data[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""" 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)
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_text_size = 14 self._default_annotation_size = 18 self._default_coord_scale = 0.05 * 100. self._default_coord_text_scale = 0.5 * 100. self._default_clipping_min = data['min_clip'] self._default_clipping_max = data['max_clip'] #self._default_annotation_size = data['annotation_size'] # int #self.default_magnify = data['magnify'] self.dim_max = data['dim_max'] self._use_gradient_background = data['use_gradient_background'] # bool self._show_corner_coord = data['show_corner_coord'] self._annotation_size = data['annotation_size'] # int #self.out_data = data self._picker_size = data['picker_size'] * 100. self._coord_scale = data['coord_scale'] * 100. self._coord_text_scale = data['coord_text_scale'] * 100. self._magnify = data['magnify'] self._text_size = data['text_size'] self._highlight_opacity = data['highlight_opacity'] 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.background_color2_float, self.background_color2_int = _check_color( data['background_color2']) self.text_color_float, self.text_color_int = _check_color( data['text_color']) self.highlight_color_float, self.highlight_color_int = _check_color( data['highlight_color']) self._nastran_is_element_quality = data['nastran_is_element_quality'] self._nastran_is_properties = data['nastran_is_properties'] self._nastran_is_3d_bars = data['nastran_is_3d_bars'] self._nastran_is_3d_bars_update = data['nastran_is_3d_bars_update'] self._nastran_is_bar_axes = data['nastran_is_bar_axes'] self._nastran_create_coords = data['nastran_create_coords'] self.setWindowTitle('Preferences') self.create_widgets() self.create_layout() self.set_connections() self.on_font(self._default_font_size) self.on_gradient_scale() #self.show() def create_widgets(self): """creates the display window""" # Text Size self.font_size_label = QLabel("Font 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_label = QLabel("Annotation Color:") self.annotation_color_edit = QPushButtonColor( self.annotation_color_int) self.annotation_color_label.hide() self.annotation_color_edit.hide() #----------------------------------------------------------------------- # Text Color self.text_size_label = QLabel("Text Size:") self.text_size_edit = QSpinBox(self) self.text_size_edit.setValue(self._default_text_size) self.text_size_edit.setRange(7, 30) self.text_size_button = QPushButton("Default") # Text Color self.text_color_label = QLabel("Text Color:") self.text_color_edit = QPushButtonColor(self.text_color_int) #----------------------------------------------------------------------- # Highlight Color self.highlight_opacity_label = QLabel("Highlight Opacity:") self.highlight_opacity_edit = QDoubleSpinBox(self) self.highlight_opacity_edit.setValue(self._highlight_opacity) self.highlight_opacity_edit.setRange(0.1, 1.0) self.highlight_opacity_edit.setDecimals(1) self.highlight_opacity_edit.setSingleStep(0.1) self.highlight_opacity_button = QPushButton("Default") # Text Color self.highlight_color_label = QLabel("Highlight Color:") self.highlight_color_edit = QPushButtonColor(self.highlight_color_int) #----------------------------------------------------------------------- # Background Color self.background_color_label = QLabel("Btm Background Color:") self.background_color_edit = QPushButtonColor( self.background_color_int) # Background Color2 self.gradient_scale_label = QLabel("Gradient Background:") self.gradient_scale_checkbox = QCheckBox() self.gradient_scale_checkbox.setChecked(self._use_gradient_background) self.background_color2_label = QLabel("Top Background Color:") self.background_color2_edit = QPushButtonColor( self.background_color2_int) #----------------------------------------------------------------------- # Annotation Size self.annotation_size_label = QLabel("Annotation Size:") self.annotation_size_edit = QSpinBox(self) self.annotation_size_edit.setRange(1, 500) self.annotation_size_edit.setValue(self._annotation_size) self.annotation_size_button = QPushButton("Default") #----------------------------------------------------------------------- # Picker Size self.picker_size_label = 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_label = QLabel("Clipping Min:") self.clipping_min_edit = QLineEdit(str(self._default_clipping_min)) self.clipping_min_button = QPushButton("Default") # Clipping Max self.clipping_max_label = QLabel("Clipping Max:") self.clipping_max_edit = QLineEdit(str(self._default_clipping_max)) self.clipping_max_button = QPushButton("Default") #----------------------------------------------------------------------- self.coord_scale_label = QLabel('Coordinate System Scale:') self.coord_scale_button = QPushButton("Default") self.coord_scale_edit = QDoubleSpinBox(self) self.coord_scale_edit.setRange(0.1, 1000.) self.coord_scale_edit.setDecimals(3) self.coord_scale_edit.setSingleStep(2.5) self.coord_scale_edit.setValue(self._coord_scale) self.coord_text_scale_label = QLabel('Coordinate System Text Scale:') self.coord_text_scale_button = QPushButton("Default") self.coord_text_scale_edit = QDoubleSpinBox(self) self.coord_text_scale_edit.setRange(0.1, 2000.) self.coord_text_scale_edit.setDecimals(3) self.coord_text_scale_edit.setSingleStep(2.5) self.coord_text_scale_edit.setValue(self._coord_text_scale) # Show corner coord self.corner_coord_label = QLabel("Show Corner Coordinate System:") self.corner_coord_checkbox = QCheckBox() self.corner_coord_checkbox.setChecked(self._show_corner_coord) #----------------------------------------------------------------------- self.magnify_label = QLabel('Screenshot Magnify:') self.magnify_edit = QSpinBox(self) self.magnify_edit.setMinimum(1) self.magnify_edit.setMaximum(10) self.magnify_edit.setValue(self._magnify) #----------------------------------------------------------------------- self.nastran_is_element_quality_checkbox = QCheckBox('Element Quality') self.nastran_is_properties_checkbox = QCheckBox('Properties') self.nastran_is_3d_bars_checkbox = QCheckBox('3D Bars') self.nastran_is_3d_bars_update_checkbox = QCheckBox('Update 3D Bars') self.nastran_create_coords_checkbox = QCheckBox('Coords') self.nastran_is_bar_axes_checkbox = QCheckBox('Bar Axes') self.nastran_is_3d_bars_checkbox.setDisabled(True) self.nastran_is_3d_bars_update_checkbox.setDisabled(True) #self.nastran_is_bar_axes_checkbox.setDisabled(True) self.nastran_is_element_quality_checkbox.setChecked( self._nastran_is_element_quality) self.nastran_is_properties_checkbox.setChecked( self._nastran_is_properties) self.nastran_is_3d_bars_checkbox.setChecked(self._nastran_is_3d_bars) #self.nastran_is_3d_bars_update_checkbox.setChecked(self._nastran_is_3d_bars_update) self.nastran_is_3d_bars_update_checkbox.setChecked(False) self.nastran_is_bar_axes_checkbox.setChecked(self._nastran_is_bar_axes) self.nastran_create_coords_checkbox.setChecked( self._nastran_create_coords) #----------------------------------------------------------------------- # 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() irow = 0 grid.addWidget(self.font_size_label, irow, 0) grid.addWidget(self.font_size_edit, irow, 1) grid.addWidget(self.font_size_button, irow, 2) irow += 1 grid.addWidget(self.gradient_scale_label, irow, 0) grid.addWidget(self.gradient_scale_checkbox, irow, 1) irow += 1 grid.addWidget(self.background_color2_label, irow, 0) grid.addWidget(self.background_color2_edit, irow, 1) irow += 1 grid.addWidget(self.background_color_label, irow, 0) grid.addWidget(self.background_color_edit, irow, 1) irow += 1 grid.addWidget(self.highlight_color_label, irow, 0) grid.addWidget(self.highlight_color_edit, irow, 1) irow += 1 grid.addWidget(self.highlight_opacity_label, irow, 0) grid.addWidget(self.highlight_opacity_edit, irow, 1) irow += 1 grid.addWidget(self.text_color_label, irow, 0) grid.addWidget(self.text_color_edit, irow, 1) irow += 1 grid.addWidget(self.text_size_label, irow, 0) grid.addWidget(self.text_size_edit, irow, 1) grid.addWidget(self.text_size_button, irow, 2) irow += 1 grid.addWidget(self.annotation_color_label, irow, 0) grid.addWidget(self.annotation_color_edit, irow, 1) irow += 1 grid.addWidget(self.annotation_size_label, irow, 0) grid.addWidget(self.annotation_size_edit, irow, 1) grid.addWidget(self.annotation_size_button, irow, 2) irow += 1 #grid.addWidget(self.clipping_min_label, irow, 0) #grid.addWidget(self.clipping_min_edit, irow, 1) #grid.addWidget(self.clipping_min_button, irow, 2) #irow += 1 #grid.addWidget(self.clipping_max_label, irow, 0) #grid.addWidget(self.clipping_max_edit, irow, 1) #grid.addWidget(self.clipping_max_button, irow, 2) #irow += 1 grid.addWidget(self.corner_coord_label, irow, 0) grid.addWidget(self.corner_coord_checkbox, irow, 1) irow += 1 grid.addWidget(self.coord_scale_label, irow, 0) grid.addWidget(self.coord_scale_edit, irow, 1) grid.addWidget(self.coord_scale_button, irow, 2) irow += 1 grid.addWidget(self.coord_text_scale_label, irow, 0) grid.addWidget(self.coord_text_scale_edit, irow, 1) grid.addWidget(self.coord_text_scale_button, irow, 2) irow += 1 #----------------------------------------------- grid.addWidget(self.magnify_label, irow, 0) grid.addWidget(self.magnify_edit, irow, 1) irow += 1 grid.addWidget(self.picker_size_label, irow, 0) grid.addWidget(self.picker_size_edit, irow, 1) irow += 1 #-------------------------------------------------- grid_nastran = QGridLayout() irow = 0 grid_nastran.addWidget(self.nastran_create_coords_checkbox, irow, 0) irow += 1 grid_nastran.addWidget(self.nastran_is_element_quality_checkbox, irow, 0) grid_nastran.addWidget(self.nastran_is_properties_checkbox, irow, 1) irow += 1 grid_nastran.addWidget(self.nastran_is_bar_axes_checkbox, irow, 0) irow += 1 grid_nastran.addWidget(self.nastran_is_3d_bars_checkbox, irow, 0) grid_nastran.addWidget(self.nastran_is_3d_bars_update_checkbox, irow, 1) irow += 1 #bold_font = make_font(self._default_font_size, is_bold=True) vbox_nastran = QVBoxLayout() self.nastran_label = QLabel('Nastran:') vbox_nastran.addWidget(self.nastran_label) vbox_nastran.addLayout(grid_nastran) #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.addLayout(vbox_nastran) #vbox.addStretch() #vbox.addLayout(grid2) vbox.addStretch() vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def set_connections(self): """creates the actions for the menu""" self.font_size_button.clicked.connect(self.on_default_font_size) self.font_size_edit.valueChanged.connect(self.on_font) self.annotation_size_edit.editingFinished.connect( self.on_annotation_size) self.annotation_size_edit.valueChanged.connect(self.on_annotation_size) self.annotation_color_edit.clicked.connect(self.on_annotation_color) self.annotation_size_button.clicked.connect( self.on_default_annotation_size) self.background_color_edit.clicked.connect(self.on_background_color) self.background_color2_edit.clicked.connect(self.on_background_color2) self.gradient_scale_checkbox.clicked.connect(self.on_gradient_scale) self.highlight_color_edit.clicked.connect(self.on_highlight_color) self.highlight_opacity_edit.valueChanged.connect( self.on_highlight_opacity) self.text_color_edit.clicked.connect(self.on_text_color) self.text_size_edit.valueChanged.connect(self.on_text_size) self.text_size_button.clicked.connect(self.on_default_text_size) self.picker_size_edit.valueChanged.connect(self.on_picker_size) self.picker_size_edit.editingFinished.connect(self.on_picker_size) self.coord_scale_edit.valueChanged.connect(self.on_coord_scale) self.coord_scale_edit.editingFinished.connect(self.on_coord_scale) self.coord_scale_button.clicked.connect(self.on_default_coord_scale) self.corner_coord_checkbox.clicked.connect(self.on_corner_coord) self.coord_text_scale_edit.valueChanged.connect( self.on_coord_text_scale) self.coord_text_scale_edit.editingFinished.connect( self.on_coord_text_scale) self.coord_text_scale_button.clicked.connect( self.on_default_coord_text_scale) self.magnify_edit.valueChanged.connect(self.on_magnify) self.magnify_edit.editingFinished.connect(self.on_magnify) self.clipping_min_button.clicked.connect(self.on_default_clipping_min) self.clipping_max_button.clicked.connect(self.on_default_clipping_max) #------------------------------------ # format-specific self.nastran_is_element_quality_checkbox.clicked.connect( self.on_nastran_is_element_quality) self.nastran_is_properties_checkbox.clicked.connect( self.on_nastran_is_properties) self.nastran_is_3d_bars_checkbox.clicked.connect( self.on_nastran_is_3d_bars) self.nastran_is_3d_bars_update_checkbox.clicked.connect( self.on_nastran_is_3d_bars) self.nastran_is_bar_axes_checkbox.clicked.connect( self.on_nastran_is_bar_axes) self.nastran_create_coords_checkbox.clicked.connect( self.on_nastran_create_coords) #------------------------------------ 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_nastran_is_element_quality(self): """set the nastran element quality preferences""" is_checked = self.nastran_is_element_quality_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_is_element_quality = is_checked def on_nastran_is_properties(self): """set the nastran properties preferences""" is_checked = self.nastran_is_properties_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_is_properties = is_checked def on_nastran_is_3d_bars(self): """set the nastran properties preferences""" is_checked = self.nastran_is_3d_bars_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_is_3d_bars = is_checked def on_nastran_is_3d_bars_update(self): """set the nastran properties preferences""" is_checked = self.nastran_is_3d_bars_update_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_is_3d_bars_update = is_checked def on_nastran_is_bar_axes(self): """set the nastran properties preferences""" is_checked = self.nastran_is_bar_axes_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_is_bar_axes = is_checked def on_nastran_create_coords(self): """set the nastran properties preferences""" is_checked = self.nastran_create_coords_checkbox.isChecked() if self.win_parent is not None: self.win_parent.settings.nastran_create_coords = is_checked 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) bold_font = make_font(value, is_bold=True) self.nastran_label.setFont(bold_font) def on_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_gradient_scale(self): is_checked = self.gradient_scale_checkbox.isChecked() self.background_color2_label.setEnabled(is_checked) self.background_color2_edit.setEnabled(is_checked) if self.win_parent is not None: self.win_parent.settings.set_gradient_background( use_gradient_background=is_checked) def on_corner_coord(self): is_checked = self.corner_coord_checkbox.isChecked() if self.win_parent is not None: self.win_parent.set_corner_axis_visiblity(is_checked, render=True) 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""" title = "Choose a primary background color" rgb_color_ints = self.background_color_int color_edit = self.background_color_edit func_name = 'set_background_color' passed, rgb_color_ints, rgb_color_floats = self._background_color( title, color_edit, rgb_color_ints, func_name) if passed: self.background_color_int = rgb_color_ints self.background_color_float = rgb_color_floats def on_background_color2(self): """ Choose a background color""" title = "Choose a secondary background color" rgb_color_ints = self.background_color2_int color_edit = self.background_color2_edit func_name = 'set_background_color2' passed, rgb_color_ints, rgb_color_floats = self._background_color( title, color_edit, rgb_color_ints, func_name) if passed: self.background_color2_int = rgb_color_ints self.background_color2_float = rgb_color_floats def on_highlight_color(self): """ Choose a highlight color""" title = "Choose a highlight color" rgb_color_ints = self.highlight_color_int color_edit = self.highlight_color_edit func_name = 'set_highlight_color' passed, rgb_color_ints, rgb_color_floats = self._background_color( title, color_edit, rgb_color_ints, func_name) if passed: self.highlight_color_int = rgb_color_ints self.highlight_color_float = rgb_color_floats def on_highlight_opacity(self, value=None): if value is None: value = self.highlight_opacity_edit.value() self._highlight_opacity = value if self.win_parent is not None: self.win_parent.settings.set_highlight_opacity(value) 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: 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_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_default_text_size(self): self.text_size_edit.setValue(self._default_text_size) self.on_text_size(self._default_text_size) def on_text_size(self, value=None): if value is None: value = self.text_size_edit.value() self._text_size = value if self.win_parent is not None: self.win_parent.settings.set_text_size(value) 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_magnify(self, value=None): if value is None: value = self.magnify_edit.value() self._magnify = value if self.win_parent is not None: self.win_parent.settings.set_magnify(value) #--------------------------------------------------------------------------- def on_coord_scale(self, value=None): if value is None: value = self.coord_scale_edit.value() self._coord_scale = value if self.win_parent is not None: self.win_parent.settings.set_coord_scale(value / 100.) def on_default_coord_scale(self): self.coord_scale_edit.setValue(self._default_coord_scale) self.on_coord_scale(self._default_coord_scale) def on_coord_text_scale(self, value=None): if value is None: value = self.coord_text_scale_edit.value() self._coord_text_scale = value if self.win_parent is not None: self.win_parent.settings.set_coord_text_scale(value / 100.) def on_default_coord_text_scale(self): self.coord_text_scale_edit.setValue(self._default_coord_text_scale) self.on_coord_text_scale(self._default_coord_text_scale) #--------------------------------------------------------------------------- def on_default_font_size(self): self.font_size_edit.setValue(self._default_font_size) self.on_font(self._default_font_size) def on_default_annotation_size(self): self.annotation_size_edit.setValue(self._default_annotation_size) self.on_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;}") def on_validate(self): font_size_value, flag0 = check_float(self.font_size_edit) annotation_size_value, flag1 = 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 = check_float(self.picker_size_edit) clipping_min_value, flag3 = check_label_float(self.clipping_min_edit) clipping_max_value, flag4 = 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['min_clip'] = min(clipping_min_value, clipping_max_value) self.out_data['max_clip'] = 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.clipping_obj.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()
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 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)
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 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 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 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 PlotPreferences(QWidget): def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow plotStyleGroup = GroupWidget("Plot style") styles = self.mainWindow.plot.getValidStyles() self.customStyle = StyleDesigner( styleKeys=self.mainWindow.plot.getStyleKeys(), symbolKeys=self.mainWindow.plot.getStyleSymbolKeys(), invalidNames=styles) self.customStyle.setEnabled(False) self.customStyle.saveStyle.connect(self._saveStyle) self.plotStyleList = QComboBox() styles = [s.capitalize() for s in styles] styles += ["Add custom theme..."] self.plotStyleList.addItems(styles) self.plotStyleList.currentTextChanged.connect( self._updateCustomStyleWidget) foregroundColour = self.palette().windowText().color() icon = makeForegroundIcon("edit", foregroundColour) self.editPlotStyleButton = QPushButton(icon, "") self.editPlotStyleButton.setCheckable(True) self.editPlotStyleButton.setToolTip("Edit theme") self.editPlotStyleButton.toggled.connect(self._editStyle) icon = makeForegroundIcon("trash", foregroundColour) self.deletePlotStyleButton = QPushButton(icon, "") self.deletePlotStyleButton.setToolTip("Delete theme") self.deletePlotStyleButton.clicked.connect(self._deleteTheme) self.plotStyleList.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.editPlotStyleButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.deletePlotStyleButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) plotStyleBox = QHBoxLayout() plotStyleBox.addWidget(self.plotStyleList) plotStyleBox.addWidget(self.editPlotStyleButton) plotStyleBox.addWidget(self.deletePlotStyleButton) plotStyleGroup.addLayout(plotStyleBox) plotStyleGroup.addWidget(self.customStyle) plotConfigGroup = GroupWidget("Default plot range", layout="vbox") self.plotRangeCombo = QComboBox() ranges = [ "1 month", "3 months", "6 months", "1 year", "Current year", "All" ] self.plotRangeCombo.addItems(ranges) self.customRangeCheckBox = QCheckBox("Custom range") self.customRangeSpinBox = QSpinBox() self.customRangeSpinBox.setSuffix(" months") maxMonths = len(mainWindow.data.splitMonths()) self.customRangeSpinBox.setRange(1, maxMonths) self.customRangeCheckBox.clicked.connect(self.setCustomRange) plotRangeLayout = QHBoxLayout() plotRangeLayout.addWidget(self.plotRangeCombo) customRangeLayout = QHBoxLayout() customRangeLayout.addWidget(self.customRangeCheckBox) customRangeLayout.addWidget(self.customRangeSpinBox) plotConfigGroup.addLayout(plotRangeLayout) plotConfigGroup.addLayout(customRangeLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(plotStyleGroup) mainLayout.addWidget(plotConfigGroup) mainLayout.addStretch(1) self.setLayout(mainLayout) self.setCurrentValues() # apply initial state self.apply() def setCurrentValues(self): self.settings = Settings() self.settings.beginGroup("plot") plotStyle = self.settings.value("style", "dark") self.plotStyleList.setCurrentText(plotStyle.capitalize()) # does setCurrentText not emit currentTextChanged signal? self._enableDisableDeleteButton(plotStyle) self.customStyle.setName(plotStyle) self.customStyle.setStyle(self.mainWindow.plot.getStyle(plotStyle)) customRange = self.settings.value("customRange", False) rng = self.settings.value("range", "All") self.setCustomRange(customRange) if customRange: rng = int(rng) self.customRangeSpinBox.setValue(rng) else: items = [ self.plotRangeCombo.itemText(idx) for idx in range(self.plotRangeCombo.count()) ] idx = items.index(rng) self.plotRangeCombo.setCurrentIndex(idx) self.settings.endGroup() def _saveStyle(self, name, style, setStyle=False): self.mainWindow.plot.addCustomStyle(name, style, setStyle=setStyle) idx = self.plotStyleList.count() - 1 self.plotStyleList.insertItem(idx, name.capitalize()) self.plotStyleList.setCurrentIndex(idx) def _editStyle(self, edit): self.customStyle.setEditMode(edit) self._updateCustomStyleWidget() def apply(self): styleName = self.plotStyleList.currentText().lower() if styleName == "add custom theme...": styleName, styleDct = self.customStyle.getStyle() self._saveStyle(styleName, styleDct, setStyle=True) else: self.mainWindow.plot.setStyle(styleName) customRange = self.customRangeCheckBox.isChecked() if customRange: months = self.customRangeSpinBox.value() else: text = self.plotRangeCombo.currentText() if text == "1 year": text = "12 months" elif text == "Current year": text = f"{date.today().month} months" months = None if text == 'All' else int(text.strip(' months')) self.mainWindow.plot.setXAxisRange(months) self.settings.beginGroup("plot") self.settings.setValue("style", styleName) self.settings.setValue("customRange", customRange) if customRange: self.settings.setValue("range", self.customRangeSpinBox.value()) else: self.settings.setValue("range", self.plotRangeCombo.currentText()) self.settings.endGroup() @Slot(bool) def setCustomRange(self, custom): self.customRangeCheckBox.setChecked(custom) if custom: self.customRangeSpinBox.setEnabled(True) self.plotRangeCombo.setEnabled(False) else: self.customRangeSpinBox.setEnabled(False) self.plotRangeCombo.setEnabled(True) def _updateCustomStyleWidget(self, name=None): if name is None or name == "Add custom theme...": self.customStyle.setEnabled(True) if name is None: name = self.customStyle.name else: name = f"custom-{self.customStyle.name}" self.customStyle.setName(name) else: name = name.lower() style = self.mainWindow.plot.getStyle(name) self.customStyle.setStyle(style, name=name) self.customStyle.setEnabled(False) self._enableDisableDeleteButton(name) def _enableDisableDeleteButton(self, plotStyle): if plotStyle in self.mainWindow.plot.getDefaultStyles(): self.deletePlotStyleButton.setEnabled(False) self.deletePlotStyleButton.setToolTip( "Cannot delete default theme") else: self.deletePlotStyleButton.setEnabled(True) self.deletePlotStyleButton.setToolTip("Delete theme") def _deleteTheme(self): styleName = self.plotStyleList.currentText() items = [ self.plotStyleList.itemText(idx) for idx in range(self.plotStyleList.count()) ] idx = items.index(styleName) self.plotStyleList.removeItem(idx) self.mainWindow.plot.removeCustomStyle(styleName.lower())
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()
class AnimationWindow(PyDialog): """ +-------------------+ | Animation | +-------------------------+ | icase ______ | | scale ______ Default | | time ______ Default | | | | nframes ______ Default | | resolu. ______ Default | | Dir ______ Browse | | iFrame ______ | | | | Animations: | | o Scale, Phase, Time | | | | x delete images | | x repeat | # TODO: change to an integer | x make gif | | | | Step, RunAll | | Close | +-------------------------+ TODO: add key-frame support """ def __init__(self, data, win_parent=None): PyDialog.__init__(self, data, win_parent) self.set_font_size(data['font_size']) self.istep = 0 self._animate_type = 'time' self._updated_animation = False self._active_deformation = 0. self._icase_fringe = data['icase_fringe'] self._icase_disp = data['icase_disp'] self._icase_vector = data['icase_vector'] self._default_name = data['name'] self._default_time = data['time'] self._default_fps = data['frames/sec'] self._default_resolution = data['resolution'] self._scale = data['scale'] self._default_scale = data['default_scale'] self._default_is_scale = data['is_scale'] self._arrow_scale = data['arrow_scale'] self._default_arrow_scale = data['default_arrow_scale'] self._phase = data['phase'] self._default_phase = data['default_phase'] self._default_dirname = data['dirname'] self._default_gif_name = os.path.join(self._default_dirname, data['name'] + '.gif') self.animation_types = [ 'Animate Scale', ] #'Animate Phase', #'Animate Time', #'Animate Frequency Sweep' #] self.setWindowTitle('Animate Model') self.create_widgets() self.create_layout() self.set_connections() self.is_gui = False if hasattr(self.win_parent, '_updated_legend'): self.win_parent.is_animate_open = True self.is_gui = True self.on_update_min_max_defaults() def create_widgets(self): """creates the menu objects""" self.box_scale = QGroupBox('Animate Scale') self.box_time = QGroupBox('Animate Time') icase_max = 1000 # TODO: update 1000 self.checkbox_fringe = QCheckBox('Animate') self.checkbox_fringe.setToolTip( 'Animate the fringe in addition to the deflection') #self.checkbox_disp = QCheckBox('Animate') self.checkbox_fringe.setEnabled(False) self.icase_fringe_label = QLabel("iCase (Fringe):") self.icase_fringe_edit = QSpinBox(self) self.icase_fringe_edit.setRange(0, icase_max) self.icase_fringe_edit.setSingleStep(1) if self._icase_fringe is not None: self.icase_fringe_edit.setValue(self._icase_fringe) self.icase_fringe_edit.setToolTip( 'Case Number for the Scale/Phase Animation Type.\n' 'Defaults to the result you had shown when you clicked "Create Animation".\n' 'iCase can be seen by clicking "Apply" on a result.') self.icase_disp_label = QLabel("iCase (Disp):") self.icase_disp_edit = QSpinBox(self) self.icase_disp_edit.setRange(1, icase_max) self.icase_disp_edit.setSingleStep(1) if self._icase_disp is not None: self.icase_disp_edit.setValue(self._icase_disp) self.checkbox_vector = QCheckBox('Animate') self.checkbox_vector.setToolTip( 'Animate the vector in addition to the deflection') self.checkbox_vector.hide() #self.checkbox_disp = QCheckBox('Animate') self.icase_vector_label = QLabel("iCase (Vector):") self.icase_vector_edit = QSpinBox(self) self.icase_vector_edit.setRange(1, icase_max) self.icase_vector_edit.setSingleStep(1) if self._icase_vector is not None: self.icase_vector_edit.setValue(self._icase_vector) self.scale_label = QLabel("True Scale:") self.scale_edit = QLineEdit(str(self._scale)) self.scale_button = QPushButton("Default") self.scale_edit.setToolTip('Scale factor of the "deflection"') self.scale_button.setToolTip('Sets the scale factor of the gif to %s' % self._scale) self.arrow_scale_label = QLabel("Arrow Scale:") self.arrow_scale_edit = QLineEdit(str(self._scale)) self.arrow_scale_button = QPushButton("Default") self.arrow_scale_edit.setToolTip('Scale factor of the "arrows"') self.arrow_scale_button.setToolTip( 'Sets the arrow scale factor of the gif to %s' % self._arrow_scale) self.arrow_scale_label.setVisible(False) self.arrow_scale_edit.setVisible(False) self.arrow_scale_button.setVisible(False) self.time_label = QLabel("Total Time (sec):") self.time_edit = QDoubleSpinBox(self) self.time_edit.setValue(self._default_time) self.time_edit.setRange(0.1, 5. * 60.) self.time_edit.setDecimals(2) self.time_edit.setSingleStep(0.1) self.time_button = QPushButton("Default") self.time_edit.setToolTip("Total time of the gif") self.time_button.setToolTip('Sets the total time of the gif to %.2f' % self._default_time) self.fps_label = QLabel("Frames/Second:") self.fps_edit = QSpinBox(self) self.fps_edit.setRange(1, 60) self.fps_edit.setSingleStep(1) self.fps_edit.setValue(self._default_fps) self.fps_button = QPushButton("Default") self.fps_edit.setToolTip( "A higher FPS is smoother, but may not play well for large gifs") self.fps_button.setToolTip('Sets the FPS to %s' % self._default_fps) self.resolution_label = QLabel("Resolution Scale:") self.resolution_edit = QSpinBox(self) self.resolution_edit.setRange(1, 5) self.resolution_edit.setSingleStep(1) self.resolution_edit.setValue(self._default_resolution) self.resolution_button = QPushButton("Default") self.resolution_edit.setToolTip( 'Scales the window resolution by an integer factor') self.resolution_button.setToolTip('Sets the resolution to %s' % self._default_resolution) #----------------- # Time plot self.fringe_label = QLabel("Fringe") self.icase_fringe_start_edit = QSpinBox(self) self.icase_fringe_start_edit.setRange(0, icase_max) self.icase_fringe_start_edit.setSingleStep(1) self.icase_fringe_start_edit.setValue(self._icase_fringe) self.icase_fringe_start_button = QPushButton("Default") self.icase_fringe_end_edit = QSpinBox(self) self.icase_fringe_end_edit.setRange(0, icase_max) self.icase_fringe_end_edit.setSingleStep(1) self.icase_fringe_end_edit.setValue(self._icase_fringe) self.icase_fringe_end_button = QPushButton("Default") self.icase_fringe_delta_edit = QSpinBox(self) self.icase_fringe_delta_edit.setRange(1, icase_max) self.icase_fringe_delta_edit.setSingleStep(1) self.icase_fringe_delta_edit.setValue(1) self.icase_fringe_delta_button = QPushButton("Default") self.displacement_label = QLabel("Displacement") self.icase_start = QLabel("iCase Start:") self.icase_disp_start_edit = QSpinBox(self) self.icase_disp_start_edit.setRange(0, icase_max) self.icase_disp_start_edit.setSingleStep(1) self.icase_disp_start_edit.setValue(self._icase_fringe) self.icase_disp_start_button = QPushButton("Default") self.icase_end_label = QLabel("iCase End:") self.icase_disp_end_edit = QSpinBox(self) self.icase_disp_end_edit.setRange(0, icase_max) self.icase_disp_end_edit.setSingleStep(1) self.icase_disp_end_edit.setValue(self._icase_fringe) self.icase_disp_end_button = QPushButton("Default") self.icase_delta_label = QLabel("iCase Delta:") self.icase_disp_delta_edit = QSpinBox(self) self.icase_disp_delta_edit.setRange(1, icase_max) self.icase_disp_delta_edit.setSingleStep(1) self.icase_disp_delta_edit.setValue(1) self.icase_disp_delta_button = QPushButton("Default") self.min_value_enable = QCheckBox() self.min_value_label = QLabel("Min Value:") self.min_value_edit = QLineEdit('') #self.min_value_edit.setRange(1, 1000) #self.min_value_edit.setSingleStep(1) #self.min_value_edit.setValue(1) self.min_value_button = QPushButton("Default") self.max_value_enable = QCheckBox() self.max_value_label = QLabel("Max Value:") self.max_value_edit = QLineEdit('') #self.min_value_edit.setRange(1, 1000) # TODO: update 1000 #self.min_value_edit.setSingleStep(1) #self.min_value_edit.setValue(1) self.max_value_button = QPushButton("Default") # TODO: enable this (uncomment) ------------------------------------------ #self.min_value_enable.hide() #self.min_value.hide() #self.min_value_edit.hide() #self.min_value_button.hide() #self.max_value_enable.hide() #self.max_value.hide() #self.max_value_edit.hide() #self.max_value_button.hide() # TODO: enable this (uncomment) ------------------------------------------ self.icase_disp_start_edit.setToolTip( 'The first frame of the animation') self.icase_disp_end_edit.setToolTip( 'The last frame of the animation\n' 'Assumes icase_start + nframes * icase_delta = icase_end') self.icase_disp_delta_edit.setToolTip( 'The frame step size (to skip non-consecutive results).\n' 'Frame skipping can be used to:\n' " - skip across results that you don't want to plot\n" ' - adjust the FPS') self.min_value_edit.setToolTip( 'Min value of the legend (not supported)') self.max_value_edit.setToolTip( 'Max value of the legend (not supported)') #'time' : 0., #'default_time' : 0, #'icase_start' : 10, #'icase_delta' : 3, #'min_value' : 0., #'max_value' : 1000., self.browse_folder_label = QLabel('Output Directory:') self.browse_folder_edit = QLineEdit(str(self._default_dirname)) self.browse_folder_button = QPushButton('Browse') self.browse_folder_edit.setToolTip( 'Location to save the png/gif files') self.gif_label = QLabel("Gif Filename:") self.gif_edit = QLineEdit(str(self._default_name + '.gif')) self.gif_button = QPushButton('Default') self.gif_edit.setToolTip('Name of the gif') self.gif_button.setToolTip('Sets the name of the gif to %s.gif' % self._default_name) # scale / phase if 1: # pragma: no cover self.animate_scale_radio = QRadioButton("Animate Scale") self.animate_phase_radio = QRadioButton("Animate Phase") self.animate_time_radio = QRadioButton("Animate Time") self.animate_freq_sweeep_radio = QRadioButton( "Animate Frequency Sweep") self.animate_scale_radio.setToolTip( 'Animates the scale factor based on the "Animation Type"') self.animate_time_radio.setToolTip( 'Animates the time/load/mode step') self.animate_scale_radio.setChecked(self._default_is_scale) self.animate_phase_radio.setChecked(not self._default_is_scale) self.animate_time_radio.setChecked(False) msg = 'Scale : Animates the scale factor based on the "Animation Profile"\n' if self._default_phase is None: self.animate_phase_radio.setDisabled(True) self.animate_phase_radio.setToolTip( 'Animates the phase angle ' '(only for complex results)') msg += 'Phase : Animates the phase angle (only for complex results)\n' else: self.animate_phase_radio.setToolTip("Animates the phase angle") msg += 'Phase : Animates the phase angle\n' msg += ( 'Time : Animates the time/load/mode step\n' 'Freq Sweep : Animates a complex result across a range of frequencies ' '(not supported)\n') self.animate_freq_sweeep_radio.setDisabled(True) self.animate_freq_sweeep_radio.setToolTip( 'Animates a complex result across a range of frequencies (not supported)' ) else: msg = 'Scale : Animates the scale factor based on the "Animation Profile"\n' if self._default_phase is None: #self.animate_phase_radio.setDisabled(True) #self.animate_phase_radio.setToolTip('Animates the phase angle ' #'(only for complex results)') msg += 'Phase : Animates the phase angle (only for complex results)\n' else: #self.animate_phase_radio.setToolTip("Animates the phase angle") msg += 'Phase : Animates the phase angle\n' msg += ( 'Time : Animates the time/load/mode step\n' 'Freq Sweep : Animates a complex result across a range of frequencies ' '(not supported)\n') self.animation_type = QLabel("Animation Type:") animation_type = OrderedDict() #scale_msg = 'Scale\n' #phase_msg = 'Phase\n' #time_msg = 'Time\n' #animation_types = [ #('Animate Scale', scale_msg), #('Animate Phase', phase_msg), #('Animate Time', time_msg), ##'Animate Frequency Sweep' #] if self._phase is not None: self.animation_types.append('Animate Phase') self.animation_types.append('Animate Time') self.animation_profile_label = QLabel("Animation Profile:") self.animation_profile_edit = QComboBox() for animation_profile in ANIMATION_PROFILES: self.animation_profile_edit.addItem(animation_profile) self.animation_profile_edit.setToolTip('The profile for a scaled GIF') self.animation_type_edit = QComboBox() # TODO: add a tooltip for each item for animation_type in self.animation_types: self.animation_type_edit.addItem(animation_type) #self.animation_type_edit.setToolTip('The profile for a scaled GIF') self.animation_type_edit.setToolTip(msg.rstrip()) self.csv_profile_label = QLabel("CSV profile:") self.csv_profile_edit = QLineEdit() self.csv_profile_browse_button = QPushButton('Browse') self.csv_profile_edit.setToolTip( 'The path to the CSV file of (Scale1, Scale2, Scale3, ...)') #widget = QWidget(self) #horizontal_vertical_group = QButtonGroup(widget) #horizontal_vertical_group.addButton(self.animate_scale_radio) #horizontal_vertical_group.addButton(self.animate_phase_radio) #horizontal_vertical_group.addButton(self.animate_time_radio) #horizontal_vertical_group.addButton(self.animate_freq_sweeep_radio) # animate in gui self.animate_in_gui_checkbox = QCheckBox("Animate In GUI?") self.animate_in_gui_checkbox.setChecked(True) # make images self.make_images_checkbox = QCheckBox("Make images?") self.make_images_checkbox.setChecked(True) # make images self.overwrite_images_checkbox = QCheckBox("Overwrite images?") self.overwrite_images_checkbox.setChecked(True) # delete images when finished self.delete_images_checkbox = QCheckBox("Delete images when finished?") self.delete_images_checkbox.setChecked(True) # endless loop self.repeat_checkbox = QCheckBox("Repeat?") self.repeat_checkbox.setChecked(True) self.repeat_checkbox.setToolTip( "Repeating creates an infinitely looping gif") # endless loop self.make_gif_checkbox = QCheckBox("Make Gif?") if IS_IMAGEIO: self.make_gif_checkbox.setChecked(True) else: self.make_gif_checkbox.setChecked(False) self.make_gif_checkbox.setEnabled(False) self.make_gif_checkbox.setToolTip( 'imageio is not available; install it') # bottom buttons self.step_button = QPushButton("Step") self.wipe_button = QPushButton("Wipe Deformed Shape") self.stop_button = QPushButton("Stop") self.run_button = QPushButton("Run") self.step_button.setToolTip( 'Steps through the animation (for testing)') self.wipe_button.setToolTip( 'Removes the existing "deflecton" from the animation') self.stop_button.setToolTip('Stops the animation') self.run_button.setToolTip('Creates the animation') self.step_button.hide() self.wipe_button.hide() self.wipe_button.setEnabled(False) #self.wipe_button.hide() self.stop_button.setEnabled(False) self.cancel_button = QPushButton("Close") #self.set_grid_time(enabled=False) #self.set_grid_scale(enabled=self._default_is_scale) if self._default_phase: self.on_animate_phase(force=True) set_combo_box_text(self.animation_type_edit, 'Animate Phase') else: self.on_animate_scale(force=True) def set_connections(self): """creates button actions""" self.checkbox_vector.clicked.connect(self.on_checkbox_vector) self.scale_button.clicked.connect(self.on_default_scale) self.arrow_scale_button.clicked.connect(self.on_default_arrow_scale) self.time_button.clicked.connect(self.on_default_time) self.fps_button.clicked.connect(self.on_default_fps) self.resolution_button.clicked.connect(self.on_default_resolution) self.browse_folder_button.clicked.connect(self.on_browse_folder) self.csv_profile_browse_button.clicked.connect(self.on_browse_csv) self.gif_button.clicked.connect(self.on_default_name) self.step_button.clicked.connect(self.on_step) self.wipe_button.clicked.connect(self.on_wipe) self.stop_button.clicked.connect(self.on_stop) self.run_button.clicked.connect(self.on_run) self.min_value_enable.clicked.connect(self.on_min_value_enable) self.max_value_enable.clicked.connect(self.on_max_value_enable) self.min_value_button.clicked.connect(self.on_min_value_default) self.max_value_button.clicked.connect(self.on_max_value_default) self.icase_disp_start_button.clicked.connect( self.on_update_min_max_defaults) #self.animate_scale_radio.clicked.connect(self.on_animate_scale) #self.animate_phase_radio.clicked.connect(self.on_animate_phase) #self.animate_time_radio.clicked.connect(self.on_animate_time) self.animation_type_edit.currentIndexChanged.connect(self.on_animate) #self.animate_freq_sweeep_radio self.cancel_button.clicked.connect(self.on_cancel) self.animate_in_gui_checkbox.clicked.connect(self.on_animate_in_gui) self.animate_in_gui_checkbox.setChecked(True) self.on_animate_in_gui() def on_checkbox_vector(self): is_enabled = self.checkbox_vector.isEnabled() is_checked = self.checkbox_vector.isChecked() enable_edit = is_enabled and is_checked self.icase_vector_label.setEnabled(is_checked) self.icase_vector_edit.setEnabled(enable_edit) def on_animate_in_gui(self): animate_in_gui = self.animate_in_gui_checkbox.isChecked() enable = not animate_in_gui if HIDE_WHEN_INACTIVE: self.make_images_checkbox.setVisible(enable) self.delete_images_checkbox.setVisible(enable) self.make_gif_checkbox.setVisible(enable) self.repeat_checkbox.setVisible(enable) self.resolution_button.setVisible(enable) self.resolution_label.setVisible(enable) self.resolution_edit.setVisible(enable) self.gif_label.setVisible(enable) self.gif_edit.setVisible(enable) self.gif_button.setVisible(enable) self.browse_folder_label.setVisible(enable) self.browse_folder_button.setVisible(enable) self.browse_folder_edit.setVisible(enable) self.step_button.setEnabled(enable) self.make_images_checkbox.setEnabled(enable) self.delete_images_checkbox.setEnabled(enable) self.make_gif_checkbox.setEnabled(enable) self.repeat_checkbox.setEnabled(enable) self.resolution_button.setEnabled(enable) self.resolution_edit.setEnabled(enable) self.gif_edit.setEnabled(enable) self.gif_button.setEnabled(enable) self.browse_folder_button.setEnabled(enable) self.browse_folder_edit.setEnabled(enable) self.step_button.setEnabled(enable) #wipe_button def on_animate(self, value): """ animate pulldown Parameters ---------- value : int index in animation_types """ #animation_types = ['Animate Scale', 'Animate Phase', 'Animate Time', #'Animate Frequency Sweep'] animation_type = self.animation_types[value] if animation_type == 'Animate Scale': self.on_animate_scale() elif animation_type == 'Animate Phase': self.on_animate_phase() elif animation_type == 'Animate Time': self.on_animate_time() else: raise NotImplementedError('value = ', value) def on_animate_time(self, force=False): """enables the secondary input""" #print('on_animate_time') if self._animate_type == 'scale' or force: self.set_grid_scale(False, 'time') self.set_grid_time(True, 'time') self._animate_type = 'time' def on_animate_scale(self, force=False): """enables the secondary input""" #print('on_animate_scale') self.set_grid_scale(True, 'scale') if self._animate_type == 'time' or force: self.set_grid_time(False, 'scale') self._animate_type = 'scale' def on_animate_phase(self, force=False): """enables the secondary input""" #print('on_animate_phase') if self._animate_type == 'scale' or force: self.set_grid_scale(False, 'phase') if self._animate_type == 'time' or force: self.set_grid_time(False, 'phase') self._animate_type = 'phase' def set_grid_scale(self, enabled=True, word=''): """enables/disables the secondary input""" #print('%s-set_grid_scale; enabled = %r' % (word, enabled)) if HIDE_WHEN_INACTIVE: self.box_scale.setVisible(enabled) self.animation_profile_label.setVisible(enabled) self.animation_profile_edit.setVisible(enabled) #self.min_value_enable.setVisible(enabled) #self.max_value_enable.setVisible(enabled) self.animation_profile_label.setEnabled(enabled) self.animation_profile_edit.setEnabled(enabled) # TODO: doesn't work... #self.csv_profile.setEnabled(enabled) #self.csv_profile_edit.setEnabled(enabled) #self.csv_profile_button.setEnabled(enabled) self.min_value_enable.setEnabled(enabled) self.max_value_enable.setEnabled(enabled) self.on_min_value_enable() self.on_max_value_enable() def set_grid_time(self, enabled=True, word=''): """enables/disables the secondary input""" #print('%s-set_grid_time; enabled = %r' % (word, enabled)) if HIDE_WHEN_INACTIVE: self.box_time.setVisible(enabled) self.checkbox_fringe.setVisible(not enabled) self.icase_fringe_label.setVisible(not enabled) self.icase_fringe_edit.setVisible(not enabled) self.icase_disp_label.setVisible(not enabled) self.icase_disp_edit.setVisible(not enabled) self.icase_vector_label.setVisible(not enabled) self.icase_vector_edit.setVisible(not enabled) #self.icase_fringe_delta_edit.setVisible(enabled) self.icase_disp_delta_edit.setVisible(enabled) self.icase_disp_delta_edit.setVisible(enabled) self.fps_label.setVisible(enabled) self.fps_edit.setVisible(enabled) self.fps_button.setVisible(enabled) self.displacement_label.setEnabled(enabled) self.fringe_label.setEnabled(enabled) self.icase_start.setEnabled(enabled) self.icase_disp_start_edit.setEnabled(enabled) self.icase_disp_start_button.setEnabled(enabled) self.icase_fringe_start_edit.setEnabled(enabled) self.icase_fringe_start_button.setEnabled(enabled) self.icase_end_label.setEnabled(enabled) self.icase_disp_end_edit.setEnabled(enabled) self.icase_disp_end_button.setEnabled(enabled) self.icase_fringe_end_edit.setEnabled(enabled) self.icase_fringe_end_button.setEnabled(enabled) self.icase_delta_label.setEnabled(enabled) self.icase_disp_delta_edit.setEnabled(enabled) self.icase_disp_delta_button.setEnabled(enabled) self.icase_fringe_delta_edit.setEnabled(enabled) self.icase_fringe_delta_button.setEnabled(enabled) #----------------------------------------------------------------------- is_min_enabled = self.min_value_enable.isChecked() self.min_value_label.setEnabled(is_min_enabled) self.min_value_edit.setEnabled(is_min_enabled) self.min_value_button.setEnabled(is_min_enabled) is_max_enabled = self.max_value_enable.isChecked() self.max_value_label.setEnabled(is_max_enabled) self.max_value_edit.setEnabled(is_max_enabled) self.max_value_button.setEnabled(is_max_enabled) self.min_value_enable.setEnabled(enabled) self.on_min_value_enable() #self.min_value.setEnabled(enabled) #self.min_value_edit.setEnabled(enabled) #self.min_value_button.setEnabled(enabled) self.max_value_enable.setEnabled(enabled) self.on_max_value_enable() #self.max_value.setEnabled(enabled) #self.max_value_edit.setEnabled(enabled) #self.max_value_button.setEnabled(enabled) self.icase_fringe_label.setEnabled(not enabled) self.icase_fringe_edit.setEnabled(not enabled) self.checkbox_fringe.setEnabled(not enabled) self.icase_disp_label.setEnabled(not enabled) self.icase_disp_edit.setEnabled(not enabled) self.icase_vector_label.setEnabled(not enabled) self.icase_vector_edit.setEnabled(not enabled) self.checkbox_vector.setEnabled(not enabled) self.on_checkbox_vector() self.fps_label.setEnabled(not enabled) self.fps_edit.setEnabled(not enabled) self.fps_button.setEnabled(not enabled) def on_min_value_enable(self): """ The min edit value box is enabled when we switch to time and the box is checked """ is_min_enabled = self.min_value_enable.isChecked( ) and self.min_value_enable.isEnabled() self.min_value_label.setEnabled(is_min_enabled) self.min_value_edit.setEnabled(is_min_enabled) self.min_value_button.setEnabled(is_min_enabled) def on_max_value_enable(self): """ The max edit value box is enabled when we switch to time and the box is checked """ is_max_enabled = self.max_value_enable.isChecked( ) and self.max_value_enable.isEnabled() self.max_value_label.setEnabled(is_max_enabled) self.max_value_edit.setEnabled(is_max_enabled) self.max_value_button.setEnabled(is_max_enabled) def on_update_min_max_defaults(self): """ When the icase is changed, the min/max value default message is changed """ icase = self.icase_disp_start_edit.value() min_value, max_value = self.get_min_max(icase) self.min_value_button.setToolTip('Sets the min value to %g' % min_value) self.max_value_button.setToolTip('Sets the max value to %g' % max_value) def on_min_value_default(self): """When min default icase is pressued, update the value""" icase = self.icase_disp_start_edit.value() min_value = self.get_min_max(icase)[0] self.min_value_edit.setText(str(min_value)) self.min_value_edit.setStyleSheet("QLineEdit{background: white;}") def on_max_value_default(self): """When max default icase is pressued, update the value""" icase = self.icase_disp_start_edit.value() max_value = self.get_min_max(icase)[1] self.max_value_edit.setText(str(max_value)) self.max_value_edit.setStyleSheet("QLineEdit{background: white;}") def on_browse_folder(self): """opens a folder dialog""" dirname = getexistingdirectory(parent=self, caption='Select a Directory', basedir='', options=QFileDialog.ShowDirsOnly) if not dirname: return self.browse_folder_edit.setText(dirname) def on_browse_csv(self): """opens a file dialog""" default_filename = '' file_types = 'Delimited Text (*.txt; *.dat; *.csv)' dirname = open_file_dialog(self, 'Select a CSV File', default_filename, file_types) if not dirname: return self.csv_profile_browse_button.setText(dirname) def on_default_name(self): """sets the default gif name""" self.gif_edit.setText(self._default_name + '.gif') def on_default_scale(self): """sets the default displacement scale factor""" self.scale_edit.setText(str(self._default_scale)) self.scale_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_arrow_scale(self): """sets the default arrow scale factor""" self.arrow_scale_edit.setText(str(self._default_arrow_scale)) self.arrow_scale_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_time(self): """sets the default gif time""" self.time_edit.setValue(self._default_time) def on_default_fps(self): """sets the default FPS""" self.fps_edit.setValue(self._default_fps) def on_default_resolution(self): """sets the default image resolution scale factor""" self.resolution_edit.setValue(self._default_resolution) def create_layout(self): """displays the menu objects""" grid = QGridLayout() irow = 0 grid.addWidget(self.icase_fringe_label, irow, 0) grid.addWidget(self.icase_fringe_edit, irow, 1) grid.addWidget(self.checkbox_fringe, irow, 2) irow += 1 grid.addWidget(self.icase_disp_label, irow, 0) grid.addWidget(self.icase_disp_edit, irow, 1) #grid.addWidget(self.checkbox_disp, irow, 2) irow += 1 grid.addWidget(self.icase_vector_label, irow, 0) grid.addWidget(self.icase_vector_edit, irow, 1) grid.addWidget(self.checkbox_vector, irow, 2) irow += 1 grid.addWidget(self.scale_label, irow, 0) grid.addWidget(self.scale_edit, irow, 1) grid.addWidget(self.scale_button, irow, 2) irow += 1 grid.addWidget(self.arrow_scale_label, irow, 0) grid.addWidget(self.arrow_scale_edit, irow, 1) grid.addWidget(self.arrow_scale_button, irow, 2) irow += 1 grid.addWidget(self.time_label, irow, 0) grid.addWidget(self.time_edit, irow, 1) grid.addWidget(self.time_button, irow, 2) irow += 1 # spacer spacer = QLabel('') grid.addWidget(self.fps_label, irow, 0) grid.addWidget(self.fps_edit, irow, 1) grid.addWidget(self.fps_button, irow, 2) irow += 1 grid.addWidget(self.animation_type, irow, 0) grid.addWidget(self.animation_type_edit, irow, 1) irow += 1 grid.addWidget(spacer, irow, 0) irow += 1 #---------- #Time grid_time = QGridLayout() jrow = 0 self.fringe_label.setAlignment(Qt.AlignCenter) self.displacement_label.setAlignment(Qt.AlignCenter) if not IS_TIME_FRINGE: self.fringe_label.hide() self.icase_fringe_delta_edit.hide() self.icase_fringe_start_edit.hide() self.icase_fringe_end_edit.hide() self.icase_fringe_delta_button.hide() grid_time.addWidget(self.displacement_label, jrow, 1) grid_time.addWidget(self.fringe_label, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_start, jrow, 0) grid_time.addWidget(self.icase_disp_start_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_start_edit, jrow, 2) #grid_time.addWidget(self.icase_disp_start_button, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_end_label, jrow, 0) grid_time.addWidget(self.icase_disp_end_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_end_edit, jrow, 2) #grid_time.addWidget(self.icase_end_button, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_delta_label, jrow, 0) grid_time.addWidget(self.icase_disp_delta_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_delta_edit, jrow, 2) #grid_time.addWidget(self.icase_delta_button, jrow, 2) jrow += 1 hbox_min = QHBoxLayout() hbox_min.addWidget(self.min_value_enable) hbox_min.addWidget(self.min_value_label) grid_time.addLayout(hbox_min, jrow, 0) grid_time.addWidget(self.min_value_edit, jrow, 1) grid_time.addWidget(self.min_value_button, jrow, 2) jrow += 1 hbox_max = QHBoxLayout() hbox_max.addWidget(self.max_value_enable) hbox_max.addWidget(self.max_value_label) grid_time.addLayout(hbox_max, jrow, 0) grid_time.addWidget(self.max_value_edit, jrow, 1) grid_time.addWidget(self.max_value_button, jrow, 2) jrow += 1 grid_time.addWidget(spacer, jrow, 0) jrow += 1 #-------------- grid_scale = QGridLayout() grid_scale.addWidget(self.animation_profile_label, 0, 0) grid_scale.addWidget(self.animation_profile_edit, 0, 1) #grid_scale.addWidget(self.csv_profile, 1, 0) #grid_scale.addWidget(self.csv_profile_edit, 1, 1) #grid_scale.addWidget(self.csv_profile_browse_button, 1, 2) self.csv_profile = QLabel("CSV profile:") self.csv_profile_edit = QLineEdit() self.csv_profile_button = QPushButton('Browse') #box_time = QVBoxLayout() # TODO: It's super annoying that the animate time box doesn't # line up with the previous box self.box_scale.setLayout(grid_scale) self.box_time.setLayout(grid_time) #---------- grid2 = QGridLayout() irow = 0 #grid2.addWidget(self.animate_scale_radio, 8, 0) #grid2.addWidget(self.animate_phase_radio, 8, 1) #grid2.addWidget(self.animate_time_radio, 8, 2) #grid2.addWidget(self.animate_freq_sweeep_radio, 8, 3) grid2.addWidget(self.animate_in_gui_checkbox, irow, 0) irow += 1 grid2.addWidget(self.resolution_label, irow, 0) grid2.addWidget(self.resolution_edit, irow, 1) grid2.addWidget(self.resolution_button, irow, 2) irow += 1 grid2.addWidget(self.browse_folder_label, irow, 0) grid2.addWidget(self.browse_folder_edit, irow, 1) grid2.addWidget(self.browse_folder_button, irow, 2) irow += 1 grid2.addWidget(self.gif_label, irow, 0) grid2.addWidget(self.gif_edit, irow, 1) grid2.addWidget(self.gif_button, irow, 2) irow += 1 grid2.addWidget(self.make_images_checkbox, irow, 0) #grid2.addWidget(self.overwrite_images_checkbox, irow, 0) grid2.addWidget(self.delete_images_checkbox, irow, 1) grid2.addWidget(self.make_gif_checkbox, irow, 2) irow += 1 grid2.addWidget(self.repeat_checkbox, irow, 0) irow += 1 grid2.addWidget(spacer, irow, 0) grid_hbox = QHBoxLayout() grid_hbox.addWidget(spacer) grid_hbox.addLayout(grid2) grid_hbox.addWidget(spacer) # bottom buttons step_run_box = QHBoxLayout() step_run_box.addWidget(self.step_button) step_run_box.addWidget(self.wipe_button) step_run_box.addWidget(self.stop_button) step_run_box.addWidget(self.run_button) ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.cancel_button) vbox = QVBoxLayout() vbox.addLayout(grid) vbox.addWidget(self.box_scale) vbox.addWidget(self.box_time) #vbox.addLayout(checkboxes) vbox.addLayout(grid_hbox) vbox.addStretch() vbox.addLayout(step_run_box) vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def on_step(self): """click the Step button""" passed, validate_out = self.on_validate() if passed: try: self._make_gif(validate_out, istep=self.istep) self.istep += 1 except IndexError: self._make_gif(validate_out, istep=0) self.istep += 1 self.wipe_button.setEnabled(True) def on_wipe(self): """click the Wipe button""" passed, validate_out = self.on_validate(wipe=True) if passed: self.istep = 0 self._make_gif(validate_out, istep=self.istep) self.wipe_button.setEnabled(False) self.stop_button.setEnabled(False) def on_stop(self): """click the Stop button""" #passed, validate_out = self.on_validate() #if passed: #self._make_gif(validate_out, stop_animation=True) if self.is_gui: self.win_parent.win_parent.stop_animation() self.wipe_button.setEnabled(True) self.stop_button.setEnabled(False) def on_run(self): """click the Run button""" self.istep = 0 self.wipe_button.setEnabled(False) self.stop_button.setEnabled(True) passed, validate_out = self.on_validate() if passed: self._make_gif(validate_out, istep=None) return passed def _make_gif(self, validate_out, istep=None, stop_animation=False): """interface for making the gif""" (icase_fringe, icase_disp, icase_vector, scale, time, fps, animate_in_gui, magnify, output_dir, gifbase, min_value, max_value) = validate_out fps = int(fps) gif_filename = None if not stop_animation and not animate_in_gui and gifbase is not None: if gifbase.lower().endswith('.gif'): gifbase = gifbase[:-4] gif_filename = os.path.join(output_dir, gifbase + '.gif') animate_fringe = self.checkbox_fringe.isChecked() animate_vector = self.checkbox_vector.isChecked() animate_scale = self.animate_scale_radio.isChecked() animate_phase = self.animate_phase_radio.isChecked() animate_time = self.animate_time_radio.isChecked() if not self.checkbox_vector.isEnabled(): icase_vector = None animate_scale = False animate_phase = False animate_time = False if self._animate_type == 'scale': animate_scale = True elif self._animate_type == 'phase': animate_phase = True elif self._animate_type == 'time': animate_time = True else: raise NotImplementedError(self._animate_type) make_images = self.make_images_checkbox.isChecked() delete_images = self.delete_images_checkbox.isChecked() make_gif = self.make_gif_checkbox.isChecked() animation_profile = str(self.animation_profile_edit.currentText()) icase_disp_start = self.icase_disp_start_edit.value() icase_disp_end = self.icase_disp_end_edit.value() icase_disp_delta = self.icase_disp_delta_edit.value() bool_repeat = self.repeat_checkbox.isChecked( ) # TODO: change this to an integer if bool_repeat: nrepeat = 0 else: nrepeat = 1 #self.out_data['is_shown'] = self.show_radio.isChecked() #icase = self._icase if self.is_gui: self.win_parent.win_parent.make_gif( gif_filename, scale, istep=istep, animate_scale=animate_scale, animate_phase=animate_phase, animate_time=animate_time, icase_fringe=icase_fringe, icase_disp=icase_disp, icase_vector=icase_vector, animate_fringe=animate_fringe, animate_vector=animate_vector, icase_start=icase_disp_start, icase_end=icase_disp_end, icase_delta=icase_disp_delta, time=time, animation_profile=animation_profile, nrepeat=nrepeat, fps=fps, magnify=magnify, make_images=make_images, delete_images=delete_images, make_gif=make_gif, stop_animation=stop_animation, animate_in_gui=animate_in_gui, min_value=min_value, max_value=max_value, ) self.out_data['clicked_ok'] = True self.out_data['close'] = True def get_min_max(self, icase): if self.is_gui: (obj, (i, name)) = self.win_parent.win_parent.result_cases[icase] min_value, max_value = obj.get_min_max(i, name) else: return 0., 1.0 return min_value, max_value def on_validate(self, wipe=False): """checks to see if the input is valid""" # requires no special validation icase_fringe, flag0 = check_int(self.icase_fringe_edit) icase_disp, unused_flaga = check_int(self.icase_disp_edit) icase_vector, unused_flagb = check_int(self.icase_vector_edit) #icase_disp = self._icase_disp #icase_vector = self._icase_vector scale, flag1 = check_float(self.scale_edit) time, flag2 = check_float(self.time_edit) fps, flag3 = check_float(self.fps_edit) min_value = max_value = None flag4 = flag5 = True if self.min_value_edit.isEnabled(): min_value, flag4 = check_float(self.min_value_edit) if self.max_value_edit.isEnabled(): max_value, flag5 = check_float(self.max_value_edit) if wipe: animate_in_gui = False scale = 0. flag1 = True else: animate_in_gui = self.animate_in_gui_checkbox.isChecked() if animate_in_gui or wipe: passed = all([flag0, flag1, flag2, flag3, flag4, flag5]) magnify, output_dir, gifbase = None, None, None else: magnify, flag6 = check_int(self.resolution_edit) output_dir, flag7 = self.check_path(self.browse_folder_edit) gifbase, flag8 = self.check_name(self.gif_edit) passed = all([ flag0, flag1, flag2, flag3, flag4, flag5, flag6, flag7, flag8 ]) return passed, (icase_fringe, icase_disp, icase_vector, scale, time, fps, animate_in_gui, magnify, output_dir, gifbase, min_value, max_value) @staticmethod def check_name(cell): """verifies that the data is string-able""" cell_value = cell.text() try: text = str(cell_value).strip() except UnicodeEncodeError: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if len(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False def check_path(self, cell): """verifies that the path exists""" text, passed = self.check_name(cell) if not passed: return None, False if os.path.exists(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False #def on_ok(self): #"""click the OK button""" #passed = self.on_apply() #if passed: #self.win_parent._animation_window_shown = False #self.close() ##self.destroy() def on_cancel(self): """click the Cancel button""" self.on_stop() self.out_data['close'] = True self.close()
class ItemPropertyDialog(QDialog): 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() def accept(self): self.item.name = self.nameLineEdit.text() self.item.unit = self.unitLineEdit.text() self.item.decimals = self.decimalsSpinBox.value() self.item.min = self.minSpinBox.value() self.item.max = self.maxSpinBox.value() self.item.scalefactor = self.scalefactorSpinBox.value() self.item.readonly = self.readonlyCheckBox.checkState() == Qt.Checked self.item.type = self.typeComboBox.currentText() return super().accept() def data_changed(self): type_numeric = self.typeComboBox.currentText() not in ('bool', 'str') type_int = self.typeComboBox.currentText() == 'int' readonly = self.readonlyCheckBox.checkState() == Qt.Checked # not used properties invisible self.decimalsSpinBox.setVisible(type_numeric) self.decimalsLabel.setVisible(type_numeric) self.scalefactorSpinBox.setVisible(type_numeric) self.scalefactorLabel.setVisible(type_numeric) self.minSpinBox.setVisible(type_numeric and not readonly) self.minLabel.setVisible(type_numeric and not readonly) self.maxSpinBox.setVisible(type_numeric and not readonly) self.maxLabel.setVisible(type_numeric and not readonly) self.unitLineEdit.setVisible(type_numeric) self.unitLabel.setVisible(type_numeric) # no decimals for int self.minSpinBox.setDecimals(self.decimalsSpinBox.value()) self.maxSpinBox.setDecimals(self.decimalsSpinBox.value()) if type_int: delta = self.decimalsSpinBox.value() - self.last_decimals self.scalefactorSpinBox.setValue( self.scalefactorSpinBox.value() / 10**delta ) self.last_decimals = self.decimalsSpinBox.value()
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 EditGeometryProperties(PyDialog): force = True def __init__(self, data, win_parent=None): """ +------------------+ | Edit Actor Props | +------------------+------+ | Name1 | | Name2 | | Name3 | | Name4 | | | | Active_Name main | | Color box | | Line_Width 2 | | Point_Size 2 | | Bar_Scale 2 | | Opacity 0.5 | | Show/Hide | | | | Apply OK Cancel | +-------------------------+ """ PyDialog.__init__(self, data, win_parent) self.set_font_size(data['font_size']) del self.out_data['font_size'] self.setWindowTitle('Edit Geometry Properties') self.allow_update = True #default #self.win_parent = win_parent #self.out_data = data self.keys = sorted(data.keys()) self.keys = data.keys() keys = self.keys #nrows = len(keys) self.active_key = 'main' items = list(keys) header_labels = ['Groups'] table_model = Model(items, header_labels, self) view = SingleChoiceQTableView(self) #Call your custom QTableView here view.setModel(table_model) #if qt_version == 4: #view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table = view #self.opacity_edit.valueChanged.connect(self.on_opacity) #mListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*))); #self.table.itemClicked.connect(self.table.mouseDoubleClickEvent) actor_obj = data[self.active_key] name = actor_obj.name line_width = actor_obj.line_width point_size = actor_obj.point_size bar_scale = actor_obj.bar_scale opacity = actor_obj.opacity color = actor_obj.color show = actor_obj.is_visible self.representation = actor_obj.representation # table header = self.table.horizontalHeader() header.setStretchLastSection(True) self._default_is_apply = False self.name = QLabel("Name:") self.name_edit = QLineEdit(str(name)) self.name_edit.setDisabled(True) self.color = QLabel("Color:") self.color_edit = QPushButton() #self.color_edit.setFlat(True) color = self.out_data[self.active_key].color qcolor = QtGui.QColor() qcolor.setRgb(*color) #print('color =%s' % str(color)) palette = QtGui.QPalette(self.color_edit.palette()) # make a copy of the palette #palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base, \ #qcolor) palette.setColor(QtGui.QPalette.Background, QtGui.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.use_slider = True self.is_opacity_edit_active = False self.is_opacity_edit_slider_active = False self.is_line_width_edit_active = False self.is_line_width_edit_slider_active = False self.is_point_size_edit_active = False self.is_point_size_edit_slider_active = False self.is_bar_scale_edit_active = False self.is_bar_scale_edit_slider_active = False 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) if self.use_slider: self.opacity_slider_edit = QSlider(QtCore.Qt.Horizontal) self.opacity_slider_edit.setRange(1, 10) self.opacity_slider_edit.setValue(opacity * 10) self.opacity_slider_edit.setTickInterval(1) self.opacity_slider_edit.setTickPosition(QSlider.TicksBelow) self.line_width = QLabel("Line Width:") self.line_width_edit = QSpinBox(self) self.line_width_edit.setRange(1, MAX_LINE_WIDTH) self.line_width_edit.setSingleStep(1) self.line_width_edit.setValue(line_width) if self.use_slider: self.line_width_slider_edit = QSlider(QtCore.Qt.Horizontal) self.line_width_slider_edit.setRange(1, MAX_LINE_WIDTH) self.line_width_slider_edit.setValue(line_width) self.line_width_slider_edit.setTickInterval(1) self.line_width_slider_edit.setTickPosition(QSlider.TicksBelow) if self.representation in ['point', 'surface']: self.line_width.setEnabled(False) self.line_width_edit.setEnabled(False) self.line_width_slider_edit.setEnabled(False) self.point_size = QLabel("Point Size:") self.point_size_edit = QSpinBox(self) self.point_size_edit.setRange(1, MAX_POINT_SIZE) self.point_size_edit.setSingleStep(1) self.point_size_edit.setValue(point_size) self.point_size.setVisible(False) self.point_size_edit.setVisible(False) if self.use_slider: self.point_size_slider_edit = QSlider(QtCore.Qt.Horizontal) self.point_size_slider_edit.setRange(1, MAX_POINT_SIZE) self.point_size_slider_edit.setValue(point_size) self.point_size_slider_edit.setTickInterval(1) self.point_size_slider_edit.setTickPosition(QSlider.TicksBelow) self.point_size_slider_edit.setVisible(False) if self.representation in ['wire', 'surface']: self.point_size.setEnabled(False) self.point_size_edit.setEnabled(False) if self.use_slider: self.point_size_slider_edit.setEnabled(False) self.bar_scale = QLabel("Bar Scale:") self.bar_scale_edit = QDoubleSpinBox(self) #self.bar_scale_edit.setRange(0.01, 1.0) # was 0.1 #self.bar_scale_edit.setRange(0.05, 5.0) self.bar_scale_edit.setDecimals(1) #self.bar_scale_edit.setSingleStep(bar_scale / 10.) self.bar_scale_edit.setSingleStep(0.1) self.bar_scale_edit.setValue(bar_scale) #if self.use_slider: #self.bar_scale_slider_edit = QSlider(QtCore.Qt.Horizontal) #self.bar_scale_slider_edit.setRange(1, 100) # 1/0.05 = 100/5.0 #self.bar_scale_slider_edit.setValue(opacity * 0.05) #self.bar_scale_slider_edit.setTickInterval(10) #self.bar_scale_slider_edit.setTickPosition(QSlider.TicksBelow) if self.representation != 'bar': self.bar_scale.setEnabled(False) self.bar_scale_edit.setEnabled(False) self.bar_scale.setVisible(False) self.bar_scale_edit.setVisible(False) #self.bar_scale_slider_edit.setVisible(False) #self.bar_scale_slider_edit.setEnabled(False) # show/hide self.checkbox_show = QCheckBox("Show") self.checkbox_hide = QCheckBox("Hide") self.checkbox_show.setChecked(show) self.checkbox_hide.setChecked(not show) if name == 'main': self.color.setEnabled(False) self.color_edit.setEnabled(False) self.point_size.setEnabled(False) self.point_size_edit.setEnabled(False) if self.use_slider: self.point_size_slider_edit.setEnabled(False) self.cancel_button = QPushButton("Close") self.create_layout() self.set_connections() def on_delete(self, irow): """deletes an actor based on the row number""" if irow == 0: # main return nkeys = len(self.keys) if nkeys in [0, 1]: return name = self.keys[irow] nrows = nkeys - 1 self.keys.pop(irow) header_labels = ['Groups'] table_model = Model(self.keys, header_labels, self) self.table.setModel(table_model) if len(self.keys) == 0: self.update() self.set_as_null() return if irow == nrows: irow -= 1 new_name = self.keys[irow] self.update_active_name(new_name) if self.is_gui: self.win_parent.delete_actor(name) def set_as_null(self): """sets the null case""" self.name.setVisible(False) self.name_edit.setVisible(False) self.color.setVisible(False) self.color_edit.setVisible(False) self.line_width.setVisible(False) self.line_width_edit.setVisible(False) self.point_size.setVisible(False) self.point_size_edit.setVisible(False) self.bar_scale.setVisible(False) self.bar_scale_edit.setVisible(False) self.opacity.setVisible(False) self.opacity_edit.setVisible(False) self.opacity_slider_edit.setVisible(False) self.point_size_slider_edit.setVisible(False) self.line_width_slider_edit.setVisible(False) self.checkbox_show.setVisible(False) self.checkbox_hide.setVisible(False) def on_update_geometry_properties_window(self, data): """Not Implemented""" return #new_keys = sorted(data.keys()) #if self.active_key in new_keys: #i = new_keys.index(self.active_key) #else: #i = 0 #self.table.update_data(new_keys) #self.out_data = data #self.update_active_key(i) def update_active_key(self, index): """ Parameters ---------- index : PyQt4.QtCore.QModelIndex the index of the list Internal Parameters ------------------- name : str the name of obj obj : CoordProperties, AltGeometry the storage object for things like line_width, point_size, etc. """ name = str(index.data()) #print('name = %r' % name) #i = self.keys.index(self.active_key) self.update_active_name(name) def update_active_name(self, name): self.active_key = name self.name_edit.setText(name) obj = self.out_data[name] if isinstance(obj, CoordProperties): opacity = 1.0 representation = 'coord' is_visible = obj.is_visible elif isinstance(obj, AltGeometry): line_width = obj.line_width point_size = obj.point_size bar_scale = obj.bar_scale opacity = obj.opacity representation = obj.representation is_visible = obj.is_visible self.color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(obj.color) + #"border:1px solid rgb(255, 170, 255); " "}") self.allow_update = False self.force = False self.line_width_edit.setValue(line_width) self.point_size_edit.setValue(point_size) self.bar_scale_edit.setValue(bar_scale) self.force = True self.allow_update = True else: raise NotImplementedError(obj) #allowed_representations = [ #'main', 'surface', 'coord', 'toggle', 'wire', 'point', 'bar'] if self.representation != representation: self.representation = representation #if representation not in allowed_representations: #msg = 'name=%r; representation=%r is invalid\nrepresentations=%r' % ( #name, representation, allowed_representations) if self.representation == 'coord': self.color.setVisible(False) self.color_edit.setVisible(False) self.line_width.setVisible(False) self.line_width_edit.setVisible(False) self.point_size.setVisible(False) self.point_size_edit.setVisible(False) self.bar_scale.setVisible(False) self.bar_scale_edit.setVisible(False) self.opacity.setVisible(False) self.opacity_edit.setVisible(False) if self.use_slider: self.opacity_slider_edit.setVisible(False) self.point_size_slider_edit.setVisible(False) self.line_width_slider_edit.setVisible(False) #self.bar_scale_slider_edit.setVisible(False) else: self.color.setVisible(True) self.color_edit.setVisible(True) self.line_width.setVisible(True) self.line_width_edit.setVisible(True) self.point_size.setVisible(True) self.point_size_edit.setVisible(True) self.bar_scale.setVisible(True) #self.bar_scale_edit.setVisible(True) self.opacity.setVisible(True) self.opacity_edit.setVisible(True) if self.use_slider: self.opacity_slider_edit.setVisible(True) self.line_width_slider_edit.setVisible(True) self.point_size_slider_edit.setVisible(True) #self.bar_scale_slider_edit.setVisible(True) if name == 'main': self.color.setEnabled(False) self.color_edit.setEnabled(False) self.point_size.setEnabled(False) self.point_size_edit.setEnabled(False) self.line_width.setEnabled(True) self.line_width_edit.setEnabled(True) self.bar_scale.setEnabled(False) self.bar_scale_edit.setEnabled(False) show_points = False show_line_width = True show_bar_scale = False if self.use_slider: self.line_width_slider_edit.setEnabled(True) #self.bar_scale_slider_edit.setVisible(False) else: self.color.setEnabled(True) self.color_edit.setEnabled(True) show_points = False if self.representation in ['point', 'wire+point']: show_points = True show_line_width = False if self.representation in ['wire', 'wire+point', 'bar', 'toggle']: show_line_width = True if representation == 'bar': show_bar_scale = True else: show_bar_scale = False #self.bar_scale_button.setVisible(show_bar_scale) #self.bar_scale_edit.setSingleStep(bar_scale / 10.) #if self.use_slider: #self.bar_scale_slider_edit.setEnabled(False) self.point_size.setEnabled(show_points) self.point_size_edit.setEnabled(show_points) self.point_size.setVisible(show_points) self.point_size_edit.setVisible(show_points) self.line_width.setEnabled(show_line_width) self.line_width_edit.setEnabled(show_line_width) self.bar_scale.setEnabled(show_bar_scale) self.bar_scale_edit.setEnabled(show_bar_scale) self.bar_scale.setVisible(show_bar_scale) self.bar_scale_edit.setVisible(show_bar_scale) if self.use_slider: self.point_size_slider_edit.setEnabled(show_points) self.point_size_slider_edit.setVisible(show_points) self.line_width_slider_edit.setEnabled(show_line_width) #if self.representation in ['wire', 'surface']: self.opacity_edit.setValue(opacity) #if self.use_slider: #self.opacity_slider_edit.setValue(opacity*10) self.checkbox_show.setChecked(is_visible) self.checkbox_hide.setChecked(not is_visible) passed = self.on_validate() #self.on_apply(force=True) # TODO: was turned on...do I want this??? #self.allow_update = True def create_layout(self): ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.cancel_button) grid = QGridLayout() irow = 0 grid.addWidget(self.name, irow, 0) grid.addWidget(self.name_edit, irow, 1) irow += 1 grid.addWidget(self.color, irow, 0) grid.addWidget(self.color_edit, irow, 1) irow += 1 grid.addWidget(self.opacity, irow, 0) if self.use_slider: grid.addWidget(self.opacity_edit, irow, 2) grid.addWidget(self.opacity_slider_edit, irow, 1) else: grid.addWidget(self.opacity_edit, irow, 1) irow += 1 grid.addWidget(self.line_width, irow, 0) if self.use_slider: grid.addWidget(self.line_width_edit, irow, 2) grid.addWidget(self.line_width_slider_edit, irow, 1) else: grid.addWidget(self.line_width_edit, irow, 1) irow += 1 grid.addWidget(self.point_size, irow, 0) if self.use_slider: grid.addWidget(self.point_size_edit, irow, 2) grid.addWidget(self.point_size_slider_edit, irow, 1) else: grid.addWidget(self.point_size_edit, irow, 1) irow += 1 grid.addWidget(self.bar_scale, irow, 0) if self.use_slider and 0: grid.addWidget(self.bar_scale_edit, irow, 2) grid.addWidget(self.bar_scale_slider_edit, irow, 1) else: grid.addWidget(self.bar_scale_edit, irow, 1) irow += 1 checkboxs = QButtonGroup(self) checkboxs.addButton(self.checkbox_show) checkboxs.addButton(self.checkbox_hide) vbox = QVBoxLayout() vbox.addWidget(self.table, stretch=1) vbox.addLayout(grid) vbox1 = QVBoxLayout() vbox1.addWidget(self.checkbox_show) vbox1.addWidget(self.checkbox_hide) vbox.addLayout(vbox1) vbox.addStretch() #vbox.addWidget(self.check_apply) vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def set_connections(self): self.opacity_edit.valueChanged.connect(self.on_opacity) self.line_width_edit.valueChanged.connect(self.on_line_width) self.point_size_edit.valueChanged.connect(self.on_point_size) self.bar_scale_edit.valueChanged.connect(self.on_bar_scale) if self.use_slider: self.opacity_slider_edit.valueChanged.connect(self.on_opacity_slider) self.line_width_slider_edit.valueChanged.connect(self.on_line_width_slider) self.point_size_slider_edit.valueChanged.connect(self.on_point_size_slider) #self.bar_scale_slider_edit.valueChanged.connect(self.on_bar_scale_slider) # self.connect(self.opacity_edit, QtCore.SIGNAL('clicked()'), self.on_opacity) # self.connect(self.line_width, QtCore.SIGNAL('clicked()'), self.on_line_width) # self.connect(self.point_size, QtCore.SIGNAL('clicked()'), self.on_point_size) if qt_version == 4: self.connect(self, QtCore.SIGNAL('triggered()'), self.closeEvent) 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.cancel_button.clicked.connect(self.on_cancel) # closeEvent def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Escape: self.close() def closeEvent(self, event): self.on_cancel() def on_color(self): """called when the user clicks on the color box""" name = self.active_key obj = self.out_data[name] rgb_color_ints = obj.color msg = name col = QColorDialog.getColor(QtGui.QColor(*rgb_color_ints), self, "Choose a %s color" % msg) if col.isValid(): color_float = col.getRgbF()[:3] obj.color = color_float color_int = [int(colori * 255) for colori in color_float] self.color_edit.setStyleSheet("QPushButton {" "background-color: rgb(%s, %s, %s);" % tuple(color_int) + #"border:1px solid rgb(255, 170, 255); " "}") self.on_apply(force=self.force) #print(self.allow_update) def on_show(self): """shows the actor""" name = self.active_key is_checked = self.checkbox_show.isChecked() self.out_data[name].is_visible = is_checked self.on_apply(force=self.force) def on_hide(self): """hides the actor""" name = self.active_key is_checked = self.checkbox_hide.isChecked() self.out_data[name].is_visible = not is_checked self.on_apply(force=self.force) def on_line_width(self): """increases/decreases the wireframe (for solid bodies) or the bar thickness""" self.is_line_width_edit_active = True name = self.active_key line_width = self.line_width_edit.value() self.out_data[name].line_width = line_width if not self.is_line_width_edit_slider_active: if self.use_slider: self.line_width_slider_edit.setValue(line_width) self.is_line_width_edit_active = False self.on_apply(force=self.force) self.is_line_width_edit_active = False def on_line_width_slider(self): """increases/decreases the wireframe (for solid bodies) or the bar thickness""" self.is_line_width_edit_slider_active = True #name = self.active_key line_width = self.line_width_slider_edit.value() if not self.is_line_width_edit_active: self.line_width_edit.setValue(line_width) self.is_line_width_edit_slider_active = False def on_point_size(self): """increases/decreases the point size""" self.is_point_size_edit_active = True name = self.active_key point_size = self.point_size_edit.value() self.out_data[name].point_size = point_size if not self.is_point_size_edit_slider_active: if self.use_slider: self.point_size_slider_edit.setValue(point_size) self.is_point_size_edit_active = False self.on_apply(force=self.force) self.is_point_size_edit_active = False def on_point_size_slider(self): """increases/decreases the point size""" self.is_point_size_edit_slider_active = True #name = self.active_key point_size = self.point_size_slider_edit.value() if not self.is_point_size_edit_active: self.point_size_edit.setValue(point_size) self.is_point_size_edit_slider_active = False def on_bar_scale(self): """ Vectors start at some xyz coordinate and can increase in length. Increases/decreases the length scale factor. """ self.is_bar_scale_edit_active = True name = self.active_key float_bar_scale = self.bar_scale_edit.value() self.out_data[name].bar_scale = float_bar_scale if not self.is_bar_scale_edit_slider_active: #int_bar_scale = int(round(float_bar_scale * 20, 0)) #if self.use_slider: #self.bar_scale_slider_edit.setValue(int_bar_scale) self.is_bar_scale_edit_active = False self.on_apply(force=self.force) self.is_bar_scale_edit_active = False def on_bar_scale_slider(self): """ Vectors start at some xyz coordinate and can increase in length. Increases/decreases the length scale factor. """ self.is_bar_scale_edit_slider_active = True #name = self.active_key int_bar_scale = self.bar_scale_slider_edit.value() if not self.is_bar_scale_edit_active: float_bar_scale = int_bar_scale / 20. self.bar_scale_edit.setValue(float_bar_scale) self.is_bar_scale_edit_slider_active = False def on_opacity(self): """ opacity = 1.0 (solid/opaque) opacity = 0.0 (invisible) """ self.is_opacity_edit_active = True name = self.active_key float_opacity = self.opacity_edit.value() self.out_data[name].opacity = float_opacity if not self.is_opacity_edit_slider_active: int_opacity = int(round(float_opacity * 10, 0)) if self.use_slider: self.opacity_slider_edit.setValue(int_opacity) self.is_opacity_edit_active = False self.on_apply(force=self.force) self.is_opacity_edit_active = False def on_opacity_slider(self): """ opacity = 1.0 (solid/opaque) opacity = 0.0 (invisible) """ self.is_opacity_edit_slider_active = True #name = self.active_key int_opacity = self.opacity_slider_edit.value() if not self.is_opacity_edit_active: float_opacity = int_opacity / 10. self.opacity_edit.setValue(float_opacity) self.is_opacity_edit_slider_active = 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.line_width = self.line_width_edit.value() old_obj.point_size = self.point_size_edit.value() old_obj.bar_scale = self.bar_scale_edit.value() old_obj.opacity = self.opacity_edit.value() #old_obj.color = self.color_edit old_obj.is_visible = self.checkbox_show.isChecked() return True #name_value, flag0 = self.check_name(self.name_edit) #ox_value, flag1 = self.check_float(self.transparency_edit) #if flag0 and flag1: #self.out_data['clicked_ok'] = True #return True #return False @property def is_gui(self): return hasattr(self.win_parent, 'on_update_geometry_properties') def on_apply(self, force=False): passed = self.on_validate() #print("passed=%s force=%s allow=%s" % (passed, force, self.allow_update)) if (passed or force) and self.allow_update and self.is_gui: #print('obj = %s' % self.out_data[self.active_key]) self.win_parent.on_update_geometry_properties(self.out_data, name=self.active_key) return passed def on_cancel(self): passed = self.on_apply(force=True) if passed: self.close()
class HighlightWindow(PyDialog): """ +-----------+ | Highlight | +--------------------------+ | Nodes ______ | | Elements ______ | | | | Highlight Close | +--------------------------+ """ def __init__(self, data, win_parent=None): """ Saves the data members from data and performs type checks """ PyDialog.__init__(self, data, win_parent) gui = win_parent if gui is None: # pragma: no cover self.highlight_color_float = [0., 0., 0.] self.highlight_color_int = [0, 0, 0] self._highlight_opacity = 0.9 self._point_size = 10 self._label_size = 10.0 self._default_text_size = 10.0 else: #self.highlight_color_float = gui.settings.highlight_color #self.highlight_color_int = [int(val) for val in self.highlight_color_float] self.highlight_color_float, self.highlight_color_int = _check_color( gui.settings.highlight_color) #self.highlight_color_int = gui.settings.highlight_color_int self._highlight_opacity = gui.settings.highlight_opacity self._point_size = 10 self._label_size = 10.0 self._default_text_size = 10.0 self._updated_highlight = False self.actors = [] self._default_font_size = data['font_size'] self.model_name = data['model_name'] assert len(self.model_name) > 0, self.model_name #self._default_annotation_size = data['annotation_size'] # int #self.default_magnify = data['magnify'] if 'nodes_pound' in data: # testing nodes_pound = data['nodes_pound'] elements_pound = data['elements_pound'] nodes = np.arange(1, nodes_pound + 1) elements = np.arange(1, elements_pound + 1) else: # gui nodes = gui.get_node_ids(model_name=self.model_name) elements = gui.get_element_ids(model_name=self.model_name) nodes_pound = nodes.max() elements_pound = elements.max() self.nodes = nodes self.elements = elements self._nodes_pound = nodes_pound self._elements_pound = elements_pound self.setWindowTitle('Highlight') self.create_widgets() self.create_layout() self.set_connections() self.on_font(self._default_font_size) #self.show() def create_widgets(self): """creates the display window""" # Text Size model_name = self.model_name self.nodes_label = QLabel("Nodes:") self.nodes_edit = QNodeEdit(self, model_name, pick_style='area', cleanup=True, tab_to_next=False) self.elements_label = QLabel("Elements:") self.elements_edit = QElementEdit(self, model_name, pick_style='area', cleanup=True, tab_to_next=False) #----------------------------------------------------------------------- # Highlight Color self.highlight_opacity_label = QLabel("Highlight Opacity:") self.highlight_opacity_edit = QDoubleSpinBox(self) self.highlight_opacity_edit.setValue(self._highlight_opacity) self.highlight_opacity_edit.setRange(0.1, 1.0) self.highlight_opacity_edit.setDecimals(1) self.highlight_opacity_edit.setSingleStep(0.1) self.highlight_opacity_button = QPushButton("Default") # Text Color self.highlight_color_label = QLabel("Highlight Color:") self.highlight_color_edit = QPushButtonColor(self.highlight_color_int) self.point_size_label = QLabel("Point Size:") self.point_size_edit = QSpinBox(self) self.point_size_edit.setValue(self._point_size) self.point_size_edit.setRange(7, 30) #self.point_size_button = QPushButton("Default") self.label_size_label = QLabel("Label Size:") self.label_size_edit = QSpinBox(self) self.label_size_edit.setValue(self._default_text_size) self.label_size_edit.setRange(7, 30) #self.label_size_button = QPushButton("Default") #----------------------------------------------------------------------- # closing self.show_button = QPushButton("Show") self.mark_button = QCheckBox("Mark") self.clear_button = QPushButton("Clear") self.close_button = QPushButton("Close") def create_layout(self): """displays the menu objects""" grid = QGridLayout() irow = 0 grid.addWidget(self.nodes_label, irow, 0) grid.addWidget(self.nodes_edit, irow, 1) irow += 1 grid.addWidget(self.elements_label, irow, 0) grid.addWidget(self.elements_edit, irow, 1) irow += 1 # TODO: enable me grid.addWidget(self.highlight_color_label, irow, 0) grid.addWidget(self.highlight_color_edit, irow, 1) self.highlight_color_label.setEnabled(False) self.highlight_color_edit.setEnabled(False) irow += 1 # TODO: enable me grid.addWidget(self.highlight_opacity_label, irow, 0) grid.addWidget(self.highlight_opacity_edit, irow, 1) self.highlight_opacity_label.setEnabled(False) self.highlight_opacity_edit.setEnabled(False) irow += 1 # TODO: enable me grid.addWidget(self.point_size_label, irow, 0) grid.addWidget(self.point_size_edit, irow, 1) self.point_size_label.setEnabled(False) self.point_size_edit.setEnabled(False) irow += 1 # TODO: enable me grid.addWidget(self.label_size_label, irow, 0) grid.addWidget(self.label_size_edit, irow, 1) self.label_size_label.setEnabled(False) self.label_size_edit.setEnabled(False) irow += 1 self.mark_button.setEnabled(False) #self.create_legend_widgets() #grid2 = self.create_legend_layout() ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.mark_button) ok_cancel_box.addWidget(self.show_button) ok_cancel_box.addWidget(self.clear_button) ok_cancel_box.addWidget(self.close_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): """creates the actions for the menu""" self.highlight_color_edit.clicked.connect(self.on_highlight_color) self.highlight_opacity_edit.valueChanged.connect( self.on_highlight_opacity) self.nodes_edit.textChanged.connect(self.on_validate) self.elements_edit.textChanged.connect(self.on_validate) self.show_button.clicked.connect(self.on_show) self.clear_button.clicked.connect(self.on_remove_actors) self.close_button.clicked.connect(self.on_close) # closeEvent 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 on_highlight_color(self): """ Choose a highlight color TODO: not implemented """ title = "Choose a highlight color" rgb_color_ints = self.highlight_color_int color_edit = self.highlight_color_edit func_name = 'set_highlight_color' passed, rgb_color_ints, rgb_color_floats = self._background_color( title, color_edit, rgb_color_ints, func_name) if passed: self.highlight_color_int = rgb_color_ints self.highlight_color_float = rgb_color_floats def on_highlight_opacity(self, value=None): """ update the highlight opacity TODO: not implemented """ if value is None: value = self.highlight_opacity_edit.value() self._highlight_opacity = value if self.win_parent is not None: self.win_parent.settings.set_highlight_opacity(value) 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: 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): """makes sure that all attributes are valid before doing any actions""" unused_nodes, flag1 = check_patran_syntax(self.nodes_edit, pound=self._nodes_pound) unused_elements, flag2 = check_patran_syntax( self.elements_edit, pound=self._elements_pound) if all([flag1, flag2]): self.out_data['clicked_ok'] = True return True return False def on_show(self): """show the highlight""" passed = self.on_validate() self.parent().mouse_actions.get_grid_selected(self.model_name) if passed and self.win_parent is not None: nodes, unused_flag1 = check_patran_syntax(self.nodes_edit, pound=self._nodes_pound) elements, unused_flag2 = check_patran_syntax( self.elements_edit, pound=self._elements_pound) if len(nodes) == 0 and len(elements) == 0: return False nodes_filtered = np.intersect1d(self.nodes, nodes) elements_filtered = np.intersect1d(self.elements, elements) nnodes = len(nodes_filtered) nelements = len(elements_filtered) if nnodes == 0 and nelements == 0: return False self.on_remove_actors() gui = self.parent() mouse_actions = gui.mouse_actions grid = mouse_actions.get_grid_selected(self.model_name) actors = create_highlighted_actors(gui, grid, all_nodes=self.nodes, nodes=nodes_filtered, set_node_scalars=True, all_elements=self.elements, elements=elements_filtered, set_element_scalars=True) iactor = 0 make_element_labels = True make_node_labels = True if make_node_labels and nnodes: mapper = actors[iactor].GetMapper() mygrid = mapper.GetInput() point_id_filter = get_ids_filter(mygrid, idsname='Ids_points', is_nids=True, is_eids=False) point_id_filter.SetFieldData(1) point_id_filter.SetPointIds(0) point_id_filter.FieldDataOn() label_actor = create_node_labels(point_id_filter, mygrid, gui.rend, label_size=self._label_size) actors.append(label_actor) iactor += 1 if make_element_labels and nelements: mapper = actors[iactor].GetMapper() mygrid = mapper.GetInput() element_id_filter = get_ids_filter(mygrid, idsname='Ids_cells', is_nids=False, is_eids=True) element_id_filter.SetFieldData(1) element_id_filter.SetCellIds(0) element_id_filter.FieldDataOn() # Create labels for cells cell_centers = vtk.vtkCellCenters() cell_centers.SetInputConnection( element_id_filter.GetOutputPort()) cell_mapper = vtk.vtkLabeledDataMapper() cell_mapper.SetInputConnection(cell_centers.GetOutputPort()) cell_mapper.SetLabelModeToLabelScalars() label_actor = vtk.vtkActor2D() label_actor.SetMapper(cell_mapper) actors.append(label_actor) iactor += 1 if actors: add_actors_to_gui(gui, actors, render=True) self.actors = actors return passed def on_remove_actors(self): """removes multiple vtk actors""" gui = self.parent() if gui is not None: if self.nodes_edit.style is not None: self.nodes_edit.style.remove_actors() if self.elements_edit.style is not None: self.elements_edit.style.remove_actors() remove_actors_from_gui(gui, self.actors, render=True, force_render=True) self.actors = [] def on_close(self): """close the window""" self.on_remove_actors() self.out_data['close'] = True self.close()
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)
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 BreakSurfaceMenu(QDialog): def __init__(self, data, win_parent=None): """ +-----------------+ | Break Surfaces | +-----------------+------+ | EngineInlet | | EngineOutlet | | | | Name EngineInlet | | RegionMode * RegionID | | * All | | | | AllowedRegions: | | Region ID 3 | | | | PickMode * All | | Pick Mode x On/Off | | Pick Angle 20 deg | | | | Revert | | RenumberRegions | | Close | +------------------------+ """ QDialog.__init__(self, win_parent) self.setWindowTitle('Break Surface') #default self.win_parent = win_parent self.out_data = data 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) header = view.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) #header.setResizeMode(QtGui.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.mode_header = QLabel("Mode:") nregions_max = 10 pick_angle = 20.0 region_id = 4 all_regions = True self.region_id = QLabel("Region ID:") self.region_id_edit = QSpinBox(self) self.region_id_edit.setRange(1, nregions_max) self.region_id_edit.setSingleStep(1) self.region_id_edit.setValue(region_id) self.pick_angle = QLabel("Pick Angle:") self.pick_angle_edit = QDoubleSpinBox(self) self.pick_angle_edit.setRange(0.0, 360.0) self.pick_angle_edit.setDecimals(3) self.pick_angle_edit.setSingleStep(0.5) self.pick_angle_edit.setValue(pick_angle) # region IDs/all self.checkbox_region_ids = QCheckBox("Region IDs") self.checkbox_region_all = QCheckBox("All Regions") self.checkbox_region_all.setChecked(all_regions) self.checkbox_region_ids.setChecked(not all_regions) # pick mode self.checkbox_pick_mode = QCheckBox("Pick Mode (Off=label)") self.checkbox_pick_mode.setChecked(False) #---------------------------------------------- self.nodes_header = QLabel("Single Node:") self.name = QLabel("ID:") self.name_edit = QLineEdit('Node %i' % name) self.name_edit.setDisabled(True) #---------------------------------------------- self.location_x = QLabel("X:") self.location_x_edit = QLineEdit('X') self.location_y = QLabel("Y:") self.location_y_edit = QLineEdit('Y') self.location_z = QLabel("Z:") self.location_z_edit = QLineEdit('Z') #---------------------------------------------- # remove these... self.description_edit = QLineEdit('Description') self.coord_edit = QSpinBox() #---------------------------------------------- # 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()) 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.setText(str(point[3])) self.location_y_edit.setText(str(point[4])) self.location_z_edit.setText(str(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) checkboxs = QButtonGroup(self) checkboxs.addButton(self.checkbox_region_all) checkboxs.addButton(self.checkbox_region_ids) vbox1 = QVBoxLayout() vbox1.addWidget(self.checkbox_region_all) vbox1.addWidget(self.checkbox_region_ids) #vbox1.addLayout(checkboxs) #----------------------------------------- irow = 0 grid2.addWidget(self.name, irow, 0) grid2.addWidget(self.name_edit, irow, 1) irow += 1 #grid2.addWidget(self.name, irow, 0) grid2.addWidget(self.description_edit, irow, 1) irow += 1 grid2.addWidget(self.location_x, irow, 0) grid2.addWidget(self.location_x_edit, irow, 1) irow += 1 grid2.addWidget(self.location_y, irow, 0) grid2.addWidget(self.location_y_edit, irow, 1) irow += 1 grid2.addWidget(self.location_z, irow, 0) grid2.addWidget(self.location_z_edit, irow, 1) irow += 1 #| Name EngineInlet | #| RegionMode * RegionID | #| * All | #| | #| AllowedRegions: | #| Region ID 3 | #| | #| PickMode * All | #| Pick Mode x On/Off | #| Pick Angle 20 deg | #| | #| Revert | #| RenumberRegions | #| Close | grid2.addWidget(self.region_id, irow, 0) grid2.addWidget(self.region_id_edit, irow, 1) irow += 1 #grid2.addWidget(self.pick_mode, irow, 0) grid2.addWidget(self.checkbox_pick_mode, irow, 0) irow += 1 grid2.addWidget(self.pick_angle, irow, 0) grid2.addWidget(self.pick_angle_edit, irow, 1) irow += 1 #grid2.addWidget(self.pi, 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): self.opacity_edit.clicked.connect(self.on_opacity) self.point_size.clicked.connect(self.on_point_size) 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.connect(self.description_edit, QtCore.SIGNAL("valueChanged(int)"), self.on_description) #self.connect(self.coord_edit, QtCore.SIGNAL("valueChanged(int)"), 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.clicked.connect(self.on_location_x) self.location_y_edit.clicked.connect(self.on_location_y) self.location_z_edit.clicked.connect(self.on_location_z) self.close_button.clicked.connect(self.on_close) #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(QtGui.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_float(self, 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 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 = self.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()