def _add_plot_type_option_menu(self, menu, ax): with errorbar_caps_removed(ax): # Able to change the plot type to waterfall if there is only one axes, it is a MantidAxes, and there is more # than one line on the axes. if len(ax.get_figure().get_axes()) > 1 or not isinstance( ax, MantidAxes) or len(ax.get_lines()) <= 1: return plot_type_menu = QMenu("Plot Type", menu) plot_type_action_group = QActionGroup(plot_type_menu) standard = plot_type_menu.addAction( "1D", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) waterfall = plot_type_menu.addAction( "Waterfall", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) for action in [waterfall, standard]: plot_type_action_group.addAction(action) action.setCheckable(True) if ax.is_waterfall(): waterfall.setChecked(True) else: standard.setChecked(True) menu.addMenu(plot_type_menu)
def _add_normalization_option_menu(self, menu, ax): # Check if toggling normalization makes sense can_toggle_normalization = self._can_toggle_normalization(ax) if not can_toggle_normalization: return None # Create menu norm_menu = QMenu("Normalization", menu) norm_actions_group = QActionGroup(norm_menu) none_action = norm_menu.addAction( 'None', lambda: self._set_normalization_none(ax)) norm_action = norm_menu.addAction( 'Bin Width', lambda: self._set_normalization_bin_width(ax)) for action in [none_action, norm_action]: norm_actions_group.addAction(action) action.setCheckable(True) # Update menu state is_normalized = self._is_normalized(ax) if is_normalized: norm_action.setChecked(True) else: none_action.setChecked(True) menu.addMenu(norm_menu)
def _connect_buttons(self, widget): w = widget.findChild(QPushButton, 'dclink_button') if w: psname = self._psname[0] dclinks = PSSearch.conv_psname_2_dclink(psname) if dclinks: dclink_type = PSSearch.conv_psname_2_psmodel(dclinks[0]) if dclink_type != 'REGATRON_DCLink': connect_window(w, PSDetailWindow, self, psname=dclinks) else: if len(dclinks) > 1: menu = QMenu(w) for dcl in dclinks: act = QAction(dcl, menu) connect_newprocess(act, [ 'sirius-hla-as-ps-regatron-individual', '-dev', dcl ], parent=self, is_pydm=True) menu.addAction(act) w.setMenu(menu) else: connect_newprocess(w, [ 'sirius-hla-as-ps-regatron-individual', '-dev', dclinks[0] ], parent=self, is_pydm=True) else: w.setHidden(True)
class ProgressWindow(FramelessWindow): def __init__(self, parent, generator): super(ProgressWindow, self).__init__(parent) self.__generator = generator self.__progress_view = QPlainTextEdit(self) self.__highlighter = Highlighter(self.__progress_view.document()) self.__progress_view.textChanged.connect(self.__on_progress_text) self.addContentWidget(self.__progress_view) self.menu = QMenu(self.__generator, self) close_action = QAction("Close", self.menu) close_action.triggered.connect(self.close) self.menu.addAction(close_action) self.addMenu(self.menu) def generator(self): return self.__generator @Slot(str) def appendProgress(self, text): self.__progress_view.appendPlainText(text) @Slot() def __on_progress_text(self): self.__progress_view.verticalScrollBar().setValue( self.__progress_view.verticalScrollBar().maximum())
def open_menu(self, position): menu = QMenu(self) preset_menu = menu.addMenu('Presets') preset_menu.addAction('New preset', self.new_preset_dialog) preset_menu.addSeparator() preset_names = CONFIG.get_header_presets() if len(preset_names) == 0: action = preset_menu.addAction('No presets') action.setEnabled(False) else: delete_menu = menu.addMenu('Delete preset') for name in preset_names: preset_menu.addAction(name, partial(self.load_preset, name)) delete_menu.addAction(name, partial(self.delete_preset, name)) menu.addSeparator() menu.addAction('New column...', self.create_new_column_dialog) if len(self.columnList.selectedIndexes()) > 0: menu.addAction('Delete selected', self.delete_selected) menu.popup(self.columnList.viewport().mapToGlobal(position))
def __init__(self): QWidget.__init__(self) self._line_edit = ClearableLineEdit() self._calendar_button = QToolButton() self._calendar_button.setPopupMode(QToolButton.InstantPopup) self._calendar_button.setFixedSize(26, 26) self._calendar_button.setAutoRaise(True) self._calendar_button.setIcon(resourceIcon("calendar.png")) self._calendar_button.setStyleSheet( "QToolButton::menu-indicator { image: none; }") tool_menu = QMenu(self._calendar_button) self._calendar_widget = QCalendarWidget(tool_menu) action = QWidgetAction(tool_menu) action.setDefaultWidget(self._calendar_widget) tool_menu.addAction(action) self._calendar_button.setMenu(tool_menu) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._line_edit) layout.addWidget(self._calendar_button) self.setLayout(layout) self._calendar_widget.activated.connect(self.setDate)
def _customContextMenu(self, pos): menu = QMenu(self) menu.addAction(self.closeAction) menu.addSeparator() menu.addAction(self.dockProperties) menu.popup(self.mapToGlobal(pos))
def __zoom(self) -> None: """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.main_canvas.zoom_to_fit) self.ResetCanvas.clicked.connect(self.main_canvas.zoom_to_fit) def zoom_level(value: int) -> Callable[[], None]: """Return a function that set the specified zoom value.""" @Slot() def func() -> None: self.zoom_bar.setValue(value) return func zoom_menu = QMenu(self) zoom_min = self.zoom_bar.minimum() zoom_min = zoom_min - zoom_min % 100 + 100 for level in range(zoom_min, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customize_zoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu)
def __free_move(self) -> None: """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon) -> Callable[[], None]: @Slot() def func() -> None: self.free_move_button.setIcon(icon_qt) self.main_canvas.set_free_move(j) self.entities_tab.setCurrentIndex(0) self.inputs_widget.variable_stop.click() return func for i, (text, icon, tip) in enumerate([ ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), ]): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(f"Ctrl+{i + 1}") action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) loadUi( os.path.abspath( os.path.join(os.path.dirname(__file__), ".", "model_editor.ui")), self) # Populate the add mode button with a dropdown containing available # fittable model objects self.add_model_button.setPopupMode(QToolButton.InstantPopup) models_menu = QMenu(self.add_model_button) self.add_model_button.setMenu(models_menu) for k, v in MODELS.items(): action = QAction(k, models_menu) action.triggered.connect( lambda x, m=v: self._add_fittable_model(m)) models_menu.addAction(action) # Initially hide the model editor tools until user has selected an # editable model spectrum object self.editor_holder_widget.setHidden(True) self.setup_holder_widget.setHidden(False) self.equation_edit_button.clicked.connect( self._on_equation_edit_button_clicked) self.new_model_button.clicked.connect(self._on_create_new_model) self.remove_model_button.clicked.connect(self._on_remove_model) # When a plot data item is select, get its model editor model # representation self.hub.workspace.current_selected_changed.connect( self._on_plot_item_selected)
def _add_plot_type_option_menu(self, menu, ax): # Error bar caps are considered lines so they are removed before checking the number of lines on the axes so # they aren't confused for "actual" lines. error_bar_caps = datafunctions.remove_and_return_errorbar_cap_lines(ax) # Able to change the plot type to waterfall if there is only one axes, it is a MantidAxes, and there is more # than one line on the axes. if len(ax.get_figure().get_axes()) > 1 or not isinstance( ax, MantidAxes) or len(ax.get_lines()) <= 1: return # Re-add error bar caps ax.lines += error_bar_caps plot_type_menu = QMenu("Plot Type", menu) plot_type_action_group = QActionGroup(plot_type_menu) standard = plot_type_menu.addAction( "1D", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) waterfall = plot_type_menu.addAction( "Waterfall", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) for action in [waterfall, standard]: plot_type_action_group.addAction(action) action.setCheckable(True) if ax.is_waterfall(): waterfall.setChecked(True) else: standard.setChecked(True) menu.addMenu(plot_type_menu)
def show_contextmenu(self, pos: QPoint): menu = QMenu(self) index = self.currentIndex() node = index.internalPointer() # insert item and node menu.addAction(self.insertitemAction) menu.addAction(self.insertnodeAction) # edit key if isinstance(node, (JsonNode, JsonItem)): menu.addSeparator() menu.addAction(self.editkeyAction) if isinstance(node, JsonItem): menu.addAction(self.editAction) self.editAction.setEnabled(not node.readonly) # remove if isinstance(node, (JsonNode, JsonItem)): menu.addSeparator() menu.addAction(self.removeitemAction) # properties if isinstance(node, JsonItem): menu.addSeparator() menu.addAction(self.propertiesAction) menu.setDefaultAction(self.propertiesAction) menu.popup(self.viewport().mapToGlobal(pos), self.editAction)
def _add_marker_option_menu(self, menu, event): """ Entry in main context menu to: - add horizontal/vertical markers - open a marker editor window. The editor window allows editing of all currently plotted markers :param menu: instance of QMenu to append this submenu to :param event: mpl event that generated the call """ marker_menu = QMenu("Markers", menu) marker_action_group = QActionGroup(marker_menu) x0, x1 = event.inaxes.get_xlim() y0, y1 = event.inaxes.get_ylim() horizontal = marker_menu.addAction( "Horizontal", lambda: self._add_horizontal_marker( event.ydata, y0, y1, event.inaxes)) vertical = marker_menu.addAction( "Vertical", lambda: self._add_vertical_marker( event.xdata, x0, x1, event.inaxes)) edit = marker_menu.addAction("Edit", lambda: self._global_edit_markers()) for action in [horizontal, vertical, edit]: marker_action_group.addAction(action) menu.addMenu(marker_menu)
class WorkflowWidget(QWidget): sigAddFunction = Signal(object) def __init__(self, workflowview: QAbstractItemView): super(WorkflowWidget, self).__init__() self.view = workflowview self.toolbar = QToolBar() self.addfunctionmenu = QToolButton() self.addfunctionmenu.setIcon(QIcon(path("icons/addfunction.png"))) self.addfunctionmenu.setText("Add Function") # Defer menu population to once the plugins have been loaded; otherwise, the menu may not contain anything # if this widget is init'd before all plugins have been loaded. self.functionmenu = QMenu() self.functionmenu.aboutToShow.connect(self.populateFunctionMenu) self.addfunctionmenu.setMenu(self.functionmenu) self.addfunctionmenu.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(self.addfunctionmenu) # self.toolbar.addAction(QIcon(path('icons/up.png')), 'Move Up') # self.toolbar.addAction(QIcon(path('icons/down.png')), 'Move Down') self.toolbar.addAction(QIcon(path("icons/folder.png")), "Load Workflow") self.toolbar.addAction(QIcon(path("icons/trash.png")), "Delete Operation", self.deleteOperation) v = QVBoxLayout() v.addWidget(self.view) v.addWidget(self.toolbar) v.setContentsMargins(0, 0, 0, 0) self.setLayout(v) def populateFunctionMenu(self): self.functionmenu.clear() sortingDict = {} for plugin in pluginmanager.get_plugins_of_type("OperationPlugin"): typeOfOperationPlugin = plugin.getCategory() if not typeOfOperationPlugin in sortingDict.keys(): sortingDict[typeOfOperationPlugin] = [] sortingDict[typeOfOperationPlugin].append(plugin) for key in sortingDict.keys(): self.functionmenu.addSeparator() self.functionmenu.addAction(key) self.functionmenu.addSeparator() for plugin in sortingDict[key]: self.functionmenu.addAction( plugin.name, partial(self.addOperation, plugin, autoconnectall=True)) def addOperation(self, operation: OperationPlugin, autoconnectall=True): self.view.model().workflow.addOperation(operation(), autoconnectall) print("selected new row:", self.view.model().rowCount() - 1) self.view.setCurrentIndex(self.view.model().index( self.view.model().rowCount() - 1, 0)) def deleteOperation(self): for index in self.view.selectedIndexes(): operation = self.view.model().workflow.operations[index.row()] self.view.model().workflow.remove_operation(operation)
def tableMenu(self, event): """Right click menu for table, can plot or print selected logs""" menu = QMenu(self) plotAction = menu.addAction("Plot selected") plotAction.triggered.connect(self.presenter.new_plot_logs) plotAction = menu.addAction("Print selected") plotAction.triggered.connect(self.presenter.print_selected_logs) menu.exec_(event.globalPos())
def tableMenu(self, event): """Right click menu for table, can plot or print selected logs""" menu = QMenu(self) plotAction = menu.addAction("Plot selected") plotAction.triggered.connect(self.presenter.new_plot_logs) plotAction = menu.addAction("Print selected") plotAction.triggered.connect(self.presenter.print_selected_logs) menu.exec_(event.globalPos())
class MOSViewerToolbar(BasicToolbar): def __init__(self, *args, **kwargs): super(MOSViewerToolbar, self).__init__(*args, **kwargs) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) # Define the toolbar actions self.cycle_previous_action = QAction( QIcon(os.path.join(ICON_DIR, "Previous-96.png")), "Previous", self) self.cycle_next_action = QAction( QIcon(os.path.join(ICON_DIR, "Next-96.png")), "Next", self) # Include the dropdown widget self.source_select = QComboBox() # Add the items to the toolbar self.addAction(self.cycle_previous_action) self.addAction(self.cycle_next_action) self.addWidget(self.source_select) # Include a button to open spectrum in specviz self.open_specviz = QAction( QIcon(os.path.join(ICON_DIR, "External-96.png")), "Open in SpecViz", self) # Create a tool button to hold the lock axes menu object tool_button = QToolButton(self) tool_button.setText("Axes Settings") tool_button.setIcon(QIcon(os.path.join(ICON_DIR, "Settings-96.png"))) tool_button.setPopupMode(QToolButton.MenuButtonPopup) tool_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) # Create a menu for the axes settings drop down self.settings_menu = QMenu(self) # Add lock x axis action self.lock_x_action = QAction("Lock spectral axis", self.settings_menu) self.lock_x_action.setCheckable(True) # Add lock y axis action self.lock_y_action = QAction("Lock vertical displacement axis", self.settings_menu) self.lock_y_action.setCheckable(True) # Add the actions to the menu self.settings_menu.addAction(self.lock_x_action) self.settings_menu.addAction(self.lock_y_action) # Set the menu object on the tool button tool_button.setMenu(self.settings_menu) # Create a widget action object to hold the tool button, this way the # toolbar behaves the way it's expected to tool_button_action = QWidgetAction(self) tool_button_action.setDefaultWidget(tool_button) self.addAction(tool_button_action) self.addSeparator() self.addAction(self.open_specviz)
class RightClickTreeView(QTreeView2): """ creates a QTreeView with: - all the features of QTreeView2 - a right click context menu with: - Edit - TODO - Cut - TODO - Copy - TODO - Rename - TODO - Delete """ def __init__(self, parent, data, choices): QTreeView2.__init__(self, parent, data, choices) self.right_click_menu = QMenu() edit = self.right_click_menu.addAction("Edit...") cut = self.right_click_menu.addAction("Cut...") copy = self.right_click_menu.addAction("Copy...") paste = self.right_click_menu.addAction("Paste...") rename = self.right_click_menu.addAction("Rename...") delete = self.right_click_menu.addAction("Delete...") edit.triggered.connect(self.on_edit) cut.triggered.connect(self.on_cut) copy.triggered.connect(self.on_copy) paste.triggered.connect(self.on_paste) rename.triggered.connect(self.on_rename) delete.triggered.connect(self.on_delete) def on_edit(self): rows = self.find_list_index() print('edit...rows=%s' % rows) def on_cut(self): pass def on_copy(self): pass def on_paste(self): pass def on_rename(self): rows = self.find_list_index() list_data = self.data for row in rows[:-1]: list_datai = list_data[row] list_data = list_datai[2] list_datai = list_data[rows[-1]] list_data = list_datai[0] print('list_datai* = ', list_datai) #print('list_data2* = %r' % list_data) list_datai[0] = 'cat' self.parent.update_data(self.data) #self.update() #def on_delete(self): #pass def on_right_mouse_button(self): self.right_click_menu.popup(QtGui.QCursor.pos())
def context_menu_requested(self, event): """Popup context menu.""" if self.fig: pos = QPoint(event.x(), event.y()) context_menu = QMenu(self) context_menu.addAction(ima.icon('editcopy'), "Copy Image", self.copy_figure, QKeySequence(get_shortcut('plots', 'copy'))) context_menu.popup(self.mapToGlobal(pos))
class TasksWidget(QWidget): """Shows tasks table.""" stop_task = Signal(Bot, Task) force_start_task = Signal(Bot, Task) def __init__(self, bot, parent): super(TasksWidget, self).__init__(parent) self.bot = bot self.menu = None self.task_details_window = None self.widget_layout = QHBoxLayout(self) self.setLayout(self.widget_layout) self.table = TasksTableView(self) self.table.context_menu_requested.connect(self.show_context_menu) self.table.task_double_clicked.connect(self.create_task_details_window) self.model = TasksTableModel(self) self.model.setBot(bot) self.table.setModel(self.model) self.widget_layout.addWidget(self.table) @Slot(list) def update_tasks(self, data): self.model.setEvents(data) @Slot(Task) def create_task_details_window(self, task: Task): self.task_details_window = TaskDetailsWindow(self) self.task_details_window.setTask(task) self.task_details_window.show() @Slot(int) def show_context_menu(self, task_id) -> None: self.menu = QMenu(self) force_start_action = QAction('Force start', self) force_start_action.triggered.connect( lambda: self.on_force_start_action_triggered(task_id)) stop_action = QAction('Stop', self) stop_action.triggered.connect( lambda: self.on_stop_action_triggered(task_id)) self.menu.addAction(force_start_action) self.menu.addAction(stop_action) self.menu.popup(QCursor.pos()) @Slot(int) def on_force_start_action_triggered(self, task_id): task = self.table.model().getEventById(task_id) if task: self.force_start_task.emit(self.bot, task) @Slot(int) def on_stop_action_triggered(self, task_id): task = self.table.model().getEventById(task_id) if task: self.stop_task.emit(self.bot, task)
def context_menu(self): try: menu = super(PyDMRelatedDisplayButton, self).context_menu() except: menu = QMenu(self) if len(menu.findChildren(QAction)) > 0: menu.addSeparator() menu.addAction(self.open_in_new_window_action) return menu
def context_menu(self): try: menu = super(PyDMRelatedDisplayButton, self).context_menu() except: menu = QMenu(self) if len(menu.findChildren(QAction)) > 0: menu.addSeparator() menu.addAction(self.open_in_new_window_action) return menu
def __make_minidsp_menu(self, func): menu = QMenu(self) current_config = QAction(menu) current_config.setText('Current') current_config.triggered.connect(func) menu.addAction(current_config) for i in range(4): self.__add_send_action(i, menu, func) return menu
def main(): logmodule = qrainbowstyle.extras.OutputLogger() qInstallMessageHandler(qrainbowstyle.extras.qt_message_handler) QtWidgets.QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) QtWidgets.QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) app = QtWidgets.QApplication(sys.argv) app.setStyleSheet(qrainbowstyle.load_stylesheet(style="oceanic")) # Package options # qrainbowstyle.align_buttons_left() # qrainbowstyle.use_darwin_buttons() qrainbowstyle.setAppIcon( os.path.join(os.path.dirname(os.path.realpath(__file__)), "github_logo.png")) # Create frameless mainwindow win = qrainbowstyle.windows.FramelessWindow() menu = QMenu(win) menu.setTitle("Some menu") menu.addAction(QAction("TEST ACTION", menu)) win.addMenu(menu) # Example for spinner spinner = qrainbowstyle.widgets.WaitingSpinner(win, centerOnParent=True, modality=Qt.WindowModal, roundness=70.0, fade=70.0, radius=9.0, lines=24, line_length=35.0, line_width=2.0) spinner.start() spinner.fadeIn() t = QTimer() t.setSingleShot(True) t.timeout.connect(spinner.fadeOut) t.start(5000) win.setMinimumSize(QSize(500, 300)) # Example of using signals win.closeClicked.connect(lambda: print("Close clicked!")) # Create content widget and pass reference to main window widget = WidgetGallery(win) # Add widget to main window and show it win.addContentWidget(widget) win.show() # Fullscreen test # win.showFullScreen() sys.exit(app.exec())
def context_menu_requested(self, event): """Popup context menu.""" if self.fig: pos = QPoint(event.x(), event.y()) context_menu = QMenu(self) context_menu.addAction(ima.icon('editcopy'), "Copy Image", self.copy_figure, QKeySequence( get_shortcut('plots', 'copy'))) context_menu.popup(self.mapToGlobal(pos))
def contextMenuEvent(self, event): """Show a custom context menu.""" point = event.pos() menu = QMenu("Actions", self) menu.addAction(self.blctrl_enbl_act) menu.addAction(self.blctrl_dsbl_act) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point))
def _display_roi_context_menu(self, roi_index): def delete_roi(event): self._dc.remove_subset_group(self._dc.subset_groups[roi_index]) context_menu = QMenu() action = QAction("Delete ROI", context_menu) action.triggered.connect(delete_roi) context_menu.addAction(action) pos = self._viewer.mapToParent(QCursor().pos()) context_menu.exec_(pos)
def _add_plugins_menu(self): """Add 'Plugins' menu to app menubar.""" self.plugins_menu = self.main_menu.addMenu('&Plugins') pip_install_action = QAction("Install/Uninstall Package(s)...", self._qt_window) pip_install_action.triggered.connect(self._show_plugin_install_dialog) self.plugins_menu.addAction(pip_install_action) order_plugin_action = QAction("Plugin Call Order...", self._qt_window) order_plugin_action.setStatusTip('Change call order for plugins') order_plugin_action.triggered.connect(self._show_plugin_sorter) self.plugins_menu.addAction(order_plugin_action) report_plugin_action = QAction("Plugin Errors...", self._qt_window) report_plugin_action.setStatusTip( 'Review stack traces for plugin exceptions and notify developers') report_plugin_action.triggered.connect(self._show_plugin_err_reporter) self.plugins_menu.addAction(report_plugin_action) self._plugin_dock_widget_menu = QMenu('Add Dock Widget', self._qt_window) if not plugins.dock_widgets: plugins.discover_dock_widgets() # Add a menu item (QAction) for each available plugin widget docks = zip(repeat("dock"), plugins.dock_widgets.items()) funcs = zip(repeat("func"), plugins.function_widgets.items()) for hook_type, (plugin_name, widgets) in chain(docks, funcs): multiprovider = len(widgets) > 1 if multiprovider: menu = QMenu(plugin_name, self._qt_window) self._plugin_dock_widget_menu.addMenu(menu) else: menu = self._plugin_dock_widget_menu for wdg_name in widgets: key = (plugin_name, wdg_name) if multiprovider: action = QAction(wdg_name, parent=self._qt_window) else: full_name = plugins.menu_item_template.format(*key) action = QAction(full_name, parent=self._qt_window) def _add_widget(*args, key=key, hook_type=hook_type): if hook_type == 'dock': self.add_plugin_dock_widget(*key) else: self._add_plugin_function_widget(*key) menu.addAction(action) action.triggered.connect(_add_widget) self.plugins_menu.addMenu(self._plugin_dock_widget_menu)
def _display_roi_context_menu(self, roi_index): def delete_roi(event): self._dc.remove_subset_group(self._dc.subset_groups[roi_index]) context_menu = QMenu() action = QAction("Delete ROI", context_menu) action.triggered.connect(delete_roi) context_menu.addAction(action) pos = self._viewer.mapToParent(QCursor().pos()) context_menu.exec_(pos)
def openWidgetMenu(self,position): indexes = self.ui.treeWidget_2.selectedIndexes() item = self.ui.treeWidget_2.itemAt(position) #item = self.ui.listWidget.itemAt(position) if len(indexes) > 0: menu = QMenu() menu.addAction(QAction("Delete", menu,checkable = True))#This function is perhaps useless #menu.triggered.connect(self.eraseItem) item = self.ui.treeWidget_2.itemAt(position) #collec = str(item.text()) menu.triggered.connect(lambda action: self.ListMethodSelected(action, item)) menu.exec_(self.ui.treeWidget_2.viewport().mapToGlobal(position))
def _make_export_button(self): """ Makes the export button menu, containing a list of export types :return: The export button menu """ export_button = QPushButton("Export") export_menu = QMenu() for text, extension in EXPORT_TYPES: export_menu.addAction(text, lambda ext=extension: self.presenter.export_plots_called(ext)) export_button.setMenu(export_menu) return export_button
def contextMenuEvent(self, event): """也叫弹出式菜单, 就是右键单击弹出""" cmenu = QMenu(self) newAct = cmenu.addAction('New') openAct = cmenu.addAction('Open') quitAct = cmenu.addAction('Quit') # 从event获取位置信息, 通过mapToGlobal action = cmenu.exec_(self.mapToGlobal(event.pos())) if action == quitAct: qApp.quit()
def _make_export_button(self): """ Makes the export button menu, containing a list of export types :return: The export button menu """ export_button = QPushButton("Export") export_menu = QMenu() for text, extension in EXPORT_TYPES: export_menu.addAction(text, lambda ext=extension: self.presenter.export_plots_called(ext)) export_button.setMenu(export_menu) return export_button
def _init_ui(self): loadUi(os.path.abspath( os.path.join(os.path.dirname(__file__), ".", "model_editor.ui")), self) # Populate the add mode button with a dropdown containing available # fittable model objects self.add_model_button.setPopupMode(QToolButton.InstantPopup) models_menu = QMenu(self.add_model_button) self.add_model_button.setMenu(models_menu) for k, v in MODELS.items(): action = QAction(k, models_menu) action.triggered.connect(lambda x, m=v: self._add_fittable_model(m)) models_menu.addAction(action) self.fit_model_thread = None # Initially hide the model editor tools until user has selected an # editable model spectrum object self.editor_holder_widget.setHidden(True) self.setup_holder_widget.setHidden(False) self.equation_edit_button.clicked.connect( self._on_equation_edit_button_clicked) self.new_model_button.clicked.connect(self._on_create_new_model) self.remove_model_button.clicked.connect(self._on_remove_model) self.advanced_settings_button.clicked.connect( lambda: ModelAdvancedSettingsDialog(self, self).exec()) self.save_model_button.clicked.connect(self._on_save_model) self.load_model_button.clicked.connect(self._on_load_from_file) self._data_item_proxy_model = DataItemProxyModel() self._data_item_proxy_model.setSourceModel(self.hub.model) self.data_selection_combo.setModel(self._data_item_proxy_model) self.data_selection_combo.currentIndexChanged.connect(self._redraw_model) # When a plot data item is select, get its model editor model # representation self.hub.workspace.current_selected_changed.connect( self._on_plot_item_selected) # When the plot window changes, reset model editor self.hub.workspace.mdi_area.subWindowActivated.connect(self._on_new_plot_activated) # Listen for when data items are added to internal model self.hub.model.data_added.connect(self._on_data_item_added) # Connect the fit model button self.fit_button.clicked.connect(self._on_fit_clicked)
def _init_ui(self): loadUi( os.path.abspath( os.path.join(os.path.dirname(__file__), ".", "model_editor.ui")), self) # Populate the add mode button with a dropdown containing available # fittable model objects self.add_model_button.setPopupMode(QToolButton.InstantPopup) models_menu = QMenu(self.add_model_button) self.add_model_button.setMenu(models_menu) for k, v in MODELS.items(): action = QAction(k, models_menu) action.triggered.connect( lambda x, m=v: self._add_fittable_model(m)) models_menu.addAction(action) # Initially hide the model editor tools until user has selected an # editable model spectrum object self.editor_holder_widget.setHidden(True) self.setup_holder_widget.setHidden(False) self.equation_edit_button.clicked.connect( self._on_equation_edit_button_clicked) self.new_model_button.clicked.connect(self._on_create_new_model) self.remove_model_button.clicked.connect(self._on_remove_model) self.advanced_settings_button.clicked.connect( lambda: ModelAdvancedSettingsDialog(self, self).exec()) self.save_model_button.clicked.connect(self._on_save_model) self.load_model_button.clicked.connect(self._on_load_from_file) self.data_selection_combo.setModel(self.hub.model) self.data_selection_combo.currentIndexChanged.connect( self._redraw_model) # When a plot data item is select, get its model editor model # representation self.hub.workspace.current_selected_changed.connect( self._on_plot_item_selected) # When the plot window changes, reset model editor self.hub.workspace.mdi_area.subWindowActivated.connect( self._on_new_plot_activated) # Listen for when data items are added to internal model self.hub.model.data_added.connect(self._on_data_item_added) # Connect the fit model button self.fit_button.clicked.connect(self._on_fit_clicked)
class WorkflowableROI(ROI): # FIXME: do we still want this for our (e.g.) CorrelationStage process_actions??? def __init__(self, *args, **kwargs): super(WorkflowableROI, self).__init__(*args, **kwargs) self.operation = ROIOperation(self) self._param = None def parameter(self) -> Parameter: raise NotImplementedError def getMenu(self): if self.menu is None: self.menu = QMenu() self.menu.setTitle("ROI") if self.removable: # FIXME: if the removable attr is changed, the menu will not react and remAct won't show remAct = QAction("Remove ROI", self.menu) remAct.triggered.connect(self.removeClicked) self.menu.addAction(remAct) self.menu.remAct = remAct editAct = QAction("Edit ROI", self.menu) editAct.triggered.connect(self.edit_parameters) self.menu.addAction(editAct) self.menu.editAct = editAct self.menu.setEnabled(True) return self.menu def contextMenuEnabled(self): return True def edit_parameters(self): class DefocusParameterTree(QWidget): def __init__(self, *args, **kwargs): super(DefocusParameterTree, self).__init__(*args, **kwargs) self.setLayout(QVBoxLayout()) self.parameter_tree = ParameterTree() self.layout().addWidget(self.parameter_tree) self.layout().setContentsMargins(0, 0, 0, 0) def setParameters(self, *args, **kwargs): self.parameter_tree.setParameters(*args, **kwargs) # self.parameter_tree = DefocusParameterTree() self.parameter_tree = DefocusParameterTree() self.parameter_tree.setParameters(self.parameter()) self.parameter_tree.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) # self.parameter_tree = QLabel('blah') self.parameter_tree.show() self.parameter_tree.activateWindow() self.parameter_tree.raise_() self.parameter_tree.move(QCursor().pos()) self.parameter_tree.setFocus(Qt.PopupFocusReason) self.parameter_tree.resize(QSize(300, 400))
def context_menu(self): try: menu = super(PyDMRelatedDisplayButton, self).context_menu() except: menu = QMenu(self) if len(menu.findChildren(QAction)) > 0: menu.addSeparator() if len(self.filenames) <= 1: menu.addAction(self.open_in_new_window_action) return menu sub_menu = menu.addMenu("Open in New Window") self._assemble_menu(sub_menu, target=self.NEW_WINDOW) return menu
def openWidgetMenu(self,position): indexes = self.ui.treeWidget_2.selectedIndexes() item = self.ui.treeWidget_2.itemAt(position) if item == None: return #item = self.ui.listWidget.itemAt(position) if len(indexes) > 0: menu = QMenu() menu.addAction(QAction("Delete", menu,checkable = True))#This function is perhaps useless #menu.triggered.connect(self.eraseItem) item = self.ui.treeWidget_2.itemAt(position) #collec = str(item.text()) menu.triggered.connect(lambda action: self.ListMethodSelected(action, item)) menu.exec_(self.ui.treeWidget_2.viewport().mapToGlobal(position))
def build_context_menu(self, index): """Build context menu for test item that given index points to.""" contextMenu = QMenu(self) if self.isExpanded(index): menuItem = create_action(self, _('Collapse'), triggered=lambda: self.collapse(index)) else: menuItem = create_action(self, _('Expand'), triggered=lambda: self.expand(index)) menuItem.setEnabled(self.model().hasChildren(index)) contextMenu.addAction(menuItem) menuItem = create_action( self, _('Go to definition'), triggered=lambda: self.go_to_test_definition(index)) test_location = self.model().data(index, Qt.UserRole) menuItem.setEnabled(test_location[0] is not None) contextMenu.addAction(menuItem) return contextMenu
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 _add_axes_scale_menu(self, menu): """Add the Axes scale options menu to the given menu""" axes_menu = QMenu("Axes", menu) axes_actions = QActionGroup(axes_menu) current_scale_types = self._get_axes_scale_types() for label, scale_types in iteritems(AXES_SCALE_MENU_OPTS): action = axes_menu.addAction(label, partial(self._quick_change_axes, scale_types)) if current_scale_types == scale_types: action.setCheckable(True) action.setChecked(True) axes_actions.addAction(action) menu.addMenu(axes_menu)
def initialize_toolbar(self): """ """ # Merge the main tool bar and the plot tool bar to get back some # real estate self.current_workspace.addToolBar( self.current_workspace.current_plot_window.tool_bar) self.current_workspace.main_tool_bar.setIconSize(QSize(15, 15)) # Hide the first five actions in the default specviz tool bar for act in self.current_workspace.main_tool_bar.actions()[:6]: act.setVisible(False) # Hide the tabs of the mdiarea in specviz. self.current_workspace.mdi_area.setViewMode(QMdiArea.SubWindowView) self.current_workspace.current_plot_window.setWindowFlags(Qt.FramelessWindowHint) self.current_workspace.current_plot_window.showMaximized() if self._layout is not None: cube_ops = QAction(QIcon(":/icons/cube.svg"), "Cube Operations", self.current_workspace.main_tool_bar) self.current_workspace.main_tool_bar.addAction(cube_ops) self.current_workspace.main_tool_bar.addSeparator() button = self.current_workspace.main_tool_bar.widgetForAction(cube_ops) button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self.current_workspace.main_tool_bar) button.setMenu(menu) # Create operation actions menu.addSection("2D Operations") act = QAction("Simple Linemap", self) act.triggered.connect(lambda: simple_linemap(self)) menu.addAction(act) act = QAction("Fitted Linemap", self) act.triggered.connect(lambda: fitted_linemap(self)) menu.addAction(act) menu.addSection("3D Operations") act = QAction("Fit Spaxels", self) act.triggered.connect(lambda: fit_spaxels(self)) menu.addAction(act) act = QAction("Spectral Smoothing", self) act.triggered.connect(lambda: spectral_smoothing(self)) menu.addAction(act)
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 _make_context_menu(self): """ Makes the context menu with options relating to plots :return: The context menu, and export sub-menu with a list of export types """ context_menu = QMenu() context_menu.addAction("Show", self.presenter.show_multiple_selected) context_menu.addAction("Hide", self.presenter.hide_selected_plots) context_menu.addAction("Delete", self.presenter.close_action_called) context_menu.addAction("Rename", self.rename_selected_in_context_menu) export_menu = context_menu.addMenu("Export") for text, extension in EXPORT_TYPES: export_menu.addAction(text, lambda ext=extension: self.presenter.export_plots_called(ext)) return context_menu, export_menu
def _rebuild_menu(self): if not any(self._filenames): self._filenames = [] if not any(self._titles): self._titles = [] if len(self._filenames) == 0: self.setEnabled(False) if len(self._filenames) <= 1: self.setMenu(None) self._menu_needs_rebuild = False return menu = QMenu(self) for i, filename in enumerate(self._filenames): if i >= len(self._titles): title = filename else: title = self._titles[i] action = menu.addAction(title) macros = "" if i < len(self._macros): macros = self._macros[i] action.triggered.connect(partial(self.open_display, filename, macros, target=None)) self.setMenu(menu) self._menu_needs_rebuild = False
class SofQView(MplGraphicsView): """ Graphics view for S(Q) """ # boundary moving signal (1) int for left/right boundary indicator (2) boundaryMoveSignal = Signal(int, float) # resolution of boundary indicator to be selected IndicatorResolution = 0.01 def __init__(self, parent): """ Initialization :param parent:t """ self._myParent = parent MplGraphicsView.__init__(self, parent) # declare event handling to indicators self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) self._myCanvas.mpl_connect('button_release_event', self.on_mouse_release_event) self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion) self._mainApp = None # dictionary to record all the plots, key: (usually) SofQ's name, value: plot ID self._sqLineDict = dict() # S(Q) plot's information including color, marker and etc. self._sqPlotInfoDict = dict() # list of SofQ that are plot on the canvas self._shownSQNameList = list() # link signal # self.boundaryMoveSignal.connect(self._myParent.update_sq_boundary) # declare class variables for moving boundary self._showBoundary = False self._leftID = None self._rightID = None self._selectedBoundary = 0 self._prevCursorPos = None return def get_plot_info(self, sofq_name): """ get the information of a plot including color, marker and etc. :param sofq_name: :return: """ # check assert isinstance(sofq_name, str), 'SofQ {0} must be a string but not a {1}'.format(sofq_name, type(sofq_name)) if sofq_name not in self._sqPlotInfoDict: raise RuntimeError('SofQ-view does not have S(Q) plot {0}'.format(sofq_name)) # return return self._sqPlotInfoDict[sofq_name] def get_shown_sq_names(self): """ get the names of S(q) workspaces that are shown on the canvas Returns ------- """ return self._shownSQNameList[:] def is_boundary_shown(self): """ Returns ------- """ return self._showBoundary def is_on_canvas(self, sq_name): """ check whether an S(Q) is on canvas now Args: sq_name: Returns: boolean. True if on canvas; Otherwise False """ # check input assert isinstance(sq_name, str), 'SofQ name %s must be a string but not of type %s.' \ '' % (str(sq_name), str(type(sq_name))) # return return sq_name in self._sqLineDict def move_left_indicator(self, displacement, relative): """ Args: displacement: relative: Returns: """ # check assert isinstance(displacement, float) assert isinstance(relative, bool) if relative: self.move_indicator(self._leftID, displacement) else: self.set_indicator_position(self._leftID, displacement, 0) return def move_right_indicator(self, displacement, relative): """ Args: displacement: relative: Returns: """ # check assert isinstance(displacement, float) assert isinstance(relative, bool) if relative: self.move_indicator(self._rightID, displacement) else: self.set_indicator_position(self._rightID, displacement, 0) return def on_mouse_motion(self, event): """ Returns ------- """ # ignore if boundary is not shown if not self._showBoundary: return # ignore if no boundary is selected if self._selectedBoundary == 0: return elif self._selectedBoundary > 2: raise RuntimeError('Impossible to have selected boundary mode %d' % self._selectedBoundary) cursor_pos = event.xdata # ignore if the cursor is out of canvas if cursor_pos is None: return cursor_displace = cursor_pos - self._prevCursorPos left_bound_pos = self.get_indicator_position(self._leftID)[0] right_bound_pos = self.get_indicator_position(self._rightID)[0] x_range = self.getXLimit() resolution = (x_range[1] - x_range[0]) * self.IndicatorResolution if self._selectedBoundary == 1: # left boundary new_left_bound = left_bound_pos + cursor_displace # return if the left boundary is too close to right if new_left_bound > right_bound_pos - resolution * 5: return # move left boundary self.move_indicator(self._leftID, cursor_displace, 0) # signal main self.boundaryMoveSignal.emit(1, new_left_bound) else: # right boundary new_right_bound = right_bound_pos + cursor_displace # return if the right boundary is too close or left to the left boundary if new_right_bound < left_bound_pos + resolution * 5: return # move right boundary self.move_indicator(self._rightID, cursor_displace, 0) # emit signal to the main app self.boundaryMoveSignal.emit(2, new_right_bound) # update cursor position self._prevCursorPos = cursor_pos return def on_mouse_press_event(self, event): """ Handle mouse pressing event (1) left mouse: in show-boundary mode, check the action to select a boundary indicator (2) right mouse: pop up the menu Returns ------- """ # get the button button = event.button if button == 3: # right button: # Pop-out menu self.menu = QMenu(self) if self.get_canvas().is_legend_on: # figure has legend: remove legend action1 = QAction('Hide legend', self) action1.triggered.connect(self._myCanvas.hide_legend) action2 = QAction('Legend font larger', self) action2.triggered.connect(self._myCanvas.increase_legend_font_size) action3 = QAction('Legend font smaller', self) action3.triggered.connect(self._myCanvas.decrease_legend_font_size) self.menu.addAction(action2) self.menu.addAction(action3) else: # figure does not have legend: add legend action1 = QAction('Show legend', self) action1.triggered.connect(self._myCanvas.show_legend) self.menu.addAction(action1) # pop up menu self.menu.popup(QCursor.pos()) return # END-IF # ignore if boundary is not shown and the pressed mouse button is left or middle if not self._showBoundary: return # get mouse cursor x position mouse_x_pos = event.xdata if mouse_x_pos is None: return else: self._prevCursorPos = mouse_x_pos # get absolute resolution x_range = self.getXLimit() resolution = (x_range[1] - x_range[0]) * self.IndicatorResolution # see whether it is close enough to any boundary left_bound_pos = self.get_indicator_position(self._leftID)[0] right_bound_pos = self.get_indicator_position(self._rightID)[0] if abs(mouse_x_pos - left_bound_pos) < resolution: self._selectedBoundary = 1 elif abs(mouse_x_pos - right_bound_pos) < resolution: self._selectedBoundary = 2 else: self._selectedBoundary = 0 # END-IF-ELSE return def on_mouse_release_event(self, event): """ handling the event that mouse is released The operations include setting some flags' values :param event: :return: """ # ignore if boundary is not shown if not self._showBoundary: return # get mouse cursor position self._prevCursorPos = event.xdata self._prevCursorPos = None self._selectedBoundary = 0 return def plot_sq(self, ws_name, sq_y_label=None, reset_color_mark=None, color=None, marker=None, plotError=False): """Plot S(Q) :param sq_name: :param sq_y_label: label for Y-axis :param reset_color_mark: boolean to reset color marker :param color: :param color_marker: :return: """ # check whether it is a new plot or an update if ws_name in self._sqLineDict: # exiting S(q) workspace, do update sq_key = self._sqLineDict[ws_name] self.updateLine(ikey=sq_key, wkspname=ws_name, wkspindex=0) else: # new S(Q) plot on the canvas assert isinstance(sq_y_label, str), 'S(Q) label {0} must be a string but not a {1}.' \ ''.format(sq_y_label, type(sq_y_label)) # define color if color is None: if reset_color_mark: self.reset_line_color_marker_index() marker, color = self.getNextLineMarkerColorCombo() else: marker = None # plot plot_id = self.add_plot_1d(ws_name, wkspindex=0, color=color, x_label='Q', y_label=sq_y_label, marker=marker, label=ws_name, plotError=plotError) self._sqLineDict[ws_name] = plot_id self._sqPlotInfoDict[ws_name] = color, marker if ws_name not in self._shownSQNameList: self._shownSQNameList.append(ws_name) # auto scale self.auto_scale_y(room_percent=0.05, lower_boundary=0.) return def set_main(self, main_app): """ Returns ------- """ self._mainApp = main_app # link signal self.boundaryMoveSignal.connect(self._mainApp.update_sq_boundary) return def remove_sq(self, sq_ws_name): """ Remove 1 S(q) line from canvas Args: sq_ws_name: workspace name as plot key Returns: """ # check whether S(Q) does exist assert isinstance(sq_ws_name, str), 'S(Q) workspace name {0} must be a string but not a {1}.' \ ''.format(sq_ws_name, type(sq_ws_name)) if sq_ws_name not in self._sqLineDict: raise RuntimeError('key (SofQ name) {0} does not exist on the S(Q) canvas.'.format(sq_ws_name)) # retrieve the plot and remove it from the dictionary plot_id = self._sqLineDict[sq_ws_name] sq_color, sq_marker = self._sqPlotInfoDict[sq_ws_name] del self._sqLineDict[sq_ws_name] del self._sqPlotInfoDict[sq_ws_name] # delete from canvas self.remove_line(plot_id) # delete from on-show S(q) list self._shownSQNameList.remove(sq_ws_name) return sq_color, sq_marker def reset(self): """ Reset the canvas including removing all the 1-D plots and boundary indicators Returns: """ # clear the dictionary and on-show Sq list self._sqLineDict.clear() self._sqPlotInfoDict.clear() self._shownSQNameList = list() # clear the image and reset the marker/color scheme self.clear_all_lines() self.reset_line_color_marker_index() # clear the boundary flag self._showBoundary = False return def toggle_boundary(self, q_left, q_right): """ Turn on or off the left and right boundary to select Q-range Parameters ---------- q_left :: q_right :: Returns ------- """ # check assert isinstance(q_left, float) and isinstance(q_right, float) assert q_left < q_right if self._showBoundary: # Q-boundary indicator is on. turn off self.remove_indicator(self._leftID) self.remove_indicator(self._rightID) self._leftID = None self._rightID = None self._showBoundary = False else: self._leftID = self.add_vertical_indicator(q_left, 'red') self._rightID = self.add_vertical_indicator(q_right, 'red') self._showBoundary = True # reset the x-range x_range = self.getXLimit() if x_range[0] > q_left - 1: self.setXYLimit(xmin=q_left-1) # END-IF-ELSE (show boundary) return
class PyDMLineEdit(QLineEdit, TextFormatter, PyDMWritableWidget, DisplayFormat): Q_ENUMS(DisplayFormat) DisplayFormat = DisplayFormat """ A QLineEdit (writable text field) with support for Channels and more from PyDM. This widget offers an unit conversion menu when users Right Click into it. Parameters ---------- parent : QWidget The parent widget for the Label init_channel : str, optional The channel to be used by the widget. """ def __init__(self, parent=None, init_channel=None): QLineEdit.__init__(self, parent) PyDMWritableWidget.__init__(self, init_channel=init_channel) self.app = QApplication.instance() self._display = None self._scale = 1 self.returnPressed.connect(self.send_value) self.unitMenu = QMenu('Convert Units', self) self.create_unit_options() self._display_format_type = self.DisplayFormat.Default self._string_encoding = "utf_8" if utilities.is_pydm_app(): self._string_encoding = self.app.get_string_encoding() @Property(DisplayFormat) def displayFormat(self): return self._display_format_type @displayFormat.setter def displayFormat(self, new_type): if self._display_format_type != new_type: self._display_format_type = new_type # Trigger the update of display format self.value_changed(self.value) def value_changed(self, new_val): """ Receive and update the PyDMLineEdit for a new channel value The actual value of the input is saved as well as the type received. This also resets the PyDMLineEdit display text using :meth:`.set_display` Parameters ---------- value: str, float or int The new value of the channel """ super(PyDMLineEdit, self).value_changed(new_val) self.set_display() def send_value(self): """ Emit a :attr:`send_value_signal` to update channel value. The text is cleaned of all units, user-formatting and scale values before being sent back to the channel. This function is attached the ReturnPressed signal of the PyDMLineEdit """ send_value = str(self.text()) # Clean text of unit string if self._show_units and self._unit and self._unit in send_value: send_value = send_value[:-len(self._unit)].strip() try: if self.channeltype not in [str, np.ndarray]: scale = self._scale if scale is None or scale == 0: scale = 1.0 if self._display_format_type in [DisplayFormat.Default, DisplayFormat.String]: if self.channeltype == float: num_value = locale.atof(send_value) else: num_value = self.channeltype(send_value) scale = self.channeltype(scale) elif self._display_format_type == DisplayFormat.Hex: num_value = int(send_value, 16) elif self._display_format_type == DisplayFormat.Binary: num_value = int(send_value, 2) elif self._display_format_type in [DisplayFormat.Exponential, DisplayFormat.Decimal]: num_value = locale.atof(send_value) num_value = num_value / scale self.send_value_signal[self.channeltype].emit(num_value) elif self.channeltype == np.ndarray: # Arrays will be in the [1.2 3.4 22.214] format if self._display_format_type == DisplayFormat.String: self.send_value_signal[str].emit(send_value) else: arr_value = list(filter(None, send_value.replace("[", "").replace("]", "").split(" "))) arr_value = np.array(arr_value, dtype=self.subtype) self.send_value_signal[np.ndarray].emit(arr_value) else: # Channel Type is String # Lets just send what we have after all self.send_value_signal[str].emit(send_value) except ValueError: logger.exception("Error trying to set data '{0}' with type '{1}' and format '{2}' at widget '{3}'." .format(self.text(), self.channeltype, self._display_format_type, self.objectName())) self.clearFocus() self.set_display() def write_access_changed(self, new_write_access): """ Change the PyDMLineEdit to read only if write access is denied """ super(PyDMLineEdit, self).write_access_changed(new_write_access) self.setReadOnly(not new_write_access) def unit_changed(self, new_unit): """ Accept a unit to display with a channel's value The unit may or may not be displayed based on the :attr:`showUnits` attribute. Receiving a new value for the unit causes the display to reset. """ super(PyDMLineEdit, self).unit_changed(new_unit) self._scale = 1 self.create_unit_options() def create_unit_options(self): """ Create the menu for displaying possible unit values The menu is filled with possible unit conversions based on the current PyDMLineEdit. If either the unit is not found in the by the :func:`utilities.find_unit_options` function, or, the :attr:`.showUnits` attribute is set to False, the menu will tell the user that there are no available conversions """ self.unitMenu.clear() units = utilities.find_unit_options(self._unit) if units and self._show_units: for choice in units: self.unitMenu.addAction(choice, partial( self.apply_conversion, choice ) ) else: self.unitMenu.addAction('No Unit Conversions found') def apply_conversion(self, unit): """ Convert the current unit to a different one This function will attempt to find a scalar to convert the current unit type to the desired one and reset the display with the new conversion. Parameters ---------- unit : str String name of desired units """ if not self._unit: logger.warning("Warning: Attempting to convert PyDMLineEdit unit, but no initial units supplied.") return None scale = utilities.convert(str(self._unit), unit) if scale: self._scale = scale * float(self._scale) self._unit = unit self.update_format_string() self.clearFocus() self.set_display() else: logging.warning("Warning: Attempting to convert PyDMLineEdit unit, but '{0}' can not be converted to '{1}'." .format(self._unit, unit)) def widget_ctx_menu(self): """ Fetch the Widget specific context menu which will be populated with additional tools by `assemble_tools_menu`. Returns ------- QMenu or None If the return of this method is None a new QMenu will be created by `assemble_tools_menu`. """ menu = self.createStandardContextMenu() menu.addSeparator() menu.addMenu(self.unitMenu) return menu def set_display(self): """ Set the text display of the PyDMLineEdit. The original value given by the PV is converted to a text entry based on the current settings for scale value, precision, a user-defined format, and the current units. If the user is currently entering a value in the PyDMLineEdit the text will not be changed. """ if self.value is None: return if self.hasFocus(): return new_value = self.value if self._display_format_type in [DisplayFormat.Default, DisplayFormat.Decimal, DisplayFormat.Exponential, DisplayFormat.Hex, DisplayFormat.Binary]: if not isinstance(new_value, (str, np.ndarray)): try: new_value *= self.channeltype(self._scale) except TypeError: logger.error("Cannot convert the value '{0}', for channel '{1}', to type '{2}'. ".format( self._scale, self._channel, self.channeltype)) new_value = parse_value_for_display(value=new_value, precision=self._prec, display_format_type=self._display_format_type, string_encoding=self._string_encoding, widget=self) self._display = str(new_value) if self._display_format_type == DisplayFormat.Default: if isinstance(new_value, (int, float)): self._display = str(self.format_string.format(new_value)) self.setText(self._display) return if self._show_units: self._display += " {}".format(self._unit) self.setText(self._display) def focusOutEvent(self, event): """ Overwrites the function called when a user leaves a PyDMLineEdit without pressing return. Resets the value of the text field to the current channel value. """ if self._display is not None: self.setText(self._display) super(PyDMLineEdit, self).focusOutEvent(event)
class MOSViewerToolbar(BasicToolbar): def __init__(self, *args, **kwargs): super(MOSViewerToolbar, self).__init__(*args, **kwargs) # self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) # Define icon path icon_path = os.path.join(os.path.dirname(__file__), 'ui', 'icons') # Define the toolbar actions self.cycle_previous_action = QAction( QIcon(os.path.join(icon_path, "Previous-96.png")), "Previous", self) self.cycle_next_action = QAction( QIcon(os.path.join(icon_path, "Next-96.png")), "Next", self) # Include the dropdown widget self.source_select = QComboBox() # Add the items to the toolbar self.addAction(self.cycle_previous_action) self.addAction(self.cycle_next_action) self.addWidget(self.source_select) # Include a button to open spectrum in specviz self.open_specviz = QAction( QIcon(os.path.join(icon_path, "External-96.png")), "Open in SpecViz", self) # Create a tool button to hold the lock axes menu object tool_button = QToolButton(self) tool_button.setText("Axes Settings") tool_button.setIcon(QIcon(os.path.join(icon_path, "Settings-96.png"))) tool_button.setPopupMode(QToolButton.MenuButtonPopup) # Create a menu for the axes settings drop down self.settings_menu = QMenu(self) # Add lock x axis action self.lock_x_action = QAction("Lock X Axis", self.settings_menu) self.lock_x_action.setCheckable(True) # Add lock y axis action self.lock_y_action = QAction("Lock Y Axis", self.settings_menu) self.lock_y_action.setCheckable(True) # Add the actions to the menu self.settings_menu.addAction(self.lock_x_action) self.settings_menu.addAction(self.lock_y_action) # Set the menu object on the tool button tool_button.setMenu(self.settings_menu) # Create a widget action object to hold the tool button, this way the # toolbar behaves the way it's expected to tool_button_action = QWidgetAction(self) tool_button_action.setDefaultWidget(tool_button) self.addAction(tool_button_action) self.addSeparator() self.addAction(self.open_specviz)
class BaseTabs(QTabWidget): """TabWidget with context menu and corner widgets""" sig_close_tab = Signal(int) def __init__(self, parent, actions=None, menu=None, corner_widgets=None, menu_use_tooltips=False): QTabWidget.__init__(self, parent) self.setUsesScrollButtons(True) # To style tabs on Mac if sys.platform == 'darwin': self.setObjectName('plugin-tab') self.corner_widgets = {} self.menu_use_tooltips = menu_use_tooltips if menu is None: self.menu = QMenu(self) if actions: add_actions(self.menu, actions) else: self.menu = menu # Corner widgets if corner_widgets is None: corner_widgets = {} corner_widgets.setdefault(Qt.TopLeftCorner, []) corner_widgets.setdefault(Qt.TopRightCorner, []) self.browse_button = create_toolbutton(self, icon=ima.icon('browse_tab'), tip=_("Browse tabs")) self.browse_tabs_menu = QMenu(self) self.browse_button.setMenu(self.browse_tabs_menu) self.browse_button.setPopupMode(self.browse_button.InstantPopup) self.browse_tabs_menu.aboutToShow.connect(self.update_browse_tabs_menu) corner_widgets[Qt.TopLeftCorner] += [self.browse_button] self.set_corner_widgets(corner_widgets) def update_browse_tabs_menu(self): """Update browse tabs menu""" self.browse_tabs_menu.clear() names = [] dirnames = [] for index in range(self.count()): if self.menu_use_tooltips: text = to_text_string(self.tabToolTip(index)) else: text = to_text_string(self.tabText(index)) names.append(text) if osp.isfile(text): # Testing if tab names are filenames dirnames.append(osp.dirname(text)) offset = None # If tab names are all filenames, removing common path: if len(names) == len(dirnames): common = get_common_path(dirnames) if common is None: offset = None else: offset = len(common)+1 if offset <= 3: # Common path is not a path but a drive letter... offset = None for index, text in enumerate(names): tab_action = create_action(self, text[offset:], icon=self.tabIcon(index), toggled=lambda state, index=index: self.setCurrentIndex(index), tip=self.tabToolTip(index)) tab_action.setChecked(index == self.currentIndex()) self.browse_tabs_menu.addAction(tab_action) def set_corner_widgets(self, corner_widgets): """ Set tabs corner widgets corner_widgets: dictionary of (corner, widgets) corner: Qt.TopLeftCorner or Qt.TopRightCorner widgets: list of widgets (may contains integers to add spacings) """ assert isinstance(corner_widgets, dict) assert all(key in (Qt.TopLeftCorner, Qt.TopRightCorner) for key in corner_widgets) self.corner_widgets.update(corner_widgets) for corner, widgets in list(self.corner_widgets.items()): cwidget = QWidget() cwidget.hide() prev_widget = self.cornerWidget(corner) if prev_widget: prev_widget.close() self.setCornerWidget(cwidget, corner) clayout = QHBoxLayout() clayout.setContentsMargins(0, 0, 0, 0) for widget in widgets: if isinstance(widget, int): clayout.addSpacing(widget) else: clayout.addWidget(widget) cwidget.setLayout(clayout) cwidget.show() def add_corner_widgets(self, widgets, corner=Qt.TopRightCorner): self.set_corner_widgets({corner: self.corner_widgets.get(corner, [])+widgets}) def contextMenuEvent(self, event): """Override Qt method""" self.setCurrentIndex(self.tabBar().tabAt(event.pos())) if self.menu: self.menu.popup(event.globalPos()) def mousePressEvent(self, event): """Override Qt method""" if event.button() == Qt.MidButton: index = self.tabBar().tabAt(event.pos()) if index >= 0: self.sig_close_tab.emit(index) event.accept() return QTabWidget.mousePressEvent(self, event) def keyPressEvent(self, event): """Override Qt method""" ctrl = event.modifiers() & Qt.ControlModifier key = event.key() handled = False if ctrl and self.count() > 0: index = self.currentIndex() if key == Qt.Key_PageUp: if index > 0: self.setCurrentIndex(index - 1) else: self.setCurrentIndex(self.count() - 1) handled = True elif key == Qt.Key_PageDown: if index < self.count() - 1: self.setCurrentIndex(index + 1) else: self.setCurrentIndex(0) handled = True if not handled: QTabWidget.keyPressEvent(self, event) def set_close_function(self, func): """Setting Tabs close function None -> tabs are not closable""" state = func is not None if state: self.sig_close_tab.connect(func) try: # Assuming Qt >= 4.5 QTabWidget.setTabsClosable(self, state) self.tabCloseRequested.connect(func) except AttributeError: # Workaround for Qt < 4.5 close_button = create_toolbutton(self, triggered=func, icon=ima.icon('fileclose'), tip=_("Close current tab")) self.setCornerWidget(close_button if state else None)
def right_click(self, position=None): _duplicate_row = -1 _plot_sofq = -1 _remove_row = -1 _new_row = -1 _copy = -1 _paste = -1 _cut = -1 _refresh_table = -1 _clear_table = -1 # _import = -1 # _export = -1 _check_all = -1 _uncheck_all = -1 _undo = -1 _redo = -1 _plot_sofq_diff_first_run_row = -1 _plot_sofq_diff_average_row = -1 _plot_cryostat = -1 _plot_furnace = -1 _invert_selection = -1 menu = QMenu(self.main_window) if self.main_window.table_selection_buffer == {}: paste_status = False else: paste_status = True if (self.main_window.postprocessing_ui.table.rowCount() > 0): _undo = menu.addAction("Undo") _undo.setEnabled(self.main_window.undo_button_enabled) _redo = menu.addAction("Redo") _redo.setEnabled(self.main_window.redo_button_enabled) menu.addSeparator() _copy = menu.addAction("Copy") _paste = menu.addAction("Paste") self._paste_menu = _paste _paste.setEnabled(paste_status) _cut = menu.addAction("Clear") menu.addSeparator() _check_all = menu.addAction("Check All") _uncheck_all = menu.addAction("Unchecked All") menu.addSeparator() _invert_selection = menu.addAction("Inverse Selection") menu.addSeparator() _new_row = menu.addAction("Insert Blank Row") if (self.main_window.postprocessing_ui.table.rowCount() > 0): _duplicate_row = menu.addAction("Duplicate Row") _remove_row = menu.addAction("Remove Row(s)") menu.addSeparator() _plot_menu = menu.addMenu('Plot') _plot_sofq = _plot_menu.addAction("S(Q) ...") _plot_sofq_diff_first_run_row = _plot_menu.addAction("S(Q) Diff (1st run)...") _plot_sofq_diff_average_row = _plot_menu.addAction("S(Q) Diff (Avg.)...") _temp_menu = _plot_menu.addMenu("Temperature") _plot_cryostat = _temp_menu.addAction("Cyrostat...") _plot_furnace = _temp_menu.addAction("Furnace...") menu.addSeparator() _refresh_table = menu.addAction("Refresh/Reset Table") _clear_table = menu.addAction("Clear Table") action = menu.exec_(QCursor.pos()) self.current_row = self.current_row() if action == _undo: self.main_window.action_undo_clicked() elif action == _redo: self.main_window.action_redo_clicked() elif action == _copy: self._copy() elif action == _paste: self._paste() elif action == _cut: self._cut() elif action == _duplicate_row: self._duplicate_row() elif action == _plot_sofq: self._plot_sofq() elif action == _plot_sofq_diff_first_run_row: self._plot_sofq_diff_first_run_row() elif action == _plot_sofq_diff_average_row: self._plot_sofq_diff_average_row() elif action == _plot_cryostat: self._plot_temperature(samp_env_choice='cryostat') elif action == _plot_furnace: self._plot_temperature(samp_env_choice='furnace') elif action == _invert_selection: self._inverse_selection() elif action == _new_row: self._new_row() elif action == _remove_row: self._remove_selected_rows() elif action == _refresh_table: self._refresh_table() elif action == _clear_table: self._clear_table() elif action == _check_all: self.check_all() elif action == _uncheck_all: self.uncheck_all()
def custom_context_menu(self, position): menu_main = QMenu() plot = QMenu("Plot...", menu_main) plot_line = QAction("Line", plot) plot_line.triggered.connect(partial(self.presenter.action_plot, PlotType.LINEAR)) plot_line_with_yerr = QAction("Line with Y Errors", plot) plot_line_with_yerr.triggered.connect(partial(self.presenter.action_plot, PlotType.LINEAR_WITH_ERR)) plot_scatter = QAction("Scatter", plot) plot_scatter.triggered.connect(partial(self.presenter.action_plot, PlotType.SCATTER)) plot_line_and_points = QAction("Line + Symbol", plot) plot_line_and_points.triggered.connect(partial(self.presenter.action_plot, PlotType.LINE_AND_SYMBOL)) plot.addAction(plot_line) plot.addAction(plot_line_with_yerr) plot.addAction(plot_scatter) plot.addAction(plot_line_and_points) menu_main.addMenu(plot) copy_bin_values = QAction(self.COPY_ICON, "Copy", menu_main) copy_bin_values.triggered.connect(self.presenter.action_copy_bin_values) set_as_x = QAction("Set as X", menu_main) set_as_x.triggered.connect(self.presenter.action_set_as_x) set_as_y = QAction("Set as Y", menu_main) set_as_y.triggered.connect(self.presenter.action_set_as_y) set_as_none = QAction("Set as None", menu_main) set_as_none.triggered.connect(self.presenter.action_set_as_none) statistics_on_columns = QAction("Statistics on Columns", menu_main) statistics_on_columns.triggered.connect(self.presenter.action_statistics_on_columns) hide_selected = QAction("Hide Selected", menu_main) hide_selected.triggered.connect(self.presenter.action_hide_selected) show_all_columns = QAction("Show All Columns", menu_main) show_all_columns.triggered.connect(self.presenter.action_show_all_columns) sort_ascending = QAction("Sort Ascending", menu_main) sort_ascending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.AscendingOrder)) sort_descending = QAction("Sort Descending", menu_main) sort_descending.triggered.connect(partial(self.presenter.action_sort_ascending, Qt.DescendingOrder)) menu_main.addAction(copy_bin_values) menu_main.addAction(self.make_separator(menu_main)) menu_main.addAction(set_as_x) menu_main.addAction(set_as_y) marked_y_cols = self.presenter.get_columns_marked_as_y() num_y_cols = len(marked_y_cols) # If any columns are marked as Y then generate the set error menu if num_y_cols > 0: menu_set_as_y_err = QMenu("Set error for Y...") for col in range(num_y_cols): set_as_y_err = QAction("Y{}".format(col), menu_main) # the column index of the column relative to the whole table, this is necessary # so that later the data of the column marked as error can be retrieved real_column_index = marked_y_cols[col] # col here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...) # this is NOT the same as the column relative to the WHOLE table set_as_y_err.triggered.connect(partial(self.presenter.action_set_as_y_err, real_column_index, col)) menu_set_as_y_err.addAction(set_as_y_err) menu_main.addMenu(menu_set_as_y_err) menu_main.addAction(set_as_none) menu_main.addAction(self.make_separator(menu_main)) menu_main.addAction(statistics_on_columns) menu_main.addAction(self.make_separator(menu_main)) menu_main.addAction(hide_selected) menu_main.addAction(show_all_columns) menu_main.addAction(self.make_separator(menu_main)) menu_main.addAction(sort_ascending) menu_main.addAction(sort_descending) menu_main.exec_(self.mapToGlobal(position))
def openMenu(self,position): indexes = self.ui.treeWidget.selectedIndexes() item = self.ui.treeWidget.itemAt(position) db_origin = "" #if item.parent(): # db_origin = item.parent().text(0) collec = str(item.text(0).encode("utf-8")) if len(indexes) > 0: level = 0 index = indexes[0] while index.parent().isValid(): index = index.parent() level = level + 1 menu = QMenu() #print((collec, db_origin)) if level ==0: pass else: #keyarray = GetKeys(collec, db_origin) #if "Open" in keyarray: if self.ui.combobox.currentText()==u"K线": menu.addAction(QAction("Kline", menu, checkable=True)) menu.addAction(QAction("Open", menu, checkable=True)) menu.addAction(QAction("Close", menu, checkable=True))#open up different menu with different kind of graphs menu.addAction(QAction("High", menu, checkable=True)) menu.addAction(QAction("Low", menu, checkable=True)) menu.addAction(QAction("Volume", menu, checkable=True)) #menu.addAction(QAction("P_change", menu, checkable=True)) #menu.addAction(QAction("Turnover",menu,checkable=True)) if self.ui.combobox.currentText()==u"复权": menu.addAction(QAction("Kline", menu, checkable=True)) menu.addAction(QAction("Open", menu, checkable=True)) menu.addAction(QAction("Close", menu, checkable=True)) menu.addAction(QAction("High", menu, checkable=True)) menu.addAction(QAction("Low", menu, checkable=True)) menu.addAction(QAction("Volume", menu, checkable=True)) menu.addAction(QAction("Amount", menu, checkable=True)) if self.ui.combobox.currentText()==u"分笔数据": menu.addAction(QAction("分笔", menu, checkable=True)) if self.ui.combobox.currentText()==u"历史分钟": menu.addAction(QAction("Kline", menu, checkable=True)) menu.addAction(QAction("Open", menu, checkable=True)) menu.addAction(QAction("Close", menu, checkable=True)) menu.addAction(QAction("High", menu, checkable=True)) menu.addAction(QAction("Low", menu, checkable=True)) menu.addAction(QAction("Volume", menu, checkable=True)) menu.addAction(QAction("Amount", menu, checkable=True)) if self.ui.combobox.currentText()==u"十大股东": menu.addAction(QAction("季度饼图", menu, checkable=True)) #menu.addAction(QAction("持股比例", menu, checkable=True)) #for g in keyarray: #menu.addAction(QAction(g, menu, checkable=True)) menu.triggered.connect(lambda action: self.methodSelected(action, collec)) menu.exec_(self.ui.treeWidget.viewport().mapToGlobal(position))
class GofRView(MplGraphicsView): """ Graphics view for G(R) """ def __init__(self, parent): """ Initialization """ MplGraphicsView.__init__(self, parent) # class variable containers self._grDict = dict() self._colorList = ['black', 'red', 'blue', 'green', 'brown', 'orange'] self._colorIndex = 0 # define the event handlers to the mouse actions self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) # self._myCanvas.mpl_connect('button_release_event', self.on_mouse_release_event) # self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion) # class variable self._minY = None self._maxY = None # variable self._isLegendOn = False return def on_mouse_press_event(self, event): """ Event handling for mouse press action Args: event: Returns: """ # get the button and position information. curr_x = event.xdata curr_y = event.ydata if curr_x is None or curr_y is None: # outside of canvas return button = event.button if button == 1: # left button: no operation pass elif button == 3: # right button: # Pop-out menu self.menu = QMenu(self) if self.get_canvas().is_legend_on: # figure has legend: remove legend action1 = QAction('Hide legend', self) action1.triggered.connect(self._myCanvas.hide_legend) action2 = QAction('Legend font larger', self) action2.triggered.connect(self._myCanvas.increase_legend_font_size) action3 = QAction('Legend font smaller', self) action3.triggered.connect(self._myCanvas.decrease_legend_font_size) self.menu.addAction(action2) self.menu.addAction(action3) else: # figure does not have legend: add legend action1 = QAction('Show legend', self) action1.triggered.connect(self._myCanvas.show_legend) self.menu.addAction(action1) # pop up menu self.menu.popup(QCursor.pos()) # END-IF-ELSE return def plot_gr(self, plot_key, ws_name, plotError=False, color='black', style='.', marker=None, alpha=1., label=None): """ Plot G(r) :param plot_key: a key to the current plot :param vec_r: numpy array for R :param vec_g: numpy array for G(r) :param vec_e: numpy array for G(r) error :param plot_error: :param color: :param style: :param marker: :param alpha: :param label: label for the line to plot :return: """ # q_min = 10., q_max = 50. # alpha = 1. - (q_now - q_min)/(q_max - q_min) if not label: label = str(plot_key) line_id = self.add_plot_1d(ws_name, wkspindex=0, marker=marker, color=color, line_style=style, alpha=alpha, label=label, x_label=r'r ($\AA$)', plotError=plotError) self._colorIndex += 1 self._grDict[str(plot_key)] = line_id # check the low/max self.auto_scale_y() def _reset_y_range(self, vec_gr): """ reset the Y range :param vec_gr: :return: """ this_min = min(vec_gr) this_max = max(vec_gr) if self._minY is None or this_min < self._minY: self._minY = this_min if self._maxY is None or this_max > self._maxY: self._maxY = this_max return def _auto_rescale_y(self): """ :return: """ if self._minY is None or self._maxY is None: return delta_y = self._maxY - self._minY lower_boundary = self._minY - delta_y * 0.05 upper_boundary = self._maxY + delta_y * 0.05 self.setXYLimit(ymin=lower_boundary, ymax=upper_boundary) return def has_gr(self, gr_ws_name): """Check whether a plot of G(r) exists on the canvas :param gr_ws_name: :return: """ return gr_ws_name in self._grDict def get_current_grs(self): """ list all the G(r) plotted on the figure now :return: """ return list(self._grDict.keys()) def remove_gr(self, plot_key): """Remove a plotted G(r) from canvas :param plot_key: key to locate the 1-D plot on canvas :return: boolean, string (as error message) """ # check assert isinstance(plot_key, str), 'Key for the plot must be a string but not %s.' % str(type(plot_key)) if plot_key not in self._grDict: return False, 'Workspace %s cannot be found in GofR dictionary of canvas' % plot_key # get line ID line_id = self._grDict[plot_key] # remove from plot self.remove_line(line_id) # clean G(r) plot del self._grDict[plot_key] # reset min and max self._minY = None self._maxY = None return def reset_color(self): """Reset color scheme :return: """ self._colorIndex = 0 def reset(self): """ Reset the canvas by deleting all lines and clean the dictionary Returns: """ # remove all lines and reset marker/color default sequence self.clear_all_lines() self.reset_line_color_marker_index() self._colorIndex = 0 # clean dictionary self._grDict.clear() return def update_gr(self, plot_key, ws_name, plotError=False): """update the value of an existing G(r) :param plot_key: :param vec_r: :param vec_g: :param vec_ge: :return: """ # check existence if plot_key not in self._grDict: raise RuntimeError('Plot with key/workspace name {0} does not exist on plot. Current plots are ' '{1}'.format(plot_key, list(self._grDict.keys()))) # update line_key = self._grDict[plot_key] self.updateLine(ikey=line_key, wkspname=ws_name) # update range self.auto_scale_y() return
class PlotWindow(QMdiSubWindow): """ Displayed plotting subwindow available in the ``QMdiArea``. """ window_removed = Signal() color_changed = Signal(PlotDataItem, QColor) width_changed = Signal(int) def __init__(self, model, *args, **kwargs): super(PlotWindow, self).__init__(*args, **kwargs) # Hide the icon in the title bar self.setWindowIcon(qta.icon('fa.circle', opacity=0)) # The central widget of the sub window will be a main window so that it # can support having tab bars self._central_widget = QMainWindow() self.setWidget(self._central_widget) loadUi(os.path.join(os.path.dirname(__file__), "ui", "plot_window.ui"), self._central_widget) # The central widget of the main window widget will be the plot self._model = model self._current_item_index = None self._plot_widget = PlotWidget(model=self._model) self._plot_widget.plotItem.setMenuEnabled(False) self._central_widget.setCentralWidget(self._plot_widget) # Setup action group for interaction modes mode_group = QActionGroup(self.tool_bar) mode_group.addAction(self._central_widget.pan_mode_action) self._central_widget.pan_mode_action.setChecked(True) mode_group.addAction(self._central_widget.zoom_mode_action) def _toggle_mode(state): view_state = self.plot_widget.plotItem.getViewBox().state.copy() view_state.update({'mouseMode': pg.ViewBox.RectMode if state else pg.ViewBox.PanMode}) self.plot_widget.plotItem.getViewBox().setState(view_state) # Setup plot settings options menu self.plot_settings_button = self.tool_bar.widgetForAction( self._central_widget.plot_settings_action) self.plot_settings_button.setPopupMode(QToolButton.InstantPopup) self.plot_settings_menu = QMenu(self.plot_settings_button) self.plot_settings_button.setMenu(self.plot_settings_menu) self.color_change_action = QAction("Line Color") self.plot_settings_menu.addAction(self.color_change_action) self.line_width_menu = QMenu("Line Widths") self.plot_settings_menu.addMenu(self.line_width_menu) # Setup the line width plot setting options for i in range(1, 4): act = QAction(str(i), self.line_width_menu) self.line_width_menu.addAction(act) act.triggered.connect(lambda *args, size=i: self._on_change_width(size)) # Setup connections self._central_widget.pan_mode_action.triggered.connect( lambda: _toggle_mode(False)) self._central_widget.zoom_mode_action.triggered.connect( lambda: _toggle_mode(True)) self._central_widget.linear_region_action.triggered.connect( self.plot_widget._on_add_linear_region) self._central_widget.remove_region_action.triggered.connect( self.plot_widget._on_remove_linear_region) self.color_change_action.triggered.connect( self._on_change_color) self._central_widget.export_plot_action.triggered.connect( self._on_export_plot) self._central_widget.reset_view_action.triggered.connect( lambda: self._on_reset_view()) @property def tool_bar(self): """ Return the tool bar for the embedded plot widget. """ return self._central_widget.tool_bar @property def current_item(self): """ The currently selected plot data item. """ if self._current_item_index is not None: return self.proxy_model.item_from_index(self._current_item_index) @property def plot_widget(self): """ Return the embedded plot widget """ return self._plot_widget @property def proxy_model(self): """ The proxy model defined in the internal plot widget. """ return self.plot_widget.proxy_model def closeEvent(self, event): """ Called by qt when window closes, upon which it emits the window_removed signal. Parameters ---------- event : ignored in this implementation. """ self.window_removed.emit() def _on_current_item_changed(self, current_idx, prev_idx): self._current_item_index = current_idx def _on_reset_view(self): """ Resets the visible range of the plot taking into consideration only the PlotDataItem objects currently attached. """ self.plot_widget.autoRange( items=[item for item in self.plot_widget.listDataItems() if isinstance(item, PlotDataItem)]) self.plot_widget.sigRangeChanged.emit(*self.plot_widget.viewRange()) def _on_change_color(self): """ Listens for color changed events in plot windows, gets the currently selected item in the data list view, and changes the stored color value. """ # If there is no currently selected rows, raise an error if self.current_item is None: message_box = QMessageBox() message_box.setText("No item selected, cannot change color.") message_box.setIcon(QMessageBox.Warning) message_box.setInformativeText( "There is currently no item selected. Please select an item " "before changing its plot color.") message_box.exec() return color = QColorDialog.getColor(options=QColorDialog.ShowAlphaChannel) if color.isValid(): self.current_item.color = color.toRgb() self.color_changed.emit(self.current_item, self.current_item.color) def _on_change_width(self, size): self.plot_widget.change_width(size) self.width_changed.emit(size) def _on_export_plot(self): file_path, key = compat.getsavefilename(filters=";;".join( EXPORT_FILTERS.keys())) if key == '': return exporter = EXPORT_FILTERS[key](self.plot_widget.plotItem) # TODO: Current issue in pyqtgraph where the user cannot explicitly # define the output size. Fix incoming. # plot_size_dialog = PlotSizeDialog(self) # plot_size_dialog.height_line_edit.setText( # str(int(exporter.params.param('height').value()))) # plot_size_dialog.width_line_edit.setText( # str(int(exporter.params.param('width').value()))) # # if key != "*.svg": # if plot_size_dialog.exec_(): # exporter.params.param('height').setValue(int(exporter.params.param('height').value()), # blockSignal=exporter.heightChanged) # exporter.params.param('width').setValue(int(exporter.params.param('height').value()), # blockSignal=exporter.widthChanged) # else: # return exporter.export(file_path)
class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. """ instruments = {} choppers = {} minE = {} maxE = {} def __init__(self): super(PyChopGui, self).__init__() self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 def setInstrument(self, instname): """ Defines the instrument parameters by the name of the instrument. """ self.engine = self.instruments[str(instname)] self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['ChopperCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Label'].setText('Frequency') self.widgets['PulseRemoverCombo']['Combo'].clear() for item in self.choppers[str(instname)]: self.widgets['ChopperCombo']['Combo'].addItem(item) rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies # At the moment, the GUI only supports up to two independent frequencies if not hasattr(maxfreq, '__len__') or len(maxfreq) == 1: self.widgets['PulseRemoverCombo']['Combo'].hide() self.widgets['PulseRemoverCombo']['Label'].hide() for fq in range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) if hasattr(self.engine.chopper_system, 'frequency_names'): self.widgets['FrequencyCombo']['Label'].setText(self.engine.chopper_system.frequency_names[0]) else: self.widgets['PulseRemoverCombo']['Combo'].show() self.widgets['PulseRemoverCombo']['Label'].show() if hasattr(self.engine.chopper_system, 'frequency_names'): for idx, chp in enumerate([self.widgets['FrequencyCombo']['Label'], self.widgets['PulseRemoverCombo']['Label']]): chp.setText(self.engine.chopper_system.frequency_names[idx]) for fq in range(rep, maxfreq[0] + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) for fq in range(rep, maxfreq[1] + 1, rep): self.widgets['PulseRemoverCombo']['Combo'].addItem(str(fq)) if len(self.engine.chopper_system.choppers) > 1: self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.engine.chopper_system.isPhaseIndependent: self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText(str(self.engine.chopper_system.defaultPhase[0])) self.widgets['Chopper2Phase']['Label'].setText(self.engine.chopper_system.phaseNames[0]) # Special case for MERLIN - hide phase control from normal users if 'MERLIN' in str(instname) and not self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() self.engine.setChopper(str(self.widgets['ChopperCombo']['Combo'].currentText())) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) nframe = self.engine.moderator.n_frame if hasattr(self.engine.moderator, 'n_frame') else 1 self.repfig_nframe_edit.setText(str(nframe)) self.repfig_nframe_rep1only.setChecked(False) if hasattr(self.engine.chopper_system, 'default_frequencies'): cb = [self.widgets['FrequencyCombo']['Combo'], self.widgets['PulseRemoverCombo']['Combo']] for idx, freq in enumerate(self.engine.chopper_system.default_frequencies): cb[idx].setCurrentIndex([i for i in range(cb[idx].count()) if str(freq) in cb[idx].itemText(i)][0]) if idx > 1: break self.tabs.setTabEnabled(self.qetabID, False) if self.engine.has_detector and hasattr(self.engine.detector, 'tthlims'): self.tabs.setTabEnabled(self.qetabID, True) def setChopper(self, choppername): """ Defines the Fermi chopper slit package type by name, or the disk chopper arrangement variant. """ self.engine.setChopper(str(choppername)) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) # Special case for MERLIN - only enable multirep for 'G' chopper if 'MERLIN' in self.engine.instname: if 'G' in str(choppername): self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() def setFreq(self, freqtext=None, **kwargs): """ Sets the chopper frequency(ies), in Hz. """ freq_gui = float(self.widgets['FrequencyCombo']['Combo'].currentText()) freq_in = kwargs['manual_freq'] if ('manual_freq' in kwargs.keys()) else freq_gui if len(self.engine.getFrequency()) > 1 and (not hasattr(freq_in, '__len__') or len(freq_in)==1): freqpr = float(self.widgets['PulseRemoverCombo']['Combo'].currentText()) freq_in = [freq_in, freqpr] if not self.widgets['Chopper2Phase']['Label'].isHidden(): chop2phase = self.widgets['Chopper2Phase']['Edit'].text() if isinstance(self.engine.chopper_system.defaultPhase[0], string_types): chop2phase = str(chop2phase) else: chop2phase = float(chop2phase) % (1e6 / self.engine.moderator.source_rep) self.engine.setFrequency(freq_in, phase=chop2phase) else: self.engine.setFrequency(freq_in) def setEi(self): """ Sets the incident energy (or focused incident energy for multi-rep case). """ try: eitxt = float(self.widgets['EiEdit']['Edit'].text()) self.engine.setEi(eitxt) if self.eiPlots.isChecked(): self.calc_callback() except ValueError: raise ValueError('No Ei specified, or Ei string not understood') def calc_callback(self): """ Calls routines to calculate the resolution / flux and to update the Matplotlib graphs. """ try: if self.engine.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) self.setEi() self.setFreq() self.calculate() if self.errormess: idx = [i for i, ei in enumerate(self.eis) if np.abs(ei - self.engine.getEi()) < 1.e-4] if idx and self.flux[idx[0]] == 0: raise ValueError(self.errormess) self.errormessage(self.errormess) self.plot_res() self.plot_frame() if self.instSciAct.isChecked(): self.update_script() except ValueError as err: self.errormessage(err) self.plot_flux_ei() self.plot_flux_hz() def calculate(self): """ Performs the resolution and flux calculations. """ self.errormess = None if self.engine.getEi() is None: self.setEi() if self.widgets['MultiRepCheck'].isChecked(): en = np.linspace(0, 0.95, 200) self.eis = self.engine.getAllowedEi() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getMultiRepResolution(en) self.flux = self.engine.getMultiRepFlux() if len(w) > 0: mess = [str(w[i].message) for i in range(len(w))] self.errormess = '\n'.join([m for m in mess if 'tchop' in m]) else: en = np.linspace(0, 0.95*self.engine.getEi(), 200) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getResolution(en) self.flux = self.engine.getFlux() if len(w) > 0: raise ValueError(w[0].message) def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): axis.hold(True) else: setattr(self, axisname+'_xlim', 0) axis.clear() axis.axhline(color='k') def plot_res(self): """ Plots the resolution in the resolution tab """ overplot = self.widgets['HoldCheck'].isChecked() multiplot = self.widgets['MultiRepCheck'].isChecked() self._set_overplot(overplot, 'resaxes') self._set_overplot(overplot, 'qeaxes') inst = self.engine.instname freq = self.engine.getFrequency() if hasattr(freq, '__len__'): freq = freq[0] if multiplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95*Ei, 200) if any(self.res[ie]): if not self.flux[ie]: continue line, = self.resaxes.plot(en, self.res[ie]) label_text = '%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, Ei, freq, self.flux[ie]) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95*ei, 200) line, = self.resaxes.plot(en, self.res) chopper = self.engine.getChopper() label_text = '%s_%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, chopper, ei, freq, self.flux) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(ei, label_text, overplot) self.resaxes_xlim = max(ei, self.resaxes_xlim) self.resaxes.set_xlim([0, self.resaxes_xlim]) self.resaxes.legend().draggable() self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.rescanvas.draw() def plot_qe(self, Ei, label_text, hold=False): """ Plots the Q-E diagram """ from scipy import constants E2q, meV2J = (2. * constants.m_n / (constants.hbar ** 2), constants.e / 1000.) en = np.linspace(-Ei / 5., Ei, 100) q2 = [] for tth in self.engine.detector.tthlims: q = np.sqrt(E2q * (2 * Ei - en - 2 * np.sqrt(Ei * (Ei - en)) * np.cos(np.deg2rad(tth))) * meV2J) / 1e10 q2.append(np.concatenate((np.flipud(q), q))) self._set_overplot(hold, 'qeaxes') self.qeaxes_xlim = max(np.max(q2), self.qeaxes_xlim) line, = self.qeaxes.plot(np.hstack(q2), np.concatenate((np.flipud(en), en)).tolist() * len(self.engine.detector.tthlims)) line.set_label(label_text) self.qeaxes.set_xlim([0, self.qeaxes_xlim]) self.qeaxes.legend().draggable() self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qecanvas.draw() def plot_flux_ei(self, **kwargs): """ Plots the flux vs Ei in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() freq = self.engine.getFrequency() overplot = self.widgets['HoldCheck'].isChecked() if hasattr(freq, '__len__'): freq = freq[0] update = kwargs['update'] if 'update' in kwargs.keys() else False # Do not recalculate if all relevant parameters still the same. _, labels = self.flxaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" ([0-9]+) Hz' tmpinst = [] if (labels and (overplot or len(labels) == 1)) or update: for prevtitle in labels: prevInst, prevChop, prevFreq = re.search(searchStr, prevtitle).groups() if update: tmpinst.append(copy.deepcopy(Instrument(self.instruments[prevInst], prevChop, float(prevFreq)))) else: if inst == prevInst and chop == prevChop and freq == float(prevFreq): return ne = 25 mn = self.minE[inst] mx = (self.flxslder.val/100)*self.maxE[inst] eis = np.linspace(mn, mx, ne) flux = eis*0 elres = eis*0 if update: self.flxaxes1.clear() self.flxaxes2.clear() if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = instrument.getFlux(ei) elres[ie] = instrument.getResolution(0., ei)[0] self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label(labels[ii]) else: for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label('%s "%s" %d Hz' % (inst, chop, freq)) self.flxaxes1.set_xlim([mn, mx]) self.flxaxes2.set_xlim([mn, mx]) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') lg = self.flxaxes2.legend() lg.draggable() self.flxcanvas.draw() def update_slider(self, val=None): """ Callback function for the x-axis slider of the flux tab """ if val is None: val = float(self.flxedt.text()) / self.maxE[self.engine.instname] * 100 if val < self.minE[self.engine.instname]: self.errormessage("Max Ei must be greater than %2.1f" % (self.minE[self.engine.instname])) val = (self.minE[self.engine.instname]+0.1) / self.maxE[self.engine.instname] * 100 self.flxslder.set_val(val) else: val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) self.plot_flux_ei(update=True) self.flxcanvas.draw() def plot_flux_hz(self): """ Plots the flux vs freq in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() ei = float(self.widgets['EiEdit']['Edit'].text()) overplot = self.widgets['HoldCheck'].isChecked() # Do not recalculate if one of the plots has the same parametersc _, labels = self.frqaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" Ei = ([0-9.-]+) meV' if labels and (overplot or len(labels) == 1): for prevtitle in labels: prevInst, prevChop, prevEi = re.search(searchStr, prevtitle).groups() if inst == prevInst and chop == prevChop and abs(ei-float(prevEi)) < 0.01: return freq0 = self.engine.getFrequency() rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies freqs = range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep) flux = np.zeros(len(freqs)) elres = np.zeros(len(freqs)) for ie, freq in enumerate(freqs): if hasattr(freq0, '__len__'): self.setFreq(manual_freq=[freq] + freq0[1:]) else: self.setFreq(manual_freq=freq) with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.frqaxes1.hold(True) self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() self.setFreq(manual_freq=freq0) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') line, = self.frqaxes1.plot(freqs, flux, 'o-') self.frqaxes1.set_xlim([0, np.max(freqs)]) self.frqaxes2.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') line, = self.frqaxes2.plot(freqs, elres, 'o-') line.set_label('%s "%s" Ei = %5.3f meV' % (inst, chop, ei)) lg = self.frqaxes2.legend() lg.draggable() self.frqaxes2.set_xlim([0, np.max(freqs)]) self.frqcanvas.draw() def instSciCB(self): """ Callback function for the "Instrument Scientist Mode" menu option """ # MERLIN is a special case - want to hide ability to change phase from users if 'MERLIN' in self.engine.instname and 'G' in self.engine.getChopper(): if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') else: self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.instSciAct.isChecked(): self.tabs.insertTab(self.scrtabID, self.scrtab, 'ScriptOutput') self.scrtab.show() else: self.tabs.removeTab(self.scrtabID) self.scrtab.hide() def errormessage(self, message): msg = QMessageBox() msg.setText(str(message)) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): yaml_file = QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)') if isinstance(yaml_file, tuple): yaml_file = yaml_file[0] yaml_file = str(yaml_file) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder try: new_inst = Instrument(yaml_file) except (RuntimeError, AttributeError, ValueError) as err: self.errormessage(err) newname = new_inst.name if newname in self.instruments.keys() and not self.overwriteload.isChecked(): overwrite, newname = self._ask_overwrite() if overwrite == 1: return elif overwrite == 0: newname = new_inst.name self.instruments[newname] = new_inst self.choppers[newname] = new_inst.getChopperNames() self.minE[newname] = max([new_inst.emin, 0.01]) self.maxE[newname] = new_inst.emax self.updateInstrumentList() combo = self.widgets['InstrumentCombo']['Combo'] idx = [i for i in range(combo.count()) if str(combo.itemText(i)) == newname] combo.setCurrentIndex(idx[0]) self.setInstrument(newname) def _ask_overwrite(self): msg = QDialog() msg.setWindowTitle('Load overwrite') layout = QGridLayout() layout.addWidget(QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) buttons = [QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to']] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 def overwriteCB(idx): self.overwrite_flag = idx msg.accept() for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) msg.exec_() newname = str(newname.text()) if not newname or newname in self.instruments: self.errormessage('Invalid instrument name. Cancelling load.') self.overwrite_flag = 1 return self.overwrite_flag, newname def updateInstrumentList(self): combo = self.widgets['InstrumentCombo']['Combo'] old_instruments = [str(combo.itemText(i)) for i in range(combo.count())] new_instruments = [inst for inst in self.instruments if inst not in old_instruments] for inst in new_instruments: combo.addItem(inst) def plot_frame(self): """ Plots the distance-time diagram in the right tab """ if len(self.engine.chopper_system.choppers) > 1: self.engine.n_frame = int(self.repfig_nframe_edit.text()) self.repaxes.clear() self.engine.plotMultiRepFrame(self.repaxes, first_rep=self.repfig_nframe_rep1only.isChecked()) self.repcanvas.draw() def _gen_text_ei(self, ei, obj_in): obj = Instrument(obj_in) obj.setEi(ei) en = np.linspace(0, 0.95*ei, 10) try: flux = self.engine.getFlux() res = self.engine.getResolution(en) except ValueError as err: self.errormessage(err) raise ValueError(err) tsqvan, tsqdic, tsqmodchop = obj.getVanVar() v_mod, v_chop = tuple(np.sqrt(tsqmodchop[:2]) * 1e6) x0, _, x1, x2, _ = obj.chopper_system.getDistances() first_component = 'moderator' if x0 != tsqmodchop[2]: x0 = tsqmodchop[2] first_component = 'chopper 1' txt = '# ------------------------------------------------------------- #\n' txt += '# Ei = %8.2f meV\n' % (ei) txt += '# Flux = %8.2f n/cm2/s\n' % (flux) txt += '# Elastic resolution = %6.2f meV\n' % (res[0]) txt += '# Time width at sample = %6.2f us, of which:\n' % (1e6*np.sqrt(tsqvan)) for ky, val in list(tsqdic.items()): txt += '# %20s : %6.2f us\n' % (ky, 1e6*np.sqrt(val)) txt += '# %s distances:\n' % (obj.instname) txt += '# x0 = %6.2f m (%s to Fermi)\n' % (x0, first_component) txt += '# x1 = %6.2f m (Fermi to sample)\n' % (x1) txt += '# x2 = %6.2f m (sample to detector)\n' % (x2) txt += '# Approximate inelastic resolution is given by:\n' txt += '# dE = 2 * E2V * sqrt(ef**3 * t_van**2) / x2\n' txt += '# where: E2V = 4.373e-4 meV/(m/us) conversion from energy to speed\n' txt += '# t_van**2 = (geom*t_mod)**2 + ((1+geom)*t_chop)**2\n' txt += '# geom = (x1 + x2*(ei/ef)**1.5) / x0\n' txt += '# and t_mod and t_chop are the moderator and chopper time widths at the\n' txt += '# moderator and chopper positions (not at the sample as listed above).\n' txt += '# Which in this case is:\n' txt += '# %.4e*sqrt(ef**3 * ( (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2 \n' % (874.78672e-6/x2, v_mod, x1/x0, x2/x0) txt += '# + (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2) )\n' % (v_chop, 1+x1/x0, x2/x0) txt += '# EN (meV) Full dE (meV) Approx dE (meV)\n' for ii in range(len(res)): ef = ei-en[ii] approx = (874.78672e-6/x2)*np.sqrt(ef**3 * ((v_mod*((x1/x0)+(x2/x0)*(ei/ef)**1.5))**2 + (v_chop*(1+(x1/x0)+(x2/x0)*(ei/ef)**1.5))**2)) txt += '%12.5f %12.5f %12.5f\n' % (en[ii], res[ii], approx) return txt def genText(self): """ Generates text output of the resolution function versus energy transfer and other information. """ multiplot = self.widgets['MultiRepCheck'].isChecked() obj = self.engine if obj.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) if obj.getEi() is None: self.setEi() instname, chtyp, freqs, ei_in = tuple([obj.instname, obj.getChopper(), obj.getFrequency(), obj.getEi()]) txt = '# ------------------------------------------------------------- #\n' txt += '# Chop calculation for instrument %s\n' % (instname) if obj.isFermi: txt += '# with chopper %s at %3i Hz\n' % (chtyp, freqs[0]) else: txt += '# in %s mode with:\n' % (chtyp) freq_names = obj.chopper_system.frequency_names for idx in range(len(freq_names)): txt += '# %s at %3i Hz\n' % (freq_names[idx], freqs[idx]) txt += self._gen_text_ei(ei_in, obj) if multiplot: for ei in sorted(self.engine.getAllowedEi()): if np.abs(ei - ei_in) > 0.001: txt += self._gen_text_ei(ei, obj) return txt def showText(self): """ Creates a dialog to show the generated text output. """ try: generatedText = self.genText() except ValueError: return self.txtwin = QDialog() self.txtedt = QTextEdit() self.txtbtn = QPushButton('OK') self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') self.txtwin.setWindowModality(Qt.ApplicationModal) self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ fname = QFileDialog.getSaveFileName(self, 'Open file', '') if isinstance(fname, tuple): fname = fname[0] fid = open(fname, 'w') fid.write(self.genText()) fid.close() def update_script(self): """ Updates the text window with information about the previous calculation. """ if self.widgets['MultiRepCheck'].isChecked(): out = self.engine.getMultiWidths() new_str = '\n' for ie, ee in enumerate(out['Eis']): res = out['Energy'][ie] percent = res / ee * 100 chop_width = out['chopper'][ie] mod_width = out['moderator'][ie] new_str += 'Ei is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ee, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) else: ei = self.engine.getEi() out = self.engine.getWidths() res = out['Energy'] percent = res / ei * 100 chop_width = out['chopper'] mod_width = out['moderator'] new_str = '\nEi is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ei, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) self.scredt.append(new_str) def onHelp(self): """ Shows the help page """ try: from pymantidplot.proxies import showCustomInterfaceHelp showCustomInterfaceHelp("PyChop") except ImportError: helpTxt = "PyChop is a tool to allow direct inelastic neutron\nscattering users to estimate the inelastic resolution\n" helpTxt += "and incident flux for a given spectrometer setting.\n\nFirst select the instrument, chopper settings and\n" helpTxt += "Ei, and then click 'Calculate and Plot'. Data for all\nthe graphs will be generated (may take 1-2s) and\n" helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." self.hlpwin = QDialog() self.hlpedt = QLabel(helpTxt) self.hlpbtn = QPushButton('OK') self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') self.hlpwin.setWindowModality(Qt.ApplicationModal) self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): """ Draws the GUI layout. """ self.widgetslist = [ ['pair', 'show', 'Instrument', 'combo', self.instruments, self.setInstrument, 'InstrumentCombo'], ['pair', 'show', 'Chopper', 'combo', '', self.setChopper, 'ChopperCombo'], ['pair', 'show', 'Frequency', 'combo', '', self.setFreq, 'FrequencyCombo'], ['pair', 'hide', 'Pulse remover chopper freq', 'combo', '', self.setFreq, 'PulseRemoverCombo'], ['pair', 'show', 'Ei', 'edit', '', self.setEi, 'EiEdit'], ['pair', 'hide', 'Chopper 2 phase delay time', 'edit', '5', self.setFreq, 'Chopper2Phase'], ['spacer'], ['single', 'show', 'Calculate and Plot', 'button', self.calc_callback, 'CalculateButton'], ['single', 'show', 'Hold current plot', 'check', lambda: None, 'HoldCheck'], ['single', 'show', 'Show multi-reps', 'check', lambda: None, 'MultiRepCheck'], ['spacer'], ['single', 'show', 'Show data ascii window', 'button', self.showText, 'ShowAsciiButton'], ['single', 'show', 'Save data as ascii', 'button', self.saveText, 'SaveAsciiButton'] ] self.droplabels = [] self.dropboxes = [] self.singles = [] self.widgets = {} self.leftPanel = QVBoxLayout() self.rightPanel = QVBoxLayout() self.tabs = QTabWidget(self) self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = {'Combo':self.dropboxes[-1], 'Label':self.droplabels[-1]} elif 'edit' in widget[3]: self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = {'Edit':self.dropboxes[-1], 'Label':self.droplabels[-1]} else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.droplabels[-1]) self.leftPanel.addWidget(self.dropboxes[-1]) if 'hide' in widget[1]: self.droplabels[-1].hide() self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.singles[-1]) if 'hide' in widget[1]: self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError('Bug in code - widget class %s is not recognised.' % (widget[0])) # Right panel, matplotlib figures self.resfig = Figure() self.resfig.patch.set_facecolor('white') self.rescanvas = FigureCanvas(self.resfig) self.resaxes = self.resfig.add_subplot(111) self.resaxes.axhline(color='k') self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) self.restab = QWidget(self.tabs) self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) self.flxfig = Figure() self.flxfig.patch.set_facecolor('white') self.flxcanvas = FigureCanvas(self.flxfig) self.flxaxes1 = self.flxfig.add_subplot(121) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes2 = self.flxfig.add_subplot(122) self.flxaxes2.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.flxfig_controls = NavigationToolbar(self.flxcanvas, self) self.flxsldfg = Figure() self.flxsldfg.patch.set_facecolor('white') self.flxsldcv = FigureCanvas(self.flxsldfg) self.flxsldax = self.flxsldfg.add_subplot(111) self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) self.flxtab = QWidget(self.tabs) self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) self.flxtab.setLayout(self.flxtabbox) self.frqfig = Figure() self.frqfig.patch.set_facecolor('white') self.frqcanvas = FigureCanvas(self.frqfig) self.frqaxes1 = self.frqfig.add_subplot(121) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.frqaxes2 = self.frqfig.add_subplot(122) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) self.frqtab = QWidget(self.tabs) self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) self.repfig = Figure() self.repfig.patch.set_facecolor('white') self.repcanvas = FigureCanvas(self.repfig) self.repaxes = self.repfig.add_subplot(111) self.repaxes.axhline(color='k') self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) self.repfig_nframe_label = QLabel('Number of frames to plot') self.repfig_nframe_edit = QLineEdit('1') self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) self.repfig_nframe_rep1only = QCheckBox('First Rep Only') self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) self.repfig_nframe_box.addWidget(self.repfig_nframe_rep1only) self.reptab = QWidget(self.tabs) self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) self.repfig_nframe.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) self.reptab.setLayout(self.reptabbox) self.qefig = Figure() self.qefig.patch.set_facecolor('white') self.qecanvas = FigureCanvas(self.qefig) self.qeaxes = self.qefig.add_subplot(111) self.qeaxes.axhline(color='k') self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) self.scrtab = QWidget(self.tabs) self.scredt = QTextEdit() self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) self.scrtab.hide() self.tabs.addTab(self.restab, 'Resolution') self.tabs.addTab(self.flxtab, 'Flux-Ei') self.tabs.addTab(self.frqtab, 'Flux-Freq') self.tabs.addTab(self.reptab, 'Time-Distance') self.tdtabID = 3 self.tabs.setTabEnabled(self.tdtabID, False) self.tabs.addTab(self.qetab, 'Q-E') self.qetabID = 4 self.tabs.setTabEnabled(self.qetabID, False) self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) self.menuLoad = QMenu('Load') self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) self.menuOptions = QMenu('Options') self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) self.leftPanelWidget.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show()