def _cfg_action( self, menu, title, setting, icon=None, shortcut=None ): action = QAction(title, self) if icon: action.setIcon(self._ide.theme.qicon(icon)) if shortcut: action.setShortcut(shortcut) action.setToolTip( u'{} ({})'.format(title.replace(u'&', u''), shortcut) ) def change_setting(value): self._ide.extension_manager.fire( 'setting_changed', setting=setting, value=value ) action.triggered.connect(change_setting) action.setCheckable(True) action.setChecked(cfg[setting]) action.setPriority(QAction.HighPriority) menu.addAction(action) self._cfg_actions[setting] = action return action
def _add_legend_toggle_action(self, menu, event): legend = event.inaxes.axes.get_legend() legend_action = QAction("Show legend", menu, checkable=True) legend_action.setChecked(legend is not None and legend.get_visible()) legend_action.toggled.connect( lambda: self._toggle_legend_and_redraw(event.inaxes.axes)) menu.addAction(legend_action)
def menu_actions(self): """ List of QtWidgets.QActions to be attached to this tool as a context menu. """ self.options = [] component_action_group = QActionGroup(self.tool_bar) action = QAction("Off", self.tool_bar, checkable=True) action.setChecked(True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.remove_contour) self.options.append(action) action = QAction("Current Component", self.tool_bar, checkable=True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.default_contour) self.options.append(action) action = QAction("Custom Component", self.tool_bar, checkable=True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.custom_contour) self.options.append(action) action = QAction(" ", self.tool_bar) action.setSeparator(True) self.options.append(action) action = QAction("Contour Settings", self.tool_bar) action.triggered.connect(self.viewer.edit_contour_settings) self.options.append(action) return self.options
def customMenuRequested(self, position): """Builds a custom menu for items items""" index = self.indexAt(position) # type: QModelIndex menu = QMenu(self) if index.isValid(): if index.data(EnsembleModel.data_type_role ) == WorkspaceDataType.Ensemble: # Allow renaming the ensemble via the context menu rename_action = QAction("Rename Collection", menu) rename_action.triggered.connect(self._rename_action) menu.addAction(rename_action) # Allow toggling the active ensemble via the context menu # * there can only be at most 1 active ensemble # * there are only 0 active ensembles when data has not yet been loaded ??? # * opening data updates the active ensemble to that data is_active = index.data(EnsembleModel.active_role) active_text = "Active" toggle_active_action = QAction(active_text, menu) toggle_active_action.setCheckable(True) if is_active is True: toggle_active_action.setChecked(True) else: toggle_active_action.setChecked(False) toggle_active_action.setText(f"Not {active_text}") # Make sure to update the model with the active / deactivated ensemble toggle_active_action.toggled.connect(self._set_active_action) # Don't allow deactivating the active ensemble if there is only one loaded if self.model().rowCount() == 1: toggle_active_action.setEnabled(False) menu.addAction(toggle_active_action) menu.addSeparator() remove_text = "Remove " data_type_role = index.data(EnsembleModel.data_type_role) if data_type_role == WorkspaceDataType.Ensemble: remove_text += "Ensemble" elif data_type_role == WorkspaceDataType.Catalog: remove_text += "Catalog" elif data_type_role == WorkspaceDataType.Intent: remove_text += "Item" remove_action = QAction(remove_text, menu) remove_action.triggered.connect(self._remove_action) menu.addAction(remove_action) else: create_ensemble_action = QAction("Create New Collection", menu) create_ensemble_action.triggered.connect( self._create_ensemble_action) menu.addAction(create_ensemble_action) # Display menu wherever the user right-clicked menu.popup(self.viewport().mapToGlobal(position))
def __init__(self, parent=None, standalone=False): super(Camera, self).__init__(parent) # This prevents doing unneeded initialization # when QtDesginer loads the plugin. if parent is None and not standalone: return if not multimedia_available: return self.ui = uic.loadUi(os.path.join(WIDGET_PATH, "camera.ui"), self) self.camera = None self.imageCapture = None self.mediaRecorder = None self.isCapturingImage = False self.applicationExiting = False self.imageSettings = QImageEncoderSettings() self.audioSettings = QAudioEncoderSettings() self.videoSettings = QVideoEncoderSettings() self.videoContainerFormat = '' camera_device = QByteArray() videoDevicesGroup = QActionGroup(self) videoDevicesGroup.setExclusive(True) if not QCamera.availableDevices(): self.ui.devicesCombo.addItem("No Device") else: for deviceName in QCamera.availableDevices(): description = QCamera.deviceDescription(deviceName) self.ui.devicesCombo.addItem(description) videoDeviceAction = QAction(description, videoDevicesGroup) videoDeviceAction.setCheckable(True) videoDeviceAction.setData(deviceName) if camera_device.isEmpty(): cameraDevice = deviceName videoDeviceAction.setChecked(True) self.ui.devicesCombo.addAction(videoDeviceAction) videoDevicesGroup.triggered.connect(self.updateCameraDevice) self.ui.captureWidget.currentChanged.connect(self.updateCaptureMode) self.ui.devicesCombo.currentIndexChanged.connect(self.get_device_action) self.ui.lockButton.hide() if not IN_DESIGNER: # Start camera 2s after the UI has loaded QTimer.singleShot(2000, lambda: self.setCamera(camera_device))
def _init_menu_buttons(self): """ Add the two menu buttons to the tool bar. Currently two are defined: View - for changing the view of the active window Data Processing - for applying a data processing step to the data. :return: """ self._option_buttons = [ self.ui.view_option_button, self.ui.cube_option_button ] # Create the View Menu view_menu = self._dict_to_menu( OrderedDict([ ('Hide Axes', ['checkable', self._toggle_viewer_axes]), ('Hide Toolbars', ['checkable', self._toggle_toolbars]), ('Hide Spaxel Value Tooltip', ['checkable', self._toggle_hover_value]), ('Hide Stats', ['checkable', self._toggle_stats_display]), ('Convert Flux Units', lambda: self._open_dialog('Convert Flux Units', None)), ('Wavelength Units/Redshift', lambda: self._open_dialog('Wavelength Units/Redshift', None)) ])) # Add toggle RA-DEC format: format_menu = view_menu.addMenu("RA-DEC Format") format_action_group = QActionGroup(format_menu) self.ra_dec_format_menu = format_menu # Make sure to change all instances of the the names # of the formats if modifications are made to them. for format_name in ["Sexagesimal", "Decimal Degrees"]: act = QAction(format_name, format_menu) act.triggered.connect(self._toggle_all_coords_in_degrees) act.setActionGroup(format_action_group) act.setCheckable(True) act.setChecked( True) if format == "Sexagesimal" else act.setChecked(False) format_menu.addAction(act) self.ui.view_option_button.setMenu(view_menu) # Create the Data Processing Menu cube_menu = self._dict_to_menu( OrderedDict([ ('Collapse Cube', lambda: self._open_dialog('Collapse Cube', None)), ('Spatial Smoothing', lambda: self._open_dialog('Spatial Smoothing', None)), ('Moment Maps', lambda: self._open_dialog('Moment Maps', None)), ('Arithmetic Operations', lambda: self._open_dialog('Arithmetic Operations', None)) ])) self.ui.cube_option_button.setMenu(cube_menu)
def _init_menu_buttons(self): """ Add the two menu buttons to the tool bar. Currently two are defined: View - for changing the view of the active window Data Processing - for applying a data processing step to the data. :return: """ self._option_buttons = [ self.ui.view_option_button, self.ui.cube_option_button ] # Create the View Menu view_menu = self._dict_to_menu(OrderedDict([ ('Hide Axes', ['checkable', self._toggle_viewer_axes]), ('Hide Toolbars', ['checkable', self._toggle_toolbars]), ('Hide Spaxel Value Tooltip', ['checkable', self._toggle_hover_value]), ('Hide Stats', ['checkable', self._toggle_stats_display]), ('Flux Units', OrderedDict([ ('Convert Displayed Units', lambda: self._open_dialog('Convert Displayed Units', None)), ('Convert Data Values', lambda: self._open_dialog('Convert Data Values', None)), ]) ), ('Wavelength Units/Redshift', lambda: self._open_dialog('Wavelength Units/Redshift', None)) ])) # Add toggle RA-DEC format: format_menu = view_menu.addMenu("RA-DEC Format") format_action_group = QActionGroup(format_menu) self.ra_dec_format_menu = format_menu # Make sure to change all instances of the the names # of the formats if modifications are made to them. for format_name in ["Sexagesimal", "Decimal Degrees"]: act = QAction(format_name, format_menu) act.triggered.connect(self._toggle_all_coords_in_degrees) act.setActionGroup(format_action_group) act.setCheckable(True) act.setChecked(True) if format == "Sexagesimal" else act.setChecked(False) format_menu.addAction(act) self.ui.view_option_button.setMenu(view_menu) # Create the Data Processing Menu cube_menu = self._dict_to_menu(OrderedDict([ ('Collapse Cube', lambda: self._open_dialog('Collapse Cube', None)), ('Spatial Smoothing', lambda: self._open_dialog('Spatial Smoothing', None)), ('Moment Maps', lambda: self._open_dialog('Moment Maps', None)), ('Arithmetic Operations', lambda: self._open_dialog('Arithmetic Operations', None)) ])) self.ui.cube_option_button.setMenu(cube_menu)
def getContextMenus(self, event=None): """Get context menus. Args: event: DESCRIPTION. Defaults to None. """ if self.menu is None: self.menu = QMenu() self.menu.setTitle(self.name + " options..") view_all = QAction("View all", self.menu) view_all.triggered.connect(self.view_all) self.menu.addAction(view_all) self.menu.view_all = view_all toggle_aspect_mode = QAction("Locked aspect", self.menu, checkable=True) toggle_aspect_mode.triggered.connect(self.toggle_aspect_mode) toggle_aspect_mode.setChecked(True) self.menu.addAction(toggle_aspect_mode) self.menu.toggle_aspect_mode = toggle_aspect_mode toggle_click_mode = QAction( "Mouse panmode", self.menu, shortcut=QKeySequence("Shift+S"), checkable=True, ) toggle_click_mode.triggered.connect(self.toggle_mouse_mode) self.menu.addAction(toggle_click_mode) self.menu.toggle_mode = toggle_click_mode export_view = QAction("Export View", self.menu) export_view.setToolTip("Axis와 Marker를 포함한 화면을 캡쳐한다.") export_view.triggered.connect(self.export_view_clicked) self.menu.addAction(export_view) export_img = QAction("Export data as png", self.menu) export_img.setToolTip("Imagesc의 Data 원본을 Image 파일로 저장한다.") export_img.triggered.connect(self.export_data_as_img_clicked) self.menu.addAction(export_img) if self.view.vb.state["mouseMode"] == self.view.vb.PanMode: self.menu.toggle_mode.setChecked(True) else: self.menu.toggle_mode.setChecked(False) return self.menu
def patternMenu(self, menu, node): a = QAction("Recursive sub-pattern add", self, checkable=True) menu.addAction(a) a.setChecked(self._recursiveAddNewNode) menu.addSeparator() for t in [n[0] for n in CGU.getAuthChildren(node)]: def genCopyPattern(arg): def copyPattern(): self.model().copyNodeRaw(CGS.profile[arg][0]) if (self.getLastEntered() is not None): self.model().pasteAsChild(self.getLastEntered()) return copyPattern a = QAction("{}".format(t), self, triggered=genCopyPattern(t)) menu.addAction(a)
class Viewer(ViewerModel): """ This extends the model by attaching a Qt Window as its view. This object is meant to be exposed to the user in an interactive console. """ def __init__(self, *, show=True, title="Demo App"): # TODO Where does title thread through? super().__init__() self._widget = QtViewer(self) self._window = Window(self._widget, show=show) menu_bar = self._window._qt_window.menuBar() menu_item_control = menu_bar.addMenu("Control Actions") self.action_activate_env_destroy = QAction( "Activate 'Destroy Environment'", self._window._qt_window) self.action_activate_env_destroy.setCheckable(True) self._update_action_env_destroy_state() self.action_activate_env_destroy.triggered.connect( self._activate_env_destroy_triggered) menu_item_control.addAction(self.action_activate_env_destroy) self._widget.model.run_engine.events.status_changed.connect( self.on_update_widgets) def _update_action_env_destroy_state(self): env_destroy_activated = self._widget.model.run_engine.env_destroy_activated self.action_activate_env_destroy.setChecked(env_destroy_activated) def _activate_env_destroy_triggered(self): env_destroy_activated = self._widget.model.run_engine.env_destroy_activated self._widget.model.run_engine.activate_env_destroy( not env_destroy_activated) def on_update_widgets(self, event): self._update_action_env_destroy_state() @property def window(self): return self._window def show(self): """Resize, show, and raise the window.""" self._window.show() def close(self): """Close the window.""" self._window.close()
def __init__(self, entity, env): d = Diagram_Item(entity) d.modified_callback = self.set_modified super().__init__(d) self.modified_callback = env.set_modified self.entity = entity show_ports_action = QAction("Connection Ports", self) self._show_ports_action = show_ports_action show_ports_action.setStatusTip("Show connection ports when hovering") show_ports_action.setCheckable(True) show_ports_action.setChecked(False) show_ports_action.setIcon(QIcon.fromTheme("edit-find")) show_ports_action.triggered.connect( lambda x: d.set_show_connection_ports_on_hover(x)) self.view_menu.addAction(show_ports_action) self.tools.append(show_ports_action)
def add_header_context_menu(self, checked=None, checkable=None, enabled=None): """ Adds the context menu from using header information checked can be a header_name -> boolean dictionary. If given, headers with the key name will get the checked value from the dictionary. The corresponding column will be hidden if checked is False. checkable can be a header_name -> boolean dictionary. If given, headers with the key name will get the checkable value from the dictionary. enabled can be a header_name -> boolean dictionary. If given, headers with the key name will get the enabled value from the dictionary. """ checked = checked if checked is not None else {} checkable = checkable if checkable is not None else {} enabled = enabled if enabled is not None else {} horizontal_header = self._horizontal_header() horizontal_header.setContextMenuPolicy(Qt.ActionsContextMenu) self.toggle_column_actions_group = QActionGroup(self) self.toggle_column_actions_group.setExclusive(False) self.__toggle_functions = [] # for keeping references for col in range(horizontal_header.count()): column_label = self.model().headerData(col, Qt.Horizontal, Qt.DisplayRole) logger.debug("Adding: col {}: {}".format(col, column_label)) action = QAction(str(column_label), self.toggle_column_actions_group, checkable=checkable.get(column_label, True), enabled=enabled.get(column_label, True), toolTip=_("Shows or hides " "the {} column").format(column_label)) func = self.__make_show_column_function(col) self.__toggle_functions.append(func) # keep reference horizontal_header.addAction(action) is_checked = checked.get( column_label, not horizontal_header.isSectionHidden(col)) horizontal_header.setSectionHidden(col, not is_checked) action.setChecked(is_checked) action.toggled.connect(func)
def dict_to_menu(parent, menu_dict, menu_widget=None): if not menu_widget: menu_widget = QMenu(parent) for k, v in menu_dict.items(): if isinstance(v, dict): new_menu = menu_widget.addMenu(k) dict_to_menu(v, menu_widget=new_menu) else: act = QAction(k, menu_widget) if isinstance(v, list): if v[0] == 'checkable': v = v[1] act.setCheckable(True) act.setChecked(False) act.triggered.connect(v) menu_widget.addAction(act) return menu_widget
def _dict_to_menu(self, menu_dict): '''Stolen shamelessly from specviz. Thanks!''' menu_widget = QMenu() for k, v in menu_dict.items(): if isinstance(v, dict): new_menu = menu_widget.addMenu(k) self._dict_to_menu(v, menu_widget=new_menu) else: act = QAction(k, menu_widget) if isinstance(v, list): if v[0] == 'checkable': v = v[1] act.setCheckable(True) act.setChecked(True) act.triggered.connect(v) menu_widget.addAction(act) return menu_widget
def _make_sort_button(self): """ Make the sort button, with separate groups for ascending and descending, sorting by name or last shown :return: The sort menu button """ sort_button = QPushButton("Sort") sort_menu = QMenu() ascending_action = QAction("Ascending", sort_menu, checkable=True) ascending_action.setChecked(True) ascending_action.toggled.connect(self.presenter.set_sort_order) descending_action = QAction("Descending", sort_menu, checkable=True) order_group = QActionGroup(sort_menu) order_group.addAction(ascending_action) order_group.addAction(descending_action) number_action = QAction("Number", sort_menu, checkable=True) number_action.setChecked(True) number_action.toggled.connect( lambda: self.presenter.set_sort_type(Column.Number)) name_action = QAction("Name", sort_menu, checkable=True) name_action.toggled.connect( lambda: self.presenter.set_sort_type(Column.Name)) last_active_action = QAction("Last Active", sort_menu, checkable=True) last_active_action.toggled.connect( lambda: self.presenter.set_sort_type(Column.LastActive)) sort_type_group = QActionGroup(sort_menu) sort_type_group.addAction(number_action) sort_type_group.addAction(name_action) sort_type_group.addAction(last_active_action) sort_menu.addAction(ascending_action) sort_menu.addAction(descending_action) sort_menu.addSeparator() sort_menu.addAction(number_action) sort_menu.addAction(name_action) sort_menu.addAction(last_active_action) sort_button.setMenu(sort_menu) return sort_button
class MainWindowMenu(QMenu): def __init__(self, parent): super(MainWindowMenu, self).__init__(parent) self.setTitle("QtPyBotnet") self.console_action = QAction('Show console', self) self.console_action.setObjectName("show_console") self.addAction(self.console_action) self.stay_top_action = QAction("Stay on top", self) self.stay_top_action.setObjectName("stay_top_action") self.stay_top_action.setCheckable(True) self.stay_top_action.setChecked( bool(self.window().windowFlags() & Qt.WindowStaysOnTopHint)) self.addAction(self.stay_top_action) self.style_picker_action = QWidgetAction(self) self.style_picker = StylePickerHorizontal(self) self.style_picker_action.setDefaultWidget(self.style_picker) self.addAction(self.style_picker_action)
def _dict_to_menu(self, menu_dict, menu_widget=None): '''Stolen shamelessly from specviz. Thanks!''' if not menu_widget: menu_widget = QMenu() for k, v in menu_dict.items(): if isinstance(v, dict): new_menu = menu_widget.addMenu(k) self._dict_to_menu(v, menu_widget=new_menu) else: act = QAction(k, menu_widget) if isinstance(v, list): if v[0] == 'checkable': v = v[1] act.setCheckable(True) act.setChecked(False) act.triggered.connect(v) menu_widget.addAction(act) return menu_widget
def _action(self, title, icon, shortcut, target, checkable=False, checked=False): action = QAction(title, self) if icon: action.setIcon(self._ide.theme.qicon(icon)) if shortcut: action.setShortcut(shortcut) action.setToolTip(u'{} ({})'.format(title.replace(u'&', u''), shortcut)) action.triggered.connect(target) if checkable: action.setCheckable(True) action.setChecked(checked) action.setPriority(QAction.HighPriority) return action
def _make_sort_button(self): """ Make the sort button, with separate groups for ascending and descending, sorting by name or last shown :return: The sort menu button """ sort_button = QPushButton("Sort") sort_menu = QMenu() ascending_action = QAction("Ascending", sort_menu, checkable=True) ascending_action.setChecked(True) ascending_action.toggled.connect(self.presenter.set_sort_order) descending_action = QAction("Descending", sort_menu, checkable=True) order_group = QActionGroup(sort_menu) order_group.addAction(ascending_action) order_group.addAction(descending_action) number_action = QAction("Number", sort_menu, checkable=True) number_action.setChecked(True) number_action.toggled.connect(lambda: self.presenter.set_sort_type(Column.Number)) name_action = QAction("Name", sort_menu, checkable=True) name_action.toggled.connect(lambda: self.presenter.set_sort_type(Column.Name)) last_active_action = QAction("Last Active", sort_menu, checkable=True) last_active_action.toggled.connect(lambda: self.presenter.set_sort_type(Column.LastActive)) sort_type_group = QActionGroup(sort_menu) sort_type_group.addAction(number_action) sort_type_group.addAction(name_action) sort_type_group.addAction(last_active_action) sort_menu.addAction(ascending_action) sort_menu.addAction(descending_action) sort_menu.addSeparator() sort_menu.addAction(number_action) sort_menu.addAction(name_action) sort_menu.addAction(last_active_action) sort_button.setMenu(sort_menu) return sort_button
def menu_actions(self): """ List of QtWidgets.QActions to be attached to this tool as a context menu. """ self.options = [] # WARNING: QAction labels are used to identify them. # Changing them can cause problems unless # all references are updated in this package. component_action_group = QActionGroup(self.tool_bar) action = QAction("Off", self.tool_bar, checkable=True) action.setChecked(True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.remove_contour) self.options.append(action) action = QAction("Current Component", self.tool_bar, checkable=True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.default_contour) self.options.append(action) action = QAction("Other Component", self.tool_bar, checkable=True) action.setActionGroup(component_action_group) action.triggered.connect(self.viewer.custom_contour) self.options.append(action) action = QAction(" ", self.tool_bar) action.setSeparator(True) self.options.append(action) action = QAction("Contour Settings", self.tool_bar) action.triggered.connect(self.viewer.edit_contour_settings) self.options.append(action) return self.options
def initactions(self): self.clear() self.actions = [] for category in self.categories: if not category in self.panels.keys(): continue panels = self.panels[category] keys = sorted(panels.keys()) for panid in keys: if category == self.parent().category and panid == self.parent( ).panid: continue panel = panels[panid] action = QAction(panel.windowTitle()) action.setCheckable(True) action.setChecked((category, panel.panid) in self.panel.bindings) action.triggered.connect( FuncToPanel(self.func, category, panel.panid)) self.addAction(action) self.actions.append(action) self.addSeparator()
def _create_action(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, checked=False): """Convenience function to create actions""" action = QAction(text, self) if icon is not None: action.setIcon(QIcon(icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.triggered.connect(slot) if checkable: action.setCheckable(True) if checked: action.setChecked(True) return action
def generateContextMenu(self): """ Generate the window's context menu. This first calls the base class's context menu generator and then extends it with the filtering options. """ qmenu = super(MessageDisplay, self).generateContextMenu() filter_menu = qmenu.addMenu("&View") framework_action = QAction('Mantid Log Output', filter_menu) framework_action.triggered.connect(self.toggle_filter_framework_output) framework_action.setCheckable(True) framework_action.setChecked(self.showFrameworkOutput()) filter_menu.addAction(framework_action) filter_menu.addSeparator() actions_to_group = [] active_script_action = QAction("Active Tab Output", filter_menu) active_script_action.triggered.connect(self.show_active_script) actions_to_group.append(active_script_action) all_script_action = QAction('All Script Output', filter_menu) all_script_action.triggered.connect(self.show_all_scripts) actions_to_group.append(all_script_action) hide_all_script_action = QAction("Hide All Script Output", filter_menu) hide_all_script_action.triggered.connect(self.hide_all_scripts) actions_to_group.append(hide_all_script_action) action_group = QActionGroup(filter_menu) for action in actions_to_group: action_group.addAction(action) filter_menu.addAction(action) action.setCheckable(True) if self.showAllScriptOutput(): all_script_action.setChecked(True) elif self.showActiveScriptOutput(): active_script_action.setChecked(True) else: hide_all_script_action.setChecked(True) return qmenu
def _popup_menu(self, event): axes = self._find_calling_axes(event) # find axes calling right click if axes is None: return pos = self.parent.mapFromGlobal(QtGui.QCursor().pos()) popup_menu = QMenu(self.parent) xScaleMenu = popup_menu.addMenu('x-scale') yScaleMenu = popup_menu.addMenu('y-scale') for coord in ['x', 'y']: menu = eval(coord + 'ScaleMenu') for type in axes.scale[coord].keys(): action = QAction(type, menu, checkable=True) if axes.scale[coord][type]: # if it's checked action.setEnabled(False) else: action.setEnabled(True) menu.addAction(action) action.setChecked(axes.scale[coord][type]) fcn = lambda event, coord=coord, type=type: self._set_scale( coord, type, axes, True) action.triggered.connect(fcn) # Create menu for AutoScale options X Y All popup_menu.addSeparator() autoscale_options = ['AutoScale X', 'AutoScale Y', 'AutoScale All'] for n, text in enumerate(autoscale_options): action = QAction(text, menu, checkable=True) if n < len(self.autoScale): action.setChecked(self.autoScale[n]) else: action.setChecked(all(self.autoScale)) popup_menu.addAction(action) action.toggled.connect( lambda event, n=n: self._setAutoScale(n, event, axes)) popup_menu.exec_(self.parent.mapToGlobal(pos))
class ObjectExplorer(BaseDialog, SpyderConfigurationAccessor): """Object explorer main widget window.""" CONF_SECTION = 'variable_explorer' def __init__(self, obj, name='', expanded=False, resize_to_contents=True, parent=None, attribute_columns=DEFAULT_ATTR_COLS, attribute_details=DEFAULT_ATTR_DETAILS, readonly=None, reset=False): """ Constructor :param name: name of the object as it will appear in the root node :param expanded: show the first visible root element expanded :param resize_to_contents: resize columns to contents ignoring width of the attributes :param obj: any Python object or variable :param attribute_columns: list of AttributeColumn objects that define which columns are present in the table and their defaults :param attribute_details: list of AttributeDetails objects that define which attributes can be selected in the details pane. :param reset: If true the persistent settings, such as column widths, are reset. """ QDialog.__init__(self, parent=parent) self.setAttribute(Qt.WA_DeleteOnClose) # Options show_callable_attributes = self.get_conf('show_callable_attributes') show_special_attributes = self.get_conf('show_special_attributes') # Model self._attr_cols = attribute_columns self._attr_details = attribute_details self.readonly = readonly self.btn_save_and_close = None self.btn_close = None self._tree_model = TreeModel(obj, obj_name=name, attr_cols=self._attr_cols) self._proxy_tree_model = TreeProxyModel( show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes) self._proxy_tree_model.setSourceModel(self._tree_model) # self._proxy_tree_model.setSortRole(RegistryTableModel.SORT_ROLE) self._proxy_tree_model.setDynamicSortFilter(True) # self._proxy_tree_model.setSortCaseSensitivity(Qt.CaseInsensitive) # Tree widget self.obj_tree = ToggleColumnTreeView() self.obj_tree.setAlternatingRowColors(True) self.obj_tree.setModel(self._proxy_tree_model) self.obj_tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.obj_tree.setUniformRowHeights(True) self.obj_tree.add_header_context_menu() # Views self._setup_actions() self._setup_menu(show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes) self._setup_views() if name: name = "{} -".format(name) self.setWindowTitle("{} {}".format(name, EDITOR_NAME)) self.setWindowFlags(Qt.Window) self._resize_to_contents = resize_to_contents self._readViewSettings(reset=reset) # Update views with model self.toggle_show_special_attribute_action.setChecked( show_special_attributes) self.toggle_show_callable_action.setChecked(show_callable_attributes) # Select first row so that a hidden root node will not be selected. first_row_index = self._proxy_tree_model.firstItemIndex() self.obj_tree.setCurrentIndex(first_row_index) if self._tree_model.inspectedNodeIsVisible or expanded: self.obj_tree.expand(first_row_index) def get_value(self): """Get editor current object state.""" return self._tree_model.inspectedItem.obj def _make_show_column_function(self, column_idx): """Creates a function that shows or hides a column.""" show_column = lambda checked: self.obj_tree.setColumnHidden( column_idx, not checked) return show_column def _setup_actions(self): """Creates the main window actions.""" # Show/hide callable objects self.toggle_show_callable_action = QAction( _("Show callable attributes"), self, checkable=True, shortcut=QKeySequence("Alt+C"), statusTip=_("Shows/hides attributes that are callable " "(functions, methods, etc)")) self.toggle_show_callable_action.toggled.connect( self._proxy_tree_model.setShowCallables) self.toggle_show_callable_action.toggled.connect( self.obj_tree.resize_columns_to_contents) # Show/hide special attributes self.toggle_show_special_attribute_action = QAction( _("Show __special__ attributes"), self, checkable=True, shortcut=QKeySequence("Alt+S"), statusTip=_("Shows or hides __special__ attributes")) self.toggle_show_special_attribute_action.toggled.connect( self._proxy_tree_model.setShowSpecialAttributes) self.toggle_show_special_attribute_action.toggled.connect( self.obj_tree.resize_columns_to_contents) def _setup_menu(self, show_callable_attributes=False, show_special_attributes=False): """Sets up the main menu.""" self.tools_layout = QHBoxLayout() callable_attributes = create_toolbutton( self, text=_("Show callable attributes"), icon=ima.icon("class"), toggled=self._toggle_show_callable_attributes_action) callable_attributes.setCheckable(True) callable_attributes.setChecked(show_callable_attributes) self.tools_layout.addWidget(callable_attributes) special_attributes = create_toolbutton( self, text=_("Show __special__ attributes"), icon=ima.icon("private2"), toggled=self._toggle_show_special_attributes_action) special_attributes.setCheckable(True) special_attributes.setChecked(show_special_attributes) self.tools_layout.addWidget(special_attributes) self.tools_layout.addStretch() self.options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) self.show_cols_submenu = QMenu(self) self.options_button.setMenu(self.show_cols_submenu) # Don't show menu arrow and remove padding if is_dark_interface(): self.options_button.setStyleSheet( ("QToolButton::menu-indicator{image: none;}\n" "QToolButton{padding: 3px;}")) else: self.options_button.setStyleSheet( "QToolButton::menu-indicator{image: none;}") self.tools_layout.addWidget(self.options_button) @Slot() def _toggle_show_callable_attributes_action(self): """Toggle show callable atributes action.""" action_checked = not self.toggle_show_callable_action.isChecked() self.toggle_show_callable_action.setChecked(action_checked) self.set_conf('show_callable_attributes', action_checked) @Slot() def _toggle_show_special_attributes_action(self): """Toggle show special attributes action.""" action_checked = ( not self.toggle_show_special_attribute_action.isChecked()) self.toggle_show_special_attribute_action.setChecked(action_checked) self.set_conf('show_special_attributes', action_checked) def _setup_views(self): """Creates the UI widgets.""" self.central_splitter = QSplitter(self, orientation=Qt.Vertical) layout = create_plugin_layout(self.tools_layout, self.central_splitter) self.setLayout(layout) # Stretch last column? # It doesn't play nice when columns are hidden and then shown again. obj_tree_header = self.obj_tree.header() obj_tree_header.setSectionsMovable(True) obj_tree_header.setStretchLastSection(False) add_actions(self.show_cols_submenu, self.obj_tree.toggle_column_actions_group.actions()) self.central_splitter.addWidget(self.obj_tree) # Bottom pane bottom_pane_widget = QWidget() bottom_layout = QHBoxLayout() bottom_layout.setSpacing(0) bottom_layout.setContentsMargins(5, 5, 5, 5) # left top right bottom bottom_pane_widget.setLayout(bottom_layout) self.central_splitter.addWidget(bottom_pane_widget) group_box = QGroupBox(_("Details")) bottom_layout.addWidget(group_box) v_group_layout = QVBoxLayout() h_group_layout = QHBoxLayout() h_group_layout.setContentsMargins(2, 2, 2, 2) # left top right bottom group_box.setLayout(v_group_layout) v_group_layout.addLayout(h_group_layout) # Radio buttons radio_widget = QWidget() radio_layout = QVBoxLayout() radio_layout.setContentsMargins(0, 0, 0, 0) # left top right bottom radio_widget.setLayout(radio_layout) self.button_group = QButtonGroup(self) for button_id, attr_detail in enumerate(self._attr_details): radio_button = QRadioButton(attr_detail.name) radio_layout.addWidget(radio_button) self.button_group.addButton(radio_button, button_id) self.button_group.buttonClicked[int].connect( self._change_details_field) self.button_group.button(0).setChecked(True) radio_layout.addStretch(1) h_group_layout.addWidget(radio_widget) # Editor widget self.editor = SimpleCodeEditor(self) self.editor.setReadOnly(True) h_group_layout.addWidget(self.editor) # Save and close buttons btn_layout = QHBoxLayout() btn_layout.addStretch() if not self.readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) v_group_layout.addLayout(btn_layout) # Splitter parameters self.central_splitter.setCollapsible(0, False) self.central_splitter.setCollapsible(1, True) self.central_splitter.setSizes([500, 320]) # Connect signals # Keep a temporary reference of the selection_model to prevent # segfault in PySide. # See http://permalink.gmane.org/gmane.comp.lib.qt.pyside.devel/222 selection_model = self.obj_tree.selectionModel() selection_model.currentChanged.connect(self._update_details) # Check if the values of the model have been changed self._proxy_tree_model.sig_setting_data.connect( self.save_and_close_enable) self._proxy_tree_model.sig_update_details.connect( self._update_details_for_item) # End of setup_methods def _readViewSettings(self, reset=False): """ Reads the persistent program settings. :param reset: If True, the program resets to its default settings. """ pos = QPoint(20, 20) window_size = QSize(825, 650) details_button_idx = 0 header = self.obj_tree.header() header_restored = False if reset: logger.debug("Resetting persistent view settings") else: pos = pos window_size = window_size details_button_idx = details_button_idx # splitter_state = settings.value("central_splitter/state") splitter_state = None if splitter_state: self.central_splitter.restoreState(splitter_state) # header_restored = self.obj_tree.read_view_settings( # 'table/header_state', # settings, reset) header_restored = False if not header_restored: column_sizes = [col.width for col in self._attr_cols] column_visible = [col.col_visible for col in self._attr_cols] for idx, size in enumerate(column_sizes): if not self._resize_to_contents and size > 0: # Just in case header.resizeSection(idx, size) else: header.resizeSections(QHeaderView.ResizeToContents) break for idx, visible in enumerate(column_visible): elem = self.obj_tree.toggle_column_actions_group.actions()[idx] elem.setChecked(visible) self.resize(window_size) button = self.button_group.button(details_button_idx) if button is not None: button.setChecked(True) @Slot() def save_and_close_enable(self): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) @Slot(QModelIndex, QModelIndex) def _update_details(self, current_index, _previous_index): """Shows the object details in the editor given an index.""" tree_item = self._proxy_tree_model.treeItem(current_index) self._update_details_for_item(tree_item) def _change_details_field(self, _button_id=None): """Changes the field that is displayed in the details pane.""" # logger.debug("_change_details_field: {}".format(_button_id)) current_index = self.obj_tree.selectionModel().currentIndex() tree_item = self._proxy_tree_model.treeItem(current_index) self._update_details_for_item(tree_item) @Slot(TreeItem) def _update_details_for_item(self, tree_item): """Shows the object details in the editor given an tree_item.""" try: # obj = tree_item.obj button_id = self.button_group.checkedId() assert button_id >= 0, ("No radio button selected. " "Please report this bug.") attr_details = self._attr_details[button_id] data = attr_details.data_fn(tree_item) self.editor.setPlainText(data) self.editor.setWordWrapMode(attr_details.line_wrap) self.editor.setup_editor( font=get_font(font_size_delta=DEFAULT_SMALL_DELTA), show_blanks=False, color_scheme=CONF.get('appearance', 'selected'), scroll_past_end=False, ) self.editor.set_text(data) if attr_details.name == 'Source code': self.editor.set_language('Python') else: self.editor.set_language('Rst') except Exception as ex: self.editor.setStyleSheet("color: red;") stack_trace = traceback.format_exc() self.editor.setPlainText("{}\n\n{}".format(ex, stack_trace)) self.editor.setWordWrapMode( QTextOption.WrapAtWordBoundaryOrAnywhere) @classmethod def create_explorer(cls, *args, **kwargs): """ Creates and shows and ObjectExplorer window. The *args and **kwargs will be passed to the ObjectExplorer constructor A (class attribute) reference to the browser window is kept to prevent it from being garbage-collected. """ object_explorer = cls(*args, **kwargs) object_explorer.exec_() return object_explorer
class MainWindow(QMainWindow): def __init__(self, config, window_theme: WindowTheme): super(MainWindow, self).__init__() self.session = None self.theme = window_theme self.node_packages = {} # {Node: str} self.script_UIs = [] # SESSION self.session = rc.Session(node_class=NodeBase) self.session.flow_view_created.connect(self.script_created) self.session.script_renamed.connect(self.script_renamed) self.session.script_deleted.connect(self.script_deleted) # LOAD DESIGN AND FLOW THEME self.session.design.load_from_config('design_config.json') if self.theme.name == 'dark': self.session.design.set_flow_theme(name='pure dark') else: # 'light' self.session.design.set_flow_theme(name='pure light') # UI self.setup_ui() self.flow_view_theme_actions = [] self.setup_menu_actions() self.setWindowTitle('Ryven') self.setWindowIcon(QIcon('../resources/pics/Ryven_icon.png')) self.ui.scripts_tab_widget.removeTab(0) # remove placeholder tab # SHORTCUTS save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) import_nodes_shortcut = QShortcut(QKeySequence('Ctrl+i'), self) import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered) # TEMP FOLDER if not os.path.exists('../temp'): os.mkdir('../temp') for f in os.listdir('../temp'): os.remove('temp/'+f) # PROJECT SETUP if 'info_msgs' in sys.argv: rc.InfoMsgs.enable() # SETUP MAIN CONSOLE MainConsole.instance.session = self.session MainConsole.instance.reset_interpreter() NodeBase.main_console = MainConsole.instance # REGISTER BUILT-IN NODES self.import_nodes(path=join(dirname(__file__), 'nodes/built_in/')) # LOAD PROJECT if config['config'] == 'create plain new project': self.session.create_script(title='hello world') elif config['config'] == 'open project': print('importing packages...') self.import_packages(config['required packages']) print('loading project...') self.session.load(config['content']) print('finished') self.resize(1500, 800) # self.showMaximized() def print_info(self): print(''' CONTROLS place: right mouse select: left mouse pan: right mouse save: ctrl+s import: ctrl+i ''') # UI def setup_ui(self): self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.statusBar.hide() # # nodes tree # self.nodes_tree_widget = NodesTreeListWidget(self, self.session) # self.ui.bottom_splitter.addWidget(self.nodes_tree_widget) # # node details widget # self.node_details_widget = NodeDetailsWidget(self, self.nodes_tree_widget) # self.ui.bottom_splitter.addWidget(self.node_details_widget) # main console if MainConsole.instance is not None: self.ui.bottom_splitter.addWidget(MainConsole.instance) # self.ui.right_vertical_splitter.setSizes([600, 0]) # splitter sizes # self.ui.left_vertical_splitter.setSizes([350, 350]) self.ui.main_vertical_splitter.setSizes([700, 0]) self.scripts_list_widget = rc.GUI.ScriptsList(self.session) self.scripts_list_widget.create_script_button.setProperty('class', 'small_button') self.scripts_list_widget.create_macro_button.setProperty('class', 'small_button') self.ui.scripts_groupBox.layout().addWidget(self.scripts_list_widget) def setup_menu_actions(self): # flow designs light_themes_menu = QMenu('light') for d in self.session.design.flow_themes: design_action = QAction(d.name, self) if d.type_ == 'dark': self.ui.menuFlow_Design_Style.addAction(design_action) else: light_themes_menu.addAction(design_action) design_action.triggered.connect(self.on_design_action_triggered) self.flow_view_theme_actions.append(design_action) self.ui.menuFlow_Design_Style.addMenu(light_themes_menu) self.ui.actionImport_Nodes.triggered.connect(self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect(self.on_save_project_triggered) self.ui.actionEnableInfoMessages.triggered.connect(self.on_enable_info_msgs_triggered) self.ui.actionDisableInfoMessages.triggered.connect(self.on_disable_info_msgs_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect(self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect(self.on_save_scene_pic_whole_triggered) # performance mode self.ac_perf_mode_fast = QAction('Fast', self) self.ac_perf_mode_fast.setCheckable(True) self.ac_perf_mode_pretty = QAction('Pretty', self) self.ac_perf_mode_pretty.setCheckable(True) perf_mode_ag = QActionGroup(self) perf_mode_ag.addAction(self.ac_perf_mode_fast) perf_mode_ag.addAction(self.ac_perf_mode_pretty) self.ac_perf_mode_fast.setChecked(self.session.design.performance_mode == 'fast') self.ac_perf_mode_pretty.setChecked(self.session.design.performance_mode == 'pretty') perf_mode_ag.triggered.connect(self.on_performance_mode_changed) perf_menu = QMenu('Performance Mode', self) perf_menu.addAction(self.ac_perf_mode_fast) perf_menu.addAction(self.ac_perf_mode_pretty) self.ui.menuView.addMenu(perf_menu) # animations self.ac_anims_active = QAction('Enabled', self) self.ac_anims_active.setCheckable(True) self.ac_anims_inactive = QAction('Disabled', self) self.ac_anims_inactive.setCheckable(True) anims_ag = QActionGroup(self) anims_ag.addAction(self.ac_anims_active) anims_ag.addAction(self.ac_anims_inactive) self.ac_anims_active.setChecked(self.session.design.animations_enabled) self.ac_anims_inactive.setChecked(not self.session.design.animations_enabled) anims_ag.triggered.connect(self.on_animation_enabling_changed) animations_menu = QMenu('Animations', self) animations_menu.addAction(self.ac_anims_active) animations_menu.addAction(self.ac_anims_inactive) self.ui.menuView.addMenu(animations_menu) def load_stylesheet(self, ss): ss_content = '' try: f = open('../resources/stylesheets/'+ss+'.txt') ss_content = f.read() f.close() finally: self.session.set_stylesheet(ss_content) self.setStyleSheet(ss_content) # SLOTS def on_import_nodes_triggered(self): file_path = QFileDialog.getOpenFileName(self, 'select nodes file', '../packages', '(*.py)',)[0] if file_path != '': self.import_nodes(path=dirname(file_path)) def on_performance_mode_changed(self, action): if action == self.ac_perf_mode_fast: self.session.design.set_performance_mode('fast') else: self.session.design.set_performance_mode('pretty') def on_animation_enabling_changed(self, action): if action == self.ac_anims_active: self.session.design.animations_enabled = True else: self.session.design.animations_enabled = False def on_design_action_triggered(self): index = self.flow_view_theme_actions.index(self.sender()) self.session.design.set_flow_theme(self.session.design.flow_themes[index]) def on_enable_info_msgs_triggered(self): rc.InfoMsgs.enable() def on_disable_info_msgs_triggered(self): rc.InfoMsgs.disable() def on_save_scene_pic_viewport_triggered(self): """Saves a picture of the currently visible viewport.""" if len(self.session.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] script = self.ui.scripts_tab_widget.currentWidget().script view = self.session.flow_views[script] img = view.get_viewport_img() img.save(file_path) def on_save_scene_pic_whole_triggered(self): """Saves a picture of the whole currently visible scene.""" if len(self.session.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] script = self.ui.scripts_tab_widget.currentWidget().script view = self.session.flow_views[script] img = view.get_whole_scene_img() img.save(file_path) def on_save_project_triggered(self): file_name = QFileDialog.getSaveFileName(self, 'select location and give file name', '../saves', '(*.json)')[0] if file_name != '': self.save_project(file_name) # SESSION def script_created(self, script, flow_view): script_widget = ScriptUI(self, script, flow_view) self.script_UIs.append(script_widget) self.ui.scripts_tab_widget.addTab(script_widget, script.title) def script_renamed(self, script): script_UI, index = self.get_script_ui(script) if index != -1: self.ui.scripts_tab_widget.setTabText( index, script.title ) def script_deleted(self, script): script_UI, index = self.get_script_ui(script) self.ui.scripts_tab_widget.removeTab(index) self.script_UIs.remove(script_UI) def get_script_ui(self, script): script_UI = None index = -1 for index in range(len(self.script_UIs)): sui = self.script_UIs[index] if sui.script == script: script_UI = sui break return script_UI, index def get_current_script(self): return self.session.scripts[self.ui.scripts_tab_widget.currentIndex()] def import_packages(self, packages_list: [NodesPackage]): for p in packages_list: self.import_nodes(p) def import_nodes(self, package: NodesPackage = None, path: str = None): if package is not None: p = package else: p = NodesPackage(path) try: nodes = import_nodes_package(p) except ModuleNotFoundError as e: QMessageBox.warning(self, title='Missing Python module', text=str(e)) sys.exit() self.session.register_nodes(nodes) for n in nodes: self.node_packages[n] = p # self.nodes_tree_widget.update_list() def save_project(self, file_name): import json file = None try: if os.path.exists(file_name): os.remove(file_name) file = open(file_name, 'w') except FileNotFoundError: rc.InfoMsgs.write('couldn\'t open file') return general_project_info_dict = {'type': 'Ryven project file'} scripts_data = self.session.serialize() required_packages = set() for node in self.session.all_node_objects(): if node.__class__ not in self.node_packages.keys() or \ self.node_packages[node.__class__] is None or \ self.node_packages[node.__class__].name == 'built_in': continue required_packages.add( self.node_packages[node.__class__] ) whole_project_dict = {'general info': general_project_info_dict, 'required packages': [p.config_data() for p in required_packages], **scripts_data} data = json.dumps(whole_project_dict, indent=4) rc.InfoMsgs.write(data) file.write(data) file.close()
class Viewer(ConfigurableMoveableTabContainer): """ Contains multiple TabbedViewingAreas """ tab_titles = Signal([tuple]) num_viewing_areas = Integer(2, config=True) def __init__(self, *args, menuBar, **kwargs): super().__init__(*args, **kwargs) self._run_to_tabs = collections.defaultdict(list) self._title_to_tab = {} self._tabs_from_streaming = [] self._overplot = OverPlotState.individual_tab self._overplot_target = None self._live_enabled = False self._live_run_router = RunRouter([self.route_live_stream]) self._containers = [ TabbedViewingArea(viewer=self, menuBar=menuBar) for _ in range(self.num_viewing_areas) ] layout = QVBoxLayout() splitter = QSplitter(Qt.Vertical) layout.addWidget(splitter) for container in self._containers: splitter.addWidget(container) self.setLayout(layout) overplot_group = QActionGroup(self) self.off = QAction('&Off', self) self.off.setStatusTip('Drop streaming data.') self.individual_tab = QAction('&New Tab', self) self.individual_tab.setStatusTip('Open a new viewer tab for each Run.') self.latest_live = QAction('&Latest Live Tab', self) self.latest_live.setStatusTip( 'Attempt to overplot on the most recent live Run.') self.fixed = QAction('&Fixed Tab...', self) self.fixed.setStatusTip('Attempt to overplot on a specific tab.') self.fixed.setEnabled(False) overplot_group.addAction(self.off) overplot_group.addAction(self.individual_tab) overplot_group.addAction(self.latest_live) overplot_group.addAction(self.fixed) for action in overplot_group.actions(): action.setCheckable(True) overplot_group.setExclusive(True) self.off.setChecked(True) overplot_menu = menuBar().addMenu('&Streaming') overplot_menu.addActions(overplot_group.actions()) self.off.triggered.connect(self.disable_live) self.individual_tab.triggered.connect( partial(self.set_overplot_state, OverPlotState.individual_tab)) self.latest_live.triggered.connect( partial(self.set_overplot_state, OverPlotState.latest_live)) def set_overplot_target(): item, ok = QInputDialog.getItem(self, "Select Tab", "Tab", tuple(self._title_to_tab), 0, False) if not ok: # Abort and fallback to Off. Would be better to fall back to # previous state (which could be latest_live) but it's not # clear how to know what that state was. self.off.setChecked(True) return self.set_overplot_state(OverPlotState.fixed) self._overplot_target = item self.fixed.triggered.connect(set_overplot_target) def enable_live(self): self._live_enabled = True def disable_live(self): self._live_enabled = False def consumer(self, item): """Slot that receives (name, doc) and unpacks it into RunRouter.""" self._live_run_router(*item) def route_live_stream(self, name, start_doc): """Create or choose a Viewer to receive this Run.""" if not self._live_enabled: log.debug("Streaming Run ignored because Streaming is disabled.") return [], [] self.fixed.setEnabled(True) target_area = self._containers[0] uid = start_doc['uid'] if self._overplot == OverPlotState.individual_tab: viewer = RunViewer() tab_title = uid[:8] index = target_area.addTab(viewer, tab_title) self._title_to_tab[tab_title] = viewer self._tabs_from_streaming.append(viewer) target_area.setCurrentIndex(index) self.tab_titles.emit(tuple(self._title_to_tab)) elif self._overplot == OverPlotState.fixed: viewer = self._title_to_tab[self._overplot_target] elif self._overplot == OverPlotState.latest_live: if self._tabs_from_streaming: viewer = self._tabs_from_streaming[-1] else: viewer = RunViewer() tab_title = uid[:8] index = target_area.addTab(viewer, tab_title) self._title_to_tab[tab_title] = viewer self._tabs_from_streaming.append(viewer) target_area.setCurrentIndex(index) self.tab_titles.emit(tuple(self._title_to_tab)) self._run_to_tabs[uid].append(viewer) viewer.run_router('start', start_doc) return [viewer.run_router], [] def show_entries(self, target, entries): self.fixed.setEnabled(True) target_area = self._containers[0] if not target: # Add new Viewer tab. viewer = RunViewer() if len(entries) == 1: entry, = entries uid = entry.describe()['metadata']['start']['uid'] tab_title = uid[:8] else: tab_title = self.get_title() index = target_area.addTab(viewer, tab_title) self._title_to_tab[tab_title] = viewer target_area.setCurrentIndex(index) self.tab_titles.emit(tuple(self._title_to_tab)) else: viewer = self._title_to_tab[target] for entry in entries: viewer.load_entry(entry) uid = entry.describe()['metadata']['start']['uid'] self._run_to_tabs[uid].append(viewer) # TODO Make last entry in the list the current widget. def get_title(self): for i in itertools.count(1): title = f'Group {i}' if title in self._title_to_tab: continue return title def set_overplot_state(self, state): self.enable_live() log.debug('Overplot state is %s', state) self._overplot = state def close_run_viewer(self, widget): try: self._tabs_from_streaming.remove(widget) except ValueError: pass for uid in widget.uids: self._run_to_tabs[uid].remove(widget) for title, tab in list(self._title_to_tab.items()): if tab == widget: del self._title_to_tab[title] self.tab_titles.emit(tuple(self._title_to_tab)) if title == self._overplot_target: self.set_overplot_state(OverPlotState.off) if not self._title_to_tab: self.fixed.setEnabled(False)
def __init__(self, control, path, fgprintindex): Q7Window.__init__(self, Q7Window.VIEW_TREE, control, path, fgprintindex) self._depthExpanded = 0 self._lastEntered = None self.lastdiag = None self._linkwindow = None self._querywindow = None self._vtkwindow = None self._selectwindow = None self._column = {NMT.COLUMN_SIDS: OCTXT.ShowSIDSColumn, NMT.COLUMN_FLAG_LINK: OCTXT.ShowLinkColumn, NMT.COLUMN_FLAG_SELECT: OCTXT.ShowSelectColumn, NMT.COLUMN_FLAG_CHECK: OCTXT.ShowCheckColumn, NMT.COLUMN_FLAG_USER: OCTXT.ShowUserColumn, NMT.COLUMN_SHAPE: OCTXT.ShowShapeColumn, NMT.COLUMN_DATATYPE: OCTXT.ShowDataTypeColumn} self.selectForLinkSrc = None # one link source per tree view allowed #self.treeview.expanded[QModelIndex].connect(self.expandNode) self.treeview.collapsed.connect(self.collapseNode) self.treeview.pressed[QModelIndex].connect(self.clickedPressedNode) self.treeview.customContextMenuRequested.connect(self.clickedNode) # QObject.connect(self.treeview, # SIGNAL("expanded(QModelIndex)"), # self.expandNode) # QObject.connect(self.treeview, # SIGNAL("collapsed()"), # self.collapseNode) # QObject.connect(self.treeview, # SIGNAL("pressed(QModelIndex)"), # self.clickedPressedNode) # QObject.connect(self.treeview, # SIGNAL("customContextMenuRequested(QPoint)"), # self.clickedNode) self.bSave.clicked.connect(self.savetree) self.lockable(self.bSave) self.bQueryView.clicked.connect(self.queryview) self.lockable(self.bQueryView) self.bSaveAs.clicked.connect(self.savetreeas) self.lockable(self.bSaveAs) self.bInfo.clicked.connect(self.infoTreeView) self.bZoomIn.clicked.connect(self.expandLevel) self.bZoomOut.clicked.connect(self.collapseLevel) self.bZoomAll.clicked.connect(self.expandMinMax) self.bFormView.clicked.connect(self.formview) self.bMarkAll.clicked.connect(self.markall) self.bUnmarkAll_1.clicked.connect(self.unmarkall) self.bUnmarkAll_2.clicked.connect(self.unmarkall) self.bPreviousMark.clicked.connect(self.previousmark) self.bNextMark.clicked.connect(self.nextmark) self.bSwapMarks.clicked.connect(self.swapmarks) self.bMarksAsList.clicked.connect(self.selectionlist) self.bVTKView.clicked.connect(self.vtkview) self.lockable(self.bVTKView) self.bScreenShot.clicked.connect(self.screenshot) self.bCheck.clicked.connect(self.check) self.bCheckList.clicked.connect(self.checklist) self.bClearChecks.clicked.connect(self.clearchecks) self.bLinkView.clicked.connect(self.linklist) self.bPatternView.clicked.connect(self.patternlist) self.bToolsView.clicked.connect(self.tools) self.setContextMenuPolicy(Qt.CustomContextMenu) self.popupmenu = QMenu() self.diagview = None lmodel = self.FG.model self.treeview.setModel(lmodel) self.treeview.setItemDelegate(Q7TreeItemDelegate(self.treeview, lmodel)) self.treeview.setControlWindow(self, self.FG.index) if (self._control.transientRecurse or OCTXT.RecursiveTreeDisplay): self.expandMinMax() if (self._control.transientVTK): self.vtkview() self._control.transientRecurse = False self._control.transientVTK = False self.clearchecks() # self.bCheckList.setDisabled(True) if (not OCTXT._HasProPackage): self.bToolsView.setDisabled(True) self.bCheckView.setDisabled(True) self.bPatternDB.setDisabled(True) self.bAddLink.clicked.connect(self.linkadd) self.bSelectLinkSrc.clicked.connect(self.linkselectsrc) self.bSelectLinkDst.clicked.connect(self.linkselectdst) self.bAddLink.setDisabled(True) self.lineEdit.returnPressed.connect(self.jumpToNode) # QObject.connect(self.lineEdit, # SIGNAL("returnPressed()"), # self.jumpToNode) tvh = self.treeview.header() tvh.setContextMenuPolicy(Qt.CustomContextMenu) tvh.customContextMenuRequested.connect(self.headerMenu) self._hmenu = QMenu() self._hmenu._idx = {} self._tlist = (('SIDS type', NMT.COLUMN_SIDS), ('Link flag', NMT.COLUMN_FLAG_LINK), ('Mark flag', NMT.COLUMN_FLAG_SELECT), ('Check flag', NMT.COLUMN_FLAG_CHECK), ('User flag', NMT.COLUMN_FLAG_USER), ('Shape', NMT.COLUMN_SHAPE), ('Data type', NMT.COLUMN_DATATYPE)) for (tag, idx) in self._tlist: a = QAction(tag, self._hmenu, checkable=True) self._hmenu._idx[idx] = a if (self._column[idx]): a.setChecked(True) else: a.setChecked(False) self._hmenu.addAction(a) self.treeview.setColumnHidden(idx, not self._column[idx]) self._recursiveAddNewNode = False self.updateTreeStatus()
class MainWindow(QMainWindow): signal_fitting_parameters_changed = Signal() def __init__(self, *, gpc): """ Parameters ---------- gpc: object reference to a class that holds references to processing classes. """ super().__init__() self._cursor_set = False # Indicates if temporary 'wait' cursor is set self.gpc = gpc self.gui_vars = global_gui_variables self.gui_vars["ref_main_window"] = self self.wnd_manage_emission_lines = WndManageEmissionLines(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_compute_roi_maps = WndComputeRoiMaps(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_image_wizard = WndImageWizard(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_load_quantitative_calibration = WndLoadQuantitativeCalibration( gpc=self.gpc, gui_vars=self.gui_vars ) self.wnd_general_fitting_settings = WndGeneralFittingSettings(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_fitting_parameters_shared = WndDetailedFittingParamsShared(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_fitting_parameters_lines = WndDetailedFittingParamsLines(gpc=self.gpc, gui_vars=self.gui_vars) # Indicates that the window was closed (used mostly for testing) self._is_closed = False global_gui_variables["gui_state"]["databroker_available"] = self.gpc.is_databroker_available() self.initialize() self.central_widget.left_panel.load_data_widget.update_main_window_title.connect(self.update_window_title) # Set the callback for update forms with fitting parameters def update_fitting_parameter_forms(): self.signal_fitting_parameters_changed.emit() gpc.add_parameters_changed_cb(update_fitting_parameter_forms) gpc.param_model.parameters_changed() def initialize(self): self.resize(_main_window_geometry["initial_width"], _main_window_geometry["initial_height"]) self.setMinimumWidth(_main_window_geometry["min_width"]) self.setMinimumHeight(_main_window_geometry["min_height"]) self.setWindowTitle(self.gpc.get_window_title()) self.central_widget = TwoPanelWidget(gpc=self.gpc, gui_vars=self.gui_vars) self.setCentralWidget(self.central_widget) # Status bar self.statusLabel = QLabel() self.statusBar().addWidget(self.statusLabel) self.statusProgressBar = QProgressBar() self.statusProgressBar.setFixedWidth(200) self.statusBar().addPermanentWidget(self.statusProgressBar) self.statusLabelDefaultText = "No data is loaded" self.statusLabel.setText(self.statusLabelDefaultText) # 'Scan Data' menu item self.action_read_file = QAction("&Read File...", self) self.action_read_file.setStatusTip("Load data from HDF5 file") self.action_read_file.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_file.clicked) self.action_load_run = QAction("&Load Run...", self) self.action_load_run.setEnabled(self.gui_vars["gui_state"]["databroker_available"]) self.action_load_run.setStatusTip("Load data from database (Databroker)") self.action_load_run.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_dbase.clicked) self.action_view_metadata = QAction("View Metadata...", self) self.action_view_metadata.setEnabled(self.gpc.is_scan_metadata_available()) self.action_view_metadata.setStatusTip("View metadata for loaded run") self.action_view_metadata.triggered.connect( self.central_widget.left_panel.load_data_widget.pb_view_metadata.clicked ) # Main menu menubar = self.menuBar() # Disable native menu bar (it doesn't work on MacOS 10.15 with PyQt<=5.11) # It may work with later versions of PyQt when they become available. menubar.setNativeMenuBar(False) loadData = menubar.addMenu("Scan &Data") loadData.addAction(self.action_read_file) loadData.addAction(self.action_load_run) loadData.addSeparator() loadData.addAction(self.action_view_metadata) # 'Fitting Model' menu item self.action_lines_find_automatically = QAction("Find &Automatically...", self) self.action_lines_find_automatically.setStatusTip("Automatically find emission lines in total spectrum") self.action_lines_find_automatically.triggered.connect( self.central_widget.left_panel.model_widget.pb_find_elines.clicked ) self.action_lines_load_from_file = QAction("Load From &File...", self) self.action_lines_load_from_file.setStatusTip( "Load processing parameters, including selected emission lines, from JSON file" ) self.action_lines_load_from_file.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_elines.clicked ) self.action_lines_load_quant_standard = QAction("Load &Quantitative Standards...", self) self.action_lines_load_quant_standard.setStatusTip( "Load quantitative standard. The emission lines from the standard are automatically selected" ) self.action_lines_load_quant_standard.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_qstandard.clicked ) self.action_add_remove_emission_lines = QAction("&Add/Remove Emission Lines...", self) self.action_add_remove_emission_lines.setStatusTip("Manually add and remove emission lines") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_manage_emission_lines.clicked ) self.action_save_model_params = QAction("&Save Model Parameters...", self) self.action_save_model_params.setStatusTip("Save model parameters to JSON file") self.action_save_model_params.triggered.connect( self.central_widget.left_panel.model_widget.pb_save_elines.clicked ) self.action_add_remove_emission_lines = QAction("Start Model &Fitting", self) self.action_add_remove_emission_lines.setStatusTip("Run computations: start fitting for total spectrum") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_start_fitting.clicked ) fittingModel = menubar.addMenu("Fitting &Model") emissionLines = fittingModel.addMenu("&Emission Lines") emissionLines.addAction(self.action_lines_find_automatically) emissionLines.addAction(self.action_lines_load_from_file) emissionLines.addAction(self.action_lines_load_quant_standard) fittingModel.addAction(self.action_add_remove_emission_lines) fittingModel.addSeparator() fittingModel.addAction(self.action_save_model_params) fittingModel.addSeparator() fittingModel.addAction(self.action_add_remove_emission_lines) # "XRF Maps" menu item self.action_start_xrf_map_fitting = QAction("Start XRF Map &Fitting", self) self.action_start_xrf_map_fitting.setStatusTip("Run computations: start fitting for XRF maps") self.action_start_xrf_map_fitting.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_start_map_fitting.clicked ) self.action_compute_rois = QAction("Compute &ROIs...", self) self.action_compute_rois.setStatusTip("Compute XRF Maps based on spectral ROIs") self.action_compute_rois.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_compute_roi_maps.clicked ) self.action_load_quant_calibration = QAction("&Load Quantitative Calibration...", self) self.action_load_quant_calibration.setStatusTip( "Load quantitative calibration from JSON file. Calibration is used for scaling of XRF Maps" ) self.action_load_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_load_quant_calib.clicked ) self.action_save_quant_calibration = QAction("&Save Quantitative Calibration...", self) self.action_save_quant_calibration.setStatusTip( "Save Quantitative Calibration based on XRF map of the standard sample" ) self.action_save_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_save_q_calibration.clicked ) self.action_export_to_tiff_and_txt = QAction("&Export to TIFF and TXT...", self) self.action_export_to_tiff_and_txt.setStatusTip("Export XRF Maps as TIFF and/or TXT files") self.action_export_to_tiff_and_txt.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_export_to_tiff_and_txt.clicked ) xrfMaps = menubar.addMenu("XRF &Maps") xrfMaps.addAction(self.action_start_xrf_map_fitting) xrfMaps.addAction(self.action_compute_rois) xrfMaps.addSeparator() xrfMaps.addAction(self.action_load_quant_calibration) xrfMaps.addSeparator() xrfMaps.addAction(self.action_save_quant_calibration) xrfMaps.addAction(self.action_export_to_tiff_and_txt) # "View" menu item self.action_show_matplotlib_toolbar = QAction("Show &Matplotlib Toolbar", self) self.action_show_matplotlib_toolbar.setCheckable(True) self.action_show_matplotlib_toolbar.setChecked(True) self.action_show_matplotlib_toolbar.setStatusTip("Show Matplotlib Toolbar on the plots") self.action_show_matplotlib_toolbar.toggled.connect(self.action_show_matplotlib_toolbar_toggled) self.action_show_widget_tooltips = QAction("Show &Tooltips", self) self.action_show_widget_tooltips.setCheckable(True) self.action_show_widget_tooltips.setChecked(self.gui_vars["show_tooltip"]) self.action_show_widget_tooltips.setStatusTip("Show widget tooltips") self.action_show_widget_tooltips.toggled.connect(self.action_show_widget_tooltips_toggled) options = menubar.addMenu("&Options") options.addAction(self.action_show_widget_tooltips) options.addAction(self.action_show_matplotlib_toolbar) # "Help" menu item self.action_online_docs = QAction("Online &Documentation", self) self.action_online_docs.setStatusTip("Open online documentation in the default browser") self.action_online_docs.triggered.connect(self.action_online_docs_triggered) self.action_about = QAction("&About PyXRF", self) self.action_about.setStatusTip("Show information about this program") self.action_about.triggered.connect(self.action_about_triggered) help = menubar.addMenu("&Help") help.addAction(self.action_online_docs) help.addSeparator() help.addAction(self.action_about) self.update_widget_state() # Connect signals self.central_widget.left_panel.load_data_widget.update_preview_map_range.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_count.update_map_range ) # Before loading a new file or run self.central_widget.left_panel.load_data_widget.signal_loading_new_run.connect( self.central_widget.right_panel.slot_activate_tab_preview ) # Open a new file or run self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.slot_activate_load_data_tab ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.slot_activate_tab_preview ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(self.slot_new_run_loaded) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_load_quantitative_calibration.update_all_data ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.fit_maps_widget.slot_update_for_new_loaded_run ) # New model is loaded or processing parameters (incident energy) was changed self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.wnd_manage_emission_lines.update_widget_data ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.redraw_plot_fit ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.slot_activate_tab_fitting_model ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_plot_fitting_model.update_controls ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.wnd_manage_emission_lines.update_widget_data ) # XRF Maps dataset changed self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_rgb_maps.signal_rgb_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_xrf_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_norm_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Quantitative calibration changed self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_ranges ) self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Selected element is changed (tools for emission line selection) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.wnd_manage_emission_lines.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_add_line.connect( self.wnd_manage_emission_lines.pb_add_eline_clicked ) self.central_widget.right_panel.tab_plot_fitting_model.signal_remove_line.connect( self.wnd_manage_emission_lines.pb_remove_eline_clicked ) self.wnd_manage_emission_lines.signal_update_element_selection_list.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_eline_selection_list ) self.wnd_manage_emission_lines.signal_update_add_remove_btn_state.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_add_remove_btn_state ) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) # Total spectrum fitting completed self.central_widget.left_panel.model_widget.signal_total_spectrum_fitting_completed.connect( self.wnd_manage_emission_lines.update_eline_table ) # Total spectrum invalidated self.wnd_manage_emission_lines.signal_parameters_changed.connect( self.central_widget.left_panel.model_widget.update_fit_status ) # New dataset loaded or different channel selected. Compute fit parameters. self.central_widget.left_panel.load_data_widget.signal_data_channel_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) # Update map datasets (Fitted maps) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) # Update map datasets (ROI maps) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) self.signal_fitting_parameters_changed.connect(self.wnd_general_fitting_settings.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_shared.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_lines.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_manage_emission_lines.update_widget_data) @Slot() @Slot(str) def update_widget_state(self, condition=None): # Update the state of the menu bar state = not self.gui_vars["gui_state"]["running_computations"] self.menuBar().setEnabled(state) state_computations = self.gui_vars["gui_state"]["running_computations"] if state_computations: if not self._cursor_set: QGuiApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self._cursor_set = True else: if self._cursor_set: QGuiApplication.restoreOverrideCursor() self._cursor_set = False # Forward to children self.central_widget.update_widget_state(condition) # Forward the updates to open windows self.wnd_manage_emission_lines.update_widget_state(condition) self.wnd_compute_roi_maps.update_widget_state(condition) self.wnd_image_wizard.update_widget_state(condition) self.wnd_load_quantitative_calibration.update_widget_state(condition) self.wnd_general_fitting_settings.update_widget_state(condition) self.wnd_fitting_parameters_shared.update_widget_state(condition) self.wnd_fitting_parameters_lines.update_widget_state(condition) def closeEvent(self, event): mb_close = QMessageBox( QMessageBox.Question, "Exit", "Are you sure you want to EXIT the program?", QMessageBox.Yes | QMessageBox.No, parent=self, ) mb_close.setDefaultButton(QMessageBox.No) if mb_close.exec() == QMessageBox.Yes: event.accept() self.wnd_manage_emission_lines.close() self.wnd_compute_roi_maps.close() self.wnd_image_wizard.close() self.wnd_load_quantitative_calibration.close() self.wnd_general_fitting_settings.close() self.wnd_fitting_parameters_shared.close() self.wnd_fitting_parameters_lines.close() # This flag is used for CI tests self._is_closed = True else: event.ignore() def action_online_docs_triggered(self): """ Display online documentation: open the URL in the default browser. """ doc_url = "http://nsls-ii.github.io/PyXRF/" try: webbrowser.open(doc_url, autoraise=True) except Exception as ex: logger.error(f"Error occurred while opening URL '{doc_url}' in the default browser") msg = f"Failed to Open Online Documentation. \n Exception: {str(ex)}" msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def action_about_triggered(self): """ Display 'About' dialog box """ dlg = DialogAbout() dlg.exec() def action_show_matplotlib_toolbar_toggled(self, state): """ Turn tooltips on or off """ self.gui_vars["show_matplotlib_toolbar"] = state self.update_widget_state() def action_show_widget_tooltips_toggled(self, state): """ Turn tooltips on or off """ self.gui_vars["show_tooltip"] = state self.update_widget_state("tooltips") @Slot() def update_window_title(self): self.setWindowTitle(self.gpc.get_window_title()) @Slot(bool) def slot_new_run_loaded(self, success): if success: # Update status bar file_name = self.gpc.get_loaded_file_name() run_id, run_uid = "", "" if self.gpc.is_scan_metadata_available(): try: run_id = self.gpc.get_metadata_scan_id() except Exception: pass try: run_uid = self.gpc.get_metadata_scan_uid() except Exception: pass else: if self.gpc.get_current_run_id() >= 0: run_id = self.gpc.get_current_run_id() s = "" if run_id: s += f"ID: {run_id} " if run_uid: if run_id: s += f"({run_uid}) " else: s += f"UID: {run_uid} " if file_name: s += f"File: '{file_name}'" self._set_status_bar_text(s) # Activate/deactivate "View Metadata ..." menu item if self.gpc.is_scan_metadata_available(): self.action_view_metadata.setEnabled(True) else: self.action_view_metadata.setEnabled(False) else: self._set_status_bar_text() def _set_status_bar_text(self, text=None): if text is None: text = self.statusLabelDefaultText self.statusLabel.setText(text)
def getMenuAction(self, menu, title='notitle', action_name='noaction', args=[], kwargs={}): # ToDo: Clean this up, it is very hacky env = {'app': QApplication.instance(), 'win': self, 'action': actions, } if action_name is not None: if action_name.startswith('settings.'): setting_id = action_name[len('settings.'):] setting = getSetting(setting_id) if setting: if setting.enum_options is not None: submenu = QMenu(parent=self, title=title) group = QActionGroup(self) group.setExclusive(True) group.triggered.connect(lambda a: setting.setValue(a.data())) def update(group, val): for act in group.actions(): if act.data() == val: act.setChecked(True) break for num, opt in enumerate(setting.enum_options): act = QAction(parent=self, text=opt) act.setCheckable(True) if setting.value == num: act.setChecked(True) act.setData(num) setting.notify(lambda v: update(group, v)) act.setActionGroup(group) submenu.addAction(act) menu.addMenu(submenu) elif setting.value_type == bool: # works for bool settings menu_action = QAction(parent=self, text=title) menu_action.setCheckable(True) menu_action.triggered.connect(setting.setValue) setting.notify(menu_action.setChecked) menu.addAction(menu_action) return try: menu_action = QAction(parent=self, text=title) mod, action = action_name.split('.', 1) method = getattr(env.get(mod, self), action) if menu_action.isCheckable(): menu_action.triggered.connect(method) else: menu_action.triggered.connect(lambda checked: method(*args, **kwargs)) menu.addAction(menu_action) return except: pass try: menu_action = QAction(parent=self, text=title) actions.bindWidget(menu_action, action_name) menu.addAction(menu_action) return except actions.InvalidAction: LOG.exception('Error binding menu action %s', action_name) menu_action = QAction(parent=self, text=title) msg = "The <b>{}</b> action specified for the " \ "<b>{}</b> menu item could not be triggered. " \ "Check the YAML config file for errors." \ .format(action_name or '', title.replace('&', '')) menu_action.triggered.connect(lambda: QMessageBox.critical(self, "Menu Action Error!", msg)) menu.addAction(menu_action)
class PlotMainWindow(QWidget): """Base class for plot main windows.""" def __init__(self, U, plot, length=1, title=None): super().__init__() layout = QVBoxLayout() if title: title = QLabel('<b>' + title + '</b>') title.setAlignment(Qt.AlignHCenter) layout.addWidget(title) layout.addWidget(plot) plot.set(U, 0) if length > 1: hlayout = QHBoxLayout() self.slider = QSlider(Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(length - 1) self.slider.setTickPosition(QSlider.TicksBelow) hlayout.addWidget(self.slider) lcd = QLCDNumber(m.ceil(m.log10(length))) lcd.setDecMode() lcd.setSegmentStyle(QLCDNumber.Flat) hlayout.addWidget(lcd) layout.addLayout(hlayout) hlayout = QHBoxLayout() toolbar = QToolBar() self.a_play = QAction( self.style().standardIcon(QStyle.SP_MediaPlay), 'Play', self) self.a_play.setCheckable(True) self.a_rewind = QAction( self.style().standardIcon(QStyle.SP_MediaSeekBackward), 'Rewind', self) self.a_toend = QAction( self.style().standardIcon(QStyle.SP_MediaSeekForward), 'End', self) self.a_step_backward = QAction( self.style().standardIcon(QStyle.SP_MediaSkipBackward), 'Step Back', self) self.a_step_forward = QAction( self.style().standardIcon(QStyle.SP_MediaSkipForward), 'Step', self) self.a_loop = QAction( self.style().standardIcon(QStyle.SP_BrowserReload), 'Loop', self) self.a_loop.setCheckable(True) toolbar.addAction(self.a_play) toolbar.addAction(self.a_rewind) toolbar.addAction(self.a_toend) toolbar.addAction(self.a_step_backward) toolbar.addAction(self.a_step_forward) toolbar.addAction(self.a_loop) if hasattr(self, 'save'): self.a_save = QAction( self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) self.a_save.triggered.connect(self.save) hlayout.addWidget(toolbar) self.speed = QSlider(Qt.Horizontal) self.speed.setMinimum(0) self.speed.setMaximum(100) hlayout.addWidget(QLabel('Speed:')) hlayout.addWidget(self.speed) layout.addLayout(hlayout) self.timer = QTimer() self.timer.timeout.connect(self.update_solution) self.slider.valueChanged.connect(self.slider_changed) self.slider.valueChanged.connect(lcd.display) self.speed.valueChanged.connect(self.speed_changed) self.a_play.toggled.connect(self.toggle_play) self.a_rewind.triggered.connect(self.rewind) self.a_toend.triggered.connect(self.to_end) self.a_step_forward.triggered.connect(self.step_forward) self.a_step_backward.triggered.connect(self.step_backward) self.speed.setValue(50) elif hasattr(self, 'save'): hlayout = QHBoxLayout() toolbar = QToolBar() self.a_save = QAction( self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save', self) toolbar.addAction(self.a_save) hlayout.addWidget(toolbar) layout.addLayout(hlayout) self.a_save.triggered.connect(self.save) self.setLayout(layout) self.plot = plot self.U = U self.length = length def slider_changed(self, ind): self.plot.set(self.U, ind) def speed_changed(self, val): self.timer.setInterval(val * 20) def update_solution(self): ind = self.slider.value() + 1 if ind >= self.length: if self.a_loop.isChecked(): ind = 0 else: self.a_play.setChecked(False) return self.slider.setValue(ind) def toggle_play(self, checked): if checked: if self.slider.value() + 1 == self.length: self.slider.setValue(0) self.timer.start() else: self.timer.stop() def rewind(self): self.slider.setValue(0) def to_end(self): self.a_play.setChecked(False) self.slider.setValue(self.length - 1) def step_forward(self): self.a_play.setChecked(False) ind = self.slider.value() + 1 if ind == self.length and self.a_loop.isChecked(): ind = 0 if ind < self.length: self.slider.setValue(ind) def step_backward(self): self.a_play.setChecked(False) ind = self.slider.value() - 1 if ind == -1 and self.a_loop.isChecked(): ind = self.length - 1 if ind >= 0: self.slider.setValue(ind)