def create_window():
    # Create app and widgets
    app = QApplication(sys.argv)
    win = QMainWindow()
    main_area = QWidget()
    button_area = QWidget()
    scroll_area = QScrollArea()
    button = QPushButton("Start Video")
    btn_grab = QPushButton("Grab Frame")

    # Create layouts
    vbox = QVBoxLayout()
    hbox = QHBoxLayout()

    # Fill Layouts
    vbox.addWidget(scroll_area)
    vbox.addWidget(button_area)
    hbox.addStretch()
    hbox.addWidget(button)
    hbox.addWidget(btn_grab)

    # Assign layouts to widgets
    main_area.setLayout(vbox)
    button_area.setLayout(hbox)
    scroll_area.setLayout(QVBoxLayout())

    # Attach some child widgets directly
    win.setCentralWidget(main_area)

    return app, win, button, btn_grab, scroll_area
Exemple #2
0
 def add_page(self, widget):
     self.check_settings.connect(widget.check_settings)
     widget.show_this_page.connect(lambda row=self.contents_widget.count():
                                   self.contents_widget.setCurrentRow(row))
     widget.apply_button_enabled.connect(self.apply_btn.setEnabled)
     scrollarea = QScrollArea(self)
     scrollarea.setWidgetResizable(True)
     scrollarea.setWidget(widget)
     self.pages_widget.addWidget(scrollarea)
     item = QListWidgetItem(self.contents_widget)
     try:
         item.setIcon(widget.get_icon())
     except TypeError:
         pass
     item.setText(widget.get_name())
     item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
     item.setSizeHint(QSize(0, 25))
Exemple #3
0
    def setup_scrollarea(self):
        """Setup the scrollarea that will contain the FigureThumbnails."""
        self.view = QWidget()

        self.scene = QGridLayout(self.view)
        self.scene.setColumnStretch(0, 100)
        self.scene.setColumnStretch(2, 100)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidget(self.view)
        self.scrollarea.setWidgetResizable(True)
        self.scrollarea.setFrameStyle(0)
        self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setSizePolicy(QSizePolicy(QSizePolicy.Ignored,
                                                  QSizePolicy.Preferred))

        # Set the vertical scrollbar explicitely :
        # This is required to avoid a "RuntimeError: no access to protected
        # functions or signals for objects not created from Python" in Linux.
        self.scrollarea.setVerticalScrollBar(QScrollBar())

        return self.scrollarea
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))

        # 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(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 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
    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))
Exemple #6
0
class ThumbnailScrollBar(QFrame):
    """
    A widget that manages the display of the FigureThumbnails that are
    created when a figure is sent to the IPython console by the kernel and
    that controls what is displayed in the FigureViewer.
    """
    redirect_stdio = Signal(bool)

    def __init__(self, figure_viewer, parent=None, background_color=None):
        super(ThumbnailScrollBar, self).__init__(parent)
        self._thumbnails = []
        self.background_color = background_color
        self.current_thumbnail = None
        self.set_figureviewer(figure_viewer)
        self.setup_gui()

    def setup_gui(self):
        """Setup the main layout of the widget."""
        scrollarea = self.setup_scrollarea()
        up_btn, down_btn = self.setup_arrow_buttons()

        self.setFixedWidth(150)
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(up_btn)
        layout.addWidget(scrollarea)
        layout.addWidget(down_btn)

    def setup_scrollarea(self):
        """Setup the scrollarea that will contain the FigureThumbnails."""
        self.view = QWidget()

        self.scene = QGridLayout(self.view)
        self.scene.setColumnStretch(0, 100)
        self.scene.setColumnStretch(2, 100)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidget(self.view)
        self.scrollarea.setWidgetResizable(True)
        self.scrollarea.setFrameStyle(0)
        self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setSizePolicy(QSizePolicy(QSizePolicy.Ignored,
                                                  QSizePolicy.Preferred))

        # Set the vertical scrollbar explicitely :
        # This is required to avoid a "RuntimeError: no access to protected
        # functions or signals for objects not created from Python" in Linux.
        self.scrollarea.setVerticalScrollBar(QScrollBar())

        return self.scrollarea

    def setup_arrow_buttons(self):
        """
        Setup the up and down arrow buttons that are placed at the top and
        bottom of the scrollarea.
        """
        # Get the size hint height of the horizontal scrollbar.
        height = self.scrollarea.horizontalScrollBar().sizeHint().height()

        # Setup the up and down arrow button.
        up_btn = up_btn = QPushButton(icon=ima.icon('last_edit_location'))
        up_btn.setFlat(True)
        up_btn.setFixedHeight(height)
        up_btn.clicked.connect(self.go_up)

        down_btn = QPushButton(icon=ima.icon('folding.arrow_down_on'))
        down_btn.setFlat(True)
        down_btn.setFixedHeight(height)
        down_btn.clicked.connect(self.go_down)

        return up_btn, down_btn

    def set_figureviewer(self, figure_viewer):
        """Set the bamespace for the FigureViewer."""
        self.figure_viewer = figure_viewer

    # ---- Save Figure
    def save_all_figures_as(self):
        """Save all the figures to a file."""
        self.redirect_stdio.emit(False)
        dirname = getexistingdirectory(self, caption='Save all figures',
                                       basedir=getcwd_or_home())
        self.redirect_stdio.emit(True)
        if dirname:
            return self.save_all_figures_todir(dirname)

    def save_all_figures_todir(self, dirname):
        """Save all figure in dirname."""
        fignames = []
        for thumbnail in self._thumbnails:
            fig = thumbnail.canvas.fig
            fmt = thumbnail.canvas.fmt
            fext = {'image/png': '.png',
                    'image/jpeg': '.jpg',
                    'image/svg+xml': '.svg'}[fmt]

            figname = get_unique_figname(dirname, 'Figure', fext)
            save_figure_tofile(fig, fmt, figname)
            fignames.append(figname)
        return fignames

    def save_current_figure_as(self):
        """Save the currently selected figure."""
        if self.current_thumbnail is not None:
            self.save_figure_as(self.current_thumbnail.canvas.fig,
                                self.current_thumbnail.canvas.fmt)

    def save_figure_as(self, fig, fmt):
        """Save the figure to a file."""
        fext, ffilt = {
            'image/png': ('.png', 'PNG (*.png)'),
            'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'),
            'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)')}[fmt]

        figname = get_unique_figname(getcwd_or_home(), 'Figure', fext)

        self.redirect_stdio.emit(False)
        fname, fext = getsavefilename(
            parent=self.parent(), caption='Save Figure',
            basedir=figname, filters=ffilt,
            selectedfilter='', options=None)
        self.redirect_stdio.emit(True)

        if fname:
            save_figure_tofile(fig, fmt, fname)

    # ---- Thumbails Handlers
    def add_thumbnail(self, fig, fmt):
        thumbnail = FigureThumbnail(background_color=self.background_color)
        thumbnail.canvas.load_figure(fig, fmt)

        # Scale the thumbnail size, while respecting the figure
        # dimension ratio.
        fwidth = thumbnail.canvas.fwidth
        fheight = thumbnail.canvas.fheight

        max_length = 100
        if fwidth/fheight > 1:
            canvas_width = max_length
            canvas_height = canvas_width / fwidth * fheight
        else:
            canvas_height = max_length
            canvas_width = canvas_height / fheight * fwidth
        thumbnail.canvas.setFixedSize(canvas_width, canvas_height)

        thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
        thumbnail.sig_remove_figure.connect(self.remove_thumbnail)
        thumbnail.sig_save_figure.connect(self.save_figure_as)
        self._thumbnails.append(thumbnail)

        self.scene.setRowStretch(self.scene.rowCount()-1, 0)
        self.scene.addWidget(thumbnail, self.scene.rowCount()-1, 1)
        self.scene.setRowStretch(self.scene.rowCount(), 100)
        self.set_current_thumbnail(thumbnail)

    def remove_current_thumbnail(self):
        """Remove the currently selected thumbnail."""
        if self.current_thumbnail is not None:
            self.remove_thumbnail(self.current_thumbnail)

    def remove_all_thumbnails(self):
        """Remove all thumbnails."""
        for thumbnail in self._thumbnails:
            self.layout().removeWidget(thumbnail)
            thumbnail.sig_canvas_clicked.disconnect()
            thumbnail.sig_remove_figure.disconnect()
            thumbnail.sig_save_figure.disconnect()
            thumbnail.deleteLater()
        self._thumbnails = []
        self.current_thumbnail = None
        self.figure_viewer.figcanvas.clear_canvas()

    def remove_thumbnail(self, thumbnail):
        """Remove thumbnail."""
        if thumbnail in self._thumbnails:
            index = self._thumbnails.index(thumbnail)
            self._thumbnails.remove(thumbnail)
        self.layout().removeWidget(thumbnail)
        thumbnail.deleteLater()
        thumbnail.sig_canvas_clicked.disconnect()
        thumbnail.sig_remove_figure.disconnect()
        thumbnail.sig_save_figure.disconnect()

        # Select a new thumbnail if any :
        if thumbnail == self.current_thumbnail:
            if len(self._thumbnails) > 0:
                self.set_current_index(min(index, len(self._thumbnails)-1))
            else:
                self.current_thumbnail = None
                self.figure_viewer.figcanvas.clear_canvas()

    def set_current_index(self, index):
        """Set the currently selected thumbnail by its index."""
        self.set_current_thumbnail(self._thumbnails[index])

    def get_current_index(self):
        """Return the index of the currently selected thumbnail."""
        try:
            return self._thumbnails.index(self.current_thumbnail)
        except ValueError:
            return -1

    def set_current_thumbnail(self, thumbnail):
        """Set the currently selected thumbnail."""
        self.current_thumbnail = thumbnail
        self.figure_viewer.load_figure(
                thumbnail.canvas.fig, thumbnail.canvas.fmt)
        for thumbnail in self._thumbnails:
            thumbnail.highlight_canvas(thumbnail == self.current_thumbnail)

    def go_previous_thumbnail(self):
        """Select the thumbnail previous to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) - 1
            index = index if index >= 0 else len(self._thumbnails) - 1
            self.set_current_index(index)
            self.scroll_to_item(index)

    def go_next_thumbnail(self):
        """Select thumbnail next to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) + 1
            index = 0 if index >= len(self._thumbnails) else index
            self.set_current_index(index)
            self.scroll_to_item(index)

    def scroll_to_item(self, index):
        """Scroll to the selected item of ThumbnailScrollBar."""
        spacing_between_items = self.scene.verticalSpacing()
        height_view = self.scrollarea.viewport().height()
        height_item = self.scene.itemAt(index).sizeHint().height()
        height_view_excluding_item = max(0, height_view - height_item)

        height_of_top_items = spacing_between_items
        for i in range(index):
            item = self.scene.itemAt(i)
            height_of_top_items += item.sizeHint().height()
            height_of_top_items += spacing_between_items

        pos_scroll = height_of_top_items - height_view_excluding_item // 2

        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(pos_scroll)

    # ---- ScrollBar Handlers
    def go_up(self):
        """Scroll the scrollbar of the scrollarea up by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() - vsb.singleStep()))

    def go_down(self):
        """Scroll the scrollbar of the scrollarea down by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() + vsb.singleStep()))
Exemple #7
0
class ThumbnailScrollBar(QFrame):
    """
    A widget that manages the display of the FigureThumbnails that are
    created when a figure is sent to the IPython console by the kernel and
    that controls what is displayed in the FigureViewer.
    """
    _min_scrollbar_width = 100

    # Signals
    sig_redirect_stdio_requested = Signal(bool)
    """
    This signal is emitted to request the main application to redirect
    standard output/error when using Open/Save/Browse dialogs within widgets.

    Parameters
    ----------
    redirect: bool
        Start redirect (True) or stop redirect (False).
    """

    sig_save_dir_changed = Signal(str)
    """
    This signal is emitted to inform that the current folder where images are
    saved has changed.

    Parameters
    ----------
    save_dir: str
        The new path where images are saved.
    """

    sig_context_menu_requested = Signal(QPoint, object)
    """
    This signal is emitted to request a context menu.

    Parameters
    ----------
    point: QPoint
        The QPoint in global coordinates where the menu was requested.
    """
    def __init__(self, figure_viewer, parent=None, background_color=None):
        super().__init__(parent)
        self._thumbnails = []

        self.background_color = background_color
        self.save_dir = getcwd_or_home()
        self.current_thumbnail = None
        self.set_figureviewer(figure_viewer)
        self.setup_gui()

        # Because the range of Qt scrollareas is not updated immediately
        # after a new item is added to it, setting the scrollbar's value
        # to its maximum value after adding a new item will scroll down to
        # the penultimate item instead of the last.
        # So to scroll programmatically to the latest item after it
        # is added to the scrollarea, we need to do it instead in a slot
        # connected to the scrollbar's rangeChanged signal.
        # See spyder-ide/spyder#10914 for more details.
        self._new_thumbnail_added = False
        self.scrollarea.verticalScrollBar().rangeChanged.connect(
            self._scroll_to_newest_item)

    def setup_gui(self):
        """Setup the main layout of the widget."""
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.setup_scrollarea())

    def setup_scrollarea(self):
        """Setup the scrollarea that will contain the FigureThumbnails."""
        self.view = QWidget()

        self.scene = QGridLayout(self.view)
        self.scene.setContentsMargins(0, 0, 0, 0)
        # The vertical spacing between the thumbnails.
        # Note that we need to set this value explicitly or else the tests
        # are failing on macOS. See spyder-ide/spyder#11576.
        self.scene.setSpacing(5)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidget(self.view)
        self.scrollarea.setWidgetResizable(True)
        self.scrollarea.setFrameStyle(0)
        self.scrollarea.setViewportMargins(2, 2, 2, 2)
        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setMinimumWidth(self._min_scrollbar_width)

        # Set the vertical scrollbar explicitly.
        # This is required to avoid a "RuntimeError: no access to protected
        # functions or signals for objects not created from Python" in Linux.
        self.scrollarea.setVerticalScrollBar(QScrollBar())

        # Install an event filter on the scrollbar.
        self.scrollarea.installEventFilter(self)

        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)

        return self.scrollarea

    def set_figureviewer(self, figure_viewer):
        """Set the namespace for the FigureViewer."""
        self.figure_viewer = figure_viewer

    def eventFilter(self, widget, event):
        """
        An event filter to trigger an update of the thumbnails size so that
        their width fit that of the scrollarea and to remap some key press
        events to mimick navigational behaviour of a Qt widget list.
        """
        if event.type() == QEvent.KeyPress:
            key = event.key()
            if key == Qt.Key_Up:
                self.go_previous_thumbnail()
                return True
            elif key == Qt.Key_Down:
                self.go_next_thumbnail()
                return True
        if event.type() == QEvent.Resize:
            self._update_thumbnail_size()
        return super().eventFilter(widget, event)

    # ---- Save Figure
    def save_all_figures_as(self):
        """Save all the figures to a file."""
        self.sig_redirect_stdio_requested.emit(False)
        dirname = getexistingdirectory(self, 'Save all figures', self.save_dir)
        self.sig_redirect_stdio_requested.emit(True)

        if dirname:
            self.sig_save_dir_changed.emit(dirname)
            return self.save_all_figures_todir(dirname)

    def save_all_figures_todir(self, dirname):
        """Save all figure in dirname."""
        fignames = []
        figname_root = ('Figure ' +
                        datetime.datetime.now().strftime('%Y-%m-%d %H%M%S'))
        for thumbnail in self._thumbnails:
            fig = thumbnail.canvas.fig
            fmt = thumbnail.canvas.fmt
            fext = {
                'image/png': '.png',
                'image/jpeg': '.jpg',
                'image/svg+xml': '.svg'
            }[fmt]

            figname = get_unique_figname(dirname,
                                         figname_root,
                                         fext,
                                         start_at_zero=True)
            save_figure_tofile(fig, fmt, figname)
            fignames.append(figname)
        return fignames

    def save_current_figure_as(self):
        """Save the currently selected figure."""
        if self.current_thumbnail is not None:
            self.save_figure_as(self.current_thumbnail.canvas.fig,
                                self.current_thumbnail.canvas.fmt)

    def save_thumbnail_figure_as(self, thumbnail):
        """Save the currently selected figure."""
        self.save_figure_as(thumbnail.canvas.fig, thumbnail.canvas.fmt)

    def save_figure_as(self, fig, fmt):
        """Save the figure to a file."""
        fext, ffilt = {
            'image/png': ('.png', 'PNG (*.png)'),
            'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'),
            'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)')
        }[fmt]

        figname = get_unique_figname(
            self.save_dir,
            'Figure ' + datetime.datetime.now().strftime('%Y-%m-%d %H%M%S'),
            fext)

        self.sig_redirect_stdio_requested.emit(False)
        fname, fext = getsavefilename(parent=self.parent(),
                                      caption='Save Figure',
                                      basedir=figname,
                                      filters=ffilt,
                                      selectedfilter='',
                                      options=None)
        self.sig_redirect_stdio_requested.emit(True)

        if fname:
            self.sig_save_dir_changed.emit(osp.dirname(fname))
            save_figure_tofile(fig, fmt, fname)

    # ---- Thumbails Handlers
    def _calculate_figure_canvas_width(self):
        """
        Calculate the width the thumbnails need to have to fit the scrollarea.
        """
        extra_padding = 10 if sys.platform == 'darwin' else 0
        figure_canvas_width = (
            self.scrollarea.width() - 2 * self.lineWidth() -
            self.scrollarea.viewportMargins().left() -
            self.scrollarea.viewportMargins().right() - extra_padding -
            self.scrollarea.verticalScrollBar().sizeHint().width())
        if is_dark_interface():
            # This is required to take into account some hard-coded padding
            # and margin in qdarkstyle.
            figure_canvas_width = figure_canvas_width - 6
        return figure_canvas_width

    def _setup_thumbnail_size(self, thumbnail):
        """
        Scale the thumbnail's canvas size so that it fits the thumbnail
        scrollbar's width.
        """
        max_canvas_size = self._calculate_figure_canvas_width()
        thumbnail.scale_canvas_size(max_canvas_size)

    def _update_thumbnail_size(self):
        """
        Update the thumbnails size so that their width fit that of
        the scrollarea.
        """
        # NOTE: We hide temporarily the thumbnails to prevent a repaint of
        # each thumbnail as soon as their size is updated in the loop, which
        # causes some flickering of the thumbnail scrollbar resizing animation.
        # Once the size of all the thumbnails has been updated, we show them
        # back so that they are repainted all at once instead of one after the
        # other. This is just a trick to make the resizing animation of the
        # thumbnail scrollbar look smoother.
        self.view.hide()
        for thumbnail in self._thumbnails:
            self._setup_thumbnail_size(thumbnail)
        self.view.show()

    def show_context_menu(self, point, thumbnail):
        """
        Emit global positioned point and thumbnail for context menu request.
        """
        point = thumbnail.canvas.mapToGlobal(point)
        self.sig_context_menu_requested.emit(point, thumbnail)

    def add_thumbnail(self, fig, fmt):
        """
        Add a new thumbnail to that thumbnail scrollbar.
        """
        thumbnail = FigureThumbnail(parent=self,
                                    background_color=self.background_color)
        thumbnail.canvas.load_figure(fig, fmt)
        thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
        thumbnail.sig_remove_figure_requested.connect(self.remove_thumbnail)
        thumbnail.sig_save_figure_requested.connect(self.save_figure_as)
        thumbnail.sig_context_menu_requested.connect(
            lambda point: self.show_context_menu(point, thumbnail))
        self._thumbnails.append(thumbnail)
        self._new_thumbnail_added = True

        self.scene.setRowStretch(self.scene.rowCount() - 1, 0)
        self.scene.addWidget(thumbnail, self.scene.rowCount() - 1, 0)
        self.scene.setRowStretch(self.scene.rowCount(), 100)
        self.set_current_thumbnail(thumbnail)

        thumbnail.show()
        self._setup_thumbnail_size(thumbnail)

    def remove_current_thumbnail(self):
        """Remove the currently selected thumbnail."""
        if self.current_thumbnail is not None:
            self.remove_thumbnail(self.current_thumbnail)

    def remove_all_thumbnails(self):
        """Remove all thumbnails."""
        for thumbnail in self._thumbnails:
            thumbnail.sig_canvas_clicked.disconnect()
            thumbnail.sig_remove_figure_requested.disconnect()
            thumbnail.sig_save_figure_requested.disconnect()
            self.layout().removeWidget(thumbnail)
            thumbnail.setParent(None)
            thumbnail.hide()
            thumbnail.close()

        self._thumbnails = []
        self.current_thumbnail = None
        self.figure_viewer.figcanvas.clear_canvas()

    def remove_thumbnail(self, thumbnail):
        """Remove thumbnail."""
        if thumbnail in self._thumbnails:
            index = self._thumbnails.index(thumbnail)

        # Disconnect signals
        try:
            thumbnail.sig_canvas_clicked.disconnect()
            thumbnail.sig_remove_figure_requested.disconnect()
            thumbnail.sig_save_figure_requested.disconnect()
        except TypeError:
            pass

        if thumbnail in self._thumbnails:
            self._thumbnails.remove(thumbnail)

        # Select a new thumbnail if any :
        if thumbnail == self.current_thumbnail:
            if len(self._thumbnails) > 0:
                self.set_current_index(min(index, len(self._thumbnails) - 1))
            else:
                self.figure_viewer.figcanvas.clear_canvas()
                self.current_thumbnail = None

        # Hide and close thumbnails
        self.layout().removeWidget(thumbnail)
        thumbnail.hide()
        thumbnail.close()

        # See: spyder-ide/spyder#12459
        QTimer.singleShot(150,
                          lambda: self._remove_thumbnail_parent(thumbnail))

    def _remove_thumbnail_parent(self, thumbnail):
        try:
            thumbnail.setParent(None)
        except RuntimeError:
            # Omit exception in case the thumbnail has been garbage-collected
            pass

    def set_current_index(self, index):
        """Set the currently selected thumbnail by its index."""
        self.set_current_thumbnail(self._thumbnails[index])

    def get_current_index(self):
        """Return the index of the currently selected thumbnail."""
        try:
            return self._thumbnails.index(self.current_thumbnail)
        except ValueError:
            return -1

    def set_current_thumbnail(self, thumbnail):
        """Set the currently selected thumbnail."""
        self.current_thumbnail = thumbnail
        self.figure_viewer.load_figure(thumbnail.canvas.fig,
                                       thumbnail.canvas.fmt)
        for thumbnail in self._thumbnails:
            thumbnail.highlight_canvas(thumbnail == self.current_thumbnail)

    def go_previous_thumbnail(self):
        """Select the thumbnail previous to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) - 1
            index = index if index >= 0 else len(self._thumbnails) - 1
            self.set_current_index(index)
            self.scroll_to_item(index)

    def go_next_thumbnail(self):
        """Select thumbnail next to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) + 1
            index = 0 if index >= len(self._thumbnails) else index
            self.set_current_index(index)
            self.scroll_to_item(index)

    def scroll_to_item(self, index):
        """Scroll to the selected item of ThumbnailScrollBar."""
        spacing_between_items = self.scene.verticalSpacing()
        height_view = self.scrollarea.viewport().height()
        height_item = self.scene.itemAt(index).sizeHint().height()
        height_view_excluding_item = max(0, height_view - height_item)

        height_of_top_items = spacing_between_items
        for i in range(index):
            item = self.scene.itemAt(i)
            height_of_top_items += item.sizeHint().height()
            height_of_top_items += spacing_between_items

        pos_scroll = height_of_top_items - height_view_excluding_item // 2

        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(pos_scroll)

    def _scroll_to_newest_item(self, vsb_min, vsb_max):
        """
        Scroll to the newest item added to the thumbnail scrollbar.

        Note that this method is called each time the rangeChanged signal
        is emitted by the scrollbar.
        """
        if self._new_thumbnail_added:
            self._new_thumbnail_added = False
            self.scrollarea.verticalScrollBar().setValue(vsb_max)

    # ---- ScrollBar Handlers
    def go_up(self):
        """Scroll the scrollbar of the scrollarea up by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() - vsb.singleStep()))

    def go_down(self):
        """Scroll the scrollbar of the scrollarea down by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() + vsb.singleStep()))
Exemple #8
0
    def setup_ui(self):
        # Create the main layout
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        #main_layout.setMargin(0)
        #main_layout.setSpacing(0)
        self.setLayout(main_layout)

        # Create a Label to be the title
        lbl_title = QLabel("Motors Diagnostic")
        # Add some StyleSheet to it
        lbl_title.setStyleSheet("\
            QLabel {\
                qproperty-alignment: AlignCenter;\
                border: 1px solid #FF17365D;\
                border-top-left-radius: 15px;\
                border-top-right-radius: 15px;\
                background-color: #FF17365D;\
                padding: 5px 0px;\
                color: rgb(255, 255, 255);\
                max-height: 25px;\
                font-size: 14px;\
            }")

        # Add the title label to the main layout
        main_layout.addWidget(lbl_title)

        # Create the Search Panel layout
        search_layout = QHBoxLayout()
        search_layout.setContentsMargins(0, 0, 0, 0)
        #search_layout.setMargin(0)
        #search_layout.setSpacing(0)

        # Create a GroupBox with "Filtering" as Title
        #gb_search = QGroupBox(parent=self)
        #gb_search.setTitle("Filtering")
        #gb_search.setLayout(search_layout)

        gb_search = QWidget()
        gb_search.setLayout(search_layout)

        # Create a label, line edit and button for filtering
        lbl_search = QLabel(text="Filter: ")
        self.txt_filter = QLineEdit()
        self.txt_filter.returnPressed.connect(self.do_search)
        btn_search = QPushButton()
        btn_search.setText("Search")
        btn_search.clicked.connect(self.do_search)

        # Add the created widgets to the layout
        search_layout.addWidget(lbl_search)
        search_layout.addWidget(self.txt_filter)
        search_layout.addWidget(btn_search)

        # Add the Groupbox to the main layout
        main_layout.addWidget(gb_search)

        # Create the Results Layout
        self.results_layout = QVBoxLayout()
        self.results_layout.setContentsMargins(0, 0, 0, 0)
        #self.results_layout.setMargin(0)
        #self.results_layout.setSpacing(0)

        # Create a Frame to host the results of search
        self.frm_result = QFrame(parent=self)
        self.frm_result.setLayout(self.results_layout)

        # Create a ScrollArea so we can properly handle
        # many entries
        scroll_area = QScrollArea(parent=self)
        scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        scroll_area.setWidgetResizable(True)

        # Add the Frame to the scroll area
        scroll_area.setWidget(self.frm_result)

        # Add the scroll area to the main layout
        main_layout.addWidget(scroll_area)
Exemple #9
0
    def _setupUi(self):
        # status
        label_status = QLabel('Status: ',
                              self,
                              alignment=Qt.AlignRight | Qt.AlignVCenter)
        channels2values = {
            self.device.substitute(propty='ForceComplete-Mon'): 1,
            self.device.substitute(propty='NegativeDoneMov-Mon'): 1,
            self.device.substitute(propty='PositiveDoneMov-Mon'): 1
        }
        self.multiled_status = PyDMLedMultiChannel(self, channels2values)
        self.multiled_status.setStyleSheet('max-width: 1.29em;')

        self.pb_details = QPushButton(qta.icon('fa5s.ellipsis-h'), '', self)
        self.pb_details.setToolTip('Open details')
        self.pb_details.setObjectName('detail')
        self.pb_details.setStyleSheet(
            "#detail{min-width:25px; max-width:25px; icon-size:20px;}")
        util.connect_window(self.pb_details,
                            _DiffCtrlDetails,
                            parent=self,
                            prefix=self.prefix,
                            device=self.device)

        self.lb_descCtrl1 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl1 = PyDMSpinbox(self)
        self.sb_Ctrl1.showStepExponent = False
        self.lb_Ctrl1 = PyDMLabel(self)
        self.lb_descCtrl2 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl2 = PyDMSpinbox(self)
        self.sb_Ctrl2.showStepExponent = False
        self.lb_Ctrl2 = PyDMLabel(self)

        self.pb_open = PyDMPushButton(
            parent=self,
            label='Open',
            pressValue=1,
            init_channel=self.device.substitute(propty='Home-Cmd'))

        tmp_file = _substitute_in_file(
            _os.path.abspath(_os.path.dirname(__file__)) + '/ui_as_ap_dev' +
            self.orientation.lower() + 'mon.ui', {'PREFIX': self.prefix})
        self.dev_widget = loadUi(tmp_file)
        self.dev_widget.setObjectName('dev')
        self.dev_widget_scrarea = QScrollArea()
        self.dev_widget_scrarea.setObjectName('scrarea')
        self.dev_widget_scrarea.setStyleSheet(
            '#scrarea{background-color: transparent; max-width: 15em;}'
            '#dev{background-color:transparent;}')
        self.dev_widget_scrarea.setWidget(self.dev_widget)

        lay = QGridLayout(self)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(label_status, 0, 0)
        lay.addWidget(self.multiled_status, 0, 1)
        lay.addWidget(self.pb_details, 0, 2, alignment=Qt.AlignRight)
        lay.addWidget(self.lb_descCtrl1, 1, 0)
        lay.addWidget(self.sb_Ctrl1, 1, 1)
        lay.addWidget(self.lb_Ctrl1, 1, 2)
        lay.addWidget(self.lb_descCtrl2, 2, 0)
        lay.addWidget(self.sb_Ctrl2, 2, 1)
        lay.addWidget(self.lb_Ctrl2, 2, 2)
        lay.addWidget(self.pb_open, 3, 1, 1, 2)
        lay.addWidget(self.dev_widget_scrarea, 0, 3, 4, 1)
    def setUpWindows(self):
        object_id, time_list = self.light_curves.object_info(self.it_object)
        _id = '{0}_{1}'.format(object_id, self.it_object)
        current_label = self.doc_label.find_object(_id)

        position = 1
        # espaceJourMinRequis = 1.0
        prev_time = time_list[0]
        line_num = 1
        process_list = [0]
        for i in range(0, len(time_list)):
            if prev_time + self.time_interval < time_list[i]:
                line_num += 1
                prev_time = time_list[i]
                process_list.append(i)

        if hasattr(self, 'bigLayout'):
            self.clearLayout(self.bigLayout)
        else:
            self.bigLayout = QVBoxLayout(self)

        self.scrollArea = QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)
        self.scrollAreaWidgetContents = QWidget()
        rect = QRect(0, 0, 800, line_num * self.IMAGE_SIZE)
        self.scrollAreaWidgetContents.setGeometry(rect)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)

        self.add_button = QPushButton("A, Q, S :::: Label=" +
                                      str(current_label))

        self.bigLayout.addWidget(self.add_button)
        obj_type = "Object type: {0}".format(self.labels[object_id])
        self.bigLayout.addWidget(QLabel(obj_type, self))
        self.bigLayout.addWidget(self.scrollArea)

        self.layoutVertical = QHBoxLayout(self.scrollAreaWidgetContents)

        self.layoutGauche = QVBoxLayout(None)
        self.layoutDroit = QVBoxLayout(None)
        self.layoutVertical.addLayout(self.layoutGauche)
        self.layoutVertical.addLayout(self.layoutDroit)

        err = 0
        for i in process_list:
            nom = '{0}-{1}.fits'.format(object_id, i)
            prefix_cal_path = self.cal_path + nom
            prefix_diff_path = self.diff_path + nom

            print(str(object_id), "  :::: ", prefix_cal_path)
            layout_tmp = QHBoxLayout(None)

            path_1 = osp.exists(prefix_cal_path)
            path_2 = osp.exists(prefix_diff_path)
            if not (path_1 or path_2):
                err_msg = "File could not be found {0}".format(nom)
                err = 1
                print(err_msg, file=sys.stderr)

            if err == 0:
                widget_cal_tmp = MatplotlibWidget(self.IMAGE_SIZE,
                                                  self.IMAGE_SIZE, None)
                axe = widget_cal_tmp.axes
                img1 = ImgFit(prefix_cal_path)
                object_curves = self.light_curves.times[self.it_object]
                light_curve = object_curves[time_list[i]]
                img1.draw(axe, "Cal t = {0}".format(light_curve))

                widget_dif_tmp = MatplotlibWidget(self.IMAGE_SIZE,
                                                  self.IMAGE_SIZE,
                                                  _id=i)
                widget_dif_tmp.sig_focus_img.connect(self.img_focus)
                widget_dif_tmp.sig_leave_img.connect(self.reset_graph)
                axe = widget_dif_tmp.axes
                img2 = ImgFit(prefix_diff_path)
                img2.draw(axe, "Diff t = {0}".format(light_curve))
                position = position + 2

                layout_tmp.addWidget(widget_cal_tmp)
                layout_tmp.addWidget(widget_dif_tmp)

                btn_layout = QHBoxLayout()
                is_transient_btn = QPushButton('Yes', self)
                not_transient_btn = QPushButton('No', self)

                # img_id = int(i)
                print(nom)

                def btn_closure(idx, state):
                    return lambda: self.label_img(idx, state)

                self.img_labels[nom] = False
                is_transient_btn.clicked.connect(btn_closure(nom, True))
                not_transient_btn.clicked.connect(btn_closure(nom, False))

                btn_layout.addWidget(is_transient_btn)
                btn_layout.addWidget(not_transient_btn)

                general_layout = QVBoxLayout()
                general_layout.addLayout(layout_tmp)
                general_layout.addLayout(btn_layout)
                self.layoutGauche.addLayout(general_layout)

        widget_CL_tmp = MatplotlibWidget(400, 400, None)
        self.light_curves_plot = widget_CL_tmp
        axe = widget_CL_tmp.axes
        self.light_curves.draw(axe, self.it_object)
        self.layoutDroit.addWidget(widget_CL_tmp)
        self.layoutDroit.addWidget(QLabel(""))

        if err == 1:
            self.passeImage()
Exemple #11
0
    def setup_page(self):
        if ERR_MSG:
            label = QLabel(_("Could not load plugin:\n{0}".format(ERR_MSG)))
            layout = QVBoxLayout()
            layout.addWidget(label)
            self.setLayout(layout)
            return

        # Layout parameter
        indent = QCheckBox().sizeHint().width()

        # General options
        options_group = QGroupBox(_("Options"))
        # Hack : the spinbox widget will be added to self.spinboxes
        spinboxes_before = set(self.spinboxes)
        passes_spin = self.create_spinbox(_("Number of pep8 passes: "),
                                          "",
                                          'passes',
                                          default=0,
                                          min_=0,
                                          max_=1000000,
                                          step=1)
        spinbox = set(self.spinboxes) - spinboxes_before
        spinbox = spinbox.pop()
        spinbox.setSpecialValueText(_("Infinite"))
        aggressive1_checkbox = self.create_checkbox("Aggressivity level 1",
                                                    "aggressive1",
                                                    default=False)
        aggressive1_label = QLabel(
            _("Allow possibly unsafe fixes (E711 and W6), shorten lines"
              " and remove trailing whitespace more aggressively (in"
              " docstrings and multiline strings)."))
        aggressive1_label.setWordWrap(True)
        aggressive1_label.setIndent(indent)
        font_description = aggressive1_label.font()
        font_description.setPointSizeF(font_description.pointSize() * 0.9)
        aggressive1_label.setFont(font_description)
        aggressive2_checkbox = self.create_checkbox("Aggressivity level 2",
                                                    "aggressive2",
                                                    default=False)
        aggressive2_label = QLabel(
            _("Allow more possibly unsafe fixes (E712) and shorten lines."))
        aggressive2_label.setWordWrap(True)
        aggressive2_label.setIndent(indent)
        aggressive2_label.setFont(font_description)

        aggressive1_checkbox.toggled.connect(aggressive2_checkbox.setEnabled)
        aggressive1_checkbox.toggled.connect(aggressive2_label.setEnabled)
        aggressive2_checkbox.setEnabled(aggressive1_checkbox.isChecked())
        aggressive2_label.setEnabled(aggressive1_checkbox.isChecked())

        # Enable/disable error codes
        fix_layout = QVBoxLayout()
        last_group = ""
        FIX_LIST.sort(key=lambda item: item[0][1])
        for code, description in FIX_LIST:
            # Create a new group if necessary
            if code[1] != last_group:
                last_group = code[1]
                group = QGroupBox(_(self.GROUPS.get(code[1], "")))
                fix_layout.addWidget(group)
                group_layout = QVBoxLayout(group)

            # Checkbox for the option
            text = code
            default = True
            if code in DEFAULT_IGNORE:
                text += _(" (UNSAFE)")
                default = False
            option = self.create_checkbox(text, code, default=default)

            # Label for description
            if code in self.CODES:
                label = QLabel("{autopep8} ({pep8}).".format(
                    autopep8=_(description).rstrip("."),
                    pep8=self.CODES[code]))
            else:
                label = QLabel(_(description))
            label.setWordWrap(True)
            label.setIndent(indent)
            label.setFont(font_description)

            # Add widgets to layout
            option_layout = QVBoxLayout()
            option_layout.setSpacing(0)
            option_layout.addWidget(option)
            option_layout.addWidget(label)
            group_layout.addLayout(option_layout)

            # Special cases
            if code in ("E711", "W6"):
                aggressive1_checkbox.toggled.connect(option.setEnabled)
                aggressive1_checkbox.toggled.connect(label.setEnabled)
                option.setEnabled(aggressive1_checkbox.isChecked())
                label.setEnabled(aggressive1_checkbox.isChecked())
            if code == "E712":

                def e712_enabled():
                    enabled = (aggressive1_checkbox.isChecked()
                               and aggressive2_checkbox.isChecked())
                    option.setEnabled(enabled)
                    label.setEnabled(enabled)

                aggressive1_checkbox.toggled.connect(e712_enabled)
                aggressive2_checkbox.toggled.connect(e712_enabled)
                e712_enabled()

        # General layout
        aggressive2_layout = QVBoxLayout()
        margins = aggressive2_layout.contentsMargins()
        margins.setLeft(indent)
        aggressive2_layout.setContentsMargins(margins)
        aggressive2_layout.addWidget(aggressive2_checkbox)
        aggressive2_layout.addWidget(aggressive2_label)

        options_layout = QVBoxLayout()
        options_layout.addWidget(passes_spin)
        options_layout.addWidget(aggressive1_checkbox)
        options_layout.addWidget(aggressive1_label)
        options_layout.addLayout(aggressive2_layout)
        options_group.setLayout(options_layout)

        widget_scroll = QWidget()
        widget_scroll.setLayout(fix_layout)
        fix_scroll = QScrollArea()
        fix_scroll.setWidget(widget_scroll)
        fix_scroll.setWidgetResizable(True)
        fig_out_layout = QVBoxLayout()
        fig_out_layout.addWidget(fix_scroll, 1)
        fix_group = QGroupBox(_("Errors/warnings to fix"))
        fix_group.setLayout(fig_out_layout)

        vlayout = QVBoxLayout()
        vlayout.addWidget(options_group)
        vlayout.addWidget(fix_group, 1)
        self.setLayout(vlayout)
Exemple #12
0
class ThumbnailScrollBar(QFrame):
    """
    A widget that manages the display of the FigureThumbnails that are
    created when a figure is sent to the IPython console by the kernel and
    that controls what is displayed in the FigureViewer.
    """
    redirect_stdio = Signal(bool)
    _min_scrollbar_width = 100

    def __init__(self, figure_viewer, parent=None, background_color=None):
        super(ThumbnailScrollBar, self).__init__(parent)
        self._thumbnails = []

        self.background_color = background_color
        self.current_thumbnail = None
        self.set_figureviewer(figure_viewer)
        self.setup_gui()

        # Because the range of Qt scrollareas is not updated immediately
        # after a new item is added to it, setting the scrollbar's value
        # to its maximum value after adding a new item will scroll down to
        # the penultimate item instead of the last.
        # So to scroll programmatically to the latest item after it
        # is added to the scrollarea, we need to do it instead in a slot
        # connected to the scrollbar's rangeChanged signal.
        # See spyder-ide/#10914 for more details.
        self._new_thumbnail_added = False
        self.scrollarea.verticalScrollBar().rangeChanged.connect(
            self._scroll_to_newest_item)

    def setup_gui(self):
        """Setup the main layout of the widget."""
        scrollarea = self.setup_scrollarea()
        up_btn, down_btn = self.setup_arrow_buttons()

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(up_btn)
        layout.addWidget(scrollarea)
        layout.addWidget(down_btn)

    def setup_scrollarea(self):
        """Setup the scrollarea that will contain the FigureThumbnails."""
        self.view = QWidget()

        self.scene = QGridLayout(self.view)
        self.scene.setContentsMargins(0, 0, 0, 0)
        self.scene.setSpacing(3)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidget(self.view)
        self.scrollarea.setWidgetResizable(True)
        self.scrollarea.setFrameStyle(0)
        self.scrollarea.setViewportMargins(2, 2, 2, 2)
        self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setMinimumWidth(self._min_scrollbar_width)

        # Set the vertical scrollbar explicitely.
        # This is required to avoid a "RuntimeError: no access to protected
        # functions or signals for objects not created from Python" in Linux.
        self.scrollarea.setVerticalScrollBar(QScrollBar())

        # Install an event filter on the scrollbar.
        self.scrollarea.installEventFilter(self)

        return self.scrollarea

    def setup_arrow_buttons(self):
        """
        Setup the up and down arrow buttons that are placed at the top and
        bottom of the scrollarea.
        """
        # Get the size hint height of the horizontal scrollbar.
        height = self.scrollarea.horizontalScrollBar().sizeHint().height()

        # Setup the up and down arrow button.
        up_btn = up_btn = QPushButton(icon=ima.icon('last_edit_location'))
        up_btn.setFlat(True)
        up_btn.setFixedHeight(height)
        up_btn.clicked.connect(self.go_up)

        down_btn = QPushButton(icon=ima.icon('folding.arrow_down_on'))
        down_btn.setFlat(True)
        down_btn.setFixedHeight(height)
        down_btn.clicked.connect(self.go_down)

        return up_btn, down_btn

    def set_figureviewer(self, figure_viewer):
        """Set the bamespace for the FigureViewer."""
        self.figure_viewer = figure_viewer

    def eventFilter(self, widget, event):
        """
        An event filter to trigger an update of the thumbnails size so that
        their width fit that of the scrollarea.
        """
        if event.type() == QEvent.Resize:
            self._update_thumbnail_size()
        return super(ThumbnailScrollBar, self).eventFilter(widget, event)

    # ---- Save Figure
    def save_all_figures_as(self):
        """Save all the figures to a file."""
        self.redirect_stdio.emit(False)
        dirname = getexistingdirectory(self,
                                       caption='Save all figures',
                                       basedir=getcwd_or_home())
        self.redirect_stdio.emit(True)
        if dirname:
            return self.save_all_figures_todir(dirname)

    def save_all_figures_todir(self, dirname):
        """Save all figure in dirname."""
        fignames = []
        for thumbnail in self._thumbnails:
            fig = thumbnail.canvas.fig
            fmt = thumbnail.canvas.fmt
            fext = {
                'image/png': '.png',
                'image/jpeg': '.jpg',
                'image/svg+xml': '.svg'
            }[fmt]

            figname = get_unique_figname(dirname, 'Figure', fext)
            save_figure_tofile(fig, fmt, figname)
            fignames.append(figname)
        return fignames

    def save_current_figure_as(self):
        """Save the currently selected figure."""
        if self.current_thumbnail is not None:
            self.save_figure_as(self.current_thumbnail.canvas.fig,
                                self.current_thumbnail.canvas.fmt)

    def save_figure_as(self, fig, fmt):
        """Save the figure to a file."""
        fext, ffilt = {
            'image/png': ('.png', 'PNG (*.png)'),
            'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'),
            'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)')
        }[fmt]

        figname = get_unique_figname(getcwd_or_home(), 'Figure', fext)

        self.redirect_stdio.emit(False)
        fname, fext = getsavefilename(parent=self.parent(),
                                      caption='Save Figure',
                                      basedir=figname,
                                      filters=ffilt,
                                      selectedfilter='',
                                      options=None)
        self.redirect_stdio.emit(True)

        if fname:
            save_figure_tofile(fig, fmt, fname)

    # ---- Thumbails Handlers
    def _calculate_figure_canvas_width(self, thumbnail):
        """
        Calculate the witdh the thumbnail's figure canvas need to have for the
        thumbnail to fit the scrollarea.
        """
        extra_padding = 10 if sys.platform == 'darwin' else 0
        figure_canvas_width = (self.scrollarea.width() - 2 * self.lineWidth() -
                               self.scrollarea.viewportMargins().left() -
                               self.scrollarea.viewportMargins().right() -
                               thumbnail.savefig_btn.width() -
                               thumbnail.layout().spacing() - extra_padding)
        if is_dark_interface():
            # This is required to take into account some hard-coded padding
            # and margin in qdarkstyle.
            figure_canvas_width = figure_canvas_width - 6
        return figure_canvas_width

    def _setup_thumbnail_size(self, thumbnail):
        """
        Scale the thumbnail's canvas size so that it fits the thumbnail
        scrollbar's width.
        """
        max_canvas_size = self._calculate_figure_canvas_width(thumbnail)
        thumbnail.scale_canvas_size(max_canvas_size)

    def _update_thumbnail_size(self):
        """
        Update the thumbnails size so that their width fit that of
        the scrollarea.
        """
        # NOTE: We hide temporarily the thumbnails to prevent a repaint of
        # each thumbnail as soon as their size is updated in the loop, which
        # causes some flickering of the thumbnail scrollbar resizing animation.
        # Once the size of all the thumbnails has been updated, we show them
        # back so that they are repainted all at once instead of one after the
        # other. This is just a trick to make the resizing animation of the
        # thumbnail scrollbar look smoother.
        self.view.hide()
        for thumbnail in self._thumbnails:
            self._setup_thumbnail_size(thumbnail)
        self.view.show()

    def add_thumbnail(self, fig, fmt):
        """
        Add a new thumbnail to that thumbnail scrollbar.
        """
        thumbnail = FigureThumbnail(parent=self,
                                    background_color=self.background_color)
        thumbnail.canvas.load_figure(fig, fmt)
        thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
        thumbnail.sig_remove_figure.connect(self.remove_thumbnail)
        thumbnail.sig_save_figure.connect(self.save_figure_as)
        self._thumbnails.append(thumbnail)
        self._new_thumbnail_added = True

        self.scene.setRowStretch(self.scene.rowCount() - 1, 0)
        self.scene.addWidget(thumbnail, self.scene.rowCount() - 1, 0)
        self.scene.setRowStretch(self.scene.rowCount(), 100)
        self.set_current_thumbnail(thumbnail)

        thumbnail.show()
        self._setup_thumbnail_size(thumbnail)

    def remove_current_thumbnail(self):
        """Remove the currently selected thumbnail."""
        if self.current_thumbnail is not None:
            self.remove_thumbnail(self.current_thumbnail)

    def remove_all_thumbnails(self):
        """Remove all thumbnails."""
        for thumbnail in self._thumbnails:
            self.layout().removeWidget(thumbnail)
            thumbnail.sig_canvas_clicked.disconnect()
            thumbnail.sig_remove_figure.disconnect()
            thumbnail.sig_save_figure.disconnect()
            thumbnail.setParent(None)
        self._thumbnails = []
        self.current_thumbnail = None
        self.figure_viewer.figcanvas.clear_canvas()

    def remove_thumbnail(self, thumbnail):
        """Remove thumbnail."""
        if thumbnail in self._thumbnails:
            index = self._thumbnails.index(thumbnail)
            self._thumbnails.remove(thumbnail)
        self.layout().removeWidget(thumbnail)
        thumbnail.setParent(None)
        thumbnail.sig_canvas_clicked.disconnect()
        thumbnail.sig_remove_figure.disconnect()
        thumbnail.sig_save_figure.disconnect()

        # Select a new thumbnail if any :
        if thumbnail == self.current_thumbnail:
            if len(self._thumbnails) > 0:
                self.set_current_index(min(index, len(self._thumbnails) - 1))
            else:
                self.current_thumbnail = None
                self.figure_viewer.figcanvas.clear_canvas()

    def set_current_index(self, index):
        """Set the currently selected thumbnail by its index."""
        self.set_current_thumbnail(self._thumbnails[index])

    def get_current_index(self):
        """Return the index of the currently selected thumbnail."""
        try:
            return self._thumbnails.index(self.current_thumbnail)
        except ValueError:
            return -1

    def set_current_thumbnail(self, thumbnail):
        """Set the currently selected thumbnail."""
        self.current_thumbnail = thumbnail
        self.figure_viewer.load_figure(thumbnail.canvas.fig,
                                       thumbnail.canvas.fmt)
        for thumbnail in self._thumbnails:
            thumbnail.highlight_canvas(thumbnail == self.current_thumbnail)

    def go_previous_thumbnail(self):
        """Select the thumbnail previous to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) - 1
            index = index if index >= 0 else len(self._thumbnails) - 1
            self.set_current_index(index)
            self.scroll_to_item(index)

    def go_next_thumbnail(self):
        """Select thumbnail next to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) + 1
            index = 0 if index >= len(self._thumbnails) else index
            self.set_current_index(index)
            self.scroll_to_item(index)

    def scroll_to_item(self, index):
        """Scroll to the selected item of ThumbnailScrollBar."""
        spacing_between_items = self.scene.verticalSpacing()
        height_view = self.scrollarea.viewport().height()
        height_item = self.scene.itemAt(index).sizeHint().height()
        height_view_excluding_item = max(0, height_view - height_item)

        height_of_top_items = spacing_between_items
        for i in range(index):
            item = self.scene.itemAt(i)
            height_of_top_items += item.sizeHint().height()
            height_of_top_items += spacing_between_items

        pos_scroll = height_of_top_items - height_view_excluding_item // 2

        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(pos_scroll)

    def _scroll_to_newest_item(self, vsb_min, vsb_max):
        """
        Scroll to the newest item added to the thumbnail scrollbar.

        Note that this method is called each time the rangeChanged signal
        is emitted by the scrollbar.
        """
        if self._new_thumbnail_added:
            self._new_thumbnail_added = False
            self.scrollarea.verticalScrollBar().setValue(vsb_max)

    # ---- ScrollBar Handlers
    def go_up(self):
        """Scroll the scrollbar of the scrollarea up by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() - vsb.singleStep()))

    def go_down(self):
        """Scroll the scrollbar of the scrollarea down by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() + vsb.singleStep()))
class MainWindow(QWidget):
    IMAGE_SIZE = 180

    def __init__(self, light_curves, it_object, doc_label, cal_path, diff_path,
                 time_interval, labels):
        super(MainWindow, self).__init__(None)
        self.doc_label = doc_label
        self.light_curves = light_curves
        self.it_object = it_object
        self.cal_path = cal_path
        self.diff_path = diff_path
        self.time_interval = time_interval
        self.cur_img = None
        self.img_labels = {}
        self.labels = labels
        self.save_file = open(args.save, 'a+')
        self.save_file.write('obj_file,label\n')
        if hasattr(self, 'bigLayout'):
            self.clearLayout(self.bigLayout)
        self.setUpWindows()

    def keyPressEvent(self, event):
        key = event.key()
        if key == Qt.Key_A:
            self.passeImage()
        if key == Qt.Key_Q:
            self.it_object = self.it_object - 2
            self.passeImage()
        if key == Qt.Key_S:
            self.doc_label.save()

        label = -1
        if key == Qt.Key_W:
            label = 0
        if key == Qt.Key_X:
            label = 1
        if key == Qt.Key_C:
            label = 2
        if label != -1:
            object_id, time_list = self.light_curves.object_info(
                self.it_object)
            _id = '{0}_{1}'.format(object_id, self.it_object)
            self.doc_label.add_object(_id, str(label))
            self.doc_label.save()
            self.add_button.setText("A, Q, S :::: Label=" + str(label))
            self.passeImage()

        label = -1
        if key == Qt.Key_K:
            label = 0
        if key == Qt.Key_L:
            label = 1
        if key == Qt.Key_M:
            label = 2
        if label != -1:
            object_id, time_list = self.light_curves.object_info(
                self.it_object)
            _id = '{0}_{1}'.format(object_id, self.it_object)
            self.doc_label.change_object(_id, str(label))
            self.doc_label.save()
            self.add_button.setText("A, Q, S :::: Label=" + str(label))
            self.passeImage()

    def passeImage(self):
        self.write_labels()
        self.img_labels = {}
        exists = False
        while not exists:
            self.it_object = self.it_object + 1
            try:
                self.setUpWindows()
            except Exception:
                continue
            break

    def write_labels(self):
        for obj in self.img_labels:
            self.save_file.write('{0},{1}\n'.format(obj,
                                                    int(self.img_labels[obj])))

    def clearLayout(self, layout):
        while layout.count():
            child = layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            elif child.layout() is not None:
                self.clearLayout(child.layout())

    def setUpWindows(self):
        object_id, time_list = self.light_curves.object_info(self.it_object)
        _id = '{0}_{1}'.format(object_id, self.it_object)
        current_label = self.doc_label.find_object(_id)

        position = 1
        # espaceJourMinRequis = 1.0
        prev_time = time_list[0]
        line_num = 1
        process_list = [0]
        for i in range(0, len(time_list)):
            if prev_time + self.time_interval < time_list[i]:
                line_num += 1
                prev_time = time_list[i]
                process_list.append(i)

        if hasattr(self, 'bigLayout'):
            self.clearLayout(self.bigLayout)
        else:
            self.bigLayout = QVBoxLayout(self)

        self.scrollArea = QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)
        self.scrollAreaWidgetContents = QWidget()
        rect = QRect(0, 0, 800, line_num * self.IMAGE_SIZE)
        self.scrollAreaWidgetContents.setGeometry(rect)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)

        self.add_button = QPushButton("A, Q, S :::: Label=" +
                                      str(current_label))

        self.bigLayout.addWidget(self.add_button)
        obj_type = "Object type: {0}".format(self.labels[object_id])
        self.bigLayout.addWidget(QLabel(obj_type, self))
        self.bigLayout.addWidget(self.scrollArea)

        self.layoutVertical = QHBoxLayout(self.scrollAreaWidgetContents)

        self.layoutGauche = QVBoxLayout(None)
        self.layoutDroit = QVBoxLayout(None)
        self.layoutVertical.addLayout(self.layoutGauche)
        self.layoutVertical.addLayout(self.layoutDroit)

        err = 0
        for i in process_list:
            nom = '{0}-{1}.fits'.format(object_id, i)
            prefix_cal_path = self.cal_path + nom
            prefix_diff_path = self.diff_path + nom

            print(str(object_id), "  :::: ", prefix_cal_path)
            layout_tmp = QHBoxLayout(None)

            path_1 = osp.exists(prefix_cal_path)
            path_2 = osp.exists(prefix_diff_path)
            if not (path_1 or path_2):
                err_msg = "File could not be found {0}".format(nom)
                err = 1
                print(err_msg, file=sys.stderr)

            if err == 0:
                widget_cal_tmp = MatplotlibWidget(self.IMAGE_SIZE,
                                                  self.IMAGE_SIZE, None)
                axe = widget_cal_tmp.axes
                img1 = ImgFit(prefix_cal_path)
                object_curves = self.light_curves.times[self.it_object]
                light_curve = object_curves[time_list[i]]
                img1.draw(axe, "Cal t = {0}".format(light_curve))

                widget_dif_tmp = MatplotlibWidget(self.IMAGE_SIZE,
                                                  self.IMAGE_SIZE,
                                                  _id=i)
                widget_dif_tmp.sig_focus_img.connect(self.img_focus)
                widget_dif_tmp.sig_leave_img.connect(self.reset_graph)
                axe = widget_dif_tmp.axes
                img2 = ImgFit(prefix_diff_path)
                img2.draw(axe, "Diff t = {0}".format(light_curve))
                position = position + 2

                layout_tmp.addWidget(widget_cal_tmp)
                layout_tmp.addWidget(widget_dif_tmp)

                btn_layout = QHBoxLayout()
                is_transient_btn = QPushButton('Yes', self)
                not_transient_btn = QPushButton('No', self)

                # img_id = int(i)
                print(nom)

                def btn_closure(idx, state):
                    return lambda: self.label_img(idx, state)

                self.img_labels[nom] = False
                is_transient_btn.clicked.connect(btn_closure(nom, True))
                not_transient_btn.clicked.connect(btn_closure(nom, False))

                btn_layout.addWidget(is_transient_btn)
                btn_layout.addWidget(not_transient_btn)

                general_layout = QVBoxLayout()
                general_layout.addLayout(layout_tmp)
                general_layout.addLayout(btn_layout)
                self.layoutGauche.addLayout(general_layout)

        widget_CL_tmp = MatplotlibWidget(400, 400, None)
        self.light_curves_plot = widget_CL_tmp
        axe = widget_CL_tmp.axes
        self.light_curves.draw(axe, self.it_object)
        self.layoutDroit.addWidget(widget_CL_tmp)
        self.layoutDroit.addWidget(QLabel(""))

        if err == 1:
            self.passeImage()

    @Slot(str, bool)
    def label_img(self, idx, label):
        print(idx, label)
        self.img_labels[idx] = label
        # self.save_file.write('{0}, {1}\n'.format(idx, int(label)))

    @Slot(int)
    def img_focus(self, idx):
        # print("Info")
        object_id, time_list = self.light_curves.object_info(self.it_object)
        # mag = self.light_curves.object_mag(self.it_object).data
        # print(mag.data.shape)
        off = time_list[idx]
        self.light_curves.focus_obj(self.it_object, off, False)
        self.light_curves_plot.draw()

    @Slot(object)
    def img_clicked(self, idx):
        # if self.cur_img is not None:
        #     self.cur_img.reset()
        #     self.cur_img = None
        self.cur_img = idx

    @Slot(bool)
    def reset_graph(self, persist):
        self.light_curves.reset(self.it_object)
        # if self.cur_img is not None:
        #     self.img_focus(self.cur_img.id)
        #     self.light_curves_plot.draw()

    def close_file(self):
        self.save_file.close()
Exemple #14
0
class ColormapList(QWidget):
    """
    Show list of colormaps
    """

    edit_signal = Signal(ColorMap)
    """Colormap for edit"""

    remove_signal = Signal(str)
    """Name of colormap to remove"""

    visibility_colormap_change = Signal(str, bool)
    """Hide or show colormap"""
    def __init__(self,
                 colormap_map: Dict[str, Tuple[ColorMap, bool]],
                 selected: Optional[Iterable[str]] = None):
        super().__init__()
        if selected is None:
            self._selected = set()
        else:
            self._selected = set(selected)
        self._blocked = set()
        self.current_columns = 1
        self.colormap_map = colormap_map
        self._widget_dict: Dict[str, ChannelPreview] = {}
        self.scroll_area = QScrollArea()
        self.central_widget = QWidget()
        layout2 = QVBoxLayout()
        self.grid_layout = QGridLayout()
        layout2.addLayout(self.grid_layout)
        layout2.addStretch(1)
        layout2.setContentsMargins(0, 0, 0, 0)

        self.central_widget.setLayout(layout2)
        self.central_widget.setMinimumWidth(300)
        self.scroll_area.setWidget(self.central_widget)
        self.scroll_area.setWidgetResizable(True)

        layout = QVBoxLayout()
        layout.addWidget(self.scroll_area)
        self.setLayout(layout)

    def showEvent(self, event: QShowEvent):
        self.refresh()

    def get_selected(self) -> Set[str]:
        """Already selected colormaps"""
        return set(self._selected)

    def change_selection(self, name, selected):
        if selected:
            self._selected.add(name)
        else:
            self._selected.remove(name)
        self.visibility_colormap_change.emit(name, selected)

    def blocked(self) -> Set[str]:
        """Channels that cannot be turn of and remove"""
        return self._blocked

    def _get_columns(self):
        return max(1, self.width() // 400)

    def resizeEvent(self, event: QResizeEvent):
        if self._get_columns() != self.current_columns:
            self.refresh()
            self.central_widget.repaint()

    def refresh(self):
        layout: QGridLayout = self.grid_layout
        cache_dict: Dict[str, ChannelPreview] = {}
        self._widget_dict = {}
        for _ in range(layout.count()):
            el: ChannelPreview = layout.takeAt(0).widget()
            if el.name in self.colormap_map:
                cache_dict[el.name] = el
            else:
                el.deleteLater()
                el.edit_request[ColorMap].disconnect()
                el.remove_request.disconnect()
                el.selection_changed.disconnect()
        selected = self.get_selected()
        blocked = self.blocked()
        columns = self._get_columns()
        for i, (name, (colormap,
                       removable)) in enumerate(self.colormap_map.items()):
            if name in cache_dict:
                widget = cache_dict[name]
                widget.set_blocked(name in blocked)
                widget.set_chosen(name in selected)
            else:
                widget = ChannelPreview(colormap,
                                        name in selected,
                                        name,
                                        removable=removable,
                                        used=name in blocked)
                widget.edit_request[ColorMap].connect(self.edit_signal)
                widget.remove_request.connect(self._remove_request)
                widget.selection_changed.connect(self.change_selection)
            layout.addWidget(widget, i // columns, i % columns)
            self._widget_dict[name] = widget
        widget: QWidget = layout.itemAt(0).widget()
        height = widget.minimumHeight()
        self.current_columns = columns
        self.central_widget.setMinimumHeight(
            (height + 10) * ceil(len(self.colormap_map) / columns))

    def check_state(self, name: str) -> bool:
        """
        Check state of widget representing given colormap

        :param name: name of colormap which representing widget should be checked
        """
        return self._widget_dict[name].is_checked

    def set_state(self, name: str, state: bool) -> None:
        """
        Set if given colormap is selected

        :param name: name of colormap
        :param state: state to be set
        """
        self._widget_dict[name].set_chosen(state)

    def get_colormap_widget(self, name) -> ChannelPreview:
        """Access to widget showing colormap. Created for testing purpose."""
        return self._widget_dict[name]

    def _remove_request(self, name):
        _, removable = self.colormap_map[name]
        if not removable:
            raise ValueError(f"ColorMap {name} is protected from remove")
        if name not in self.colormap_map:
            raise ValueError(f"color with name {name} not found in any dict")
        del self.colormap_map[name]
Exemple #15
0
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)
        self._shortcuts_summary_title = SHORTCUTS_SUMMARY_TITLE

        # Calculate font and amount of elements in each column
        # according screen size
        width, height = self.get_screen_resolution()
        font_size = int(round(height / 80))
        font_size = max(min(font_size, MAX_FONT_SIZE), MIN_FONT_SIZE)
        shortcuts_column = (height - 8 * font_size) / (font_size + 16)

        # Widgets
        style = """
            QDialog {
              margin:0px;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        font_names = QFont()
        font_names.setPointSize(font_size)
        font_names.setBold(True)

        font_keystr = QFont()
        font_keystr.setPointSize(font_size)

        font_title = QFont()
        font_title.setPointSize(font_size + 2)
        font_title.setBold(True)

        title_label = QLabel(self._shortcuts_summary_title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setFont(font_title)

        # iter over shortcuts and create GroupBox for each context
        # with shortcuts in a grid

        columns_layout = QHBoxLayout()
        added_shortcuts = 0
        group = None
        # group shortcuts by context
        shortcuts = groupby(sorted(CONF.iter_shortcuts()), key=itemgetter(0))

        for __, group_shortcuts in shortcuts:
            for i, (context, name, keystr) in enumerate(group_shortcuts):
                # start of every column
                if added_shortcuts == 0:
                    column_layout = QVBoxLayout()

                # at start of new context add previous context group
                if i == 0 and added_shortcuts > 0:
                    column_layout.addWidget(group)

                # create group at start of column or context
                if added_shortcuts == 0 or i == 0:
                    if context == '_': context = 'Global'

                    group = QGroupBox(context.capitalize())
                    group.setFont(font_names)

                    group_layout = QGridLayout()
                    group.setLayout(group_layout)

                    # Count space for titles
                    added_shortcuts += 1

                # Widgets
                label_name = QLabel(name.capitalize().replace('_', ' '))
                label_name.setFont(font_names)

                keystr = QKeySequence(keystr).toString(QKeySequence.NativeText)
                label_keystr = QLabel(keystr)
                label_keystr.setFont(font_keystr)

                group_layout.addWidget(label_name, i, 0)
                group_layout.addWidget(label_keystr, i, 1)

                added_shortcuts += 1

                if added_shortcuts >= shortcuts_column:
                    column_layout.addWidget(group)
                    columns_layout.addLayout(column_layout)
                    added_shortcuts = 0

        column_layout.addWidget(group)
        column_layout.addStretch()  # avoid lasts sections to appear too big
        columns_layout.addLayout(column_layout)

        # Scroll widget
        self.scroll_widget = QWidget()
        self.scroll_widget.setLayout(columns_layout)
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.scroll_widget)

        # widget setup
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)

        # layout
        self._layout = QVBoxLayout()
        self._layout.addWidget(title_label)

        self._layout.addWidget(self.scroll_area)
        self.setLayout(self._layout)

        self.setGeometry(0, 0, width, height)
Exemple #16
0
 def viewportEvent(self, event):
     ok = QScrollArea.viewportEvent(self, event)
     if event.type() == QEvent.Resize:
         self.layoutContents()
     return ok
Exemple #17
0
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)

        self._shortcuts_summary_title = _("Spyder Keyboard ShortCuts")

        # Calculate font and amount of elements in each column according screen size
        width, height = self.get_screen_resolution()
        font_size = height / 80
        font_size = max(min(font_size, MAX_FONT_SIZE), MIN_FONT_SIZE)
        shortcuts_column = (height - 8 * font_size) / (font_size +16)

        # Widgets
        style = """
            QDialog {
              margin:0px;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        font_names = QFont()
        font_names.setPointSize(font_size)
        font_names.setBold(True)

        font_keystr = QFont()
        font_keystr.setPointSize(font_size)

        font_title = QFont()
        font_title.setPointSize(font_size+2)
        font_title.setBold(True)

        title_label = QLabel(self._shortcuts_summary_title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setFont(font_title)

        # iter over shortcuts and create GroupBox for each context
        # with shortcuts in a grid

        columns_layout = QHBoxLayout()
        added_shortcuts = 0
        group = None
        # group shortcuts by context
        shortcuts = groupby(sorted(iter_shortcuts()), key=itemgetter(0))

        for context, group_shortcuts in shortcuts:
            for i, (context, name, keystr) in enumerate(group_shortcuts):
                # start of every column
                if added_shortcuts == 0:
                    column_layout = QVBoxLayout()

                # at start of new context add previous context group
                if i == 0 and added_shortcuts > 0:
                    column_layout.addWidget(group)

                # create group at start of column or context
                if added_shortcuts == 0 or i == 0:
                    if context == '_': context = 'Global'

                    group = QGroupBox(context.capitalize())
                    group.setFont(font_names)

                    group_layout = QGridLayout()
                    group.setLayout(group_layout)

                    # Count space for titles
                    added_shortcuts += 1

                # Widgets
                label_name = QLabel(name.capitalize().replace('_', ' '))
                label_name.setFont(font_names)

                keystr = QKeySequence(keystr).toString(QKeySequence.NativeText)
                label_keystr = QLabel(keystr)
                label_keystr.setFont(font_keystr)

                group_layout.addWidget(label_name, i, 0)
                group_layout.addWidget(label_keystr, i, 1)

                added_shortcuts += 1

                if added_shortcuts >= shortcuts_column:
                    column_layout.addWidget(group)
                    columns_layout.addLayout(column_layout)
                    added_shortcuts = 0

        column_layout.addWidget(group)
        column_layout.addStretch()  # avoid lasts sections to appear too big
        columns_layout.addLayout(column_layout)

        # Scroll widget
        self.scroll_widget = QWidget()
        self.scroll_widget.setLayout(columns_layout)
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.scroll_widget)

        # widget setup
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)

        # layout
        self._layout = QVBoxLayout()
        self._layout.addWidget(title_label)

        self._layout.addWidget(self.scroll_area)
        self.setLayout(self._layout)

        self.setGeometry(0, 0, width, height)
Exemple #18
0
class ThumbnailScrollBar(QFrame):
    """
    A widget that manages the display of the FigureThumbnails that are
    created when a figure is sent to the IPython console by the kernel and
    that controls what is displayed in the FigureViewer.
    """
    redirect_stdio = Signal(bool)

    def __init__(self, figure_viewer, parent=None, background_color=None):
        super(ThumbnailScrollBar, self).__init__(parent)
        self._thumbnails = []
        self.background_color = background_color
        self.current_thumbnail = None
        self.set_figureviewer(figure_viewer)
        self.setup_gui()

    def setup_gui(self):
        """Setup the main layout of the widget."""
        scrollarea = self.setup_scrollarea()
        up_btn, down_btn = self.setup_arrow_buttons()

        self.setFixedWidth(150)
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(up_btn)
        layout.addWidget(scrollarea)
        layout.addWidget(down_btn)

    def setup_scrollarea(self):
        """Setup the scrollarea that will contain the FigureThumbnails."""
        self.view = QWidget()

        self.scene = QGridLayout(self.view)
        self.scene.setColumnStretch(0, 100)
        self.scene.setColumnStretch(2, 100)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidget(self.view)
        self.scrollarea.setWidgetResizable(True)
        self.scrollarea.setFrameStyle(0)
        self.scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollarea.setSizePolicy(QSizePolicy(QSizePolicy.Ignored,
                                                  QSizePolicy.Preferred))

        # Set the vertical scrollbar explicitely :
        # This is required to avoid a "RuntimeError: no access to protected
        # functions or signals for objects not created from Python" in Linux.
        self.scrollarea.setVerticalScrollBar(QScrollBar())

        return self.scrollarea

    def setup_arrow_buttons(self):
        """
        Setup the up and down arrow buttons that are placed at the top and
        bottom of the scrollarea.
        """
        # Get the height of the up/down arrow of the default vertical
        # scrollbar :
        vsb = self.scrollarea.verticalScrollBar()
        style = vsb.style()
        opt = QStyleOptionSlider()
        vsb.initStyleOption(opt)
        vsb_up_arrow = style.subControlRect(
                QStyle.CC_ScrollBar, opt, QStyle.SC_ScrollBarAddLine, self)

        # Setup the up and down arrow button :
        up_btn = up_btn = QPushButton(icon=ima.icon('last_edit_location'))
        up_btn.setFlat(True)
        up_btn.setFixedHeight(vsb_up_arrow.size().height())
        up_btn.clicked.connect(self.go_up)

        down_btn = QPushButton(icon=ima.icon('folding.arrow_down_on'))
        down_btn.setFlat(True)
        down_btn.setFixedHeight(vsb_up_arrow.size().height())
        down_btn.clicked.connect(self.go_down)

        return up_btn, down_btn

    def set_figureviewer(self, figure_viewer):
        """Set the bamespace for the FigureViewer."""
        self.figure_viewer = figure_viewer

    # ---- Save Figure
    def save_all_figures_as(self):
        """Save all the figures to a file."""
        self.redirect_stdio.emit(False)
        dirname = getexistingdirectory(self, caption='Save all figures',
                                       basedir=getcwd_or_home())
        self.redirect_stdio.emit(True)
        if dirname:
            return self.save_all_figures_todir(dirname)

    def save_all_figures_todir(self, dirname):
        """Save all figure in dirname."""
        fignames = []
        for thumbnail in self._thumbnails:
            fig = thumbnail.canvas.fig
            fmt = thumbnail.canvas.fmt
            fext = {'image/png': '.png',
                    'image/jpeg': '.jpg',
                    'image/svg+xml': '.svg'}[fmt]

            figname = get_unique_figname(dirname, 'Figure', fext)
            save_figure_tofile(fig, fmt, figname)
            fignames.append(figname)
        return fignames

    def save_current_figure_as(self):
        """Save the currently selected figure."""
        if self.current_thumbnail is not None:
            self.save_figure_as(self.current_thumbnail.canvas.fig,
                                self.current_thumbnail.canvas.fmt)

    def save_figure_as(self, fig, fmt):
        """Save the figure to a file."""
        fext, ffilt = {
            'image/png': ('.png', 'PNG (*.png)'),
            'image/jpeg': ('.jpg', 'JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)'),
            'image/svg+xml': ('.svg', 'SVG (*.svg);;PNG (*.png)')}[fmt]

        figname = get_unique_figname(getcwd_or_home(), 'Figure', fext)

        self.redirect_stdio.emit(False)
        fname, fext = getsavefilename(
            parent=self.parent(), caption='Save Figure',
            basedir=figname, filters=ffilt,
            selectedfilter='', options=None)
        self.redirect_stdio.emit(True)

        if fname:
            save_figure_tofile(fig, fmt, fname)

    # ---- Thumbails Handlers
    def add_thumbnail(self, fig, fmt):
        thumbnail = FigureThumbnail(background_color=self.background_color)
        thumbnail.canvas.load_figure(fig, fmt)

        # Scale the thumbnail size, while respecting the figure
        # dimension ratio.
        fwidth = thumbnail.canvas.fwidth
        fheight = thumbnail.canvas.fheight

        max_length = 100
        if fwidth/fheight > 1:
            canvas_width = max_length
            canvas_height = canvas_width / fwidth * fheight
        else:
            canvas_height = max_length
            canvas_width = canvas_height / fheight * fwidth
        thumbnail.canvas.setFixedSize(canvas_width, canvas_height)

        thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
        thumbnail.sig_remove_figure.connect(self.remove_thumbnail)
        thumbnail.sig_save_figure.connect(self.save_figure_as)
        self._thumbnails.append(thumbnail)

        self.scene.setRowStretch(self.scene.rowCount()-1, 0)
        self.scene.addWidget(thumbnail, self.scene.rowCount()-1, 1)
        self.scene.setRowStretch(self.scene.rowCount(), 100)
        self.set_current_thumbnail(thumbnail)

    def remove_current_thumbnail(self):
        """Remove the currently selected thumbnail."""
        if self.current_thumbnail is not None:
            self.remove_thumbnail(self.current_thumbnail)

    def remove_all_thumbnails(self):
        """Remove all thumbnails."""
        for thumbnail in self._thumbnails:
            self.layout().removeWidget(thumbnail)
            thumbnail.sig_canvas_clicked.disconnect()
            thumbnail.sig_remove_figure.disconnect()
            thumbnail.sig_save_figure.disconnect()
            thumbnail.deleteLater()
        self._thumbnails = []
        self.current_thumbnail = None
        self.figure_viewer.figcanvas.clear_canvas()

    def remove_thumbnail(self, thumbnail):
        """Remove thumbnail."""
        if thumbnail in self._thumbnails:
            index = self._thumbnails.index(thumbnail)
            self._thumbnails.remove(thumbnail)
        self.layout().removeWidget(thumbnail)
        thumbnail.deleteLater()
        thumbnail.sig_canvas_clicked.disconnect()
        thumbnail.sig_remove_figure.disconnect()
        thumbnail.sig_save_figure.disconnect()

        # Select a new thumbnail if any :
        if thumbnail == self.current_thumbnail:
            if len(self._thumbnails) > 0:
                self.set_current_index(min(index, len(self._thumbnails)-1))
            else:
                self.current_thumbnail = None
                self.figure_viewer.figcanvas.clear_canvas()

    def set_current_index(self, index):
        """Set the currently selected thumbnail by its index."""
        self.set_current_thumbnail(self._thumbnails[index])

    def get_current_index(self):
        """Return the index of the currently selected thumbnail."""
        try:
            return self._thumbnails.index(self.current_thumbnail)
        except ValueError:
            return -1

    def set_current_thumbnail(self, thumbnail):
        """Set the currently selected thumbnail."""
        self.current_thumbnail = thumbnail
        self.figure_viewer.load_figure(
                thumbnail.canvas.fig, thumbnail.canvas.fmt)
        for thumbnail in self._thumbnails:
            thumbnail.highlight_canvas(thumbnail == self.current_thumbnail)

    def go_previous_thumbnail(self):
        """Select the thumbnail previous to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) - 1
            index = index if index >= 0 else len(self._thumbnails) - 1
            self.set_current_index(index)
            self.scroll_to_item(index)

    def go_next_thumbnail(self):
        """Select thumbnail next to the currently selected one."""
        if self.current_thumbnail is not None:
            index = self._thumbnails.index(self.current_thumbnail) + 1
            index = 0 if index >= len(self._thumbnails) else index
            self.set_current_index(index)
            self.scroll_to_item(index)

    def scroll_to_item(self, index):
        """Scroll to the selected item of ThumbnailScrollBar."""
        spacing_between_items = self.scene.verticalSpacing()
        height_view = self.scrollarea.viewport().height()
        height_item = self.scene.itemAt(index).sizeHint().height()
        height_view_excluding_item = max(0, height_view - height_item)

        height_of_top_items = spacing_between_items
        for i in range(index):
            item = self.scene.itemAt(i)
            height_of_top_items += item.sizeHint().height()
            height_of_top_items += spacing_between_items

        pos_scroll = height_of_top_items - height_view_excluding_item // 2

        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(pos_scroll)

    # ---- ScrollBar Handlers
    def go_up(self):
        """Scroll the scrollbar of the scrollarea up by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() - vsb.singleStep()))

    def go_down(self):
        """Scroll the scrollbar of the scrollarea down by a single step."""
        vsb = self.scrollarea.verticalScrollBar()
        vsb.setValue(int(vsb.value() + vsb.singleStep()))
    def setup_page(self):
        if ERR_MSG:
            label = QLabel(_("Could not load plugin:\n{0}".format(ERR_MSG)))
            layout = QVBoxLayout()
            layout.addWidget(label)
            self.setLayout(layout)
            return

        # Layout parameter
        indent = QCheckBox().sizeHint().width()

        # General options
        options_group = QGroupBox(_("Options"))
        # Hack : the spinbox widget will be added to self.spinboxes
        spinboxes_before = set(self.spinboxes)
        passes_spin = self.create_spinbox(
            _("Number of pep8 passes: "), "", 'passes',
            default=0, min_=0, max_=1000000, step=1)
        spinbox = set(self.spinboxes) - spinboxes_before
        spinbox = spinbox.pop()
        spinbox.setSpecialValueText(_("Infinite"))
        aggressive1_checkbox = self.create_checkbox(
            "Aggressivity level 1", "aggressive1", default=False)
        aggressive1_label = QLabel(_(
            "Allow possibly unsafe fixes (E711 and W6), shorten lines"
            " and remove trailing whitespace more aggressively (in"
            " docstrings and multiline strings)."))
        aggressive1_label.setWordWrap(True)
        aggressive1_label.setIndent(indent)
        font_description = aggressive1_label.font()
        font_description.setPointSizeF(font_description.pointSize() * 0.9)
        aggressive1_label.setFont(font_description)
        aggressive2_checkbox = self.create_checkbox(
            "Aggressivity level 2", "aggressive2", default=False)
        aggressive2_label = QLabel(_(
            "Allow more possibly unsafe fixes (E712) and shorten lines."))
        aggressive2_label.setWordWrap(True)
        aggressive2_label.setIndent(indent)
        aggressive2_label.setFont(font_description)

        aggressive1_checkbox.toggled.connect(
            aggressive2_checkbox.setEnabled)
        aggressive1_checkbox.toggled.connect(
            aggressive2_label.setEnabled)
        aggressive2_checkbox.setEnabled(aggressive1_checkbox.isChecked())
        aggressive2_label.setEnabled(aggressive1_checkbox.isChecked())

        # Enable/disable error codes
        fix_layout = QVBoxLayout()
        last_group = ""
        FIX_LIST.sort(key=lambda item: item[0][1])
        for code, description in FIX_LIST:
            # Create a new group if necessary
            if code[1] != last_group:
                last_group = code[1]
                group = QGroupBox(_(self.GROUPS.get(code[1], "")))
                fix_layout.addWidget(group)
                group_layout = QVBoxLayout(group)

            # Checkbox for the option
            text = code
            default = True
            if code in DEFAULT_IGNORE:
                text += _(" (UNSAFE)")
                default = False
            option = self.create_checkbox(text, code, default=default)

            # Label for description
            if code in self.CODES:
                label = QLabel("{autopep8} ({pep8}).".format(
                    autopep8=_(description).rstrip("."),
                    pep8=self.CODES[code]))
            else:
                label = QLabel(_(description))
            label.setWordWrap(True)
            label.setIndent(indent)
            label.setFont(font_description)

            # Add widgets to layout
            option_layout = QVBoxLayout()
            option_layout.setSpacing(0)
            option_layout.addWidget(option)
            option_layout.addWidget(label)
            group_layout.addLayout(option_layout)

            # Special cases
            if code in ("E711", "W6"):
                aggressive1_checkbox.toggled.connect(option.setEnabled)
                aggressive1_checkbox.toggled.connect(label.setEnabled)
                option.setEnabled(aggressive1_checkbox.isChecked())
                label.setEnabled(aggressive1_checkbox.isChecked())
            if code == "E712":
                def e712_enabled():
                    enabled = (aggressive1_checkbox.isChecked()
                               and aggressive2_checkbox.isChecked())
                    option.setEnabled(enabled)
                    label.setEnabled(enabled)
                aggressive1_checkbox.toggled.connect(e712_enabled)
                aggressive2_checkbox.toggled.connect(e712_enabled)
                e712_enabled()

        # General layout
        aggressive2_layout = QVBoxLayout()
        margins = aggressive2_layout.contentsMargins()
        margins.setLeft(indent)
        aggressive2_layout.setContentsMargins(margins)
        aggressive2_layout.addWidget(aggressive2_checkbox)
        aggressive2_layout.addWidget(aggressive2_label)

        options_layout = QVBoxLayout()
        options_layout.addWidget(passes_spin)
        options_layout.addWidget(aggressive1_checkbox)
        options_layout.addWidget(aggressive1_label)
        options_layout.addLayout(aggressive2_layout)
        options_group.setLayout(options_layout)

        widget_scroll = QWidget()
        widget_scroll.setLayout(fix_layout)
        fix_scroll = QScrollArea()
        fix_scroll.setWidget(widget_scroll)
        fix_scroll.setWidgetResizable(True)
        fig_out_layout = QVBoxLayout()
        fig_out_layout.addWidget(fix_scroll, 1)
        fix_group = QGroupBox(_("Errors/warnings to fix"))
        fix_group.setLayout(fig_out_layout)

        vlayout = QVBoxLayout()
        vlayout.addWidget(options_group)
        vlayout.addWidget(fix_group, 1)
        self.setLayout(vlayout)
Exemple #20
0
    def __init__(self, items, itemHeader=None):
        super().__init__(emptyDateValid=False)

        if itemHeader is None:
            itemHeader = self.headerLabels

        self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.table.insertColumn(self.table.columnCount())
        self.table.setHorizontalHeaderLabels([""] + self.headerLabels)
        self.headerLabelColumnOffset = 1

        self.rows = []

        for rowNum, item in enumerate(items):
            self.table.insertRow(rowNum)
            col = 0

            checkBox = QCheckBox()
            checkBox.setChecked(True)
            checkBox.setToolTip("Uncheck to remove this data")
            # have to make a widget with a layout and add the check box to
            # the layout in order to have the check box centred...
            widget = QWidget()
            layout = QVBoxLayout()
            layout.addWidget(checkBox)
            layout.setAlignment(Qt.AlignCenter)
            widget.setLayout(layout)
            self.table.setCellWidget(rowNum, col, widget)
            col += 1

            tableItems = {}
            for idx in range(item.columnCount()):
                if itemHeader[idx] in self.headerLabels:
                    text = item.text(idx)
                    tableItem = QTableWidgetItem(text)
                    tableItem.setTextAlignment(Qt.AlignCenter)
                    tableItem.setFlags(Qt.ItemIsEditable | Qt.ItemIsEnabled)
                    self.table.setItem(rowNum, col, tableItem)
                    tableItems[itemHeader[idx]] = tableItem
                    col += 1

            self.rows.append(Row(item.index, tableItems, checkBox))
            col += 1

        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)

        self.okButton = self.buttonBox.button(QDialogButtonBox.Ok)
        self.okButton.clicked.connect(self.accept)
        cancelButton = self.buttonBox.button(QDialogButtonBox.Cancel)
        cancelButton.clicked.connect(self.reject)

        scrollArea = QScrollArea()
        scrollArea.setWidget(self.table)

        layout = QVBoxLayout()
        layout.addWidget(scrollArea)
        layout.addWidget(self.buttonBox)
        self.setLayout(layout)

        # set the table size
        # i don't know why this is such a faff
        pad = 2
        self.table.resizeRowsToContents()
        self.table.resizeColumnsToContents()
        # manually add the widths of each column for width
        # then do the same for the height
        # getting the `length` of the header items did not work
        width = pad
        for i in range(self.table.columnCount()):
            width += self.table.columnWidth(i)
        # add some extra width, in case there's a vertical scroll bar
        # (scrollArea.verticalScrollBar().width() is too much...)
        width += 30
        # start with one row height for the header
        # directly getting the header height is completely wrong
        height = self.table.rowHeight(0) + pad
        for i in range(self.table.rowCount()):
            height += self.table.rowHeight(i)
        # then set the table's minimum size
        self.table.setMinimumSize(width, height)

        self.setWindowTitle("Edit or remove data")
class DockWidgetPrivate:
    public: 'DockWidget'
    layout: QBoxLayout
    widget: QWidget
    tab_widget: 'DockWidgetTab'
    features: DockWidgetFeature
    dock_manager: 'DockManager'
    dock_area: 'DockAreaWidget'
    toggle_view_action: QAction
    closed: bool
    scroll_area: QScrollArea
    tool_bar: QToolBar
    tool_bar_style_docked: int
    tool_bar_style_floating: int
    tool_bar_icon_size_docked: QSize
    tool_bar_icon_size_floating: QSize
    is_floating_top_level: bool

    def __init__(self, public: 'DockWidget'):
        self.public = public
        self.layout = None
        self.widget = None
        self.tab_widget = None
        self.features = DockWidgetFeature.all_features
        self.dock_manager = None
        self.dock_area = None
        self.toggle_view_action = None
        self.closed = False
        self.scroll_area = None
        self.tool_bar = None
        self.tool_bar_style_docked = Qt.ToolButtonIconOnly
        self.tool_bar_style_floating = Qt.ToolButtonTextUnderIcon
        self.tool_bar_icon_size_docked = QSize(16, 16)
        self.tool_bar_icon_size_floating = QSize(24, 24)
        self.is_floating_top_level = False

    def show_dock_widget(self):
        '''
        Show dock widget
        '''
        from .floating_dock_container import FloatingDockContainer
        if not self.dock_area:
            floating_widget = FloatingDockContainer(dock_widget=self.public)
            floating_widget.resize(self.public.size())
            floating_widget.show()
            return

        self.dock_area.toggle_view(True)
        self.dock_area.set_current_dock_widget(self.public)
        self.tab_widget.show()

        splitter = find_parent(QSplitter, self.dock_area)

        while splitter and not splitter.isVisible():
            splitter.show()
            splitter = find_parent(QSplitter, splitter)

        container = self.dock_area.dock_container()
        if container.is_floating():
            floating_widget = find_parent(FloatingDockContainer, container)
            floating_widget.show()

    def hide_dock_widget(self):
        '''
        Hide dock widget.
        '''
        self.tab_widget.hide()
        self.update_parent_dock_area()

    def update_parent_dock_area(self):
        '''
        Hides a dock area if all dock widgets in the area are closed. This
        function updates the current selected tab and hides the parent dock
        area if it is empty
        '''
        if not self.dock_area:
            return

        next_dock_widget = self.dock_area.next_open_dock_widget(self.public)
        if next_dock_widget is not None:
            self.dock_area.set_current_dock_widget(next_dock_widget)
        else:
            self.dock_area.hide_area_with_no_visible_content()

    def setup_tool_bar(self):
        '''
        Setup the top tool bar
        '''
        self.tool_bar = QToolBar(self.public)
        self.tool_bar.setObjectName("dockWidgetToolBar")
        self.layout.insertWidget(0, self.tool_bar)
        self.tool_bar.setIconSize(QSize(16, 16))
        self.tool_bar.toggleViewAction().setEnabled(False)
        self.tool_bar.toggleViewAction().setVisible(False)
        self.public.top_level_changed.connect(self.public.set_toolbar_floating_style)

    def setup_scroll_area(self):
        '''
        Setup the main scroll area
        '''
        self.scroll_area = QScrollArea(self.public)
        self.scroll_area.setObjectName("dockWidgetScrollArea")
        self.scroll_area.setWidgetResizable(True)
        self.layout.addWidget(self.scroll_area)
Exemple #22
0
class ShortcutsSummaryDialog(QDialog):
    """
    Dialog window listing the spyder and plugins shortcuts.

    It contains all the shortcuts avalaible through iter_shortcuts
    function

    The shortcuts are dysplayed in different columns, and grouped by
    context (global, editor, console...)
    """
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)

        self._shortcuts_summary_title = _("Spyder Keyboard ShortCuts")

        # Calculate font and amount of elements in each column according screen size
        width, height = self.get_screen_resolution()
        font_size = height / 80
        font_size = max(min(font_size, MAX_FONT_SIZE), MIN_FONT_SIZE)
        shortcuts_column = (height - 8 * font_size) / (font_size + 16)

        # Widgets
        style = """
            QDialog {
              margin:0px;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        font_names = QFont()
        font_names.setPointSize(font_size)
        font_names.setBold(True)

        font_keystr = QFont()
        font_keystr.setPointSize(font_size)

        font_title = QFont()
        font_title.setPointSize(font_size + 2)
        font_title.setBold(True)

        title_label = QLabel(self._shortcuts_summary_title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setFont(font_title)

        # iter over shortcuts and create GroupBox for each context
        # with shortcuts in a grid

        columns_layout = QHBoxLayout()
        added_shortcuts = 0
        group = None
        # group shortcuts by context
        shortcuts = groupby(sorted(CONF.iter_shortcuts()), key=itemgetter(0))

        for context, group_shortcuts in shortcuts:
            for i, (context, name, keystr) in enumerate(group_shortcuts):
                # start of every column
                if added_shortcuts == 0:
                    column_layout = QVBoxLayout()

                # at start of new context add previous context group
                if i == 0 and added_shortcuts > 0:
                    column_layout.addWidget(group)

                # create group at start of column or context
                if added_shortcuts == 0 or i == 0:
                    if context == '_': context = 'Global'

                    group = QGroupBox(context.capitalize())
                    group.setFont(font_names)

                    group_layout = QGridLayout()
                    group.setLayout(group_layout)

                    # Count space for titles
                    added_shortcuts += 1

                # Widgets
                label_name = QLabel(name.capitalize().replace('_', ' '))
                label_name.setFont(font_names)

                keystr = QKeySequence(keystr).toString(QKeySequence.NativeText)
                label_keystr = QLabel(keystr)
                label_keystr.setFont(font_keystr)

                group_layout.addWidget(label_name, i, 0)
                group_layout.addWidget(label_keystr, i, 1)

                added_shortcuts += 1

                if added_shortcuts >= shortcuts_column:
                    column_layout.addWidget(group)
                    columns_layout.addLayout(column_layout)
                    added_shortcuts = 0

        column_layout.addWidget(group)
        column_layout.addStretch()  # avoid lasts sections to appear too big
        columns_layout.addLayout(column_layout)

        # Scroll widget
        self.scroll_widget = QWidget()
        self.scroll_widget.setLayout(columns_layout)
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.scroll_widget)

        # widget setup
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)

        # layout
        self._layout = QVBoxLayout()
        self._layout.addWidget(title_label)

        self._layout.addWidget(self.scroll_area)
        self.setLayout(self._layout)

        self.setGeometry(0, 0, width, height)

    def get_screen_resolution(self):
        """Return the screen resolution of the primary screen."""
        widget = QDesktopWidget()
        geometry = widget.availableGeometry(widget.primaryScreen())
        return geometry.width(), geometry.height()
Exemple #23
0
class DiffCtrlDevMonitor(QWidget):
    """Diff Ctrl Dev Monitor Widget."""
    def __init__(self, parent=None, prefix='', device=''):
        """Init."""
        super(DiffCtrlDevMonitor, self).__init__(parent)
        if not prefix:
            self.prefix = _VACA_PREFIX
        else:
            self.prefix = prefix
        self.device = _PVName(device)
        self.device = self.device.substitute(prefix=self.prefix)
        self.section = self.device.sec
        self.orientation = self.device.dev[-1]
        self.setObjectName(self.section + 'App')
        self._setupUi()
        self._createConnectors()
        self._setupControlWidgets()
        self.updateDevWidget()

        self.setStyleSheet("""
            PyDMSpinbox, PyDMLabel{
                min-width:5em; max-width: 5em;
            }""")

    def _setupUi(self):
        # status
        label_status = QLabel('Status: ',
                              self,
                              alignment=Qt.AlignRight | Qt.AlignVCenter)
        channels2values = {
            self.device.substitute(propty='ForceComplete-Mon'): 1,
            self.device.substitute(propty='NegativeDoneMov-Mon'): 1,
            self.device.substitute(propty='PositiveDoneMov-Mon'): 1
        }
        self.multiled_status = PyDMLedMultiChannel(self, channels2values)
        self.multiled_status.setStyleSheet('max-width: 1.29em;')

        self.pb_details = QPushButton(qta.icon('fa5s.ellipsis-h'), '', self)
        self.pb_details.setToolTip('Open details')
        self.pb_details.setObjectName('detail')
        self.pb_details.setStyleSheet(
            "#detail{min-width:25px; max-width:25px; icon-size:20px;}")
        util.connect_window(self.pb_details,
                            _DiffCtrlDetails,
                            parent=self,
                            prefix=self.prefix,
                            device=self.device)

        self.lb_descCtrl1 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl1 = PyDMSpinbox(self)
        self.sb_Ctrl1.showStepExponent = False
        self.lb_Ctrl1 = PyDMLabel(self)
        self.lb_descCtrl2 = QLabel('',
                                   self,
                                   alignment=Qt.AlignRight | Qt.AlignVCenter)
        self.sb_Ctrl2 = PyDMSpinbox(self)
        self.sb_Ctrl2.showStepExponent = False
        self.lb_Ctrl2 = PyDMLabel(self)

        self.pb_open = PyDMPushButton(
            parent=self,
            label='Open',
            pressValue=1,
            init_channel=self.device.substitute(propty='Home-Cmd'))

        tmp_file = _substitute_in_file(
            _os.path.abspath(_os.path.dirname(__file__)) + '/ui_as_ap_dev' +
            self.orientation.lower() + 'mon.ui', {'PREFIX': self.prefix})
        self.dev_widget = loadUi(tmp_file)
        self.dev_widget.setObjectName('dev')
        self.dev_widget_scrarea = QScrollArea()
        self.dev_widget_scrarea.setObjectName('scrarea')
        self.dev_widget_scrarea.setStyleSheet(
            '#scrarea{background-color: transparent; max-width: 15em;}'
            '#dev{background-color:transparent;}')
        self.dev_widget_scrarea.setWidget(self.dev_widget)

        lay = QGridLayout(self)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(label_status, 0, 0)
        lay.addWidget(self.multiled_status, 0, 1)
        lay.addWidget(self.pb_details, 0, 2, alignment=Qt.AlignRight)
        lay.addWidget(self.lb_descCtrl1, 1, 0)
        lay.addWidget(self.sb_Ctrl1, 1, 1)
        lay.addWidget(self.lb_Ctrl1, 1, 2)
        lay.addWidget(self.lb_descCtrl2, 2, 0)
        lay.addWidget(self.sb_Ctrl2, 2, 1)
        lay.addWidget(self.lb_Ctrl2, 2, 2)
        lay.addWidget(self.pb_open, 3, 1, 1, 2)
        lay.addWidget(self.dev_widget_scrarea, 0, 3, 4, 1)

    def _createConnectors(self):
        """Create connectors to monitor device positions."""
        raise NotImplementedError

    def _setDevPos(self, new_value):
        """Set device widget positions."""
        raise NotImplementedError

    def _setupControlWidgets(self):
        """Setup control widgets channels/labels."""
        raise NotImplementedError

    def updateDevWidget(self):
        """Update device illustration."""
        raise NotImplementedError

    def channels(self):
        """Return channels."""
        raise NotImplementedError
Exemple #24
0
class ShortcutsSummaryDialog(QDialog):
    """
    Dialog window listing the spyder and plugins shortcuts.

    It contains all the shortcuts avalaible through iter_shortcuts
    function

    The shortcuts are dysplayed in different columns, and grouped by
    context (global, editor, console...)
    """
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)

        self._shortcuts_summary_title = _("Spyder Keyboard ShortCuts")

        # Calculate font and amount of elements in each column according screen size
        width, height = self.get_screen_resolution()
        font_size = height / 80
        font_size = max(min(font_size, MAX_FONT_SIZE), MIN_FONT_SIZE)
        shortcuts_column = (height - 8 * font_size) / (font_size +16)

        # Widgets
        style = """
            QDialog {
              margin:0px;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        font_names = QFont()
        font_names.setPointSize(font_size)
        font_names.setBold(True)

        font_keystr = QFont()
        font_keystr.setPointSize(font_size)

        font_title = QFont()
        font_title.setPointSize(font_size+2)
        font_title.setBold(True)

        title_label = QLabel(self._shortcuts_summary_title)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setFont(font_title)

        # iter over shortcuts and create GroupBox for each context
        # with shortcuts in a grid

        columns_layout = QHBoxLayout()
        added_shortcuts = 0
        group = None
        # group shortcuts by context
        shortcuts = groupby(sorted(iter_shortcuts()), key=itemgetter(0))

        for context, group_shortcuts in shortcuts:
            for i, (context, name, keystr) in enumerate(group_shortcuts):
                # start of every column
                if added_shortcuts == 0:
                    column_layout = QVBoxLayout()

                # at start of new context add previous context group
                if i == 0 and added_shortcuts > 0:
                    column_layout.addWidget(group)

                # create group at start of column or context
                if added_shortcuts == 0 or i == 0:
                    if context == '_': context = 'Global'

                    group = QGroupBox(context.capitalize())
                    group.setFont(font_names)

                    group_layout = QGridLayout()
                    group.setLayout(group_layout)

                    # Count space for titles
                    added_shortcuts += 1

                # Widgets
                label_name = QLabel(name.capitalize().replace('_', ' '))
                label_name.setFont(font_names)
                label_keystr = QLabel(keystr)
                label_keystr.setFont(font_keystr)

                group_layout.addWidget(label_name, i, 0)
                group_layout.addWidget(label_keystr, i, 1)

                added_shortcuts += 1

                if added_shortcuts >= shortcuts_column:
                    column_layout.addWidget(group)
                    columns_layout.addLayout(column_layout)
                    added_shortcuts = 0

        column_layout.addWidget(group)
        column_layout.addStretch()  # avoid lasts sections to appear too big
        columns_layout.addLayout(column_layout)

        # Scroll widget
        self.scroll_widget = QWidget()
        self.scroll_widget.setLayout(columns_layout)
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.scroll_widget)

        # widget setup
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)

        # layout
        self._layout = QVBoxLayout()
        self._layout.addWidget(title_label)

        self._layout.addWidget(self.scroll_area)
        self.setLayout(self._layout)

        self.setGeometry(0, 0, width, height)

    def get_screen_resolution(self):
        """Return the screen resolution of the primary screen."""
        widget = QDesktopWidget()
        geometry = widget.availableGeometry(widget.primaryScreen())
        return geometry.width(), geometry.height()