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
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))
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))
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()))
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()))
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)
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()
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)
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()
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]
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)
def viewportEvent(self, event): ok = QScrollArea.viewportEvent(self, event) if event.type() == QEvent.Resize: self.layoutContents() return ok
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)
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)
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)
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()
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
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()