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 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 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 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 TimeChartDisplay(Display): def __init__(self, parent=None, args=[], macros=None, show_pv_add_panel=True, config_file=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 show_pv_add_panel : bool Whether or not to show the PV add panel on top of the graph """ super(TimeChartDisplay, self).__init__(parent=parent, args=args, macros=macros) self.legend_font = None self.channel_map = dict() self.setWindowTitle("TimeChart Tool") self.main_layout = QVBoxLayout() self.body_layout = QVBoxLayout() self.pv_add_panel = QFrame() self.pv_add_panel.setVisible(show_pv_add_panel) self.pv_add_panel.setMaximumHeight(50) self.pv_layout = QHBoxLayout() self.pv_name_line_edt = QLineEdit() self.pv_name_line_edt.setAcceptDrops(True) self.pv_name_line_edt.returnPressed.connect(self.add_curve) self.pv_protocol_cmb = QComboBox() self.pv_protocol_cmb.addItems(["ca://", "archive://"]) self.pv_protocol_cmb.setEnabled(False) self.pv_connect_push_btn = QPushButton("Connect") self.pv_connect_push_btn.clicked.connect(self.add_curve) self.tab_panel = QTabWidget() self.tab_panel.setMinimumWidth(350) self.tab_panel.setMaximumWidth(350) self.curve_settings_tab = QWidget() self.data_settings_tab = QWidget() self.chart_settings_tab = QWidget() self.charting_layout = QHBoxLayout() self.chart = PyDMTimePlot(plot_by_timestamps=False) self.chart.setDownsampling(ds=False, auto=False, mode=None) self.chart.plot_redrawn_signal.connect(self.update_curve_data) self.chart.setBufferSize(DEFAULT_BUFFER_SIZE) self.chart.setPlotTitle(DEFAULT_CHART_TITLE) 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("Crosshair") self.crosshair_coord_lbl = QLabel() self.crosshair_coord_lbl.setWordWrap(True) 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.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.curve_settings_scroll.setWidget(self.curve_settings_inner_frame) self.curve_settings_scroll.setWidgetResizable(True) 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.curves_tab_layout = QHBoxLayout() self.curves_tab_layout.addWidget(self.curve_settings_scroll) self.data_tab_layout = QVBoxLayout() self.data_tab_layout.setAlignment(Qt.AlignTop) self.data_tab_layout.setSpacing(5) self.chart_settings_layout = QVBoxLayout() self.chart_settings_layout.setAlignment(Qt.AlignTop) self.chart_settings_layout.setSpacing(5) self.chart_layout = QVBoxLayout() self.chart_layout.setSpacing(10) self.chart_panel = QWidget() self.chart_panel.setMinimumHeight(400) self.chart_control_layout = QHBoxLayout() self.chart_control_layout.setAlignment(Qt.AlignHCenter) self.chart_control_layout.setSpacing(10) self.zoom_x_layout = QVBoxLayout() self.zoom_x_layout.setAlignment(Qt.AlignTop) self.zoom_x_layout.setSpacing(5) self.plus_icon = IconFont().icon("plus", color=QColor("green")) self.minus_icon = IconFont().icon("minus", color=QColor("red")) self.view_all_icon = IconFont().icon("globe", color=QColor("blue")) self.reset_icon = IconFont().icon("circle-o-notch", color=QColor("green")) self.zoom_in_x_btn = QPushButton("X Zoom") self.zoom_in_x_btn.setIcon(self.plus_icon) self.zoom_in_x_btn.clicked.connect( partial(self.handle_zoom_in_btn_clicked, "x", True)) self.zoom_in_x_btn.setEnabled(False) self.zoom_out_x_btn = QPushButton("X Zoom") self.zoom_out_x_btn.setIcon(self.minus_icon) self.zoom_out_x_btn.clicked.connect( partial(self.handle_zoom_in_btn_clicked, "x", False)) self.zoom_out_x_btn.setEnabled(False) self.zoom_y_layout = QVBoxLayout() self.zoom_y_layout.setAlignment(Qt.AlignTop) self.zoom_y_layout.setSpacing(5) self.zoom_in_y_btn = QPushButton("Y Zoom") self.zoom_in_y_btn.setIcon(self.plus_icon) self.zoom_in_y_btn.clicked.connect( partial(self.handle_zoom_in_btn_clicked, "y", True)) self.zoom_in_y_btn.setEnabled(False) self.zoom_out_y_btn = QPushButton("Y Zoom") self.zoom_out_y_btn.setIcon(self.minus_icon) self.zoom_out_y_btn.clicked.connect( partial(self.handle_zoom_in_btn_clicked, "y", False)) self.zoom_out_y_btn.setEnabled(False) self.view_all_btn = QPushButton("View All") self.view_all_btn.setIcon(self.view_all_icon) self.view_all_btn.clicked.connect(self.handle_view_all_button_clicked) self.view_all_btn.setEnabled(False) self.view_all_reset_chart_layout = QVBoxLayout() self.view_all_reset_chart_layout.setAlignment(Qt.AlignTop) self.view_all_reset_chart_layout.setSpacing(5) self.pause_chart_layout = QVBoxLayout() self.pause_chart_layout.setAlignment(Qt.AlignTop) self.pause_chart_layout.setSpacing(5) self.reset_chart_btn = QPushButton("Reset") self.reset_chart_btn.setIcon(self.reset_icon) self.reset_chart_btn.clicked.connect( self.handle_reset_chart_btn_clicked) self.reset_chart_btn.setEnabled(False) self.pause_icon = IconFont().icon("pause", color=QColor("red")) self.play_icon = IconFont().icon("play", color=QColor("green")) self.pause_chart_btn = QPushButton() self.pause_chart_btn.setIcon(self.pause_icon) self.pause_chart_btn.clicked.connect( self.handle_pause_chart_btn_clicked) self.title_settings_layout = QVBoxLayout() self.title_settings_layout.setAlignment(Qt.AlignTop) self.title_settings_layout.setSpacing(5) self.title_settings_grpbx = QGroupBox("Title and Legend") self.title_settings_grpbx.setMaximumHeight(120) self.import_export_data_layout = QVBoxLayout() self.import_export_data_layout.setAlignment(Qt.AlignTop) self.import_export_data_layout.setSpacing(5) self.import_data_btn = QPushButton("Import...") self.import_data_btn.clicked.connect( self.handle_import_data_btn_clicked) self.export_data_btn = QPushButton("Export...") self.export_data_btn.clicked.connect( self.handle_export_data_btn_clicked) self.chart_title_layout = QHBoxLayout() self.chart_title_layout.setSpacing(10) self.chart_title_lbl = QLabel(text="Graph 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_title_font_btn = QPushButton() self.chart_title_font_btn.setFixedHeight(24) self.chart_title_font_btn.setFixedWidth(24) self.chart_title_font_btn.setIcon(IconFont().icon("font")) self.chart_title_font_btn.clicked.connect( partial(self.handle_chart_font_changed, "title")) 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.setMaximumHeight(100) 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.graph_drawing_settings_layout.setAlignment(Qt.AlignVCenter) self.chart_interval_layout = QFormLayout() 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.editingFinished.connect( self.handle_redraw_rate_changed) self.chart_data_sampling_rate_lbl = QLabel("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.editingFinished.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("Hr:Min:Sec") self.chart_limit_time_span_hours_spin_box = QSpinBox() self.chart_limit_time_span_hours_spin_box.setMaximum(999) self.chart_limit_time_span_minutes_spin_box = QSpinBox() self.chart_limit_time_span_minutes_spin_box.setMaximum(59) self.chart_limit_time_span_seconds_spin_box = QSpinBox() self.chart_limit_time_span_seconds_spin_box.setMaximum(59) self.chart_limit_time_span_activate_btn = QPushButton("Apply") self.chart_limit_time_span_activate_btn.setDisabled(True) self.chart_ring_buffer_layout = QFormLayout() self.chart_ring_buffer_size_lbl = QLabel("Ring Buffer Size") self.chart_ring_buffer_size_edt = QLineEdit() self.chart_ring_buffer_size_edt.returnPressed.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.clicked.connect( self.handle_show_legend_checkbox_clicked) self.show_legend_chk.setChecked(self.chart.showLegend) self.legend_font_btn = QPushButton() self.legend_font_btn.setFixedHeight(24) self.legend_font_btn.setFixedWidth(24) self.legend_font_btn.setIcon(IconFont().icon("font")) self.legend_font_btn.clicked.connect( partial(self.handle_chart_font_changed, "legend")) self.graph_background_color_layout = QFormLayout() self.axis_grid_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(10) 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_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.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_data_settings_btn = QPushButton("Reset Data Settings") self.reset_data_settings_btn.clicked.connect( self.handle_reset_data_settings_btn_clicked) 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("Graph Intervals") self.graph_drawing_settings_grpbx.setAlignment(Qt.AlignTop) self.axis_settings_grpbx = QGroupBox("Graph Appearance") 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 # If there is an imported config file, let's start TimeChart with the imported configuration data if config_file: importer = SettingsImporter(self) try: importer.import_settings(config_file) except SettingsImporterException: display_message_box( QMessageBox.Critical, "Import Failure", "Cannot import the file '{0}'. Check the log for the error details." .format(config_file)) logger.exception( "Cannot import the file '{0}'.".format(config_file)) 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) self.pv_add_panel.setLayout(self.pv_layout) 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.data_settings_tab.setLayout(self.data_tab_layout) self.setup_data_tab_layout() self.tab_panel.addTab(self.curve_settings_tab, "Curves") self.tab_panel.addTab(self.data_settings_tab, "Data") self.tab_panel.addTab(self.chart_settings_tab, "Graph") self.crosshair_settings_layout.addWidget(self.enable_crosshair_chk) self.crosshair_settings_layout.addWidget(self.crosshair_coord_lbl) self.zoom_x_layout.addWidget(self.zoom_in_x_btn) self.zoom_x_layout.addWidget(self.zoom_out_x_btn) self.zoom_y_layout.addWidget(self.zoom_in_y_btn) self.zoom_y_layout.addWidget(self.zoom_out_y_btn) self.view_all_reset_chart_layout.addWidget(self.reset_chart_btn) self.view_all_reset_chart_layout.addWidget(self.view_all_btn) self.pause_chart_layout.addWidget(self.pause_chart_btn) self.import_export_data_layout.addWidget(self.import_data_btn) self.import_export_data_layout.addWidget(self.export_data_btn) self.chart_control_layout.addLayout(self.zoom_x_layout) self.chart_control_layout.addLayout(self.zoom_y_layout) self.chart_control_layout.addLayout(self.view_all_reset_chart_layout) self.chart_control_layout.addLayout(self.pause_chart_layout) self.chart_control_layout.addLayout(self.crosshair_settings_layout) self.chart_control_layout.addLayout(self.import_export_data_layout) self.chart_control_layout.insertSpacing(5, 30) 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.setSizes([1, 0]) self.splitter.setHandleWidth(10) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.charting_layout.addWidget(self.splitter) self.body_layout.addWidget(self.pv_add_panel) self.body_layout.addLayout(self.charting_layout) self.body_layout.setSpacing(0) self.body_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.addLayout(self.body_layout) self.enable_chart_control_buttons(False) handle = self.splitter.handle(1) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) button = QToolButton(handle) button.setArrowType(Qt.LeftArrow) button.clicked.connect(lambda: self.handle_splitter_button(True)) layout.addWidget(button) button = QToolButton(handle) button.setArrowType(Qt.RightArrow) button.clicked.connect(lambda: self.handle_splitter_button(False)) layout.addWidget(button) handle.setLayout(layout) def handle_splitter_button(self, left=True): if left: self.splitter.setSizes([1, 1]) else: self.splitter.setSizes([1, 0]) def change_legend_font(self, font): if font is None: return self.legend_font = font items = self.chart.plotItem.legend.items for i in items: i[1].item.setFont(font) i[1].resizeEvent(None) i[1].updateGeometry() def change_title_font(self, font): current_text = self.chart.plotItem.titleLabel.text args = { "family": font.family, "size": "{}pt".format(font.pointSize()), "bold": font.bold(), "italic": font.italic(), } self.chart.plotItem.titleLabel.setText(current_text, **args) def handle_chart_font_changed(self, target): if target not in ("title", "legend"): return dialog = QFontDialog(self) dialog.setOption(QFontDialog.DontUseNativeDialog, True) if target == "title": dialog.fontSelected.connect(self.change_title_font) else: dialog.fontSelected.connect(self.change_legend_font) dialog.open() def setup_data_tab_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.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.data_tab_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_spin_box) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_minutes_spin_box) self.chart_limit_time_span_layout.addWidget( self.chart_limit_time_span_seconds_spin_box) 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_spin_box.hide() self.chart_limit_time_span_minutes_spin_box.hide() self.chart_limit_time_span_seconds_spin_box.hide() self.chart_limit_time_span_activate_btn.hide() self.chart_limit_time_span_hours_spin_box.valueChanged.connect( self.handle_time_span_changed) self.chart_limit_time_span_minutes_spin_box.valueChanged.connect( self.handle_time_span_changed) self.chart_limit_time_span_seconds_spin_box.valueChanged.connect( self.handle_time_span_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_interval_layout.addRow(self.chart_redraw_rate_lbl, self.chart_redraw_rate_spin) self.chart_interval_layout.addRow( self.chart_data_sampling_rate_lbl, self.chart_data_async_sampling_rate_spin) self.graph_drawing_settings_layout.addLayout( self.chart_interval_layout) 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.chart_ring_buffer_layout.addRow(self.chart_ring_buffer_size_lbl, self.chart_ring_buffer_size_edt) self.graph_drawing_settings_layout.addLayout( self.chart_ring_buffer_layout) self.graph_drawing_settings_grpbx.setLayout( self.graph_drawing_settings_layout) self.data_tab_layout.addWidget(self.graph_drawing_settings_grpbx) self.chart_sync_mode_async_radio.toggled.emit(True) self.data_tab_layout.addWidget(self.reset_data_settings_btn) def setup_chart_settings_layout(self): self.chart_title_layout.addWidget(self.chart_title_lbl) self.chart_title_layout.addWidget(self.chart_title_line_edt) self.chart_title_layout.addWidget(self.chart_title_font_btn) self.title_settings_layout.addLayout(self.chart_title_layout) legend_layout = QHBoxLayout() legend_layout.addWidget(self.show_legend_chk) legend_layout.addWidget(self.legend_font_btn) self.title_settings_layout.addLayout(legend_layout) 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.graph_background_color_layout.addRow(self.background_color_lbl, self.background_color_btn) self.axis_settings_layout.addLayout(self.graph_background_color_layout) self.axis_grid_color_layout.addRow(self.axis_color_lbl, self.axis_color_btn) self.axis_settings_layout.addLayout(self.axis_grid_color_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.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.axis_settings_grpbx) self.chart_settings_layout.addWidget(self.reset_chart_settings_btn) self.update_datetime_timer.start(1000) def add_curve(self): """ Add a new curve to the chart. """ pv_name = self._get_full_pv_name(self.pv_name_line_edt.text()) if pv_name and len(pv_name): color = random_color(curve_colors_only=True) for k, v in self.channel_map.items(): if color == v.color: color = random_color(curve_colors_only=True) self.add_y_channel(pv_name=pv_name, curve_name=pv_name, color=color) self.handle_splitter_button(left=True) def show_mouse_coordinates(self, x, y): self.crosshair_coord_lbl.clear() self.crosshair_coord_lbl.setText("x = {0:.3f}\ny = {1:.3f}".format( x, y)) def handle_enable_crosshair_checkbox_clicked(self, is_checked): self.chart.enableCrosshair(is_checked) self.crosshair_coord_lbl.setVisible(is_checked) self.chart.crosshair_position_updated.connect( self.show_mouse_coordinates) def add_y_channel(self, pv_name, curve_name, color, line_style=Qt.SolidLine, line_width=2, symbol=None, symbol_size=None, is_visible=True): 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) curve.show() if is_visible else curve.hide() if self.show_legend_chk.isChecked(): self.change_legend_font(self.legend_font) self.channel_map[pv_name] = curve self.generate_pv_controls(pv_name, color) self.enable_chart_control_buttons() try: self.app.add_connection(curve.channel) except AttributeError: # these methods are not needed on future versions of pydm pass 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. Three buttons -- Modify..., Focus, and Remove. Modify... will bring up the Curve Settings dialog. Focus adjusts the chart's zooming for the current curve. Remove will delete the curve from the chart 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 """ individual_curve_layout = QVBoxLayout() size_policy = QSizePolicy() size_policy.setVerticalPolicy(QSizePolicy.Fixed) size_policy.setHorizontalPolicy(QSizePolicy.Fixed) individual_curve_grpbx = QGroupBox() individual_curve_grpbx.setMinimumWidth(300) individual_curve_grpbx.setMinimumHeight(120) individual_curve_grpbx.setAlignment(Qt.AlignTop) individual_curve_grpbx.setSizePolicy(size_policy) individual_curve_grpbx.setObjectName(pv_name + "_grb") individual_curve_grpbx.setLayout(individual_curve_layout) checkbox = QCheckBox(parent=individual_curve_grpbx) checkbox.setObjectName(pv_name + "_chb") 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(parent=individual_curve_grpbx) data_text.setWordWrap(True) data_text.setObjectName(pv_name + "_lbl") data_text.setPalette(palette) checkbox.setChecked(True) checkbox.toggled.connect( partial(self.handle_curve_chkbox_toggled, checkbox)) if not self.chart.findCurve(pv_name).isVisible(): checkbox.setChecked(False) modify_curve_btn = QPushButton("Modify...", parent=individual_curve_grpbx) modify_curve_btn.setObjectName(pv_name + "_btn_modify") modify_curve_btn.setMaximumWidth(80) modify_curve_btn.clicked.connect( partial(self.display_curve_settings_dialog, pv_name)) focus_curve_btn = QPushButton("Focus", parent=individual_curve_grpbx) focus_curve_btn.setObjectName(pv_name + "_btn_focus") focus_curve_btn.setMaximumWidth(80) focus_curve_btn.clicked.connect(partial(self.focus_curve, pv_name)) clear_curve_btn = QPushButton("Clear", parent=individual_curve_grpbx) clear_curve_btn.setObjectName(pv_name + "_btn_clear") clear_curve_btn.setMaximumWidth(80) clear_curve_btn.clicked.connect(partial(self.clear_curve, pv_name)) # annotate_curve_btn = QPushButton("Annotate...", # parent=individual_curve_grpbx) # annotate_curve_btn.setObjectName(pv_name+"_btn_ann") # annotate_curve_btn.setMaximumWidth(80) # annotate_curve_btn.clicked.connect( # partial(self.annotate_curve, pv_name)) remove_curve_btn = QPushButton("Remove", parent=individual_curve_grpbx) remove_curve_btn.setObjectName(pv_name + "_btn_remove") remove_curve_btn.setMaximumWidth(80) remove_curve_btn.clicked.connect(partial(self.remove_curve, pv_name)) curve_btn_layout = QHBoxLayout() curve_btn_layout.setSpacing(5) curve_btn_layout.addWidget(modify_curve_btn) curve_btn_layout.addWidget(focus_curve_btn) curve_btn_layout.addWidget(clear_curve_btn) # curve_btn_layout.addWidget(annotate_curve_btn) curve_btn_layout.addWidget(remove_curve_btn) individual_curve_layout.addWidget(checkbox) individual_curve_layout.addWidget(data_text) individual_curve_layout.addLayout(curve_btn_layout) self.curve_settings_layout.addWidget(individual_curve_grpbx) self.tab_panel.setCurrentIndex(0) 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: curve.show() self.chart.addLegendItem(curve, pv_name, self.show_legend_chk.isChecked()) self.change_legend_font(self.legend_font) 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 clear_curve(self, pv_name): curve = self.chart.findCurve(pv_name) if curve: curve.initialize_buffer() 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)) 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: try: self.app.remove_connection(curve.channel) except AttributeError: # these methods are not needed on future versions of pydm pass self.chart.removeYChannel(curve) del self.channel_map[pv_name] self.chart.removeLegendItem(pv_name) widget = self.findChild(QGroupBox, pv_name + "_grb") if widget: widget.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_spin_box.setVisible(is_checked) self.chart_limit_time_span_minutes_spin_box.setVisible(is_checked) self.chart_limit_time_span_seconds_spin_box.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_changed(self): self.time_span_limit_hours = self.chart_limit_time_span_hours_spin_box.value( ) self.time_span_limit_minutes = self.chart_limit_time_span_minutes_spin_box.value( ) self.time_span_limit_seconds = self.chart_limit_time_span_seconds_spin_box.value( ) status = self.time_span_limit_hours > 0 or self.time_span_limit_minutes > 0 or self.time_span_limit_seconds > 0 self.chart_limit_time_span_activate_btn.setEnabled(status) def handle_chart_limit_time_span_activate_btn_clicked(self): 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): try: new_buffer_size = int(self.chart_ring_buffer_size_edt.text()) 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): self.chart.maxRedrawRate = self.chart_redraw_rate_spin.value() def handle_data_sampling_rate_changed(self): # The chart expects the value in milliseconds sampling_rate_seconds = 1.0 / self.chart_data_async_sampling_rate_spin.value( ) buffer_size = self.chart.getBufferSize() self.chart.setUpdateInterval(sampling_rate_seconds) if self.chart.getBufferSize() < buffer_size: self.chart.setBufferSize(buffer_size) self.chart_ring_buffer_size_edt.setText(str( self.chart.getBufferSize())) 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.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.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="Open File", directory=os.path.expanduser('~'), filter=IMPORT_FILE_FORMAT) open_filename = open_file_info[0] if open_filename: try: importer = SettingsImporter(self) importer.import_settings(open_filename) except SettingsImporterException: display_message_box( QMessageBox.Critical, "Import Failure", "Cannot import the file '{0}'. Check the log for the error details." .format(open_filename)) logger.exception( "Cannot import the file '{0}'".format(open_filename)) 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.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.chart.setUpdatesAsynchronously(True) def handle_zoom_in_btn_clicked(self, axis, is_zoom_in): scale_factor = 0.5 if not is_zoom_in: scale_factor += 1.0 if axis == "x": self.chart.getViewBox().scaleBy(x=scale_factor) elif axis == "y": self.chart.getViewBox().scaleBy(y=scale_factor) def handle_view_all_button_clicked(self): self.chart.plotItem.getViewBox().autoRange() def handle_pause_chart_btn_clicked(self): if self.chart.pausePlotting(): self.pause_chart_btn.setIcon(self.pause_icon) else: self.pause_chart_btn.setIcon(self.play_icon) 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.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) @Slot() def handle_reset_data_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.handle_redraw_rate_changed() 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) def enable_chart_control_buttons(self, enabled=True): self.zoom_in_x_btn.setEnabled(enabled) self.zoom_out_x_btn.setEnabled(enabled) self.zoom_in_y_btn.setEnabled(enabled) self.zoom_out_y_btn.setEnabled(enabled) self.view_all_btn.setEnabled(enabled) self.reset_chart_btn.setEnabled(enabled) self.pause_chart_btn.setIcon(self.pause_icon) 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: " + TimeChartDisplay.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.address min_y = curve.minY if curve.minY else 0 max_y = curve.maxY if curve.maxY else 0 current_y = curve.data_buffer[1, -1] grb = self.findChild(QGroupBox, pv_name + "_grb") lbl = grb.findChild(QLabel, pv_name + "_lbl") lbl.setText("(yMin = {0:.3f}, yMax = {1:.3f}) y = {2:.3f}".format( min_y, max_y, current_y)) chb = grb.findChild(QCheckBox, pv_name + "_chb") connected = curve.connected if connected and chb.isEnabled(): return chb.setEnabled(connected) btn_modify = grb.findChild(QPushButton, pv_name + "_btn_modify") btn_modify.setEnabled(connected) btn_focus = grb.findChild(QPushButton, pv_name + "_btn_focus") btn_focus.setEnabled(connected) # btn_ann = grb.findChild(QPushButton, pv_name + "_btn_ann") # btn_ann.setEnabled(connected) @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