Example #1
0
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()
Example #2
0
class DimensionMDE(Dimension):
    binningChanged = Signal()
    """
    MDEventWorkspace has additional properties for either number_of_bins or thickness

    from mantidqt.widgets.sliceviewer.dimensionwidget import DimensionMDE
    from qtpy.QtWidgets import QApplication
    app = QApplication([])
    window = DimensionMDE({'minimum':-1.1, 'number_of_bins':11, 'width':0.2, 'name':'Dim0', 'units':'A'})
    window.show()
    app.exec_()
    """
    def __init__(self, dim_info, number=0, state=State.NONE, parent=None):

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

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

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

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

        self.layout.addWidget(self.spinBins)
        self.layout.addWidget(self.spinThick)
        self.layout.addWidget(self.rebinLabel)

    def get_bins(self):
        return int(self.spinBins.value())

    def get_thickness(self):
        return float(self.spinThick.value())

    def set_state(self, state):
        super().set_state(state)
        if self.state == State.X:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.Y:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.NONE:
            self.spinBins.hide()
            self.spinThick.show()
            self.rebinLabel.setText('thick')
        else:
            self.spinBins.hide()
            self.spinThick.hide()
            self.rebinLabel.hide()
Example #3
0
class PyDMChartingDisplay(Display):
    def __init__(self, parent=None, args=[], macros=None):
        """
        Create all the widgets, including any child dialogs.

        Parameters
        ----------
        parent : QWidget
            The parent widget of the charting display
        args : list
            The command parameters
        macros : str
            Macros to modify the UI parameters at runtime
        """
        super(PyDMChartingDisplay, self).__init__(parent=parent,
                                                  args=args,
                                                  macros=macros)

        self.channel_map = dict()
        self.setWindowTitle("PyDM Charting Tool")

        self.main_layout = QVBoxLayout()
        self.body_layout = QVBoxLayout()

        self.pv_layout = QHBoxLayout()
        self.pv_name_line_edt = QLineEdit()
        self.pv_name_line_edt.setAcceptDrops(True)
        self.pv_name_line_edt.installEventFilter(self)

        self.pv_protocol_cmb = QComboBox()
        self.pv_protocol_cmb.addItems(["ca://", "archive://"])

        self.pv_connect_push_btn = QPushButton("Connect")
        self.pv_connect_push_btn.clicked.connect(self.add_curve)

        self.tab_panel = QTabWidget()
        self.tab_panel.setMaximumWidth(450)
        self.curve_settings_tab = QWidget()
        self.chart_settings_tab = QWidget()

        self.charting_layout = QHBoxLayout()
        self.chart = PyDMTimePlot(plot_by_timestamps=False, plot_display=self)
        self.chart.setPlotTitle("Time Plot")

        self.splitter = QSplitter()

        self.curve_settings_layout = QVBoxLayout()
        self.curve_settings_layout.setAlignment(Qt.AlignTop)
        self.curve_settings_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
        self.curve_settings_layout.setSpacing(5)

        self.crosshair_settings_layout = QVBoxLayout()
        self.crosshair_settings_layout.setAlignment(Qt.AlignTop)
        self.crosshair_settings_layout.setSpacing(5)

        self.enable_crosshair_chk = QCheckBox("Enable Crosshair")
        self.cross_hair_coord_lbl = QLabel()

        self.curve_settings_inner_frame = QFrame()
        self.curve_settings_inner_frame.setLayout(self.curve_settings_layout)

        self.curve_settings_scroll = QScrollArea()
        self.curve_settings_scroll.setVerticalScrollBarPolicy(
            Qt.ScrollBarAsNeeded)
        self.curve_settings_scroll.setWidget(self.curve_settings_inner_frame)

        self.curves_tab_layout = QHBoxLayout()
        self.curves_tab_layout.addWidget(self.curve_settings_scroll)

        self.enable_crosshair_chk.setChecked(False)
        self.enable_crosshair_chk.clicked.connect(
            self.handle_enable_crosshair_checkbox_clicked)
        self.enable_crosshair_chk.clicked.emit(False)

        self.chart_settings_layout = QVBoxLayout()
        self.chart_settings_layout.setAlignment(Qt.AlignTop)

        self.chart_layout = QVBoxLayout()
        self.chart_panel = QWidget()

        self.chart_control_layout = QHBoxLayout()
        self.chart_control_layout.setAlignment(Qt.AlignHCenter)
        self.chart_control_layout.setSpacing(10)

        self.view_all_btn = QPushButton("View All")
        self.view_all_btn.clicked.connect(self.handle_view_all_button_clicked)
        self.view_all_btn.setEnabled(False)

        self.auto_scale_btn = QPushButton("Auto Scale")
        self.auto_scale_btn.clicked.connect(self.handle_auto_scale_btn_clicked)
        self.auto_scale_btn.setEnabled(False)

        self.reset_chart_btn = QPushButton("Reset")
        self.reset_chart_btn.clicked.connect(
            self.handle_reset_chart_btn_clicked)
        self.reset_chart_btn.setEnabled(False)

        self.resume_chart_text = "Resume"
        self.pause_chart_text = "Pause"
        self.pause_chart_btn = QPushButton(self.pause_chart_text)
        self.pause_chart_btn.clicked.connect(
            self.handle_pause_chart_btn_clicked)

        self.title_settings_layout = QVBoxLayout()
        self.title_settings_layout.setSpacing(10)

        self.title_settings_grpbx = QGroupBox()
        self.title_settings_grpbx.setFixedHeight(150)

        self.import_data_btn = QPushButton("Import Data...")
        self.import_data_btn.clicked.connect(
            self.handle_import_data_btn_clicked)

        self.export_data_btn = QPushButton("Export Data...")
        self.export_data_btn.clicked.connect(
            self.handle_export_data_btn_clicked)

        self.chart_title_lbl = QLabel(text="Chart Title")
        self.chart_title_line_edt = QLineEdit()
        self.chart_title_line_edt.setText(self.chart.getPlotTitle())
        self.chart_title_line_edt.textChanged.connect(
            self.handle_title_text_changed)

        self.chart_change_axis_settings_btn = QPushButton(
            text="Change Axis Settings...")
        self.chart_change_axis_settings_btn.clicked.connect(
            self.handle_change_axis_settings_clicked)

        self.update_datetime_timer = QTimer(self)
        self.update_datetime_timer.timeout.connect(
            self.handle_update_datetime_timer_timeout)

        self.chart_sync_mode_layout = QVBoxLayout()
        self.chart_sync_mode_layout.setSpacing(5)

        self.chart_sync_mode_grpbx = QGroupBox("Data Sampling Mode")
        self.chart_sync_mode_grpbx.setFixedHeight(80)

        self.chart_sync_mode_sync_radio = QRadioButton("Synchronous")
        self.chart_sync_mode_async_radio = QRadioButton("Asynchronous")
        self.chart_sync_mode_async_radio.setChecked(True)

        self.graph_drawing_settings_layout = QVBoxLayout()

        self.chart_redraw_rate_lbl = QLabel("Redraw Rate (Hz)")
        self.chart_redraw_rate_spin = QSpinBox()
        self.chart_redraw_rate_spin.setRange(MIN_REDRAW_RATE_HZ,
                                             MAX_REDRAW_RATE_HZ)
        self.chart_redraw_rate_spin.setValue(DEFAULT_REDRAW_RATE_HZ)
        self.chart_redraw_rate_spin.valueChanged.connect(
            self.handle_redraw_rate_changed)

        self.chart_data_sampling_rate_lbl = QLabel(
            "Asynchronous Data Sampling Rate (Hz)")
        self.chart_data_async_sampling_rate_spin = QSpinBox()
        self.chart_data_async_sampling_rate_spin.setRange(
            MIN_DATA_SAMPLING_RATE_HZ, MAX_DATA_SAMPLING_RATE_HZ)
        self.chart_data_async_sampling_rate_spin.setValue(
            DEFAULT_DATA_SAMPLING_RATE_HZ)
        self.chart_data_async_sampling_rate_spin.valueChanged.connect(
            self.handle_data_sampling_rate_changed)
        self.chart_data_sampling_rate_lbl.hide()
        self.chart_data_async_sampling_rate_spin.hide()

        self.chart_limit_time_span_layout = QHBoxLayout()
        self.chart_limit_time_span_layout.setSpacing(5)

        self.limit_time_plan_text = "Limit Time Span"
        self.chart_limit_time_span_chk = QCheckBox(self.limit_time_plan_text)
        self.chart_limit_time_span_chk.hide()
        self.chart_limit_time_span_lbl = QLabel("Hours : Minutes : Seconds")
        self.chart_limit_time_span_hours_line_edt = QLineEdit()
        self.chart_limit_time_span_minutes_line_edt = QLineEdit()
        self.chart_limit_time_span_seconds_line_edt = QLineEdit()
        self.chart_limit_time_span_activate_btn = QPushButton("Apply")
        self.chart_limit_time_span_activate_btn.setDisabled(True)

        self.chart_ring_buffer_size_lbl = QLabel("Ring Buffer Size")
        self.chart_ring_buffer_size_edt = QLineEdit()
        self.chart_ring_buffer_size_edt.installEventFilter(self)
        self.chart_ring_buffer_size_edt.textChanged.connect(
            self.handle_buffer_size_changed)
        self.chart_ring_buffer_size_edt.setText(str(DEFAULT_BUFFER_SIZE))

        self.show_legend_chk = QCheckBox("Show Legend")
        self.show_legend_chk.setChecked(self.chart.showLegend)
        self.show_legend_chk.clicked.connect(
            self.handle_show_legend_checkbox_clicked)

        self.graph_background_color_layout = QFormLayout()

        self.background_color_lbl = QLabel("Graph Background Color ")
        self.background_color_btn = QPushButton()
        self.background_color_btn.setStyleSheet(
            "background-color: " + self.chart.getBackgroundColor().name())
        self.background_color_btn.setContentsMargins(10, 0, 5, 5)
        self.background_color_btn.setMaximumWidth(20)
        self.background_color_btn.clicked.connect(
            self.handle_background_color_button_clicked)

        self.axis_settings_layout = QVBoxLayout()
        self.axis_settings_layout.setSpacing(5)

        self.show_x_grid_chk = QCheckBox("Show x Grid")
        self.show_x_grid_chk.setChecked(self.chart.showXGrid)
        self.show_x_grid_chk.clicked.connect(
            self.handle_show_x_grid_checkbox_clicked)

        self.show_y_grid_chk = QCheckBox("Show y Grid")
        self.show_y_grid_chk.setChecked(self.chart.showYGrid)
        self.show_y_grid_chk.clicked.connect(
            self.handle_show_y_grid_checkbox_clicked)

        self.axis_color_lbl = QLabel("Axis and Grid Color")
        self.axis_color_lbl.setEnabled(False)

        self.axis_color_btn = QPushButton()
        self.axis_color_btn.setStyleSheet("background-color: " +
                                          DEFAULT_CHART_AXIS_COLOR.name())
        self.axis_color_btn.setContentsMargins(10, 0, 5, 5)
        self.axis_color_btn.setMaximumWidth(20)
        self.axis_color_btn.clicked.connect(
            self.handle_axis_color_button_clicked)
        self.axis_color_btn.setEnabled(False)

        self.grid_opacity_lbl = QLabel("Grid Opacity")
        self.grid_opacity_lbl.setEnabled(False)

        self.grid_opacity_slr = QSlider(Qt.Horizontal)
        self.grid_opacity_slr.setFocusPolicy(Qt.StrongFocus)
        self.grid_opacity_slr.setRange(0, 10)
        self.grid_opacity_slr.setValue(5)
        self.grid_opacity_slr.setTickInterval(1)
        self.grid_opacity_slr.setSingleStep(1)
        self.grid_opacity_slr.setTickPosition(QSlider.TicksBelow)
        self.grid_opacity_slr.valueChanged.connect(
            self.handle_grid_opacity_slider_mouse_release)
        self.grid_opacity_slr.setEnabled(False)

        self.reset_chart_settings_btn = QPushButton("Reset Chart Settings")
        self.reset_chart_settings_btn.clicked.connect(
            self.handle_reset_chart_settings_btn_clicked)

        self.curve_checkbox_panel = QWidget()

        self.graph_drawing_settings_grpbx = QGroupBox()
        self.graph_drawing_settings_grpbx.setFixedHeight(270)

        self.axis_settings_grpbx = QGroupBox()
        self.axis_settings_grpbx.setFixedHeight(180)

        self.app = QApplication.instance()
        self.setup_ui()

        self.curve_settings_disp = None
        self.axis_settings_disp = None
        self.chart_data_export_disp = None
        self.chart_data_import_disp = None
        self.grid_alpha = 5
        self.time_span_limit_hours = None
        self.time_span_limit_minutes = None
        self.time_span_limit_seconds = None
        self.data_sampling_mode = ASYNC_DATA_SAMPLING

    def minimumSizeHint(self):
        """
        The minimum recommended size of the main window.
        """
        return QSize(1490, 800)

    def ui_filepath(self):
        """
        The path to the UI file created by Qt Designer, if applicable.
        """
        # No UI file is being used
        return None

    def ui_filename(self):
        """
        The name the UI file created by Qt Designer, if applicable.
        """
        # No UI file is being used
        return None

    def setup_ui(self):
        """
        Initialize the widgets and layouts.
        """
        self.setLayout(self.main_layout)

        self.pv_layout.addWidget(self.pv_protocol_cmb)
        self.pv_layout.addWidget(self.pv_name_line_edt)
        self.pv_layout.addWidget(self.pv_connect_push_btn)
        QTimer.singleShot(0, self.pv_name_line_edt.setFocus)

        self.curve_settings_tab.setLayout(self.curves_tab_layout)
        self.chart_settings_tab.setLayout(self.chart_settings_layout)
        self.setup_chart_settings_layout()

        self.tab_panel.addTab(self.curve_settings_tab, "Curves")
        self.tab_panel.addTab(self.chart_settings_tab, "Chart")
        self.tab_panel.hide()

        self.crosshair_settings_layout.addWidget(self.enable_crosshair_chk)
        self.crosshair_settings_layout.addWidget(self.cross_hair_coord_lbl)

        self.chart_control_layout.addWidget(self.auto_scale_btn)
        self.chart_control_layout.addWidget(self.view_all_btn)
        self.chart_control_layout.addWidget(self.reset_chart_btn)
        self.chart_control_layout.addWidget(self.pause_chart_btn)
        self.chart_control_layout.addLayout(self.crosshair_settings_layout)
        self.chart_control_layout.addWidget(self.import_data_btn)
        self.chart_control_layout.addWidget(self.export_data_btn)

        self.chart_control_layout.setStretch(4, 15)
        self.chart_control_layout.insertSpacing(5, 350)

        self.chart_layout.addWidget(self.chart)
        self.chart_layout.addLayout(self.chart_control_layout)

        self.chart_panel.setLayout(self.chart_layout)

        self.splitter.addWidget(self.chart_panel)
        self.splitter.addWidget(self.tab_panel)
        self.splitter.setStretchFactor(0, 0)
        self.splitter.setStretchFactor(1, 1)

        self.charting_layout.addWidget(self.splitter)

        self.body_layout.addLayout(self.pv_layout)
        self.body_layout.addLayout(self.charting_layout)
        self.body_layout.addLayout(self.chart_control_layout)
        self.main_layout.addLayout(self.body_layout)

        self.enable_chart_control_buttons(False)

    def setup_chart_settings_layout(self):
        self.chart_sync_mode_sync_radio.toggled.connect(
            partial(self.handle_sync_mode_radio_toggle,
                    self.chart_sync_mode_sync_radio))
        self.chart_sync_mode_async_radio.toggled.connect(
            partial(self.handle_sync_mode_radio_toggle,
                    self.chart_sync_mode_async_radio))

        self.title_settings_layout.addWidget(self.chart_title_lbl)
        self.title_settings_layout.addWidget(self.chart_title_line_edt)
        self.title_settings_layout.addWidget(self.show_legend_chk)
        self.title_settings_layout.addWidget(
            self.chart_change_axis_settings_btn)
        self.title_settings_grpbx.setLayout(self.title_settings_layout)
        self.chart_settings_layout.addWidget(self.title_settings_grpbx)

        self.chart_sync_mode_layout.addWidget(self.chart_sync_mode_sync_radio)
        self.chart_sync_mode_layout.addWidget(self.chart_sync_mode_async_radio)
        self.chart_sync_mode_grpbx.setLayout(self.chart_sync_mode_layout)
        self.chart_settings_layout.addWidget(self.chart_sync_mode_grpbx)

        self.chart_settings_layout.addWidget(self.chart_sync_mode_grpbx)

        self.chart_limit_time_span_layout.addWidget(
            self.chart_limit_time_span_lbl)
        self.chart_limit_time_span_layout.addWidget(
            self.chart_limit_time_span_hours_line_edt)

        self.chart_limit_time_span_layout.addWidget(
            self.chart_limit_time_span_minutes_line_edt)
        self.chart_limit_time_span_layout.addWidget(
            self.chart_limit_time_span_seconds_line_edt)
        self.chart_limit_time_span_layout.addWidget(
            self.chart_limit_time_span_activate_btn)

        self.chart_limit_time_span_lbl.hide()
        self.chart_limit_time_span_hours_line_edt.hide()
        self.chart_limit_time_span_minutes_line_edt.hide()
        self.chart_limit_time_span_seconds_line_edt.hide()
        self.chart_limit_time_span_activate_btn.hide()

        self.chart_limit_time_span_hours_line_edt.textChanged.connect(
            self.handle_time_span_edt_text_changed)
        self.chart_limit_time_span_minutes_line_edt.textChanged.connect(
            self.handle_time_span_edt_text_changed)
        self.chart_limit_time_span_seconds_line_edt.textChanged.connect(
            self.handle_time_span_edt_text_changed)

        self.chart_limit_time_span_chk.clicked.connect(
            self.handle_limit_time_span_checkbox_clicked)
        self.chart_limit_time_span_activate_btn.clicked.connect(
            self.handle_chart_limit_time_span_activate_btn_clicked)
        self.chart_limit_time_span_activate_btn.installEventFilter(self)

        self.graph_background_color_layout.addRow(self.background_color_lbl,
                                                  self.background_color_btn)

        self.graph_drawing_settings_layout.addLayout(
            self.graph_background_color_layout)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_redraw_rate_lbl)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_redraw_rate_spin)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_data_sampling_rate_lbl)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_data_async_sampling_rate_spin)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_limit_time_span_chk)
        self.graph_drawing_settings_layout.addLayout(
            self.chart_limit_time_span_layout)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_ring_buffer_size_lbl)
        self.graph_drawing_settings_layout.addWidget(
            self.chart_ring_buffer_size_edt)
        self.graph_drawing_settings_grpbx.setLayout(
            self.graph_drawing_settings_layout)

        self.axis_settings_layout.addWidget(self.show_x_grid_chk)
        self.axis_settings_layout.addWidget(self.show_y_grid_chk)
        self.axis_settings_layout.addWidget(self.axis_color_lbl)
        self.axis_settings_layout.addWidget(self.axis_color_btn)
        self.axis_settings_layout.addWidget(self.grid_opacity_lbl)
        self.axis_settings_layout.addWidget(self.grid_opacity_slr)
        self.axis_settings_grpbx.setLayout(self.axis_settings_layout)

        self.chart_settings_layout.addWidget(self.graph_drawing_settings_grpbx)
        self.chart_settings_layout.addWidget(self.axis_settings_grpbx)
        self.chart_settings_layout.addWidget(self.reset_chart_settings_btn)

        self.chart_sync_mode_async_radio.toggled.emit(True)
        self.update_datetime_timer.start(1000)

    def eventFilter(self, obj, event):
        """
        Handle key and mouse events for any applicable widget.

        Parameters
        ----------
        obj : QWidget
            The current widget that accepts the event
        event : QEvent
            The key or mouse event to handle

        Returns
        -------
            True if the event was handled successfully; False otherwise
        """
        if obj == self.pv_name_line_edt and event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
                self.add_curve()
                return True
        elif obj == self.chart_limit_time_span_activate_btn and event.type(
        ) == QEvent.KeyPress:
            if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
                self.handle_chart_limit_time_span_activate_btn_clicked()
                return True
        elif obj == self.chart_ring_buffer_size_edt:
            if event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return) or \
                    event.type() == QEvent.FocusOut:
                try:
                    buffer_size = int(self.chart_ring_buffer_size_edt.text())
                    if buffer_size < MINIMUM_BUFFER_SIZE:
                        self.chart_ring_buffer_size_edt.setText(
                            str(MINIMUM_BUFFER_SIZE))
                except ValueError:
                    display_message_box(QMessageBox.Critical, "Invalid Values",
                                        "Only integer values are accepted.")
                return True
        return super(PyDMChartingDisplay, self).eventFilter(obj, event)

    def add_curve(self):
        """
        Add a new curve to the chart.
        """
        pv_name = self._get_full_pv_name(self.pv_name_line_edt.text())
        color = random_color()
        for k, v in self.channel_map.items():
            if color == v.color:
                color = random_color()

        self.add_y_channel(pv_name=pv_name, curve_name=pv_name, color=color)

    def handle_enable_crosshair_checkbox_clicked(self, is_checked):
        self.chart.enableCrosshair(is_checked)
        self.cross_hair_coord_lbl.setVisible(is_checked)

    def add_y_channel(self,
                      pv_name,
                      curve_name,
                      color,
                      line_style=Qt.SolidLine,
                      line_width=2,
                      symbol=None,
                      symbol_size=None):
        if pv_name in self.channel_map:
            logger.error("'{0}' has already been added.".format(pv_name))
            return

        curve = self.chart.addYChannel(y_channel=pv_name,
                                       name=curve_name,
                                       color=color,
                                       lineStyle=line_style,
                                       lineWidth=line_width,
                                       symbol=symbol,
                                       symbolSize=symbol_size)
        self.channel_map[pv_name] = curve
        self.generate_pv_controls(pv_name, color)

        self.enable_chart_control_buttons()
        self.app.establish_widget_connections(self)

    def generate_pv_controls(self, pv_name, curve_color):
        """
        Generate a set of widgets to manage the appearance of a curve. The set of widgets includes:
            1. A checkbox which shows the curve on the chart if checked, and hide the curve if not checked
            2. Two buttons -- Modify... and Remove. Modify... will bring up the Curve Settings dialog. Remove will
               delete the curve from the chart
        This set of widgets will be hidden initially, until the first curve is plotted.

        Parameters
        ----------
        pv_name: str
            The name of the PV the current curve is being plotted for

        curve_color : QColor
            The color of the curve to paint for the checkbox label to help the user track the curve to the checkbox
        """
        checkbox = QCheckBox()
        checkbox.setObjectName(pv_name)

        palette = checkbox.palette()
        palette.setColor(QPalette.Active, QPalette.WindowText, curve_color)
        checkbox.setPalette(palette)

        display_name = pv_name.split("://")[1]
        if len(display_name) > MAX_DISPLAY_PV_NAME_LENGTH:
            # Only display max allowed number of characters of the PV Name
            display_name = display_name[:int(MAX_DISPLAY_PV_NAME_LENGTH / 2) - 1] + "..." + \
                           display_name[-int(MAX_DISPLAY_PV_NAME_LENGTH / 2) + 2:]

        checkbox.setText(display_name)

        data_text = QLabel()
        data_text.setObjectName(pv_name)
        data_text.setPalette(palette)

        checkbox.setChecked(True)
        checkbox.clicked.connect(
            partial(self.handle_curve_chkbox_toggled, checkbox))

        curve_btn_layout = QHBoxLayout()

        modify_curve_btn = QPushButton("Modify...")
        modify_curve_btn.setObjectName(pv_name)
        modify_curve_btn.setMaximumWidth(100)
        modify_curve_btn.clicked.connect(
            partial(self.display_curve_settings_dialog, pv_name))

        focus_curve_btn = QPushButton("Focus")
        focus_curve_btn.setObjectName(pv_name)
        focus_curve_btn.setMaximumWidth(100)
        focus_curve_btn.clicked.connect(partial(self.focus_curve, pv_name))

        annotate_curve_btn = QPushButton("Annotate...")
        annotate_curve_btn.setObjectName(pv_name)
        annotate_curve_btn.setMaximumWidth(100)
        annotate_curve_btn.clicked.connect(
            partial(self.annotate_curve, pv_name))

        remove_curve_btn = QPushButton("Remove")
        remove_curve_btn.setObjectName(pv_name)
        remove_curve_btn.setMaximumWidth(100)
        remove_curve_btn.clicked.connect(partial(self.remove_curve, pv_name))

        curve_btn_layout.addWidget(modify_curve_btn)
        curve_btn_layout.addWidget(focus_curve_btn)
        curve_btn_layout.addWidget(annotate_curve_btn)
        curve_btn_layout.addWidget(remove_curve_btn)

        individual_curve_layout = QVBoxLayout()
        individual_curve_layout.addWidget(checkbox)
        individual_curve_layout.addWidget(data_text)
        individual_curve_layout.addLayout(curve_btn_layout)

        size_policy = QSizePolicy()
        size_policy.setVerticalPolicy(QSizePolicy.Fixed)
        individual_curve_grpbx = QGroupBox()
        individual_curve_grpbx.setSizePolicy(size_policy)

        individual_curve_grpbx.setObjectName(pv_name)
        individual_curve_grpbx.setLayout(individual_curve_layout)

        self.curve_settings_layout.addWidget(individual_curve_grpbx)
        self.tab_panel.show()

    def handle_curve_chkbox_toggled(self, checkbox):
        """
        Handle a checkbox's checked and unchecked events.

        If a checkbox is checked, find the curve from the channel map. If found, re-draw the curve with its previous
        appearance settings.

        If a checkbox is unchecked, remove the curve from the chart, but keep the cached data in the channel map.

        Parameters
        ----------
        checkbox : QCheckBox
            The current checkbox being toggled
        """
        pv_name = self._get_full_pv_name(checkbox.text())

        if checkbox.isChecked():
            curve = self.channel_map.get(pv_name, None)
            if curve:
                self.chart.addLegendItem(curve, pv_name,
                                         self.show_legend_chk.isChecked())
                curve.show()
        else:
            curve = self.chart.findCurve(pv_name)
            if curve:
                curve.hide()
                self.chart.removeLegendItem(pv_name)

    def display_curve_settings_dialog(self, pv_name):
        """
        Bring up the Curve Settings dialog to modify the appearance of a curve.

        Parameters
        ----------
        pv_name : str
            The name of the PV the curve is being plotted for

        """
        self.curve_settings_disp = CurveSettingsDisplay(self, pv_name)
        self.curve_settings_disp.show()

    def focus_curve(self, pv_name):
        curve = self.chart.findCurve(pv_name)
        if curve:
            self.chart.plotItem.setYRange(curve.minY, curve.maxY, padding=0)

    def annotate_curve(self, pv_name):
        curve = self.chart.findCurve(pv_name)
        if curve:
            annot = TextItem(
                html=
                '<div style="text-align: center"><span style="color: #FFF;">This is the'
                '</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>',
                anchor=(-0.3, 0.5),
                border='w',
                fill=(0, 0, 255, 100))
            annot = TextItem("test", anchor=(-0.3, 0.5))
            self.chart.annotateCurve(curve, annot)

    def remove_curve(self, pv_name):
        """
        Remove a curve from the chart permanently. This will also clear the channel map cache from retaining the
        removed curve's appearance settings.

        Parameters
        ----------
        pv_name : str
            The name of the PV the curve is being plotted for
        """
        curve = self.chart.findCurve(pv_name)
        if curve:
            self.chart.removeYChannel(curve)
            del self.channel_map[pv_name]
            self.chart.removeLegendItem(pv_name)

            widgets = self.findChildren(
                (QCheckBox, QLabel, QPushButton, QGroupBox), pv_name)
            for w in widgets:
                w.deleteLater()

        if len(self.chart.getCurves()) < 1:
            self.enable_chart_control_buttons(False)
            self.show_legend_chk.setChecked(False)

    def handle_title_text_changed(self, new_text):
        self.chart.setPlotTitle(new_text)

    def handle_change_axis_settings_clicked(self):
        self.axis_settings_disp = AxisSettingsDisplay(self)
        self.axis_settings_disp.show()

    def handle_limit_time_span_checkbox_clicked(self, is_checked):
        self.chart_limit_time_span_lbl.setVisible(is_checked)
        self.chart_limit_time_span_hours_line_edt.setVisible(is_checked)
        self.chart_limit_time_span_minutes_line_edt.setVisible(is_checked)
        self.chart_limit_time_span_seconds_line_edt.setVisible(is_checked)
        self.chart_limit_time_span_activate_btn.setVisible(is_checked)

        self.chart_ring_buffer_size_lbl.setDisabled(is_checked)
        self.chart_ring_buffer_size_edt.setDisabled(is_checked)

        if not is_checked:
            self.chart_limit_time_span_chk.setText(self.limit_time_plan_text)

    def handle_time_span_edt_text_changed(self, new_text):
        try:
            self.time_span_limit_hours = int(
                self.chart_limit_time_span_hours_line_edt.text())
            self.time_span_limit_minutes = int(
                self.chart_limit_time_span_minutes_line_edt.text())
            self.time_span_limit_seconds = int(
                self.chart_limit_time_span_seconds_line_edt.text())
        except ValueError as e:
            self.time_span_limit_hours = None
            self.time_span_limit_minutes = None
            self.time_span_limit_seconds = None

        if self.time_span_limit_hours is not None and self.time_span_limit_minutes is not None and \
                self.time_span_limit_seconds is not None:
            self.chart_limit_time_span_activate_btn.setEnabled(True)
        else:
            self.chart_limit_time_span_activate_btn.setEnabled(False)

    def handle_chart_limit_time_span_activate_btn_clicked(self):
        if self.time_span_limit_hours is None or self.time_span_limit_minutes is None or \
                self.time_span_limit_seconds is None:
            display_message_box(
                QMessageBox.Critical, "Invalid Values",
                "Hours, minutes, and seconds expect only integer values.")
        else:
            timeout_milliseconds = (self.time_span_limit_hours * 3600 +
                                    self.time_span_limit_minutes * 60 +
                                    self.time_span_limit_seconds) * 1000
            self.chart.setTimeSpan(timeout_milliseconds / 1000.0)
            self.chart_ring_buffer_size_edt.setText(
                str(self.chart.getBufferSize()))

    def handle_buffer_size_changed(self, new_buffer_size):
        try:
            if new_buffer_size and int(new_buffer_size) > MINIMUM_BUFFER_SIZE:
                self.chart.setBufferSize(new_buffer_size)
        except ValueError:
            display_message_box(QMessageBox.Critical, "Invalid Values",
                                "Only integer values are accepted.")

    def handle_redraw_rate_changed(self, new_redraw_rate):
        self.chart.maxRedrawRate = new_redraw_rate

    def handle_data_sampling_rate_changed(self, new_data_sampling_rate):
        # The chart expects the value in milliseconds
        sampling_rate_seconds = 1 / new_data_sampling_rate
        self.chart.setUpdateInterval(sampling_rate_seconds)

    def handle_background_color_button_clicked(self):
        selected_color = QColorDialog.getColor()
        self.chart.setBackgroundColor(selected_color)
        self.background_color_btn.setStyleSheet("background-color: " +
                                                selected_color.name())

    def handle_axis_color_button_clicked(self):
        selected_color = QColorDialog.getColor()
        self.chart.setAxisColor(selected_color)
        self.axis_color_btn.setStyleSheet("background-color: " +
                                          selected_color.name())

    def handle_grid_opacity_slider_mouse_release(self):
        self.grid_alpha = float(self.grid_opacity_slr.value()) / 10.0
        self.chart.setShowXGrid(self.show_x_grid_chk.isChecked(),
                                self.grid_alpha)
        self.chart.setShowYGrid(self.show_y_grid_chk.isChecked(),
                                self.grid_alpha)

    def handle_show_x_grid_checkbox_clicked(self, is_checked):
        self.chart.setShowXGrid(is_checked, self.grid_alpha)

        self.axis_color_lbl.setEnabled(is_checked
                                       or self.show_y_grid_chk.isChecked())
        self.axis_color_btn.setEnabled(is_checked
                                       or self.show_y_grid_chk.isChecked())
        self.grid_opacity_lbl.setEnabled(is_checked
                                         or self.show_y_grid_chk.isChecked())
        self.grid_opacity_slr.setEnabled(is_checked
                                         or self.show_y_grid_chk.isChecked())

    def handle_show_y_grid_checkbox_clicked(self, is_checked):
        self.chart.setShowYGrid(is_checked, self.grid_alpha)

        self.axis_color_lbl.setEnabled(is_checked
                                       or self.show_x_grid_chk.isChecked())
        self.axis_color_btn.setEnabled(is_checked
                                       or self.show_x_grid_chk.isChecked())
        self.grid_opacity_lbl.setEnabled(is_checked
                                         or self.show_x_grid_chk.isChecked())
        self.grid_opacity_slr.setEnabled(is_checked
                                         or self.show_x_grid_chk.isChecked())

    def handle_show_legend_checkbox_clicked(self, is_checked):
        self.chart.setShowLegend(is_checked)

    def handle_export_data_btn_clicked(self):
        self.chart_data_export_disp = ChartDataExportDisplay(self)
        self.chart_data_export_disp.show()

    def handle_import_data_btn_clicked(self):
        open_file_info = QFileDialog.getOpenFileName(self,
                                                     caption="Save File",
                                                     filter="*." +
                                                     IMPORT_FILE_FORMAT)
        open_file_name = open_file_info[0]
        if open_file_name:
            importer = SettingsImporter(self)
            importer.import_settings(open_file_name)

    def handle_sync_mode_radio_toggle(self, radio_btn):
        if radio_btn.isChecked():
            if radio_btn.text() == "Synchronous":
                self.data_sampling_mode = SYNC_DATA_SAMPLING

                self.chart_data_sampling_rate_lbl.hide()
                self.chart_data_async_sampling_rate_spin.hide()

                self.chart.resetTimeSpan()
                self.chart_limit_time_span_chk.setChecked(False)
                self.chart_limit_time_span_chk.clicked.emit(False)
                self.chart_limit_time_span_chk.hide()
                self.graph_drawing_settings_grpbx.setFixedHeight(180)

                self.chart.setUpdatesAsynchronously(False)
            elif radio_btn.text() == "Asynchronous":
                self.data_sampling_mode = ASYNC_DATA_SAMPLING

                self.chart_data_sampling_rate_lbl.show()
                self.chart_data_async_sampling_rate_spin.show()
                self.chart_limit_time_span_chk.show()
                self.graph_drawing_settings_grpbx.setFixedHeight(270)

                self.chart.setUpdatesAsynchronously(True)
        self.app.establish_widget_connections(self)

    def handle_auto_scale_btn_clicked(self):
        self.chart.resetAutoRangeX()
        self.chart.resetAutoRangeY()

    def handle_view_all_button_clicked(self):
        self.chart.getViewBox().autoRange()

    def handle_pause_chart_btn_clicked(self):
        if self.chart.pausePlotting():
            self.pause_chart_btn.setText(self.pause_chart_text)
        else:
            self.pause_chart_btn.setText(self.resume_chart_text)

    def handle_reset_chart_btn_clicked(self):
        self.chart.getViewBox().setXRange(DEFAULT_X_MIN, 0)
        self.chart.resetAutoRangeY()

    @Slot()
    def handle_reset_chart_settings_btn_clicked(self):
        self.chart_ring_buffer_size_edt.setText(str(DEFAULT_BUFFER_SIZE))

        self.chart_redraw_rate_spin.setValue(DEFAULT_REDRAW_RATE_HZ)
        self.chart_data_async_sampling_rate_spin.setValue(
            DEFAULT_DATA_SAMPLING_RATE_HZ)
        self.chart_data_sampling_rate_lbl.hide()
        self.chart_data_async_sampling_rate_spin.hide()

        self.chart_sync_mode_async_radio.setChecked(True)
        self.chart_sync_mode_async_radio.toggled.emit(True)

        self.chart_limit_time_span_chk.setChecked(False)
        self.chart_limit_time_span_chk.setText(self.limit_time_plan_text)
        self.chart_limit_time_span_chk.clicked.emit(False)

        self.chart.setUpdatesAsynchronously(True)
        self.chart.resetTimeSpan()
        self.chart.resetUpdateInterval()
        self.chart.setBufferSize(DEFAULT_BUFFER_SIZE)

        self.chart.setBackgroundColor(DEFAULT_CHART_BACKGROUND_COLOR)
        self.background_color_btn.setStyleSheet(
            "background-color: " + DEFAULT_CHART_BACKGROUND_COLOR.name())

        self.chart.setAxisColor(DEFAULT_CHART_AXIS_COLOR)
        self.axis_color_btn.setStyleSheet("background-color: " +
                                          DEFAULT_CHART_AXIS_COLOR.name())

        self.grid_opacity_slr.setValue(5)

        self.show_x_grid_chk.setChecked(False)
        self.show_x_grid_chk.clicked.emit(False)

        self.show_y_grid_chk.setChecked(False)
        self.show_y_grid_chk.clicked.emit(False)

        self.show_legend_chk.setChecked(False)

        self.chart.setShowXGrid(False)
        self.chart.setShowYGrid(False)
        self.chart.setShowLegend(False)

    def enable_chart_control_buttons(self, enabled=True):
        self.auto_scale_btn.setEnabled(enabled)
        self.view_all_btn.setEnabled(enabled)
        self.reset_chart_btn.setEnabled(enabled)
        self.pause_chart_btn.setText(self.pause_chart_text)
        self.pause_chart_btn.setEnabled(enabled)
        self.export_data_btn.setEnabled(enabled)

    def _get_full_pv_name(self, pv_name):
        """
        Append the protocol to the PV Name.

        Parameters
        ----------
        pv_name : str
            The name of the PV the curve is being plotted for
        """
        if pv_name and "://" not in pv_name:
            pv_name = ''.join([self.pv_protocol_cmb.currentText(), pv_name])
        return pv_name

    def handle_update_datetime_timer_timeout(self):
        current_label = self.chart.getBottomAxisLabel()
        new_label = "Current Time: " + PyDMChartingDisplay.get_current_datetime(
        )

        if X_AXIS_LABEL_SEPARATOR in current_label:
            current_label = current_label[current_label.
                                          find(X_AXIS_LABEL_SEPARATOR) +
                                          len(X_AXIS_LABEL_SEPARATOR):]
            new_label += X_AXIS_LABEL_SEPARATOR + current_label

        self.chart.setLabel("bottom", text=new_label)

    def update_curve_data(self, curve):
        """
        Determine if the PV is active. If not, disable the related PV controls. If the PV is active, update the PV
        controls' states.

        Parameters
        ----------
        curve : PlotItem
           A PlotItem, i.e. a plot, to draw on the chart.
        """
        pv_name = curve.name()
        max_x = self.chart.getViewBox().viewRange()[1][0]
        max_y = self.chart.getViewBox().viewRange()[1][1]
        current_y = curve.data_buffer[1, -1]

        widgets = self.findChildren((QCheckBox, QLabel, QPushButton), pv_name)
        for w in widgets:
            if np.isnan(current_y):
                if isinstance(w, QCheckBox):
                    w.setChecked(False)
            else:
                if isinstance(w, QCheckBox) and not w.isEnabled():
                    w.setChecked(True)
                if isinstance(w, QLabel):
                    w.clear()
                    w.setText(
                        "(yMin = {0:.3f}, yMax = {1:.3f}) y = {2:.3f}".format(
                            max_x, max_y, current_y))
                    w.show()
            w.setEnabled(not np.isnan(current_y))

            if isinstance(w, QPushButton) and w.text() == "Remove":
                # Enable the Remove button to make removing inactive PVs possible anytime
                w.setEnabled(True)

    def show_mouse_coordinates(self, x, y):
        self.cross_hair_coord_lbl.clear()
        self.cross_hair_coord_lbl.setText("x = {0:.3f}, y = {1:.3f}".format(
            x, y))

    @staticmethod
    def get_current_datetime():
        current_date = datetime.datetime.now().strftime("%b %d, %Y")
        current_time = datetime.datetime.now().strftime("%H:%M:%S")
        current_datetime = current_time + ' (' + current_date + ')'

        return current_datetime

    @property
    def gridAlpha(self):
        return self.grid_alpha
Example #4
0
class DimensionNonIntegrated(Dimension):
    binningChanged = Signal()
    """
    A dimension that can either be sliced through or rebinned. It
    has additional properties for either number_of_bins or thickness

    from mantidqt.widgets.sliceviewer.dimensionwidget import DimensionMDE
    from qtpy.QtWidgets import QApplication
    app = QApplication([])
    window = DimensionNonIntegrated({'minimum':-1.1, 'number_of_bins':11,
                                     'width':0.2, 'name':'Dim0', 'units':'A'})
    window.show()
    app.exec_()
    """
    def __init__(self, dim_info, number=0, state=State.NONE, parent=None):
        # hack in a number_of_bins for MDEventWorkspace
        if dim_info['type'] == 'MDE':
            dim_info['number_of_bins'] = 100
            dim_info['width'] = (dim_info['maximum'] -
                                 dim_info['minimum']) / 100

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

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

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

        self.layout.addWidget(self.spinBins)
        self.layout.addWidget(self.spinThick)
        self.layout.addWidget(self.rebinLabel)

    def get_bins(self):
        return int(self.spinBins.value())

    def get_thickness(self):
        return float(self.spinThick.value())

    def set_state(self, state):
        super().set_state(state)
        if self.state == State.X:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.Y:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.NONE:
            self.spinBins.hide()
            self.spinThick.show()
            self.rebinLabel.setText('thick')
        else:
            self.spinBins.hide()
            self.spinThick.hide()
            self.rebinLabel.hide()

    def set_value(self, value):
        """Override the set_value for MDE, this allows the exact value to be
        set instead of limiting to the value of the slider. This
        allows when selecting a peak to go to the exact layer where
        the peak is.

        """
        self.value = value
        # temporary disable updating value from slider change
        self.update_value_from_slider = False
        self.update_slider()
        self.update_value_from_slider = True
        self.update_spinbox()
Example #5
0
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
        items = list(keys)

        #nrows = len(keys)
        active_key = 'main'
        if 'main' not in items:
            active_key = items[0]
        self.active_key = active_key

        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]
        if isinstance(actor_obj, CoordProperties):
            opacity = 1.0
            representation = 'coord'
            show = actor_obj.is_visible
            color = None
            line_width = 0
            point_size = 0
            bar_scale = 0
            name = 'Coord'
        else:
            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
            representation = actor_obj.representation
        self.representation = 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)

        if color is not None:
            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.representation_label = QLabel('Representation:')
        self.checkbox_wire = QCheckBox('Wireframe')
        self.checkbox_surf = QCheckBox('Surface/Solid')
        #print('representation = %s' % self.representation)
        #self.check_point = QCheckBox()

        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()

        if isinstance(actor_obj, CoordProperties):
            self.color_edit.hide()
            self.color.hide()
            self.opacity.hide()
            self.opacity_edit.hide()
            self.opacity_slider_edit.hide()
            self.line_width.hide()
            self.line_width_edit.hide()
            self.line_width_slider_edit.hide()

    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', 'wire+surf', '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

        wire_surf_checkboxes = QButtonGroup(self)
        wire_surf_checkboxes.addButton(self.checkbox_wire)
        wire_surf_checkboxes.addButton(self.checkbox_surf)

        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_wire)
        vbox1.addWidget(self.checkbox_surf)

        vbox2 = QVBoxLayout()
        vbox2.addWidget(self.checkbox_show)
        vbox2.addWidget(self.checkbox_hide)

        #vbox.addLayout(vbox1)
        vbox.addLayout(vbox2)

        vbox.addStretch()
        #vbox.addWidget(self.check_apply)
        vbox.addLayout(ok_cancel_box)
        self.setLayout(vbox)

    def set_connections(self):
        """creates the actions for the menu"""
        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 = 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()
Example #6
0
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
Example #7
0
class DimensionMDE(Dimension):
    binningChanged = Signal()
    """
    MDEventWorkspace has additional properties for either number_of_bins or thickness

    from mantidqt.widgets.sliceviewer.dimensionwidget import DimensionMDE
    from qtpy.QtWidgets import QApplication
    app = QApplication([])
    window = DimensionMDE({'minimum':-1.1, 'number_of_bins':11, 'width':0.2, 'name':'Dim0', 'units':'A'})
    window.show()
    app.exec_()
    """
    def __init__(self, dim_info, number=0, state=State.NONE, parent=None):

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

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

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

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

        self.layout.addWidget(self.spinBins)
        self.layout.addWidget(self.spinThick)
        self.layout.addWidget(self.rebinLabel)

    def get_bins(self):
        return int(self.spinBins.value())

    def get_thickness(self):
        return float(self.spinThick.value())

    def set_state(self, state):
        super(DimensionMDE, self).set_state(state)
        if self.state == State.X:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.Y:
            self.spinBins.show()
            self.spinThick.hide()
            self.rebinLabel.setText('bins')
        elif self.state == State.NONE:
            self.spinBins.hide()
            self.spinThick.show()
            self.rebinLabel.setText('thick')
        else:
            self.spinBins.hide()
            self.spinThick.hide()
            self.rebinLabel.hide()